Spring依赖注入与配置全解析

以下这段内容是来自 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 在容器启动时会检查其实现的具体泛型类型(StringInteger),并以此作为"资格筛选条件",实现精准匹配。

💡 应用场景:

当你有多个同类接口的不同数据类型的实现时,比如:

  • 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

💡 实际开发建议:

  1. 优先使用构造器注入 + @Autowired(Spring Boot 默认)

    java 复制代码
    @Component
    public class MovieService {
        private final MovieRepository repository;
        
        public MovieService(MovieRepository repository) {
            this.repository = repository;
        }
    }
  2. 多实现时,使用 @Qualifier 或泛型区分

  3. 配置项统一用 @Value@ConfigurationProperties

  4. 生命周期控制尽量用 @PostConstruct / @PreDestroy,简洁清晰

  5. JDK >= 11 时记得加 javax.annotation-api 依赖


如果你想进一步了解某一部分(比如 SpEL 语法大全、如何自定义 Converter、或者 Spring Boot 中如何替代 XML 配置),欢迎继续提问!

相关推荐
百锦再4 小时前
破茧成蝶:全方位解析Java学习难点与征服之路
java·python·学习·struts·kafka·maven·intellij-idea
羊锦磊4 小时前
[ Redis ] SpringBoot集成使用Redis(补充)
java·数据库·spring boot·redis·spring·缓存·json
兮动人5 小时前
Maven 多配置文件的使用
java·maven·maven 多配置文件的使用
毕设源码-钟学长5 小时前
【开题答辩全过程】以 餐健一体化管理系统为例,包含答辩的问题和答案
java·eclipse
摇滚侠5 小时前
Spring Boot3零基础教程,整合 SSM,笔记52
java·spring boot·笔记
毕设源码-朱学姐6 小时前
【开题答辩全过程】以 查寝打卡系统为例,包含答辩的问题和答案
java·eclipse
QMY5205207 小时前
爬虫的意义
java·spring·tomcat·maven
lang201509287 小时前
Spring Boot Actuator深度解析与实战
java·spring boot·后端
重生之我是Java开发战士7 小时前
【Java EE】了解Spring Web MVC:请求与响应的全过程
spring boot·spring·java-ee·1024程序员节