Spring 注解开发进阶:Bean作用域/生命周期 + 依赖注入 + 注解读取Properties全攻略
Spring注解开发是现代Java项目的主流开发方式,相比传统XML配置,它更简洁、更符合Java代码的设计逻辑。本文将围绕Spring注解开发的核心进阶知识点展开:Bean作用域与生命周期管理 、多层级依赖注入(Autowired/Qualifier/Resource) 、注解方式读取Properties配置文件,通过完整的实战案例与源码解析,帮你彻底掌握Spring注解开发的核心能力。
一、Bean作用范围与生命周期管理
1.1 环境准备
首先搭建标准的Maven项目,为后续注解开发做好基础环境。
1.1.1 Maven核心依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<!-- 解决PostConstruct/PreDestroy注解缺失问题(JDK9+需手动引入) -->
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
</dependencies>
1.1.2 核心业务代码与配置类
1. 配置类(SpringConfig)
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan("com.itheima")
public class SpringConfig {
}
2. 数据访问层(BookDao & BookDaoImpl)
// BookDao接口
public interface BookDao {
void save();
}
// BookDaoImpl实现类
import org.springframework.stereotype.Repository;
@Repository
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
}
3. 运行类(App)
import com.itheima.config.SpringConfig;
import com.itheima.dao.BookDao;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class App {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
// 获取两次Bean,观察地址
BookDao bookDao1 = ctx.getBean(BookDao.class);
BookDao bookDao2 = ctx.getBean(BookDao.class);
System.out.println(bookDao1);
System.out.println(bookDao2);
ctx.close();
}
}
1.2 Bean的作用范围(单例/多例)
1.2.1 单例模式默认行为
运行上述代码,控制台会输出两个完全相同的地址 ,说明Spring默认情况下Bean是单例的(IOC容器中仅创建一个实例)。
1.2.2 改为多例模式(@Scope注解)
在BookDaoImpl上添加@Scope注解,指定作用范围为prototype(多例):
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Repository;
@Repository
@Scope("prototype") // 多例模式:每次getBean都会创建新实例
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
}
再次运行,控制台输出两个不同的地址,说明多例模式生效。
1.3 Bean的生命周期
Bean的生命周期包括:初始化 → 使用 → 销毁。Spring通过注解精准控制生命周期的执行时机。
1.3.1 核心生命周期注解
| 注解 | 作用 | 执行时机 |
|---|---|---|
| @PostConstruct | 初始化方法 | 构造方法执行完毕后立即执行 |
| @PreDestroy | 销毁方法 | 容器关闭(ctx.close())时执行 |
1.3.2 实战代码实现
修改BookDaoImpl,添加生命周期注解:
import org.springframework.stereotype.Repository;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@Repository
public class BookDaoImpl implements BookDao {
// 构造方法
public BookDaoImpl() {
System.out.println("constructor ...");
}
public void save() {
System.out.println("book dao save ...");
}
// 初始化方法
@PostConstruct
public void init() {
System.out.println("init ...");
}
// 销毁方法
@PreDestroy
public void destroy() {
System.out.println("destroy ...");
}
}
1.3.3 运行验证
public class App {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
BookDao bookDao1 = ctx.getBean(BookDao.class);
// 关闭容器,触发销毁方法
ctx.close();
}
}
1.4 生命周期与作用域核心对比
| 特性 | 单例(Singleton) | 多例(Prototype) |
|---|---|---|
| 实例数量 | 容器中仅一个 | 每次getBean创建新实例 |
| 生命周期 | 随容器创建,随容器销毁 | 容器创建实例后,不再管理 |
| 销毁方法 | 执行@PreDestroy | 不执行销毁方法 |
二、注解开发依赖注入(核心难点)
Spring注解注入分为:引用类型注入 、简单类型注入 、多实现类注入。
2.1 环境准备与问题复现
// BookService接口
public interface BookService {
void save();
}
// BookServiceImpl实现类
import org.springframework.stereotype.Service;
@Service
public class BookServiceImpl implements BookService {
// 依赖BookDao
private BookDao bookDao;
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}
运行代码会抛出NullPointerException ,原因是bookDao未被注入,为null。
2.2 引用类型注入方案
2.2.1 方案一:@Autowired(按类型注入)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class BookServiceImpl implements BookService {
// 按类型自动装配
@Autowired
private BookDao bookDao;
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}
核心知识点:@Autowired 省略Setter方法原理
-
@Autowired可以写在属性上,也可以写在setter方法上,最简单的处理方式是:写在属性上并将setter方法删除掉 -
为什么setter方法可以删除?
✅ 自动装配基于暴力反射设计,直接为私有属性设值
✅ 普通反射只能操作public内容,暴力反射可操作private内容
✅ 因此无需提供setter方法,Spring可直接赋值私有属性
核心知识点:@Autowired 注入匹配规则
-
@Autowired默认按照类型自动装配 -
如果IOC容器中同类的Bean找到多个,就按照变量名和Bean的名称匹配
-
示例:变量名叫
bookDao,容器中存在一个同名(同类型)Bean,则成功注入;有多个同名(同类型)bean(即使不指定bean名称,默认类型名首字母小写为bean名),会抛出NoUniqueBeanDefinitionException -
如果变量名与所有Bean名称都不匹配(如容器只有bookDao1、bookDao2),则抛出NoUniqueBeanDefinitionException异常
2.2.2 方案二:多实现类冲突与@Qualifier(按名称注入)
如果BookDao有多个实现类,@Autowired会因类型冲突报错,需配合@Qualifier指定名称:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
@Service
public class BookServiceImpl implements BookService {
@Autowired
@Qualifier("bookDao2") // 指定注入bean名称
private BookDao bookDao;
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}
2.2.3 方案三:@Resource(JDK自带,按名称优先)
import javax.annotation.Resource;
import org.springframework.stereotype.Service;
@Service
public class BookServiceImpl implements BookService {
@Resource(name = "bookDao2")
private BookDao bookDao;
}
2.3 简单类型注入(@Value)
注入简单类型(String、Integer等)时,使用@Value注解,支持直接赋值、SpEL表达式、读取Properties配置。
@Repository("bookDao")
public class BookDaoImpl implements BookDao {
// 直接赋值
@Value("itheima888")
private String name;
public void save() {
System.out.println("book dao save ..." + name);
}
}
三、注解方式读取Properties配置文件
3.1 基础使用(根路径文件)
# jdbc.properties
name=itheima888
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
@Configuration
@ComponentScan("com.itheima")
@PropertySource("jdbc.properties")
public class SpringConfig {
}
@Repository("bookDao")
public class BookDaoImpl implements BookDao {
@Value("${name}")
private String name;
}
3.2 进阶使用(子包路径文件)
3.2.1 项目结构
src
└── main
└── resources
└── config
└── jdbc.properties // 在config子包下
4.2.2 配置类指定路径
@Configuration
@ComponentScan("com.itheima")
// classpath:指向类路径根,进入config子目录
@PropertySource("classpath:config/jdbc.properties")
public class SpringConfig {
}
3.2.3 核心原理
classpath指向类路径根(src/main/resources或编译后的target/classes);
子包名对应目录层级,Spring 自动解析开发 / 编译状态下的路径。
3.3 多配置文件加载与注意事项
3.3.1 加载多个配置文件
@PropertySource({"jdbc.properties", "jdbc2.properties"})
3.3.2 不支持通配符
@PropertySource("*.properties")会报错,需手动列出所有文件。
3.3.3 严格区分大小写
Linux/Mac 环境下,文件名大小写敏感,需与实际文件一致。
四、核心知识点总结
@Autowired基于暴力反射,可直接注入私有属性,无需setter方法@Autowired注入规则:先按类型,类型多个则按变量名匹配,不匹配则报错@Scope控制单例/多例,@PostConstruct/@PreDestroy控制生命周期@PropertySource+@Value实现注解方式读取Properties配置文件