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 配置),欢迎继续提问!

相关推荐
专吃海绵宝宝菠萝屋的派大星2 分钟前
使用Dify对接自己开发的mcp
java·服务器·前端
大数据新鸟20 分钟前
操作系统之虚拟内存
java·服务器·网络
Tong Z22 分钟前
常见的限流算法和实现原理
java·开发语言
凭君语未可25 分钟前
Java 中的实现类是什么
java·开发语言
He少年27 分钟前
【基础知识、Skill、Rules和MCP案例介绍】
java·前端·python
克里斯蒂亚诺更新37 分钟前
myeclipse的pojie
java·ide·myeclipse
迷藏4941 小时前
**eBPF实战进阶:从零构建网络流量监控与过滤系统**在现代云原生架构中,**网络可观测性**和**安全隔离**已成为
java·网络·python·云原生·架构
迷藏4941 小时前
**发散创新:基于Solid协议的Web3.0去中心化身份认证系统实战解析**在Web3.
java·python·web3·去中心化·区块链
qq_433502181 小时前
Codex cli 飞书文档创建进阶实用命令 + Skill 创建&使用 小白完整教程
java·前端·飞书
safestar20121 小时前
ES批量写入性能调优:BulkProcessor 参数详解与实战案例
java·大数据·运维·jenkins