Add articles on Spring BeanFactory and FactoryBean, database ACID properties, and Redis JSON serialization

- Created a comprehensive guide on Spring's BeanFactory and FactoryBean, detailing their differences, implementations, and best practices.
- Added an article explaining the ACID properties of databases, including atomicity, consistency, isolation, and durability.
- Introduced a guide on configuring Redis with JSON serialization, including code examples and explanations of serialization techniques.
This commit is contained in:
2025-08-08 07:11:29 +00:00
parent 5f367f8c2e
commit a405f29312
8 changed files with 781 additions and 0 deletions

View File

@@ -0,0 +1,265 @@
---
title: Spring中的BeanFactory与FactoryBean
published: 2025-08-08
description: 深入理解Spring容器的核心接口BeanFactory与特殊工厂Bean——FactoryBean的区别、使用场景与最佳实践
image: ''
tags: [Java, Spring, IoC, DI, BeanFactory, FactoryBean]
category: 'JAVA > Spring'
draft: false
lang: zh-CN
---
# BeanFactory
BeanFactory是一个工厂接口是一个负责生产和管理bean的一个工厂。BeanFactory是工厂的顶层接口是IOC容器的核心接口BeanFactory定义了管理Bean的通用方法如getBean和containsBean等它的职责包括
- Bean实例化: 根据XML注解等创建Bean对象。
- 依赖注入: 自动将Bean所需的依赖注入进去。
- 生命周期管理: 管理Bean的初始化销毁等生命周期方法。
- 延迟加载: 默认采用懒加载策略只有在调用getBean()时才创建Bean实例。
- Bean获取 提供getBean()方法来获取Bean实例。
![](https://blog.meowrain.cn/api/i/2025/08/08/fkc5xn-1.webp)
BeanFactory只是一个接口不是IOC容器的具体实现所以Spring容器给出了很多种实现如XmlBeanFactory、AnnotationConfigApplicationContext,ApplicationContext等。
## BeanFactory 的常见实现类
Spring 提供了多种 BeanFactory 的实现,每种实现都有其特定的使用场景:
### DefaultListableBeanFactory
最常用的完整实现支持完整的Bean生命周期管理
```java
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
public class BeanFactoryExample {
public static void main(String[] args) {
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
// 手动注册Bean定义
RootBeanDefinition beanDefinition = new RootBeanDefinition(MyService.class);
factory.registerBeanDefinition("myService", beanDefinition);
// 获取Bean
MyService service = factory.getBean("myService", MyService.class);
service.doSomething();
}
}
class MyService {
public void doSomething() {
System.out.println("Service is working!");
}
}
```
### XmlBeanFactory已废弃
基于XML配置的BeanFactory实现Spring 5.x后已废弃推荐使用ApplicationContext
```java
// 传统用法(不推荐)
// XmlBeanFactory factory = new XmlBeanFactory(new ClassPathResource("beans.xml"));
// 现代替代方案
import org.springframework.context.support.ClassPathXmlApplicationContext;
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
MyService service = context.getBean("myService", MyService.class);
```
### StaticListableBeanFactory
静态Bean工厂适用于Bean集合固定的场景
```java
import org.springframework.beans.factory.support.StaticListableBeanFactory;
StaticListableBeanFactory factory = new StaticListableBeanFactory();
factory.addBean("myService", new MyService());
factory.addBean("anotherService", new AnotherService());
MyService service = factory.getBean("myService", MyService.class);
```
### ApplicationContext实现类
作为BeanFactory的高级实现提供更多企业级特性
```java
// 注解配置
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);
// XML配置
import org.springframework.context.support.ClassPathXmlApplicationContext;
ClassPathXmlApplicationContext xmlContext =
new ClassPathXmlApplicationContext("applicationContext.xml");
// Web环境
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
AnnotationConfigWebApplicationContext webContext =
new AnnotationConfigWebApplicationContext();
webContext.register(WebConfig.class);
```
### 实际应用示例
结合Bean定义构建器的完整示例
```java
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
public class CustomBeanFactoryDemo {
public static void main(String[] args) {
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
// 使用BeanDefinitionBuilder构建复杂Bean
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.rootBeanDefinition(DatabaseService.class)
.addPropertyValue("url", "jdbc:mysql://localhost:3306/mydb")
.addPropertyValue("username", "root")
.setScope("singleton")
.setLazyInit(true);
factory.registerBeanDefinition("dbService", builder.getBeanDefinition());
// 懒加载验证
System.out.println("Bean定义已注册但未实例化");
DatabaseService dbService = factory.getBean("dbService", DatabaseService.class);
System.out.println("现在Bean被实例化了");
}
}
class DatabaseService {
private String url;
private String username;
// getters and setters
public void setUrl(String url) { this.url = url; }
public void setUsername(String username) { this.username = username; }
public void connect() {
System.out.println("连接到: " + url + " 用户: " + username);
}
}
```
## BeanFactory 与 ApplicationContext 的区别
- 预实例化策略
- BeanFactory单例默认懒加载。
- ApplicationContext默认预实例化单例提高启动后首次访问的吞吐
- 扩展能力
- ApplicationContext 额外提供国际化、事件发布、AOP自动代理、资源模式解析等企业特性。
- 适用场景
- BeanFactory资源受限、极致冷启动、强控制懒加载/条件加载。
- ApplicationContext大多数应用优先选择。
- 调优提示
- 需要懒加载时可结合ApplicationContext + @Lazy 或者使用ObjectProvider/Provider按需获取。
# FactoryBean
在Spring中所有的Bean都是由BeanFactory管理的IOC容器
这个FactoryBean不是简单的Bean而是一个能生产或者修饰对象生成的工厂Bean它的实现与设计模式中的工厂模式和修饰器模式类似。
## FactoryBean 的作用
![](https://blog.meowrain.cn/api/i/2025/08/08/gzh7kd-1.webp)
- 将“复杂对象的创建逻辑”封装到工厂Bean中对外暴露的是“产品对象”而不是工厂本身。
- 常用于动态代理AOP/远程代理、框架桥接如MyBatis的SqlSessionFactoryBean、复杂构建连接池、客户端SDK
## 核心接口方法
- getObject(): 返回实际产品对象对外暴露的Bean
- getObjectType(): 返回产品类型,便于类型匹配与自动装配。
- isSingleton(): 决定产品是否为单例(影响缓存与生命周期)。
## 获取“产品”还是“工厂本身”
- 普通名context.getBean("beanName") 获取的是产品对象getObject返回值
- 带&前缀context.getBean("&beanName") 获取的是FactoryBean自身。
- 命名规则:注册名为 x 的 FactoryBean会对外暴露“产品”名为 x“工厂自身”为 &x。
## 最小可运行示例
```java
// 一个业务产品
public class ApiClient {
private final String endpoint;
public ApiClient(String endpoint) { this.endpoint = endpoint; }
public String call(String path) { return "GET " + endpoint + path; }
}
// FactoryBean 实现
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
public class ApiClientFactoryBean implements FactoryBean<ApiClient>, InitializingBean {
private String endpoint;
public void setEndpoint(String endpoint) { this.endpoint = endpoint; }
@Override
public ApiClient getObject() {
// 可在此放入复杂构建/代理/缓存等逻辑
return new ApiClient(endpoint);
}
@Override
public Class<?> getObjectType() { return ApiClient.class; }
@Override
public boolean isSingleton() { return true; }
@Override
public void afterPropertiesSet() {
Assert.hasText(endpoint, "endpoint must not be empty");
}
}
// Java 配置注册 FactoryBean
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean(name = "apiClient")
public ApiClientFactoryBean apiClientFactoryBean() {
ApiClientFactoryBean fb = new ApiClientFactoryBean();
fb.setEndpoint("https://api.example.com");
return fb;
}
}
// 取产品与取工厂本身
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Demo {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
ApiClient client = ctx.getBean("apiClient", ApiClient.class); // 产品
ApiClientFactoryBean fb = ctx.getBean("&apiClient", ApiClientFactoryBean.class); // 工厂本身
System.out.println(client.call("/ping"));
}
}
```
## 常见坑与最佳实践
- getObjectType 切勿返回 null尽量返回接口/具体类型便于按类型注入与AOT分析。
- isSingleton 与产品生命周期单例将被容器缓存非单例每次getBean都会重新创建产品。
- 懒加载与预实例化在ApplicationContext中如希望延迟创建可使用@Lazy或将FactoryBean产品设为非单例
- 自动装配歧义按类型注入时注入到的是产品类型而非FactoryBean需要注入工厂本身时使用@Qualifier("&name")或@Resource(name="&name")。
- 原型产品与循环依赖:原型产品不参与循环依赖的三级缓存提前暴露,避免在原型链路中引入循环依赖。
- 命名规范:确保文档/注释标明“&”语义,避免团队误用。
## 什么时候用哪一个?
- 仅需容器功能优先ApplicationContext功能更全默认预实例化
- 需要懒加载到极致考虑BeanFactory或在ApplicationContext中对关键Bean标注@Lazy
- 对象构建复杂/需代理/外部SDK桥接使用FactoryBean封装构建细节对外仅暴露产品。
## 小结
- BeanFactory 是IoC容器的最小抽象ApplicationContext在其上增强了企业级特性。
- FactoryBean 是“创建Bean的Bean”对外暴露产品使用“&name”获取工厂本身。
- 合理利用FactoryBean可显著简化复杂对象创建并保持应用装配的清晰与解耦。

View File

@@ -0,0 +1,291 @@
---
title: Spring配置相关的注解
published: 2025-08-08
description: ''
image: ''
tags: ["Spring","Java"]
category: 'Java > Spring'
draft: false
lang: ''
---
当然!这是一个非常重要且实用的主题。在 Spring 和 Spring Boot 中与属性Property相关的注解是实现“配置与代码分离”这一核心原则的关键。
我将为你全面、系统地讲解这些注解,从最基础到最常用,再到高级用法,并配上清晰的示例。
我们将主要围绕以下几个核心注解展开:
1. **`@Value`**: 最基础的,用于注入单个属性值。
2. **`@PropertySource`**: 用于加载指定的属性文件。
3. **`@ConfigurationProperties`**: 最强大、最推荐的用于类型安全地将一组属性绑定到Java对象上。
4. **`@EnableConfigurationProperties`**: 与 `@ConfigurationProperties` 配合使用,用于激活属性绑定。
5. **`@TestPropertySource`**: 在测试环境中加载或覆盖属性。
---
### 注解族谱概览
为了方便理解,我们可以把它们分为三类:
* **值注入 (Value Injection)**: `@Value`
* **配置源 (Configuration Source)**: `@PropertySource`, `@TestPropertySource`
* **批量绑定 (Bulk Binding)**: `@ConfigurationProperties`, `@EnableConfigurationProperties`
---
### 1. `@Value`:简单直接的“单兵作战”
这是注入属性最基本的方式。
* **作用**: 将 Spring 环境Environment中的单个属性值注入到类的字段或方法参数中。
* **语法**: 使用 SpEL (Spring Expression Language) 表达式 `"${property.key}"`
* **优点**: 简单、直接,适合注入少量、分散的配置。
* **缺点**:
* 当属性很多时,代码会显得分散和混乱。
* 类型安全性较弱都是字符串需要Spring转换
* 重构时(如修改前缀)非常痛苦。
**示例:**
**`src/main/resources/application.properties`**
```properties
app.name=My Awesome App
app.version=2.1.5
app.author.name=Alex
# 如果某个属性可能不存在,可以提供默认值
# mail.default.sender=default@example.com
```
**Java 类**
```java
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class AppInfoService {
// 注入 app.name 属性
@Value("${app.name}")
private String appName;
// 注入 app.version 属性
@Value("${app.version}")
private String appVersion;
// 注入一个不存在的属性,但提供了默认值 "unknown"
@Value("${app.description:unknown description}")
private String appDescription;
// 也可以注入其他 Bean 的属性(使用 SpEL
@Value("#{someOtherBean.someProperty}")
private String otherProperty;
public void printAppInfo() {
System.out.println("App Name: " + appName);
System.out.println("App Version: " + appVersion);
System.out.println("App Description: " + appDescription);
}
}
```
---
### 2. `@PropertySource`:指定“情报来源”
默认情况下Spring Boot 会自动加载 `application.properties``application.yml`。如果你想加载其他配置文件,就需要用到 `@PropertySource`
* **作用**: 将指定的属性文件加载到 Spring 的 `Environment` 中。
* **使用场景**:
* 模块化配置,将不同功能的配置放在不同文件里(如 `mail.properties`, `db.properties`)。
* 加载 classpath 之外的文件系统中的配置。
* **注意**: 它只负责**加载**,不负责注入。加载后,你可以用 `@Value``@ConfigurationProperties` 来使用这些属性。
**示例:**
**`src/main/resources/mail.properties`**
```properties
mail.host=smtp.gmail.com
mail.port=587
```
**Java 配置类**
```java
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.beans.factory.annotation.Value;
@Configuration
// 加载 classpath 下的 mail.properties 文件
@PropertySource("classpath:mail.properties")
public class MailConfig {
@Value("${mail.host}")
private String host;
@Value("${mail.port}")
private int port;
// ...
}
```
---
### 3. `@ConfigurationProperties`
这是 Spring Boot **最推荐**的属性管理方式。它将一组相关的属性映射到一个类型安全的 Java 对象POJO上。
* **作用**: 将具有相同前缀的属性批量绑定到一个 POJO 的字段上。
* **优点**:
* **类型安全**: 直接映射到 `int`, `List`, `Duration` 等各种类型。
* **结构清晰**: 将相关配置聚合在一个类中,非常易于管理和维护。
* **强大的绑定**: 支持复杂的对象图比如嵌套对象、列表、Map等。
* **IDE 友好**: 主流 IDE如 IntelliJ IDEA支持对 `application.properties` 中这类属性的自动补全和导航(需要添加 `spring-boot-configuration-processor` 依赖)。
**示例:**
**`application.properties`**
```properties
app.server.name=prod-server
app.server.ip-address=192.168.1.100
app.server.timeout=30s # Spring Boot 2.x 支持时间单位
app.server.admins[0].name=admin1
app.server.admins[0].email=admin1@corp.com
app.server.admins[1].name=admin2
app.server.admins[1].email=admin2@corp.com
```
**Java 属性类 (POJO)**
```java
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.time.Duration;
import java.util.List;
// 告诉 Spring 这个类要绑定前缀为 "app.server" 的属性
@ConfigurationProperties(prefix = "app.server")
public class ServerProperties {
private String name;
private String ipAddress;
private Duration timeout; // 自动将 "30s" 转换为 Duration 对象
private List<Admin> admins;
// 嵌套类
public static class Admin {
private String name;
private String email;
// Getters and Setters for Admin
}
// ⭐ 重要: 必须为所有字段提供 public Getters and Setters
// Spring 通过它们来注入值
// ... Getters and Setters for ServerProperties ...
}
```
---
### 4. `@EnableConfigurationProperties`
`@ConfigurationProperties` 只是一个声明,它本身不会让这个 POJO 成为一个 Spring Bean。你需要一种方式来“激活”它。`@EnableConfigurationProperties` 就是这个开关。
* **作用**:
1. 告诉 Spring 去处理被 `@ConfigurationProperties` 注解的类。
2. 将被注解的类(如 `ServerProperties`)注册到 Spring 容器中,让它成为一个 Bean。这样你就可以在其他地方 `@Autowired` 注入它了。
* **通常放在哪**: 主启动类或任何 `@Configuration` 类上。
**示例:**
```java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@SpringBootApplication
// 激活对 ServerProperties 类的绑定,并将其注册为 Bean
@EnableConfigurationProperties(ServerProperties.class)
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
// 现在可以在任何其他组件中注入它
@Service
public class MyService {
private final ServerProperties serverProps;
@Autowired
public MyService(ServerProperties serverProps) {
this.serverProps = serverProps;
System.out.println("Server Name: " + serverProps.getName());
}
}
```
> **快捷方式**: 如果你在 `ServerProperties` 类上同时加上 `@Component` 和 `@ConfigurationProperties`,就可以省略 `@EnableConfigurationProperties`。但显式使用 `@EnableConfigurationProperties` 通常被认为是更清晰的做法,因为它明确表达了这是一个配置类。
---
### 5. `@TestPropertySource`:为测试“定制情报”
在进行单元测试或集成测试时,我们经常需要使用一套不同于生产环境的配置(比如连接到内存数据库 H2
* **作用**: 在测试上下文中加载属性,它可以覆盖`application.properties`中的属性或添加新属性。
* **常用属性**:
* `locations`: 指定要加载的属性文件路径。
* `properties`: 以 `key=value` 形式直接定义内联属性。
**示例:**
```java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.env.Environment;
import org.springframework.test.context.TestPropertySource;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest
// 1. 加载 test.properties 文件
// 2. 直接定义一个内联属性,它会覆盖 test.properties 或 application.properties 中的同名属性
@TestPropertySource(
locations = "classpath:test.properties",
properties = "app.version=test-1.0"
)
public class AppInfoServiceTest {
@Autowired
private Environment env;
@Test
void testPropertiesAreLoaded() {
// 来自 test.properties
assertThat(env.getProperty("app.name")).isEqualTo("Test App");
// 被内联属性覆盖
assertThat(env.getProperty("app.version")).isEqualTo("test-1.0");
}
}
```
---
### 总结与最佳实践
| 注解 | 用途 | 何时使用 |
| :--- | :--- | :--- |
| **`@Value`** | 注入单个值 | 当你只需要一两个简单的配置时。 |
| **`@PropertySource`** | 加载额外的属性文件 | 当你的配置分散在多个自定义文件中时。 |
| **`@ConfigurationProperties`** | **批量**、**类型安全**地绑定属性到对象 | **首选方式**。当你有一组相关配置时如数据库、邮件、API密钥等。 |
| **`@EnableConfigurationProperties`** | 激活 `@ConfigurationProperties` 的类 | 总是与 `@ConfigurationProperties` 配合使用(除非用了`@Component`快捷方式)。 |
| **`@TestPropertySource`** | 在测试中覆盖或提供配置 | 编写需要特定配置的集成测试或单元测试时。 |
**最佳实践**:
* **优先使用 `@ConfigurationProperties`**:对于任何超过两三个的相关配置,都应创建一个专用的 `Properties` 类。这会让你的代码更健壮、更易于维护。
* **集中管理**: 将 `@EnableConfigurationProperties` 放在主配置类或一个集中的 `AppConfig` 类中,而不是到处分散。
* **利用元数据**: 添加 `spring-boot-configuration-processor` 依赖到 `pom.xml``build.gradle`,以获得强大的 IDE 支持。

View File

@@ -0,0 +1,68 @@
---
title: 数据库ACID四大特性
published: 2025-08-07
description: ''
image: ''
tags: [ACID,四大特性]
category: '中间件 > MySQL'
draft: false
lang: ''
---
# 什么是ACID四大特性
A Atomicity原子性
C Consistency一致性
I Isolation隔离性
D Durability持久性
# Atomicity原子性
这里要先讲一下什么是事务: 简单说事务就是一组原子性的SQL执行单元。如果数据库引擎能够成功地对数据库应 用该组査询的全部语句那么就执行该组SQL。如果其中有任何一条语句因为崩溃或其 他原因无法执行那么所有的语句都不会执行。要么全部执行成功commit要么全部执行失败rollback
**原子性指的是一个事务中所有操作要么全部成功要么全部失败。**
# Consistency一致性
数据库的一致性指的是: 每个事务必须使数据库从一个合法的状态,转变到另一个合法的状态,并且在事务执行前后,数据库的各种完整性约束都得以保持。
什么是完整性约束呢?
完整性约束是指数据库中数据的规则和限制,比如主键约束、外键约束、唯一性约束等。
主键约束: 确保每条记录都有唯一标识。
外键约束: 确保数据之间的引用关系正确。
唯一性约束: 确保某列的值在表中是唯一的
![](https://blog.meowrain.cn/api/i/2025/08/08/f8ed5e-1.webp)
举个例子:
当我们向订单表插入一条记录的时候,如果指定的`customer_id`在客户表中不存在,那么这个事务就不应该被提交,因为这会破坏数据的一致性。因此,外键约束会阻止事务提交,确保数据库的一致性。抛出外键约束异常
# Isolation隔离性
数据库的隔离性指的是: 每个事务的执行都应该是独立的,互不干扰。即使多个事务同时执行,也不会影响彼此的结果。
隔离性确保了事务之间的独立性,防止了脏读、不可重复读和幻读等问题。
如果没有隔离性,在多个用户并发访问数据库的情况下,可能会出现以下问题:
- 脏读(Dirty Read):
一个事务读取到另一个事务尚未提交的修改,如果该事务回滚,那么读取到的就是无效数据。
- 不可重复读(Non-repeatable Read):
一个事务在同一事务内多次读取同一数据,却得到不同的结果,这是因为其他事务修改并提交了数据。
- 幻读(Phantom Read):
一个事务在同一事务内多次执行相同的查询,但是每次查询返回的结果集不同,这是因为其他事务插入或删除了数据。
![](https://blog.meowrain.cn/api/i/2025/08/08/f82hdp-1.webp)
事务隔离级别:
- 读未提交 >> 事务可以读取其他事务未提交的数据,可能会导致脏读。
- 读已提交 >> 事务只能读取已提交的数据,防止脏读。
- 可重复读 >> 事务在执行期间多次读取同一数据,结果保持一致,防止不可重复读。
- 串行化 >> 事务完全隔离,按顺序执行,防止幻读。
数据库的默认隔离级别是**可重复读Repeatable Read**,它可以防止脏读和不可重复读,但可能会出现幻读。(也就是无法避免读取数据的时候,其他事务提交新的数据或者删除数据,导致查询的结果集发生变化。
> 之前老把幻读和不可重复读搞混,现在再讲一下,所谓幻读,就是说读取数据过程中,另外一个数据库事务插入或者删除了数据,导致查询的数据结果集发生变化。而不可重复读是指在同一个事务中多次读取同一个数据,期间其他事务修改了数据,导致数据结果集不一致。
每个隔离级别都提供了不同程度的隔离性和性能,具体选择取决于应用场景和需求。
# Durability持久性
数据库的持久性指的是: 一旦事务提交,对数据库的修改就会永久保存,即使系统崩溃也不会丢失。
持久性确保了数据的可靠性和稳定性,即使在系统故障或崩溃后,已提交的事务数据仍然可以恢复。

View File

@@ -0,0 +1,157 @@
---
title: redis配置json序列化
published: 2025-08-08
description: ''
image: 'https://blog.meowrain.cn/api/i/2025/07/19/p9zr81-1.webp'
tags: [Redis, 中间件, Redis数据类型]
category: '中间件 > Redis'
draft: false
lang: ''
---
# Redis配置序列化
序列化的最终目的是为了对象可以跨平台存储,进行网络传输。
Redis默认用的是JdkSerializationRedisSerializer它使用JDK提供的序列化功能优点是反序列化的时候不需要提供类型信息但缺点是序列化后的数据体积较大性能较低。
因此通常会使用更高效的序列化方式如JSON、Protobuf等
Jackson2JsonRedisSerializer 使用Jackson库将对象序列化为JSON字符串。
优点是速度快序列化后的字符串短小精悍不需要实现Serializable接口。
但缺点也非常致命,那就是此类的构造函数中有一个类型参数,必须提供要序列化对象的类型信息(.class对象)。 通过查看源代码,发现其只在反序列化过程中用到了类型信息。
现在的问题是: 如果使用默认的JDK序列化方式在Redis可视化工具中查看kv值的时候会出现乱码而使用Jackson2JsonRedisSerializer序列化后kv值在Redis可视化工具中查看时是正常的。
StringRedisTemplate → Key 和 Value 都是 String 序列化简单粗暴适合存验证码、token、计数器、纯 JSON 文本之类的轻量数据。
自定义 RedisTemplate<String, Object> → 用了 JSON 序列化器,直接存 Java 对象,取出来就能反序列化成原类型,适合缓存业务对象、集合、复杂结构等。
# 配置
```java
package org.example.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class MyRedisConfig {
/**
* RedisTemplate 配置类
* - 使用自定义的 ObjectMapper + GenericJackson2JsonRedisSerializer
* - 实现 key 用字符串序列化value 用 JSON 序列化
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
// 创建 RedisTemplate指定 key 类型为 Stringvalue 类型为 Object
RedisTemplate<String, Object> template = new RedisTemplate<>();
// 设置 Redis 连接工厂(由 Spring Boot 配置的连接信息决定)
template.setConnectionFactory(factory);
// 创建并配置 Jackson 的 ObjectMapper用于 JSON 序列化/反序列化)
ObjectMapper mapper = new ObjectMapper();
// 设置可见性:让所有字段(包括 private都参与序列化和反序列化
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 启用默认类型信息(多态类型序列化)
// NON_FINAL 表示对所有非 final 类(如普通类、接口实现类)写入类型信息
// 好处:反序列化时可以还原原始对象类型(避免反成 LinkedHashMap
mapper.activateDefaultTyping(
mapper.getPolymorphicTypeValidator(), // 类型验证器,防止反序列化攻击
ObjectMapper.DefaultTyping.NON_FINAL // 应用于所有非 final 的类
);
// 创建 JSON 序列化器,并注入自定义的 ObjectMapper
GenericJackson2JsonRedisSerializer serializer =
new GenericJackson2JsonRedisSerializer(mapper);
// key 采用字符串序列化器,保证可读性(在 Redis CLI 中能直接看到)
template.setKeySerializer(new StringRedisSerializer());
// value 采用 JSON 序列化器,支持存储任意对象
template.setValueSerializer(serializer);
// hash 结构的 key 也用字符串序列化
template.setHashKeySerializer(new StringRedisSerializer());
// hash 结构的 value 也用 JSON 序列化
template.setHashValueSerializer(serializer);
// 初始化 RedisTemplate 的配置
template.afterPropertiesSet();
return template;
}
}
```
> 什么是反序列化攻击?
反序列化攻击是指攻击者通过构造恶意的序列化数据,利用应用程序在反序列化过程中执行不安全的代码或操作,从而导致安全漏洞。攻击者可以通过发送特制的序列化数据包,触发应用程序执行未授权的操作、获取敏感信息或执行任意代码。
# 使用
```java
package org.example;
import org.example.entity.Human;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ApplicationContext;
import org.springframework.data.redis.core.RedisTemplate;
@SpringBootApplication
public class Main {
public static void main(String[] args) {
ApplicationContext ctx = new SpringApplicationBuilder(Main.class)
.web(WebApplicationType.NONE) // 不启动 Tomcat
.run(args);
// 按名字拿,避免和 stringRedisTemplate 冲突
RedisTemplate<String, Object> redisTemplate =
(RedisTemplate<String, Object>) ctx.getBean("redisTemplate");
// 打印一下序列化器,确认确实是你配置的
System.out.println("KeySerializer = " + redisTemplate.getKeySerializer().getClass().getName());
System.out.println("ValueSerializer = " + redisTemplate.getValueSerializer().getClass().getName());
String key = "test:human:" + System.currentTimeMillis();
Human h = new Human("jackv", "dfasdfssfsdf");
redisTemplate.opsForValue().set(key, h);
Object v = redisTemplate.opsForValue().get(key);
System.out.println("Fetched value class = " + (v == null ? "null" : v.getClass().getName()));
System.out.println("Fetched value = " + v);
// 简单校验:能拿回对象、类型是你期望的
if (!(v instanceof Human)) {
throw new IllegalStateException("不是 Human而是" + (v == null ? "null" : v.getClass()));
}
// 可选:清理
redisTemplate.delete(key);
System.out.println("OK, JSON 序列化/反序列化正常。");
}
}
```
在其他类的时候直接@Autowired注入RedisTemplate即可使用
```java
@Autowired
@Qualifier("redisTemplate")
private RedisTemplate<String, Object> redisTemplate;
```