以下这段内容是来自 Spring Framework 官方文档(或类似资料)中关于依赖注入和配置的部分,主要涵盖了以下几个核心知识点:
- 使用泛型作为自动装配的"隐式限定符"(1.9.5)
- 自定义
@Qualifier注解类型(1.9.6) - 使用 JSR-250 的
@Resource进行注入(1.9.7) - 使用
@Value注入外部化属性与 SpEL 表达式(1.9.8) - 使用
@PostConstruct和@PreDestroy管理 Bean 生命周期(1.9.9)
下面我将用通俗易懂的方式逐段解释这些概念,并帮助你理解它们在实际开发中的用途。
🔹 1.9.5 使用泛型作为自动装配限定符(Using Generics as Autowiring Qualifiers)
📌 核心思想:
Spring 可以根据 接口的泛型类型 来决定注入哪个 Bean。这相当于一种"隐式的 @Qualifier"。
🧩 示例解析:
java
@Bean
public StringStore stringStore() { return new StringStore(); }
@Bean
public IntegerStore integerStore() { return new IntegerStore(); }
假设这两个类都实现了同一个泛型接口:
java
interface Store<T> { ... }
class StringStore implements Store<String> { ... }
class IntegerStore implements Store<Integer> { ... }
那么你可以这样自动装配:
java
@Autowired
private Store<String> s1; // 自动注入 stringStore
@Autowired
private Store<Integer> s2; // 自动注入 integerStore
✅ 原理说明:
虽然两个 Bean 都实现的是 Store<T> 接口,但 Spring 在容器启动时会检查其实现的具体泛型类型(String 或 Integer),并以此作为"资格筛选条件",实现精准匹配。
💡 应用场景:
当你有多个同类接口的不同数据类型的实现时,比如:
Repository<User>Repository<Order>
可以用泛型来避免写一堆@Qualifier("userRepo")。
⚠️ 注意事项:
- 必须是接口或父类带有泛型,且子类明确指定了具体类型。
- 对于 List/Map 数组也适用:
java
@Autowired
private List<Store<Integer>> stores; // 只包含泛型为 Integer 的 Store 实例
🔹 1.9.6 使用 CustomAutowireConfigurer(高级用法)
📌 核心思想:
允许你注册自定义的注解作为 @Qualifier 的替代品,即使这个注解没有标记 @Qualifier。
🧩 XML 配置示例:
xml
<bean class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer">
<property name="customQualifierTypes">
<set>
<value>example.CustomQualifier</value>
</set>
</property>
</bean>
然后你可以定义自己的注解:
java
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomQualifier {
}
并在 Bean 上使用它:
java
@Component
@CustomQualifier
public class MyService implements SomeService { ... }
再通过 @Autowired + 类型匹配 + 自定义注解进行限定:
java
@Autowired
@CustomQualifier
private SomeService service;
✅ 意义:
让你可以创建领域相关的语义化注解,比如:
@PaypalPayment@AlipayPayment
而不是总是用@Qualifier("alipay")字符串硬编码。
🔹 1.9.7 使用 @Resource 注入(JSR-250 标准)
📌 核心思想:
@Resource 是 Java EE 提供的标准注解(属于 JSR-250),Spring 支持它,其默认行为是 按名称(by-name) 查找 Bean。
🆚 对比 @Autowired:
| 注解 | 来源 | 默认行为 | 是否支持名称 |
|---|---|---|---|
@Autowired |
Spring | 按类型(by-type)+ 主要候选者(primary) | 否(需配合 @Qualifier) |
@Resource |
Java EE (JSR-250) | 按名称(by-name) | 是(name 属性) |
✅ 示例:
java
@Resource(name = "myMovieFinder")
private MovieFinder movieFinder;
等价于查找名为 "myMovieFinder" 的 Bean。
如果不指定 name:
java
@Resource
private MovieFinder movieFinder;
则 Spring 会尝试找一个叫 movieFinder 的 Bean(字段名 → Bean 名称)。
🔄 特殊情况回退机制:
如果没有找到同名 Bean,Spring 会退回到 按类型匹配,并且还能识别一些特殊类型:
java
@Resource
private ApplicationContext context; // OK: 自动注入 ApplicationContext
因为 ApplicationContext 是 Spring 内部已知的"可解析依赖"。
💡 小结:
@Resource更偏向"名字匹配"@Autowired更偏向"类型匹配"- 多数情况下推荐使用
@Autowired + @Qualifier,更符合 Spring 风格 - 但在某些遗留系统或 Java EE 兼容项目中可能见到
@Resource
🔹 1.9.8 使用 @Value 注入值
📌 核心功能:
从配置文件、环境变量、系统属性或 SpEL 表达式中读取值,注入到字段或构造函数参数中。
✅ 基本用法:读取 properties 文件
properties
# application.properties
catalog.name=MovieCatalog
java
@Component
public class MovieRecommender {
public MovieRecommender(@Value("${catalog.name}") String catalog) {
// catalog == "MovieCatalog"
}
}
✅ 设置默认值:
java
@Value("${catalog.name:defaultCatalog}")
如果 catalog.name 找不到,则使用 defaultCatalog。
✅ 使用 SpEL(Spring Expression Language)动态计算值:
java
@Value("#{systemProperties['user.home']}")
private String homeDir;
@Value("#{T(java.lang.Math).random() * 100}")
private double randomNumber;
// 创建 Map 结构
@Value("#{{'Thriller': 100, 'Comedy': 300}}")
private Map<String, Integer> genreCounts;
⚙️ 背后原理:
- Spring 使用
ConversionService自动转换字符串 → 目标类型(如 int、boolean、List 等) - 若你需要自定义类型转换(例如把
"red"转成Color.RED),可以注册自己的ConversionService:
java
@Bean
public ConversionService conversionService() {
DefaultFormattingConversionService cs = new DefaultFormattingConversionService();
cs.addConverter(new StringToColorConverter());
return cs;
}
❗ 关于占位符未解析的问题:
Spring 默认不会因 ${xxx} 找不到而报错,而是原样保留字符串。
✅ 解决方案:显式声明 PropertySourcesPlaceholderConfigurer:
java
@Bean
public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
⚠️ 注意:必须是
static方法,否则可能导致早期初始化问题。
📌 Spring Boot 用户注意 :
Boot 已经自动配置了这个 bean,所以可以直接使用 ${} 而无需手动添加。
🔹 1.9.9 使用 @PostConstruct 和 @PreDestroy 管理生命周期
📌 核心作用:
定义 Bean 初始化完成之后 和 销毁之前 要执行的方法。
| 注解 | 触发时机 | 等价于 |
|---|---|---|
@PostConstruct |
Bean 创建完毕,依赖注入完成后调用 | InitializingBean.afterPropertiesSet() 或 <init-method> |
@PreDestroy |
Bean 销毁前调用(仅适用于 singleton) | DisposableBean.destroy() 或 <destroy-method> |
✅ 示例:
java
public class CachingMovieLister {
@PostConstruct
public void populateMovieCache() {
System.out.println("缓存预热...");
}
@PreDestroy
public void clearMovieCache() {
System.out.println("清理缓存...");
}
}
⚠️ JDK 11+ 的注意事项:
@PostConstruct和@PreDestroy属于javax.annotation包- 从 JDK 9 开始被移出标准库,JDK 11 彻底移除
- 所以如果你用的是 JDK 11 或更高版本,需要手动引入依赖:
Maven:
xml
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
Gradle:
groovy
implementation 'javax.annotation:javax.annotation-api:1.3.2'
否则编译会报错找不到注解。
✅ 总结:一张表帮你理清区别
| 功能 | 注解 | 来源 | 匹配方式 | 说明 |
|---|---|---|---|---|
| 按类型自动注入 | @Autowired |
Spring | by-type | 最常用 |
| 添加限定条件 | @Qualifier("xxx") |
Spring | by-name (配合 @Autowired) | 精确指定 Bean 名称 |
| 泛型限定 | List<Store<Integer>> |
Spring | by generic type | 隐式 qualifier |
| 按名称注入 | @Resource(name="xxx") |
Java EE | by-name | 不依赖 Spring |
| 注入配置值 | @Value("${key}") |
Spring | - | 支持 SpEL 和默认值 |
| 初始化方法 | @PostConstruct |
Java EE | - | 替代 init-method |
| 销毁方法 | @PreDestroy |
Java EE | - | 替代 destroy-method |
💡 实际开发建议:
-
优先使用构造器注入 +
@Autowired(Spring Boot 默认)java@Component public class MovieService { private final MovieRepository repository; public MovieService(MovieRepository repository) { this.repository = repository; } } -
多实现时,使用
@Qualifier或泛型区分 -
配置项统一用
@Value或@ConfigurationProperties -
生命周期控制尽量用
@PostConstruct/@PreDestroy,简洁清晰 -
JDK >= 11 时记得加
javax.annotation-api依赖
如果你想进一步了解某一部分(比如 SpEL 语法大全、如何自定义 Converter、或者 Spring Boot 中如何替代 XML 配置),欢迎继续提问!