Spring 注解开发进阶实战:Bean 生命周期、 依赖注入及Properties配置(Spring系列4)

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方法原理

  1. @Autowired 可以写在属性上,也可以写在setter方法上,最简单的处理方式是:写在属性上并将setter方法删除掉

  2. 为什么setter方法可以删除?

✅ 自动装配基于暴力反射设计,直接为私有属性设值

✅ 普通反射只能操作public内容,暴力反射可操作private内容

✅ 因此无需提供setter方法,Spring可直接赋值私有属性
核心知识点:@Autowired 注入匹配规则

  1. @Autowired 默认按照类型自动装配

  2. 如果IOC容器中同类的Bean找到多个,就按照变量名和Bean的名称匹配

  3. 示例:变量名叫bookDao,容器中存在一个同名(同类型)Bean,则成功注入;有多个同名(同类型)bean(即使不指定bean名称,默认类型名首字母小写为bean名),会抛出NoUniqueBeanDefinitionException

  4. 如果变量名与所有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配置文件
相关推荐
牛奔2 小时前
升级Go 版本,导致兼容性依赖编译错误排查并解决
开发语言·后端·golang
小邓的技术笔记2 小时前
聊聊 ASP.NET Core 中间件和过滤器的区别
后端·中间件·asp.net
运维行者_2 小时前
网络监控告警设置指南:如何配置智能告警规避“告警风暴”?
linux·运维·服务器·网络·后端
知识汲取者2 小时前
初识 RuoYi-Vue
java·spring boot·后端·开源软件
弹简特2 小时前
【JavaEE27-后端部分】Spring AOP 核心概念详解——把抽象变具象,让理论不再“飘”
java·spring·spring aop
曹牧2 小时前
Java:上传文件到网页
java·开发语言
弹简特2 小时前
【JavaEE29-后端部分】Spring AOP 切点表达式详解——精准定位,想切哪里切哪里
java·spring·spring aop
gf13211112 小时前
【python_使用指定应用发送飞书卡片】
java·python·飞书
弹简特2 小时前
【JavaEE28-后端部分】Spring AOP 通知详解——五种“增强时机”,一网打尽
java·spring·spring aop