本篇是对ssm速通1的补充
2、注入组件
依赖注入(Dependency Injection, DI)是Spring框架的核心特性之一,它通过将对象依赖关系的创建和管理外部化,实现了松耦合和更易测试的代码结构。
2.1、依赖注入的基本方式
字段最省事,构造最靠谱,Setter 最灵活;
①、构造器注入 (Constructor Injection)
推荐方式,特别是对于必需依赖:
@Service
public class UserService {
private final UserRepository userRepository; // 可 final
// Spring 4.3+ 对于单构造器可以省略@Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
特点
-
官方推荐(Spring 团队、Effective Spring)
-
支持
final,immutable 对象,测试直接new -
启动期即可发现循环依赖,失败早暴露
-
多依赖时参数列表过长 → 可转用 Setter / 工厂方法 或 配置聚合
②、Setter注入 (Setter Injection)
适用于可选依赖:
@Service
public class OrderService {
private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
}
特点
-
可选依赖:方法可带
@Autowired(required = false) -
适合“有默认值、运行期可能重新设置”场景
-
可被多次调用(re-configuration、测试 mock)
-
无法使用
final
③、字段注入 (Field Injection)
不推荐在生产代码中使用,主要用于测试或快速原型:
@Service
public class ProductService {
@Autowired // 或 @Inject / @Resource
private ProductRepository productRepository;
}
特点
-
代码最少,可读性高
-
测试需反射或容器,不易写纯单元测试
-
无法加
final,线程安全语义弱 -
循环依赖时容器只能降级用代理,问题被推迟到运行时
2.2、依赖注入的注解
①、@Autowired
@Autowired 是 Spring 框架的依赖注入(DI)核心注解,用来自动把容器里的 Bean 装配到字段、构造器、方法参数里,省去手动 getBean()。
Ⅰ、基本语法
@Service
public class OrderService {
@Autowired // 1. 字段注入
private UserService userService;
}
// -------------------------------------------------------------
@Service
public class OrderService {
private final UserService userService;
@Autowired // 2. 构造器注入(Spring 4.3+ 可省略)
public OrderService(UserService userService) {
this.userService = userService;
}
}
// -------------------------------------------------------------
@Autowired // 3. 方法注入(也可用在 setter)
public void prepare(UserService userService, ProductService productService) {
...
}
Ⅱ、特性
-
默认要求依赖必须存在,可通过
@Autowired(required=false)设为可选 -
可与
@Qualifier配合使用解决歧义
Ⅲ、自动装配流程
先按照类型找
-
有且仅有一个找到,则直接注入,不在乎名字
-
若按照类型找到多个,则再按照名称找,变量名就是要找的名称
-
如果找到则直接注入
-
否则没找到直接报错
-
②、@Resource
JSR-250标准注解,优先按名称匹配:
@Resource(name="mySpecialBean")
private SpecialBean bean;
③、@Inject
JSR-330标准注解,功能类似@Autowired:
@Inject
private PaymentService paymentService;
④、@Resource 与 @Autowired 注解深度对比
在Spring依赖注入中,@Resource和@Autowired是两个最常用的注解,它们有相似之处但也有重要区别
Ⅰ、包来源即兼容性
| 特性 | @Autowired | @Resource |
|---|---|---|
| 所属标准 | Spring框架自有注解 | JSR-250 (Java标准注解) |
| 包路径 | org.springframework.beans.factory.annotation | javax.annotation |
| 引入版本 | Spring 2.5+ | Java EE 5+ |
| 环境 | @Autowired | @Resource |
|---|---|---|
| Spring原生应用 | 完全支持 | 支持 |
| JavaEE/JakartaEE | 需要Spring | 原生支持 |
| Quarkus/Micronaut | 有限支持 | 更好支持 |
| 单元测试 | 需要Spring | 更通用 |
即理论上 @Resource 更灵活,可使用多个其他框架(包含 Spring 框架),但是 Spring 一家独大,所以使用 @Autowired 也够
Ⅱ、注入规则
@Autowired 注入规则
-
默认按类型匹配 (byType)
-
配合 @Qualifier 可实现按名称匹配
@Resource 注入规则
-
默认按名称匹配 (byName)
-
名称未指定时回退到按类型匹配
建议的话,二者均可用,在纯Spring环境中,
@Autowired提供了更丰富的功能集成,而@Resource则在跨环境兼容性上更有优势。使用 @Autowired 就够了
2.3、处理依赖歧义
当同一类型有多个实现时,即上方提到的自动装配过程中按照类型找到多个,我们需要进行消除歧义:
①、使用@Qualifier精确选取
在 Spring 容器里,“先按类型、再按名称” 的注入策略遇到多个同类型 Bean 时,会抛NoUniqueBeanDefinitionException。@Qualifier 的核心价值就是“给每个候选 Bean 打标签”,然后在注入点按标签精确点名,从而消除歧义。
前提:我们在Bean定义时已经给了名字
@Configuration
public class RepoConfig {
@Bean("mysqlUserRepo") // ① 定义时给名字
public UserRepository mysqlRepo() { return new MySqlUserRepo(); }
@Bean("mongoUserRepo")
public UserRepository mongoRepo() { return new MongoUserRepo(); }
}
@Service
public class UserService {
@Autowired
@Qualifier("mysqlUserRepo") // 注入时点名
private UserRepository userRepository;
}
// 相当于我们先通过查找 UserRepository 该类型的组件,发现多个实现,使用 @Qualifier 将其精确锁定到了一个名为 mysqlUserRepo 的 UserRepository 类型的实现
②、使用自定义限定符注解
前提:我们在Bean定义时已经给了名字
@Bean
@DatabaseType("mysql")
public DataSource mysqlUserRepo() { return new MySqlUserRepo(); }
@Bean
@DatabaseType("mongo")
public DataSource mongoUserRepo() { return new MongoUserRepo(); }
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier // 核心元注解
public @interface DatabaseType {
String value();
}
// 使用
@Autowired
@DatabaseType("mysql")
private DataSource dataSource;
③、使用@Primary默认组件
前提:我们在Bean定义时已经给了名字
@Bean
@DatabaseType("mysql")
public DataSource mysqlUserRepo() { return new MySqlUserRepo(); }
@Primary // 使用该注解,即当按照类型找到多个,默认选择这个
@Bean
@DatabaseType("mongo")
public DataSource mongoUserRepo() { return new MongoUserRepo(); }
@Qualifier("mysql") // 当然即使默认是选择名为 mongo 的组件,可以使用 @Qualifier 切换为其他组件
@Autowired
private DataSource dataSource;
注意有 @Primary 存在,修改属性名就不生效
2.4、特殊类型的依赖注入
①、集合注入
@Autowired
private List<Validator> validators; // 注入所有Validator实现
@Autowired
private Map<String, Processor> processors; // key为bean名称
②、可选依赖
// Java 8+
@Autowired
private Optional<CacheManager> cacheManager;
// 或
@Autowired(required = false)
private CacheManager cacheManager;
③、延迟注入
@Autowired
private ObjectProvider<ExpensiveService> expensiveServiceProvider;
public void someMethod() {
ExpensiveService service = expensiveServiceProvider.getIfAvailable();
// 或
ExpensiveService service = expensiveServiceProvider.getObject();
}
2.5、依赖注入的最佳实践
-
优先使用构造器注入,特别是对于必需依赖
-
对于可选依赖,考虑使用Setter注入或
ObjectProvider -
避免字段注入在生产代码中使用
-
使用
@Qualifier或自定义限定符处理多实现情况 -
保持依赖不可变(final字段)
-
避免循环依赖,这是设计问题的信号
-
对于复杂对象的创建,考虑使用FactoryBean
2.6、Spring Boot中的依赖注入
Spring Boot简化了依赖注入的配置:
①、自动配置
@SpringBootApplication
public class MyApp {
public static void main(String[] args) {
SpringApplication.run(MyApp.class, args);
}
}
②、条件化Bean
@Bean
@ConditionalOnMissingBean
public DataSource dataSource() {
// 默认数据源配置
}
③、属性驱动注入
@Configuration
@EnableConfigurationProperties(AppProperties.class)
public class AppConfig {
}
@ConfigurationProperties(prefix = "app")
public class AppProperties {
private String name;
// getters/setters
}
2.7、补充注解
①、@Value
@Value是Spring提供的最常用的属性注入注解,用于从各种来源注入值到Spring管理的Bean中。
Ⅰ、基本语法
@Component
public class AppConfig {
// 注入简单值
@Value("defaultValue") // 即默认 simpleValue = defaultValue
private String simpleValue;
// 注入系统属性
@Value("${user.home}")
private String userHome;
// 注入环境变量
@Value("${JAVA_HOME}")
private String javaHome;
// 注入配置文件属性
@Value("${tom.age}")
// 即默认 myage = tom.age,tom.age是在 application.properties 中配置的
private int myage;
@Value("${app.timeout:30}") // 即默认 timeout 取不到 app.timeout 时默认是 30
private int timeout;
// 注入SpEL表达式,即使用#{}内可加 Spring 表达式语言
@Value("#{1>2?1:2}")
// 相当于 int max = 1>2?1:2;
private int max;
@Value("#{T(java.util.UUID).randomUUID().toString}")
// 相当于 String myUUID = UUID.randomUUID().toString
private String myUUID;
}
Ⅱ、支持的内容类型
| 表达式类型 | 示例 | 说明 |
|---|---|---|
| 字面量 | @Value("hello") | 直接字符串值 |
| 属性占位符 | @Value("${app.name}") | 从Environment解析 |
| SpEL表达式 | @Value("#{systemProperties['user.region']}") | Spring表达式语言 |
| 默认值 | @Value("${app.threads:4}") | 冒号后为默认值 |
| 类型转换 | @Value("${app.percentage}") double percent | 自动类型转换 |
②、@PropertySource
@PropertySource 是 Spring 3.1 开始提供的 “外挂”式配置加载器:让容器在启动时把任意 .properties(或 XML/YAML,自定义工厂即可)文件读进来,转成 Environment 里的 PropertySource,随后就可通过 @Value、@ConfigurationProperties 或 Environment 接口随取随用。它只负责“加载”,不做注入;一次声明,全局可用。
Ⅰ、核心功能
-
指定文件路径(classpath / file / URL)
-
支持多文件、编码、忽略缺失、自定义解析工厂
-
加载后内容并入
Environment,优先级 高于application.properties低于 命令行参数 -
与
@Value、@ConfigurationProperties无缝配合
Ⅱ、基本语法
首先在 application.properties 的当前目录先创建对应的 jdbc.properties
jdbc.url=jdbc:mysql://127.0.0.1:3306/demo
jdbc.user=root
jdbc.pwd=root
之后加载配置类即可
@Configuration
@PropertySource("classpath:jdbc.properties") // 加载
public class DataSourceConfig {
jdbc.properties
@Value("${jdbc.url}")
// 使用,相当于从你自定义创建的 jdbc.properties 中取出
// jdbc.url=jdbc:mysql://127.0.0.1:3306/demo 并且赋值给 url
private String url;
@Value("${jdbc.user}")
private String user;
@Value("${jdbc.pwd}")
private String pwd;
@Bean
public DataSource dataSource() {
return DataSourceBuilder.create()
.url(url).username(user).password(pwd)
.build();
}
}
即该注解可以帮助我们将原来所有要写入 application.properties 的配置属性分类存放,如商铺对应的配置属性新建一个 shop.properties 存放,用户对应的配置属性新建一个 user.properties 存放,之后使用 @PropertySource("classpath:shop.properties") 加载进我们的商铺配置类,使用 @PropertySource("classpath:user.properties") 加载进我们的用户配置类即可
注意:
-
从自己项目中找 properties 使用 classpath:后面接 application.properties 到要寻找的 properties 的路径
-
从第三方项目中找 properties 使用 classpath*:后面接要寻找的 properties 的路径
③、@Profile
@Profile 是 Spring 的 “环境开关”:只有当前激活的 Profile 与注解值匹配时,相应的 Bean / 配置类才会被注册到容器;不匹配就整体跳过,实现“一套代码,多环境差异化部署”。
举例:
@Profile("test") // 只有激活 test 环境才可与进行以下配置的实现
@Configuration
public class DataSourceConfig {
@Bean
@Profile("dev") // 仅 dev 环境生效
public DataSource devDs() {
return DataSourceBuilder.create()
.url("jdbc:h2:mem:dev")
.build();
}
@Bean
@Profile("prod") // 仅 prod 环境生效
public DataSource prodDs() {
return DataSourceBuilder.create()
.url("jdbc:mysql://prod/db")
.username("${db.user}")
.password("${db.pwd}")
.build();
}
@Bean
@Profile("build") // 仅 build 环境生效
public DataSource devDs() {
return DataSourceBuilder.create()
.url("jdbc:h2:mem:build")
.build();
}
}
如上数据源有三个,@Profile 中配置的环境是自定义的(如 test、dev、prod等),默认是 default
@Component
public class DeliveryDao {
@Autowired
private MyDataSource myDataSource;
public void saveDelivery() {
System.out.println("数据源:保存数据" + myDataSource);
}
}
当我们在其他组件内使用 @Autowired 注入数据源时,由于数据源有多个,则自动注入不知道选择哪个,默认选择带有 default 标识的,没有则报错
此时我们可以在 @Profile("") 内再添加一个 default 标识,如@Profile("build","default"),则会默认使用 build 环境的数据源,当然可以在 application.properties 中激活数据源环境,使用spring.profiles.active=你要激活的环境
3、生命周期
大多数 Java 组件(Servlet、Spring Bean、EJB 等)都遵循类似的四个主要生命周期阶段:
-
创建(Instantiation)
-
初始化(Initialization)
-
运行(In Service/Runtime)
-
销毁(Destruction)
3.1、创建阶段
-
实例化对象:通过构造函数或工厂方法创建组件实例
-
依赖注入:为对象的属性/字段注入依赖项(在IoC容器中)
3.2、初始化阶段
-
Aware接口回调(Spring特有):
-
BeanNameAware.setBeanName() -
BeanFactoryAware.setBeanFactory() -
ApplicationContextAware.setApplicationContext()
-
-
初始化前处理:
-
Spring:
BeanPostProcessor.postProcessBeforeInitialization()
-
-
初始化操作:
-
调用
@PostConstruct注解的方法 -
实现
InitializingBean接口的afterPropertiesSet()方法(Spring) -
调用自定义的
init-method(XML配置或@Bean(initMethod="..."))
-
-
初始化后处理:
-
Spring:
BeanPostProcessor.postProcessAfterInitialization()
-
3.3、运行阶段
-
组件处于就绪状态,处理业务请求
-
对于有状态组件可能涉及状态转换:
-
EJB 的激活/钝化(
@PostActivate,@PrePassivate) -
Web 应用的会话管理
-
3.4、销毁阶段
-
销毁前处理:
-
调用
@PreDestroy注解的方法 -
实现
DisposableBean接口的destroy()方法(Spring) -
调用自定义的
destroy-method(XML配置或@Bean(destroyMethod="..."))
-
-
资源释放:
-
关闭数据库连接
-
释放文件句柄
-
清理缓存等
-
以下给出一个流程图:



被折叠的 条评论
为什么被折叠?



