ssm速通1(2/2)
本篇是对ssm速通1的补充
2、注入组件
依赖注入(Dependency Injection, DI)是Spring框架的核心特性之一,它通过将对象依赖关系的创建和管理外部化,实现了松耦合和更易测试的代码结构。
2.1、依赖注入的基本方式
字段最省事,构造最靠谱,Setter 最灵活;
①、构造器注入 (Constructor Injection)
推荐方式,特别是对于必需依赖:
@Servicepublic class UserService {private final UserRepository userRepository; // 可 final// Spring 4.3+ 对于单构造器可以省略@Autowiredpublic UserService(UserRepository userRepository) {this.userRepository = userRepository;}}
特点
官方推荐(Spring 团队、Effective Spring)
支持
final
,immutable 对象,测试直接new
启动期即可发现循环依赖,失败早暴露
多依赖时参数列表过长 → 可转用 Setter / 工厂方法 或 配置聚合
②、Setter注入 (Setter Injection)
适用于可选依赖:
@Servicepublic class OrderService {private UserService userService;@Autowiredpublic void setUserService(UserService userService) {this.userService = userService;}}
特点
可选依赖:方法可带
@Autowired(required = false)
适合“有默认值、运行期可能重新设置”场景
可被多次调用(re-configuration、测试 mock)
无法使用
final
③、字段注入 (Field Injection)
不推荐在生产代码中使用,主要用于测试或快速原型:
@Servicepublic class ProductService {@Autowired // 或 @Inject / @Resourceprivate ProductRepository productRepository;}
特点
代码最少,可读性高
测试需反射或容器,不易写纯单元测试
无法加
final
,线程安全语义弱循环依赖时容器只能降级用代理,问题被推迟到运行时
2.2、依赖注入的注解
①、@Autowired
@Autowired
是 Spring 框架的依赖注入(DI)核心注解,用来自动把容器里的 Bean 装配到字段、构造器、方法参数里,省去手动 getBean()
。
Ⅰ、基本语法
@Servicepublic class OrderService {@Autowired // 1. 字段注入private UserService userService;}// -------------------------------------------------------------@Servicepublic 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
:
@Injectprivate 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定义时已经给了名字
@Configurationpublic class RepoConfig {@Bean("mysqlUserRepo") // ① 定义时给名字public UserRepository mysqlRepo() { return new MySqlUserRepo(); }@Bean("mongoUserRepo")public UserRepository mongoRepo() { return new MongoUserRepo(); }}@Servicepublic 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 切换为其他组件@Autowiredprivate DataSource dataSource;
注意有 @Primary 存在,修改属性名就不生效
2.4、特殊类型的依赖注入
①、集合注入
@Autowiredprivate List<Validator> validators; // 注入所有Validator实现@Autowiredprivate Map<String, Processor> processors; // key为bean名称
②、可选依赖
// Java 8+@Autowiredprivate Optional<CacheManager> cacheManager;// 或@Autowired(required = false)private CacheManager cacheManager;
③、延迟注入
@Autowiredprivate 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简化了依赖注入的配置:
①、自动配置
@SpringBootApplicationpublic class MyApp {public static void main(String[] args) {SpringApplication.run(MyApp.class, args);}}
②、条件化Bean
@Bean@ConditionalOnMissingBeanpublic 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中。
Ⅰ、基本语法
@Componentpublic class AppConfig {// 注入简单值@Value("defaultValue") // 即默认 simpleValue = defaultValueprivate 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().toStringprivate 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/demojdbc.user=rootjdbc.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 并且赋值给 urlprivate String url;@Value("${jdbc.user}")private String user;@Value("${jdbc.pwd}")private String pwd;@Beanpublic 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 环境才可与进行以下配置的实现@Configurationpublic 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
@Componentpublic class DeliveryDao {@Autowiredprivate 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="...")
)
资源释放:
关闭数据库连接
释放文件句柄
清理缓存等
以下给出一个流程图: