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:
265
src/content/posts/Java/Spring/Spring中的BeanFactory与FactoryBean.md
Normal file
265
src/content/posts/Java/Spring/Spring中的BeanFactory与FactoryBean.md
Normal 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实例。
|
||||
|
||||

|
||||
|
||||
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 的作用
|
||||
|
||||

|
||||
|
||||
- 将“复杂对象的创建逻辑”封装到工厂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可显著简化复杂对象创建,并保持应用装配的清晰与解耦。
|
||||
291
src/content/posts/Java/Spring/Spring配置相关的注解.md
Normal file
291
src/content/posts/Java/Spring/Spring配置相关的注解.md
Normal 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 支持。
|
||||
68
src/content/posts/中间件/MySQL/数据库ACID四大特性.md
Normal file
68
src/content/posts/中间件/MySQL/数据库ACID四大特性.md
Normal 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(一致性)
|
||||
数据库的一致性指的是: 每个事务必须使数据库从一个合法的状态,转变到另一个合法的状态,并且在事务执行前后,数据库的各种完整性约束都得以保持。
|
||||
|
||||
什么是完整性约束呢?
|
||||
完整性约束是指数据库中数据的规则和限制,比如主键约束、外键约束、唯一性约束等。
|
||||
主键约束: 确保每条记录都有唯一标识。
|
||||
外键约束: 确保数据之间的引用关系正确。
|
||||
唯一性约束: 确保某列的值在表中是唯一的
|
||||
|
||||

|
||||
|
||||
举个例子:
|
||||
当我们向订单表插入一条记录的时候,如果指定的`customer_id`在客户表中不存在,那么这个事务就不应该被提交,因为这会破坏数据的一致性。因此,外键约束会阻止事务提交,确保数据库的一致性。抛出外键约束异常
|
||||
|
||||
|
||||
# Isolation(隔离性)
|
||||
数据库的隔离性指的是: 每个事务的执行都应该是独立的,互不干扰。即使多个事务同时执行,也不会影响彼此的结果。
|
||||
隔离性确保了事务之间的独立性,防止了脏读、不可重复读和幻读等问题。
|
||||
|
||||
如果没有隔离性,在多个用户并发访问数据库的情况下,可能会出现以下问题:
|
||||
- 脏读(Dirty Read):
|
||||
一个事务读取到另一个事务尚未提交的修改,如果该事务回滚,那么读取到的就是无效数据。
|
||||
- 不可重复读(Non-repeatable Read):
|
||||
一个事务在同一事务内多次读取同一数据,却得到不同的结果,这是因为其他事务修改并提交了数据。
|
||||
- 幻读(Phantom Read):
|
||||
一个事务在同一事务内多次执行相同的查询,但是每次查询返回的结果集不同,这是因为其他事务插入或删除了数据。
|
||||
|
||||

|
||||
|
||||
事务隔离级别:
|
||||
- 读未提交 >> 事务可以读取其他事务未提交的数据,可能会导致脏读。
|
||||
- 读已提交 >> 事务只能读取已提交的数据,防止脏读。
|
||||
- 可重复读 >> 事务在执行期间多次读取同一数据,结果保持一致,防止不可重复读。
|
||||
- 串行化 >> 事务完全隔离,按顺序执行,防止幻读。
|
||||
|
||||
|
||||
数据库的默认隔离级别是**可重复读(Repeatable Read)**,它可以防止脏读和不可重复读,但可能会出现幻读。(也就是无法避免读取数据的时候,其他事务提交新的数据或者删除数据,导致查询的结果集发生变化。
|
||||
|
||||
> 之前老把幻读和不可重复读搞混,现在再讲一下,所谓幻读,就是说读取数据过程中,另外一个数据库事务插入或者删除了数据,导致查询的数据结果集发生变化。而不可重复读是指在同一个事务中多次读取同一个数据,期间其他事务修改了数据,导致数据结果集不一致。
|
||||
|
||||
每个隔离级别都提供了不同程度的隔离性和性能,具体选择取决于应用场景和需求。
|
||||
# Durability(持久性)
|
||||
数据库的持久性指的是: 一旦事务提交,对数据库的修改就会永久保存,即使系统崩溃也不会丢失。
|
||||
持久性确保了数据的可靠性和稳定性,即使在系统故障或崩溃后,已提交的事务数据仍然可以恢复。
|
||||
157
src/content/posts/中间件/Redis/redis配置序列化.md
Normal file
157
src/content/posts/中间件/Redis/redis配置序列化.md
Normal 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 类型为 String,value 类型为 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;
|
||||
```
|
||||
Reference in New Issue
Block a user