🌟 总体结构概览
这个章节主要回答以下几个关键问题:
- 注解 vs XML:哪个更好?
- 如何启用基于注解的配置?
- 常用的注解有哪些?它们怎么工作?
- 当有多个候选 Bean 时,如何精确选择?(@Primary 和 @Qualifier)
- 如何自定义更复杂的匹配规则?
🔹 一、注解 vs XML:哪个更好?
原文开头就提出这个问题,并给出中立的回答。
✅ 核心观点:
- 没有绝对的"更好",取决于团队风格和项目需求。
- 注解的优点:
- 更简洁,配置贴近代码,语义清晰。
- 开发效率高,适合现代开发模式(如 JavaConfig)。
- XML 的优点:
- 不修改源码即可调整配置(适合运维、多环境部署)。
- 集中式管理所有 Bean 的依赖关系,便于全局掌控。
- Spring 支持两者混合使用 ,并且推荐使用
<context:annotation-config/>来开启注解支持。
💡 类比:注解像是"内联样式",XML 像是"外部 CSS 文件"。
🔹 二、如何启用注解支持?
使用 <context:annotation-config/>
xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="...">
<context:annotation-config/>
</beans>
它做了什么?
自动注册以下 BeanPostProcessor(后处理器),让注解生效:
| 后处理器 | 功能 |
|---|---|
AutowiredAnnotationBeanPostProcessor |
处理 @Autowired, @Inject |
CommonAnnotationBeanPostProcessor |
处理 @PostConstruct, @PreDestroy, @Resource |
PersistenceAnnotationBeanPostProcessor |
处理 JPA 注解(如 @PersistenceContext) |
EventListenerMethodProcessor |
处理事件监听 @EventListener |
ConfigurationClassPostProcessor |
处理 @Configuration, @ComponentScan 等 |
⚠️ 注意:
<context:annotation-config/>只对当前 ApplicationContext 中定义的 Bean 起作用。比如在 Web 应用中,如果你在
DispatcherServlet的上下文中加了它,它只会影响 Controller,不会影响 Service 层(除非 Service 也在该上下文里)。
🔹 三、核心注解详解
1️⃣ @Required(已废弃)
- 作用: 标记某个 setter 方法必须被设置值(通过 XML 或自动装配)。
- 未设置会抛异常。
- ❌ 已过时(Deprecated),Spring 5.1 开始建议使用构造函数注入代替。
推荐做法:用构造函数注入 +
final字段确保必填。
2️⃣ @Autowired
这是最核心的注解,用于自动装配依赖。
✅ 可以标注在:
| 位置 | 示例 |
|---|---|
| 构造函数 | 推荐方式,保证依赖不可变 |
| Setter 方法 | 传统方式 |
| 任意多参数方法 | 自定义初始化逻辑 |
| 字段 | 最常见,但不推荐(破坏封装性) |
示例:构造函数注入
java
public class MovieRecommender {
private final CustomerPreferenceDao dao;
@Autowired
public MovieRecommender(CustomerPreferenceDao dao) {
this.dao = dao;
}
}
✅ Spring 4.3+:如果类只有一个构造函数,
@Autowired可省略。
自动装配集合类型
Spring 支持将所有实现某接口的 Bean 注入到集合中:
java
@Autowired
private MovieCatalog[] catalogs; // 数组
@Autowired
private List<MovieCatalog> catalogs; // List
@Autowired
private Map<String, MovieCatalog> catalogs; // key=bean名称, value=bean实例
这非常适用于插件式设计、策略模式等场景。
控制是否为"必需"
默认情况下,找不到匹配的 Bean 会报错。
可以用 required = false 允许为空:
java
@Autowired(required = false)
public void setMovieFinder(MovieFinder finder) {
// 如果没有 MovieFinder Bean,此方法不会被调用
}
或者使用:
Optional<MovieFinder>(Java 8+)@Nullable MovieFinder finder- Kotlin 的可空类型
MovieFinder?
3️⃣ 自动注入特殊对象
Spring 可以自动注入一些"上下文"对象,无需显式声明 Bean:
java
@Autowired
private ApplicationContext context;
@Autowired
private Environment environment;
@Autowired
private BeanFactory beanFactory;
这些是 Spring 内建的支持,非常方便获取运行时信息。
🔹 四、解决"多个候选 Bean"的问题
当 Spring 发现多个类型匹配的 Bean 时,就会抛出 NoUniqueBeanDefinitionException。
解决方法有两个层次:
方法一:@Primary
标记一个 Bean 为"首选项"。
java
@Bean
@Primary
public MovieCatalog mainCatalog() {
return new SimpleMovieCatalog();
}
@Bean
public MovieCatalog backupCatalog() {
return new SimpleMovieCatalog();
}
此时:
java
@Autowired
private MovieCatalog catalog; // 注入的是 mainCatalog
相当于在 XML 中写
primary="true"
方法二:@Qualifier(更精细控制)
@Qualifier 是"限定符",用来进一步缩小候选范围。
基本用法:
java
@Autowired
@Qualifier("main")
private MovieCatalog catalog;
对应的 Bean 定义:
xml
<bean class="SimpleMovieCatalog">
<qualifier value="main"/>
</bean>
或 Java Config:
java
@Bean
@Qualifier("main")
public MovieCatalog mainCatalog() { ... }
💡 提示:如果没有
@Qualifier,Spring 会尝试用字段名/参数名去匹配 Bean 名称(fallback 机制)。
自定义 @Qualifier 注解(高级用法)
你可以创建自己的注解,带上语义信息。
示例:按"电影类型"区分
java
@Target({FIELD, PARAMETER})
@Retention(RUNTIME)
@Qualifier
public @interface Genre {
String value();
}
然后使用:
java
@Autowired
@Genre("Action")
private MovieCatalog actionCatalog;
XML 配置:
xml
<bean class="SimpleMovieCatalog">
<qualifier type="Genre" value="Action"/>
</bean>
复合条件的 Qualifier
还可以定义带多个属性的注解:
java
@Qualifier
public @interface MovieQualifier {
String genre();
Format format(); // enum: VHS, DVD, BLURAY
}
然后:
java
@Autowired
@MovieQualifier(genre = "Action", format = Format.DVD)
private MovieCatalog actionDvd;
XML 中可以用 <attribute> 匹配:
xml
<qualifier type="MovieQualifier">
<attribute key="genre" value="Action"/>
<attribute key="format" value="DVD"/>
</attribute>
🔹 五、其他重要细节
⏱ 注入顺序:注解先于 XML
注解注入发生在 XML 注入之前。
这意味着:XML 配置可以覆盖注解注入的值。
例如:
java
@Autowired
private MovieFinder finder; // 注解注入
但在 XML 中:
xml
<bean id="client" class="...">
<property name="finder" ref="myCustomFinder"/>
</bean>
最终结果是 XML 的配置生效。
🚫 注意:不能在 BeanPostProcessor 中使用 @Autowired
因为 @Autowired 是由 BeanPostProcessor 实现的,所以你不能在你自己写的 BeanPostProcessor 或 BeanFactoryPostProcessor 中使用 @Autowired ------ 它们还没被处理!
这类组件必须通过 XML 或 @Bean 显式配置。
🔚 总结:一张图帮你理解
Spring 注解配置核心流程
┌────────────────────┐
│ 启用注解支持 │ ← <context:annotation-config/>
└────────────────────┘
↓
┌────────────────────┐
│ @Autowired 找类型匹配 │ → 可能出现多个候选
└────────────────────┘
↓
┌────────────┐
│ @Primary? ├── 是 → 选 primary 的那个
└────────────┘
↓ 否
┌────────────┐
│ @Qualifier? ├── 是 → 按 qualifier 匹配
└────────────┘
↓ 否
┌────────────┐
│ 字段名匹配? ├── 是 → 按字段名找 Bean
└────────────┘
↓ 否
报错!
✅ 最佳实践建议
| 建议 | 说明 |
|---|---|
| ✅ 优先使用构造函数注入 | 更安全、不可变、易于测试 |
| ✅ 少用字段注入 | 虽然方便,但破坏封装,不利于单元测试 |
✅ 用 @Primary 或 @Qualifier 解决歧义 |
清晰表达意图 |
✅ 自定义 @Qualifier 提升可读性 |
如 @Offline, @Production |
✅ 避免 @Autowired(required=false) |
容易隐藏问题,建议用 Optional |
✅ 不要在后处理器中使用 @Autowired |
无效!需手动注册 |
📚 延伸阅读建议
- Spring 官方文档 - Core Technologies
@Component,@Service,@Repository,@Controller@ComponentScan:自动发现并注册组件@Configuration+@Bean:Java 配置类@Value:注入属性值@Profile:条件化加载 Bean
如果你想进一步了解某个具体注解(比如 @Resource vs @Autowired),或者想看实际项目中的配置案例,也可以继续问我!