在 Spring 容器中,Bean 的定义与装配方式直接决定了项目的可维护性、可读性和团队协作效率。从早期经典的 XML 配置,到后来的注解驱动,再到如今主流的 JavaConfig,Spring 为开发者提供了灵活多变的装配策略。理解这三种方式的各自特点和适用场景,是驾驭 Spring 容器的基本功。
8.1.1 XML 配置:经典的结构化声明
早期的 Spring 应用,几乎完全依赖 XML 来描述 Bean 以及它们之间的依赖关系。虽然现在不再是主流,但仍有大量遗留系统在使用,并且某些场景(如解耦外部集成)依然实用。
一个典型的 XML 配置如下(假设存在 applicationContext.xml):
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 定义一个数据源 -->
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mydb"/>
<property name="username" value="root"/>
<property name="password" value="secret"/>
</bean>
<!-- 定义一个 DAO,并注入数据源 -->
<bean id="userDao" class="com.example.dao.UserDao">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 定义一个 Service,构造器注入 DAO -->
<bean id="userService" class="com.example.service.UserService">
<constructor-arg ref="userDao"/>
</bean>
</beans>
然后在代码中通过 ClassPathXmlApplicationContext 加载这个配置文件:
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = context.getBean("userService", UserService.class);
优点:
- 完全解耦 Java 代码:Bean 的定义和装配完全集中在配置文件中,业务类完全是纯 POJO,不带任何 Spring 注解。
- 集中管理:所有 Bean 的关系一目了然,适合大规模系统做全局概览。
- 无侵入性:切换不同的 Spring 版本或框架时,核心业务类不受影响。
缺点:
- 编写繁琐:每个 Bean 及其属性、依赖都必须手动配置,项目变大后 XML 文件会迅速膨胀,难以维护。
- 缺乏编译期检查:类名、属性名都是字符串,拼写错误要到容器启动时才能发现。
- 重构困难:重命名类或包时,必须同步修改 XML 文件,容易遗漏。
适用场景:遗留系统维护、需要高可配置性且与 Java 代码完全解耦的模块、基础框架的底层配置(如 AOP 全局配置)。
8.1.2 注解驱动:内聚的声明式装配
为了解决 XML 的臃肿问题,Spring 从 2.5 版本起引入了注解驱动的 Bean 配置。通过 @Component、@Autowired 等标准注解,把配置信息放到了 Java 类本身上。
开启组件扫描(XML 方式或 JavaConfig 方式):
<!-- XML 中开启扫描 -->
<context:component-scan base-package="com.example"/>
或者用 JavaConfig:
@Configuration
@ComponentScan("com.example")
public class AppConfig {
}
标记 Bean 并注入依赖:
@Repository
public class UserDao {
// ...
}
@Service
public class UserService {
private final UserDao userDao;
@Autowired // 构造器注入(Spring 4.3+ 可省略 @Autowired)
public UserService(UserDao userDao) {
this.userDao = userDao;
}
}
为了让 Spring 识别这些注解,必须显式启用注解驱动支持(在 XML 中通过 <context:annotation-config/> 或组件扫描自动激活)。Spring Boot 中自动配置已经替我们完成了这些操作。
常用的原型注解(@Component 的语义特化):
@Component:通用组件@Service:服务层@Repository:数据访问层(并自动提供异常转译)@Controller/@RestController:Web 控制器
注入注解:
@Autowired:按类型注入(可配合@Qualifier按名称精确匹配)@Resource(JSR-250):默认按名称,再按类型@Inject(JSR-330):与@Autowired类似,但功能稍弱
优点:
- 代码简洁:Bean 的定义与业务类本身放在一起,减少了配置文件。
- 重构友好:类名、包名变更后,依赖关系仍然由注解自动维护(IDE 可安全重构)。
- 开发体验好:使用注解驱动后,大部分常规 Bean 不再需要显式声明,Spring 自动发现并装配。
缺点:
- 侵入性:业务类带上了 Spring 注解,技术上不再是纯 POJO(虽然影响很小)。
- 分散配置:大型项目中,依赖关系散落在各个类中,不如集中配置文件直观。
- 隐式依赖:
@Autowired自动按类型匹配,如果存在多个同类型 Bean,容易引发启动异常,需要借助@Primary或@Qualifier解决。
适用场景:绝大多数现代 Spring 应用,特别是 Web 服务、微服务,注解驱动是主力配置手段。
8.1.3 JavaConfig:显式、类型安全的装配
JavaConfig 是 Spring 3.0 引入的纯 Java 配置方式,它使用 @Configuration 和 @Bean 注解来定义 Bean,完全摒弃 XML,同时保留集中配置的优点。
示例:
@Configuration
public class AppConfig {
@Bean
public DataSource dataSource() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/mydb");
config.setUsername("root");
config.setPassword("secret");
return new HikariDataSource(config);
}
@Bean
public UserDao userDao(DataSource dataSource) { // 参数自动注入
return new UserDao(dataSource);
}
@Bean
public UserService userService(UserDao userDao) {
return new UserService(userDao);
}
}
加载方式:
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = context.getBean(UserService.class);
关键点:
@Configuration标记类为配置类,相当于一个增强的@Component,内部可定义多个@Bean方法。@Bean方法返回的对象会被注册为 Spring 容器中的 Bean,方法名默认就是 Bean 名称。- 方法参数会被容器自动注入其他 Bean(按类型匹配),无需显式
@Autowired。 - JavaConfig 类本身也是容器管理的 Bean(可被注入),所以可以依赖其他配置。
与注解驱动的关系:JavaConfig 常和注解驱动组合使用。通常用 @ComponentScan 启用自动发现,同时用 @Bean 手动定义那些无法直接加注解的 Bean(例如第三方库的对象、需要复杂初始化的组件)。Spring Boot 的自动配置底层正是无数的 @Configuration 类。
优点:
- 集中可见:将一组相关的 Bean 定义集中在一个配置类中,便于理解和维护(例如
DataSourceConfig、SecurityConfig)。 - 类型安全:一切都是 Java 代码,拼写错误、类型错误都在编译期暴露。
- 灵活:可以在
@Bean方法内部编写任意初始化逻辑、条件判断,甚至调用其他方法。 - 可测试:配置类本身也可以作为单元测试的对象。
缺点:
- 相对注解驱动,需要写更多的显式声明代码。
- 若配置类过多,仍然会面临一定的管理复杂度。
适用场景:
- 定义第三方库的 Bean(如
RestTemplate、DataSource、RedisConnectionFactory) - 需要复杂初始化逻辑的对象
- 同一接口多个实现需要显式命名的场合
- 框架级基础设施的配置(安全、事务、调度)
8.1.4 三种方式的混合与演变
Spring 并不强制开发者只选择一种方式,三种方式可以在同一个项目中混合使用。典型的组合是:
- 注解驱动负责业务 Bean 的自动扫描(
@Service、@Repository等) - JavaConfig负责基础设施 Bean 和第三方集成的显式声明
- XML(若有遗留)通过
<import resource="classpath:old-config.xml"/>或@ImportResource并入
在 Spring Boot 项目中,几乎看不到 XML 配置的身影,主流模式是 “自动配置 + 少量 JavaConfig + 注解驱动”。但理解 XML 仍然重要,因为很多老的 Spring 项目、企业集成文档以及 Spring 内部某些命名空间的配置(如 Spring Security 早期版本)依然基于 XML 示例。
从演变路径来看:XML → 注解 + XML → JavaConfig + 注解 → 习惯优于配置(Boot 自动配置)。每一种新方式的出现,都在降低开发者的配置成本,同时让配置更加内聚和类型安全。掌握这三种方式,你就能读懂任何时期的 Spring 项目,并根据实际场景做出最合适的选择。