前言

前面两天我们用 XML 配置文件扎实掌握了 Spring 的 IoC 和 DI。但配置写多了总感觉繁琐,Spring 早就想到了这一点,从 2.5 版开始全面支持注解,3.0 版更是实现了纯注解开发,让我们彻底告别 XML。

今天我要带你跟上时代的步伐,用注解重新定义 Bean、注入依赖,还会把第三方的 Druid 数据源、MyBatis 甚至 Junit 都整合进 Spring 容器。学完这一章,你就能体会到 Spring Boot 自动配置的雏形了。

💬 课前唠一唠:前两天写 XML 写到手酸了吗?今天换成注解,你最想先“删除”哪段 XML 配置?评论区说说你的怨念,看看有没有同病相怜的伙伴。


一、IOC/DI 配置管理第三方 Bean

之前我们把自家写的类交给 Spring 管,那第三方的 jar 包里的类(比如 Druid 连接池)怎么管?

1.1 案例:管理 Druid 数据源

需求:把 Druid 连接池对象交给 Spring 的 IoC 容器。

步骤

  1. 导入依赖
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.16</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
</dependency>
  1. applicationContext.xml 中配置 Bean,注入数据库四要素
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/spring_db"/>
    <property name="username" value="root"/>
    <property name="password" value="root"/>
</bean>
  1. 从容器获取并打印
DataSource dataSource = (DataSource) ctx.getBean("dataSource");
System.out.println(dataSource);

🧪 动手尝试:换成 C3P0 试试?管理它的类是 ComboPooledDataSource,注意属性名不一样(driverClass, jdbcUrl 等)。你会发现 C3P0 初始化就要求驱动,需要额外引入 mysql-connector-java

1.2 加载 properties 文件

把数据库连接信息硬编码在 XML 里不利于维护,咱们把它提到 jdbc.properties 里。

步骤

  1. 创建 jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/spring_db
jdbc.username=root
jdbc.password=root
  1. 在 XML 中开启 context 命名空间,并加载文件
<beans ... xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="... spring-context.xsd">
    <!-- 加载 properties,system-properties-mode="NEVER" 防止系统环境变量覆盖 -->
    <context:property-placeholder location="jdbc.properties" system-properties-mode="NEVER"/>
</beans>
  1. ${key} 引用值
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>

多个配置文件加载方式

<!-- 方式一:用逗号分隔 -->
<context:property-placeholder location="jdbc.properties,jdbc2.properties"/>
<!-- 方式二:通配符 *.properties(不建议) -->
<!-- 方式三:标准写法,classpath: 开头 -->
<context:property-placeholder location="classpath:*.properties"/>

⚠️ 注意username 这个 key 会跟系统环境变量冲突,最好用 jdbc.username 这类带前缀的名字,或加上 system-properties-mode="NEVER"


二、核心容器

我们已经用过 ApplicationContext,这里系统梳理一下容器的创建和获取 Bean 的方式。

2.1 容器的创建方式

  • 推荐ClassPathXmlApplicationContext("applicationContext.xml") – 从类路径加载
  • 了解FileSystemXmlApplicationContext("绝对路径") – 从文件系统加载,路径写死不推荐

2.2 获取 Bean 的三种方式

// 方式1:按名称,需要强转
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
// 方式2:按名称 + 类型,省去强转
BookDao bookDao = ctx.getBean("bookDao", BookDao.class);
// 方式3:按类型,容器中该类型 Bean 必须唯一
BookDao bookDao = ctx.getBean(BookDao.class);

2.3 容器层次与 BeanFactory

  • BeanFactory:IoC 容器的顶层接口,延迟加载(获取 bean 时才创建)
  • ApplicationContext:是 BeanFactory 的子接口,立即加载(初始化容器时就创建所有单例 bean)

若想让 ApplicationContext 也延迟加载某个 bean,可在配置中加 lazy-init="true"

💡 小结:日常开发多采用 ClassPathXmlApplicationContext 和按类型获取 bean 的方式。


三、IOC/DI 注解开发

终于不用写 XML 里的 <bean> 了!Spring 提供了几个注解帮我们自动注册 Bean。

3.1 注解定义 Bean

  1. 在类上添加 @Component("bookDao"),相当于 <bean id="bookDao" class="...">
  2. 在 xml 中配置包扫描,让 Spring 能发现这些注解
<context:component-scan base-package="com.itheima"/>
  1. 以后直接在类上写注解即可。

常用分层注解(功能同 @Component,只是语义更明确)

  • @Controller:表现层
  • @Service:业务层
  • @Repository:数据层

如果不指定名称,默认是类名首字母小写。

3.2 纯注解开发(告别 XML)

配置类完全替代 XML。

@Configuration                      // 标志这是一个配置类,相当于 applicationContext.xml
@ComponentScan("com.itheima")       // 包扫描
public class SpringConfig {
}

启动容器改用

ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);

🔄 对比:XML → ClassPathXmlApplicationContext;配置类 → AnnotationConfigApplicationContext

3.3 bean 作用范围与生命周期

作用范围:默认单例,用 @Scope("prototype") 改为非单例。

生命周期

  • 初始化:@PostConstruct 标注的方法(构造后执行)
  • 销毁前:@PreDestroy 标注的方法(容器关闭前执行)

注意:从 JDK9 开始需要额外引入 javax.annotation-api 包,否则这两个注解找不到。

3.4 注解依赖注入

自动装配(按类型注入)
@Service
public class BookServiceImpl implements BookService {
    @Autowired
    private BookDao bookDao;   // 按类型自动注入,setter 方法都可以删掉
}

原理@Autowired 基于暴力反射,可以直接给私有属性赋值。

有多个同类型 Bean 时:@Autowired 会先按类型找,如果找到多个,再根据变量名匹配 bean 名称。若还找不到,就需要配合 @Qualifier("bookDao1") 指定名称。

注入简单类型 + 读取 properties
@Repository("bookDao")
public class BookDaoImpl implements BookDao {
    @Value("itheima")
    private String name;
}

配合 @PropertySource 读取配置文件:

@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
public class SpringConfig { }

// 使用
@Value("${name}")  // 对应 jdbc.properties 中的 name=itheima888
private String name;

四、注解开发管理第三方 Bean

第三方 jar 包里的类我们不能直接加 @Component,需要用 @Bean 注解。

方法:在配置类中写一个方法,返回要管理的对象,并标注 @Bean

public class JdbcConfig {
    @Bean
    public DataSource dataSource() {
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName("com.mysql.jdbc.Driver");
        ds.setUrl("jdbc:mysql://localhost:3306/spring_db");
        ds.setUsername("root");
        ds.setPassword("root");
        return ds;
    }
}

引入这个配置类:在主配置类上用 @Import(JdbcConfig.class)

@Configuration
@ComponentScan("com.itheima")
@Import(JdbcConfig.class)
public class SpringConfig { }

🎯 最佳实践:把不同类型的第三方 Bean 分开放到各自的配置类(如 JdbcConfigMyBatisConfig),再用 @Import 统一引入,结构清晰。

为第三方 Bean 注入资源

方法参数中直接写要注入的对象,Spring 会自动装配。

@Bean
public DataSource dataSource(BookDao bookDao) {   // 自动注入 bookDao
    System.out.println(bookDao);
    ...
}

简单类型还是用 @Value 注入,也可以配合 @PropertySource 从配置文件读取。


五、XML 配置 vs 注解开发 对照表

功能 XML 配置 注解开发
定义 bean <bean id="" class=""> @Component / @Controller / @Service / @Repository + @ComponentScan
注入依赖(引用) <property name="" ref=""> @Autowired + @Qualifier
注入依赖(简单) <property name="" value=""> @Value
配置第三方 bean <bean> 配合工厂方法 @Bean 标注在方法上
作用范围 scope 属性 @Scope
生命周期 init-method / destroy-method @PostConstruct / @PreDestroy
加载 properties <context:property-placeholder> @PropertySource + @Value("${}")

六、Spring 整合 MyBatis

现在要把 MyBatis 的核心对象也交给 Spring 管,让容器帮我们创建 SqlSessionFactory 和 Mapper 代理对象。

6.1 整合思路

回顾原生 MyBatis 开发核心对象:

  • SqlSessionFactory 需要数据源、配置等环境
  • SqlSessionSqlSessionFactory 产生
  • Mapper 接口通过 SqlSession.getMapper() 获得

Spring 整合后的目标:

  • DataSource → Spring 管理(Druid)
  • SqlSessionFactory → Spring 管理(用 SqlSessionFactoryBean
  • Mapper 接口 → Spring 通过 MapperScannerConfigurer 自动扫描并创建代理对象,放入容器

6.2 具体步骤

  1. 引入整合包
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.2.10.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>1.3.0</version>
</dependency>
  1. 创建 MyBatis 配置类
public class MyBatisConfig {
    @Bean
    public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource);                     // 数据源
        factoryBean.setTypeAliasesPackage("com.itheima.domain");   // 别名扫描
        return factoryBean;
    }

    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer() {
        MapperScannerConfigurer scanner = new MapperScannerConfigurer();
        scanner.setBasePackage("com.itheima.dao");                 // Mapper 接口所在包
        return scanner;
    }
}
  1. 主配置类引入
@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class, MyBatisConfig.class})
public class SpringConfig { }

现在就可以直接注入 Service 使用了,完全不用手动操作 MyBatis 的 SqlSession 了。


七、Spring 整合 JUnit

测试类里也可以让 Spring 环境自动开启,通过简单注解就能注入需要的 Bean。

步骤

  1. 引入 spring-test 依赖
  2. 测试类上添加:
    • @RunWith(SpringJUnit4ClassRunner.class):指定 Spring 的运行器
    • @ContextConfiguration(classes = SpringConfig.class):指定配置类(或 locations 指定 xml)
  3. 直接 @Autowired 注入要测试的 Bean,编写 @Test 方法
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest {
    @Autowired
    private AccountService accountService;

    @Test
    public void testFindById() {
        System.out.println(accountService.findById(1));
    }
}

🧪 挑战任务:用注解方式重写前两天所有的 XML 配置,再整合 MyBatis 和 JUnit,跑通一个从 Controller 到 Service 到 Dao 再到数据库的全链调用。遇到 NoUniqueBeanDefinitionException 不要慌,先用 @Qualifier 指定,再回来优化你的设计。


八、总结

今天我们从 XML 成功过渡到了注解驱动开发,并完成了 Spring 与 MyBatis、JUnit 的整合。核心能力矩阵如下:

技能 关键注解/类
定义 Bean @Component, @Service, @Repository, @Configuration + @Bean
包扫描 @ComponentScan
依赖注入(引用/简单) @Autowired, @Qualifier, @Value
加载 properties @PropertySource
生命周期/作用范围 @PostConstruct, @PreDestroy, @Scope
整合 MyBatis SqlSessionFactoryBean, MapperScannerConfigurer
整合 JUnit @RunWith, @ContextConfiguration

学到这里,你会发现 Spring Boot 的 @SpringBootApplication 其实就是 @Configuration + @ComponentScan + 一堆自动配置的封装。地基已打牢,后续学习将势如破竹。

🎤 结课小调查:今天的内容中,让你感觉最“爽”的点是哪个?
A. 终于不用写 XML 了
B. @Autowired 自动注入
C. MapperScannerConfigurer 自动扫描 DAO
D. Junit 测试也能用 @Autowired

评论区告诉我,看看大家是不是都选了 A!有任何疑问或整合时遇到的错误,也欢迎贴出来,我们一起排查。


本文为 Spring Framework 第三天授课内容整理,至此我们已经完成了 Spring 核心原理与注解开发的学习。下期我们将进入 AOP 的学习,敬请期待!如果觉得有帮助,别忘了点赞、收藏、关注,你的支持是我最大的动力。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐