Spring注解配置全解析

🌟 总体结构概览

这个章节主要回答以下几个关键问题:

  1. 注解 vs XML:哪个更好?
  2. 如何启用基于注解的配置?
  3. 常用的注解有哪些?它们怎么工作?
  4. 当有多个候选 Bean 时,如何精确选择?(@Primary 和 @Qualifier)
  5. 如何自定义更复杂的匹配规则?

🔹 一、注解 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 实现的,所以你不能在你自己写的 BeanPostProcessorBeanFactoryPostProcessor 中使用 @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),或者想看实际项目中的配置案例,也可以继续问我!

相关推荐
xiezhr1 分钟前
Java开发中那些常见的坑,你踩过几个?
java·spring boot·spring
程序员爱钓鱼7 分钟前
Python 实战:如何读取多格式 Excel 并实现跨表匹配合并(支持 XLS / XLSX)
后端·python·面试
xiezhr13 分钟前
Java开发中最那些常见的坑,你踩过几个?
java·spring·springboot·后端开发
程序员爱钓鱼14 分钟前
Python编程实战:实现一个 Excel 批量处理工具(桌面实用脚本)
后端·python·ipython
q***235722 分钟前
Spring Boot+Vue项目从零入手
vue.js·spring boot·后端
风象南26 分钟前
Spring Boot + MyBatis:实现数据库字段级加密
后端
q***071432 分钟前
Spring Boot管理用户数据
java·spring boot·后端
Victor3562 小时前
Redis(129)Redis的字典(Dictionary)是如何实现的?
后端
麦麦鸡腿堡2 小时前
Java绘图技术
java·开发语言
Victor3562 小时前
Redis(128)Redis的跳表(Skip List)是如何实现的?
后端