Spring中怎么把对象给到ioc容器里?
- Spring中怎么把对象给到ioc容器里?
- 一、核心原理铺垫:IOC容器的本质与BeanDefinition核心地位
- 二、基础注册方式(入门必备,简化开发)
-
- 方式1:XML配置文件(传统方式,理解原理必备)
-
- [1.1 核心标签:<bean>](#1.1 核心标签:<bean>)
- [1.2 实战案例](#1.2 实战案例)
- [1.3 原理解析](#1.3 原理解析)
- 方式2:注解驱动(主流方式,简化开发)
-
- [2.1 基础注解:@Component及其衍生注解](#2.1 基础注解:@Component及其衍生注解)
- [2.2 实战案例](#2.2 实战案例)
- [2.3 配置类注解:@Configuration + @Bean](#2.3 配置类注解:@Configuration + @Bean)
- [2.4 实战案例](#2.4 实战案例)
- [2.5 原理解析](#2.5 原理解析)
- 方式3:编程式注册(基础高级,动态控制)
-
- [3.1 核心API说明](#3.1 核心API说明)
- [3.2 实战案例:动态注册Bean](#3.2 实战案例:动态注册Bean)
- [3.3 适用场景](#3.3 适用场景)
- 三、高级注册方式(企业级开发/框架开发必备)
-
- 方式4:@Import注解(批量导入/动态注册Bean)
-
- [4.1 三种导入类型及实战](#4.1 三种导入类型及实战)
- 方式5:实现FactoryBean接口(工厂Bean,创建复杂Bean)
-
- [5.1 核心接口](#5.1 核心接口)
- [5.2 实战案例:自定义FactoryBean创建Redis客户端](#5.2 实战案例:自定义FactoryBean创建Redis客户端)
- [5.3 原理解析](#5.3 原理解析)
- [5.4 典型应用场景](#5.4 典型应用场景)
- 方式6:@Conditional注解(条件注册Bean)
-
- [6.1 核心逻辑](#6.1 核心逻辑)
- [6.2 实战案例:存在Jedis类才注册RedisClient](#6.2 实战案例:存在Jedis类才注册RedisClient)
- 四、所有注册方式对比与选型建议
- 五、Bean注册的完整逻辑与核心心法
Spring中怎么把对象给到ioc容器里?
Spring IOC(控制反转)是整个Spring框架的核心,而"如何把对象交给IOC容器管理"则是入门Spring的第一道关键门槛。本文会从底层原理 到基础实战 ,再到高级扩展,全方位讲解Spring中注册Bean到IOC容器的核心方式。
一、核心原理铺垫:IOC容器的本质与BeanDefinition核心地位
在讲"如何把对象交给容器"之前,必须先理解IOC容器的本质和核心载体------这是看懂所有注册方式的关键。
IOC容器本质:是一个"Bean工厂(BeanFactory)",核心职责是管理Bean的全生命周期(实例化、依赖注入、初始化、销毁)。我们无需手动new对象,而是由容器统一创建、管理和注入,这就是"控制反转"的核心思想。
核心载体:BeanDefinition :Spring的IOC容器底层完全基于BeanDefinition(Bean定义)实现,所有"注册Bean"的操作,本质上都是向容器中注册BeanDefinition------它是描述Bean的"元数据",包含Bean的类全路径、作用域、属性、依赖、初始化方法等信息。
Bean注册的核心流程可总结为:
- 我们通过各种方式(XML/注解/编程)向容器提交"Bean定义信息";
- 容器解析这些信息,生成对应的
BeanDefinition对象并注册到BeanDefinitionRegistry(BeanDefinition注册器); - 容器启动时,根据
BeanDefinition的信息,通过反射等方式创建Bean实例; - 将Bean实例缓存到容器的Map中(key为Bean名称,value为Bean实例),供后续获取和依赖注入。
关键补充:并非所有Bean都直接通过BeanDefinition实例化 (如FactoryBean生成的"二级Bean"),但绝大多数场景(包括基础方式和大部分高级方式)最终都落地到BeanDefinition的注册。
二、基础注册方式(入门必备,简化开发)
基础方式覆盖日常开发中80%的场景,从传统的XML配置到主流的注解驱动,特点是简单、易上手。
方式1:XML配置文件(传统方式,理解原理必备)
这是Spring最早期的注册方式,虽然现在主流用注解,但理解XML方式能帮你彻底吃透Bean注册的本质(手动声明BeanDefinition)。
1.1 核心标签:
标签是XML中注册Bean的核心,通过指定id(Bean的唯一标识)、class(Bean的全类名),容器会反射创建该类的实例并管理。
1.2 实战案例
步骤1:创建普通Java类(待注册的Bean)
java
// 普通POJO,无任何注解
public class UserService {
private String userName;
public void sayHello() {
System.out.println("Hello, " + (userName == null ? "默认用户" : userName));
}
// getter/setter
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
}
步骤2:编写Spring XML配置文件(beans.xml)
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">
<!-- 注册UserService到IOC容器 -->
<bean id="userService" class="com.example.demo.service.UserService">
<!-- 设置属性(依赖注入) -->
<property name="userName" value="张三"/>
</bean>
</beans>
步骤3:加载XML配置,获取容器中的Bean
java
public class XmlBeanTest {
public static void main(String[] args) {
// 加载XML配置,创建IOC容器(ClassPathXmlApplicationContext是BeanFactory的实现)
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
// 从容器中获取Bean(两种方式:按id、按类型)
UserService userService1 = (UserService) context.getBean("userService");
UserService userService2 = context.getBean(UserService.class);
// 验证Bean是否由容器管理
userService1.sayHello(); // 输出:Hello, 张三
System.out.println(userService1 == userService2); // 输出:true(默认单例)
}
}
1.3 原理解析
- 当创建
ClassPathXmlApplicationContext时,容器会启动XML解析器,解析beans.xml; - 解析到标签时,容器会创建
BeanDefinition对象,将id、class、属性等信息存入该对象; - 容器初始化时,根据
BeanDefinition的class属性,通过反射(Class.forName().newInstance())创建Bean实例; - 最后将Bean实例缓存到容器的Map中,供后续获取。
方式2:注解驱动(主流方式,简化开发)
Spring 2.5+引入注解驱动,核心注解包括@Component及其衍生注解(@Service、@Repository、@Controller),以及配置类注解@Configuration、@Bean,彻底替代了繁琐的XML配置。
2.1 基础注解:@Component及其衍生注解
核心逻辑 :通过注解标记类,容器扫描时自动识别并为该类创建BeanDefinition,进而注册Bean到容器中。衍生注解仅用于区分Bean的用途,本质与@Component一致。
衍生注解分工(规范命名,便于维护):
| 注解 | 适用场景 | 本质 |
|---|---|---|
| @Component | 通用普通类 | 基础注解 |
| @Service | 业务逻辑层(Service) | 特殊的@Component |
| @Repository | 数据访问层(DAO/Mapper) | 特殊的@Component |
| @Controller | 控制层(Controller) | 特殊的@Component |
2.2 实战案例
步骤1:用注解标记Bean类
java
// 业务层Bean,用@Service标记
@Service
public class UserService {
private String userName = "李四";
public void sayHello() {
System.out.println("Hello, " + userName);
}
}
// 数据访问层Bean,用@Repository标记
@Repository
public class UserDao {
public String getUserId() {
return "1001";
}
}
步骤2:配置扫描包(两种方式)
容器不会主动扫描注解,需显式指定扫描包路径:
方式A:XML中配置扫描(过渡方式,适用于XML向注解迁移的场景)
xml
<!-- beans.xml中添加组件扫描 -->
<context:component-scan base-package="com.example.demo.service,com.example.demo.dao"/>
方式B:配置类+@ComponentScan(纯注解方式,Spring Boot主流)
java
// 配置类,用@Configuration标记(替代XML配置文件)
@Configuration
// 扫描指定包下的所有注解标记的类(子包会递归扫描)
@ComponentScan(basePackages = "com.example.demo")
public class SpringConfig {
}
步骤3:加载配置,获取Bean
java
public class AnnotationBeanTest {
public static void main(String[] args) {
// 纯注解方式创建容器(AnnotationConfigApplicationContext)
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
// 获取Service Bean
UserService userService = context.getBean(UserService.class);
userService.sayHello(); // 输出:Hello, 李四
// 获取Dao Bean
UserDao userDao = context.getBean(UserDao.class);
System.out.println("用户ID:" + userDao.getUserId()); // 输出:用户ID:1001
}
}
2.3 配置类注解:@Configuration + @Bean
核心逻辑 :@Configuration标记配置类(替代XML配置文件),@Bean标记方法------方法的返回值会被注册为容器中的Bean,方法体可自定义Bean的创建逻辑。
适用场景:
- 注册第三方类的Bean(如RedisTemplate、DataSource,无法在第三方类上加
@Component); - 自定义Bean的创建逻辑(如需要复杂初始化、依赖外部资源的Bean)。
2.4 实战案例
步骤1:编写配置类,用@Bean注册Bean
java
@Configuration
public class BeanConfig {
// 注册第三方类(假设HttpClient是第三方类,无任何注解)
@Bean("httpClient") // 指定Bean的id为httpClient,默认是方法名(createHttpClient)
public HttpClient createHttpClient() {
// 自定义Bean的创建逻辑(复杂初始化)
HttpClient client = new HttpClient();
client.setTimeout(5000); // 设置超时时间
client.setMaxConnections(100); // 设置最大连接数
return client;
}
// 注册自定义类,自定义初始化逻辑
@Bean
public UserService userService() {
UserService service = new UserService();
service.setUserName("王五");
return service;
}
}
// 第三方类示例(无注解,无法修改源码)
public class HttpClient {
private int timeout;
private int maxConnections;
// getter/setter
public int getTimeout() {
return timeout;
}
public void setTimeout(int timeout) {
this.timeout = timeout;
}
public int getMaxConnections() {
return maxConnections;
}
public void setMaxConnections(int maxConnections) {
this.maxConnections = maxConnections;
}
}
步骤2:获取容器中的Bean
java
public class ConfigBeanTest {
public static void main(String[] args) {
// 加载配置类,创建容器
ApplicationContext context = new AnnotationConfigApplicationContext(BeanConfig.class);
// 获取@Bean注册的HttpClient Bean
HttpClient httpClient = context.getBean("httpClient", HttpClient.class);
System.out.println("超时时间:" + httpClient.getTimeout()); // 输出:超时时间:5000
// 获取@Bean注册的UserService Bean
UserService userService = context.getBean(UserService.class);
userService.sayHello(); // 输出:Hello, 王五
}
}
2.5 原理解析
-
@Component扫描原理:
- 容器启动时,
@ComponentScan指定的包会被递归扫描; - 扫描到带有
@Component(及衍生注解)的类时,容器会为该类创建BeanDefinition(Bean的id默认是类名首字母小写,如UserService→userService); - 后续流程同XML方式,容器根据
BeanDefinition创建Bean实例并缓存。
- 容器启动时,
-
@Bean原理:
@Configuration标记的类会被容器解析为"配置类"(本身也会被注册为Bean,因为@Configuration是@Component的衍生注解);- 容器扫描到
@Bean注解的方法时,会创建BeanDefinition,记录方法返回值类型、Bean id(默认方法名)、方法参数(依赖注入)等信息; - 容器初始化时,调用该方法获取返回值,将其作为Bean实例注册到容器中;
- 注意:配置类中的
@Bean方法会被容器代理,即使直接调用该方法,返回的也是容器中的单例Bean,避免重复创建。
方式3:编程式注册(基础高级,动态控制)
在一些简单动态场景(如动态注册单个Bean),可直接通过容器的API手动注册BeanDefinition,核心API是BeanDefinitionRegistry(AnnotationConfigApplicationContext的父接口)。
3.1 核心API说明
-
BeanDefinitionRegistry:BeanDefinition注册器,提供registerBeanDefinition()方法注册Bean; -
GenericBeanDefinition:BeanDefinition的通用实现类,可手动设置类、属性、作用域等; -
AnnotationConfigApplicationContext:实现了BeanDefinitionRegistry,可直接作为容器并调用注册方法。
3.2 实战案例:动态注册Bean
java
public class ProgrammaticBeanTest {
public static void main(String[] args) {
// 1. 创建容器(未初始化,便于手动注册)
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 2. 手动创建BeanDefinition
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(UserService.class); // 设置Bean的类
// 手动设置属性(依赖注入)
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("userName", "赵六");
beanDefinition.setPropertyValues(propertyValues);
// 设置作用域(默认单例,可选原型:prototype)
beanDefinition.setScope(SingletonBeanRegistry.SINGLETON_OBJECT);
// 3. 注册BeanDefinition到容器
context.registerBeanDefinition("customUserService", beanDefinition);
// 4. 刷新容器(必须调用,否则Bean不会初始化)
context.refresh();
// 5. 获取动态注册的Bean
UserService userService = context.getBean("customUserService", UserService.class);
userService.sayHello(); // 输出:Hello, 赵六
}
}
3.3 适用场景
动态加载配置文件后注册Bean、简单的条件注册(复杂条件建议用后续的ImportSelector+@Conditional)、小型框架的Bean扩展。
三、高级注册方式(企业级开发/框架开发必备)
高级方式主要用于复杂场景(动态选择、批量注册、复杂Bean创建、框架扩展),是Spring Boot自动配置、Spring Cloud组件的底层核心,也是区分初级和中级开发者的关键。
方式4:@Import注解(批量导入/动态注册Bean)
@Import是Spring中批量注册Bean的核心注解,标注在配置类上,支持直接导入"普通类""ImportSelector实现类""ImportBeanDefinitionRegistrar实现类"三种类型,无需加@Component等注解,容器会自动注册对应的Bean。
核心优势:简化批量注册、支持动态逻辑,是Spring Boot自动配置(@EnableAutoConfiguration)的底层核心。
4.1 三种导入类型及实战
类型1:导入普通类(基础用法)
适用场景:快速注册无注解的第三方类/自定义类,无需配置组件扫描(简化配置)。
实战案例:
java
// 无任何注解的普通类(第三方类/自定义类)
public class PayService {
public void pay() {
System.out.println("执行支付逻辑");
}
}
// 配置类,用@Import导入PayService
@Configuration
@Import(PayService.class) // 直接导入普通类,自动注册为Bean
public class ImportConfig {
}
// 测试类
public class ImportTest {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(ImportConfig.class);
// 获取Bean(名称为全类名,因为无@Componet指定名称)
PayService payService = context.getBean("com.example.demo.service.PayService", PayService.class);
payService.pay(); // 输出:执行支付逻辑
}
}
原理解析 :容器解析@Import(PayService.class)时,会为PayService创建默认的BeanDefinition(BeanClass=PayService,Scope=singleton),并注册到BeanDefinitionRegistry,后续流程同普通Bean。
类型2:导入ImportSelector实现类(动态选择Bean)
适用场景 :基于条件动态注册Bean(如多环境适配、多配置选择),Spring Boot的AutoConfigurationImportSelector就是该接口的核心实现。
核心接口:
java
public interface ImportSelector {
// 返回需要注册的Bean全类名数组,容器会自动注册这些类为Bean
String[] selectImports(AnnotationMetadata importingClassMetadata);
}
实战案例:动态选择注册不同环境的数据源
java
// 环境枚举
public enum EnvType {
DEV, PROD
}
// 开发环境Bean
public class DevDataSource {
public void connect() {
System.out.println("连接开发环境数据库");
}
}
// 生产环境Bean
public class ProdDataSource {
public void connect() {
System.out.println("连接生产环境数据库");
}
}
// 自定义ImportSelector,动态选择注册的Bean
public class EnvImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
// 模拟获取当前环境(实际可从配置文件/系统变量/命令行参数获取)
String env = System.getProperty("env", "DEV");
// 根据环境返回要注册的Bean全类名数组
if (EnvType.DEV.name().equals(env)) {
return new String[]{DevDataSource.class.getName()};
} else {
return new String[]{ProdDataSource.class.getName()};
}
}
}
// 配置类,导入ImportSelector实现类
@Configuration
@Import(EnvImportSelector.class)
public class EnvConfig {
}
// 测试类
public class ImportSelectorTest {
public static void main(String[] args) {
// 1. 设置环境为DEV
System.setProperty("env", "DEV");
ApplicationContext context = new AnnotationConfigApplicationContext(EnvConfig.class);
DevDataSource devDataSource = context.getBean(DevDataSource.class);
devDataSource.connect(); // 输出:连接开发环境数据库
// 2. 切换环境为PROD
System.setProperty("env", "PROD");
ApplicationContext context2 = new AnnotationConfigApplicationContext(EnvConfig.class);
ProdDataSource prodDataSource = context2.getBean(ProdDataSource.class);
prodDataSource.connect(); // 输出:连接生产环境数据库
}
}
原理解析:
- 容器解析
@Import(EnvImportSelector.class)时,会实例化EnvImportSelector; - 调用
selectImports()方法,传入当前配置类的注解元数据(importingClassMetadata),获取返回的类全名数组; - 为数组中的每个类名创建默认的
BeanDefinition并注册到容器; - 容器后续根据
BeanDefinition实例化Bean。
类型3:导入ImportBeanDefinitionRegistrar实现类(手动注册BeanDefinition)
适用场景 :需要完全自定义BeanDefinition的属性(如作用域、初始化方法、懒加载、构造函数参数),或动态注册多个BeanDefinition,比ImportSelector更灵活,是框架开发的核心方式。
核心接口:
java
public interface ImportBeanDefinitionRegistrar {
// 手动注册BeanDefinition到容器,可自定义BeanDefinition的所有属性
void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
}
实战案例:手动注册带自定义属性的BeanDefinition
java
// 自定义业务类
public class OrderService {
private String orderType;
public void createOrder() {
System.out.println("创建" + orderType + "订单");
}
// getter/setter
public String getOrderType() {
return orderType;
}
public void setOrderType(String orderType) {
this.orderType = orderType;
}
}
// 自定义ImportBeanDefinitionRegistrar
public class OrderBeanRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 1. 创建BeanDefinition(通用实现类)
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(OrderService.class); // 设置Bean的类
// 2. 自定义Bean属性(依赖注入)
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("orderType", "秒杀"); // 设置订单类型为秒杀
beanDefinition.setPropertyValues(propertyValues);
// 3. 自定义作用域(原型模式,每次获取创建新实例)
beanDefinition.setScope("prototype");
// 4. 自定义懒加载(容器启动时不初始化,第一次获取时初始化)
beanDefinition.setLazyInit(true);
// 5. 注册BeanDefinition到容器(指定Bean名称)
registry.registerBeanDefinition("orderService", beanDefinition);
}
}
// 配置类,导入ImportBeanDefinitionRegistrar实现类
@Configuration
@Import(OrderBeanRegistrar.class)
public class OrderConfig {
}
// 测试类
public class ImportBeanDefinitionRegistrarTest {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(OrderConfig.class);
// 获取手动注册的Bean
OrderService orderService1 = context.getBean("orderService", OrderService.class);
OrderService orderService2 = context.getBean("orderService", OrderService.class);
orderService1.createOrder(); // 输出:创建秒杀订单
System.out.println(orderService1 == orderService2); // 输出:false(原型作用域)
}
}
原理解析:
- 容器解析
@Import(OrderBeanRegistrar.class)时,会调用registerBeanDefinitions()方法,传入配置类的注解元数据和BeanDefinitionRegistry; - 开发者在该方法中可手动创建
BeanDefinition,自定义所有属性(类、属性、作用域、懒加载等); - 通过
BeanDefinitionRegistry的registerBeanDefinition()方法,将自定义的BeanDefinition注册到容器; - 容器初始化时,根据自定义的
BeanDefinition实例化Bean。
方式5:实现FactoryBean接口(工厂Bean,创建复杂Bean)
FactoryBean是Spring提供的"工厂Bean"接口,与普通Bean的核心区别:
- 普通Bean:容器管理的是Bean自身实例;
- FactoryBean:容器管理的是"造Bean的工厂",我们从容器中获取的是FactoryBean的
getObject()方法返回的对象(称为"二级Bean")。
核心优势:封装复杂Bean的创建逻辑(如初始化步骤多、依赖外部资源、动态代理对象),是整合第三方组件的核心方式。
5.1 核心接口
java
public interface FactoryBean<T> {
// 返回最终要使用的Bean实例(二级Bean)
T getObject() throws Exception;
// 返回二级Bean的类型(用于容器按类型获取Bean)
Class<?> getObjectType();
// 二级Bean是否为单例(默认true)
default boolean isSingleton() {
return true;
}
}
5.2 实战案例:自定义FactoryBean创建Redis客户端
模拟创建需要复杂初始化的Redis客户端(依赖host、port,需要连接测试、配置超时时间等):
java
// 复杂对象(模拟Redis客户端,需要复杂初始化)
public class RedisClient {
private String host;
private int port;
private Jedis jedis; // 模拟Jedis客户端(第三方类)
// 复杂初始化逻辑(连接Redis、设置配置等)
public void init() {
this.jedis = new Jedis(host, port);
System.out.println("Redis客户端初始化完成:" + host + ":" + port);
}
// 业务方法(获取Redis中的值)
public String get(String key) {
return jedis.get(key);
}
// getter/setter
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
}
// 自定义FactoryBean,创建RedisClient(封装复杂初始化逻辑)
public class RedisClientFactoryBean implements FactoryBean<RedisClient> {
// 工厂Bean的属性(可通过配置注入)
private String host;
private int port;
// 核心方法:创建并返回最终的RedisClient实例(二级Bean)
@Override
public RedisClient getObject() throws Exception {
RedisClient client = new RedisClient();
client.setHost(host);
client.setPort(port);
client.init(); // 执行复杂初始化逻辑
return client;
}
// 返回二级Bean的类型
@Override
public Class<?> getObjectType() {
return RedisClient.class;
}
// 注入工厂Bean的属性(通过@Bean方法设置)
public void setHost(String host) {
this.host = host;
}
public void setPort(int port) {
this.port = port;
}
}
// 配置类,注册FactoryBean(注意:注册的是工厂,不是RedisClient)
@Configuration
public class RedisConfig {
@Bean("redisClient") // Bean名称为redisClient,对应FactoryBean生成的二级Bean
public RedisClientFactoryBean redisClientFactoryBean() {
RedisClientFactoryBean factoryBean = new RedisClientFactoryBean();
factoryBean.setHost("127.0.0.1");
factoryBean.setPort(6379);
return factoryBean;
}
}
// 测试类
public class FactoryBeanTest {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(RedisConfig.class);
// 1. 获取FactoryBean生成的二级Bean(RedisClient)
RedisClient redisClient = context.getBean("redisClient", RedisClient.class);
System.out.println("Redis地址:" + redisClient.getHost() + ":" + redisClient.getPort()); // 输出:127.0.0.1:6379
// 2. 获取FactoryBean本身(需在Bean名称前加&符号)
RedisClientFactoryBean factoryBean = context.getBean("&redisClient", RedisClientFactoryBean.class);
System.out.println("工厂Bean的Host:" + factoryBean.getHost()); // 输出:127.0.0.1
}
}
5.3 原理解析
- 容器注册
RedisClientFactoryBean时,先为其创建BeanDefinition,并实例化FactoryBean本身(这是"一级Bean",但很少直接使用); - 当调用
context.getBean("redisClient")时,容器检测到该Bean是FactoryBean,会自动调用其getObject()方法; getObject()返回的RedisClient是最终使用的"二级Bean"------该对象无需注册BeanDefinition,直接由FactoryBean生成;- 若要获取FactoryBean本身(一级Bean),需在Bean名称前加&符号(如
&redisClient),这是Spring的约定。
5.4 典型应用场景
-
MyBatis整合Spring:
MapperFactoryBean将Mapper接口动态代理为实现类,注入到Service中; -
Spring整合第三方组件:如Redis(
RedisTemplate)、MQ(RabbitTemplate)、Dubbo(服务代理)等,均通过FactoryBean封装复杂初始化逻辑; -
动态代理对象创建:如AOP代理(Spring的
ProxyFactoryBean)、远程服务代理(RMI)。
方式6:@Conditional注解(条件注册Bean)
@Conditional不是独立的注册方式,而是"条件开关",可与上述所有方式(@Bean、@Component、@Import等)结合,基于条件决定是否注册Bean(如"存在某个类才注册""配置文件满足条件才注册")。
6.1 核心逻辑
自定义Condition接口实现类,重写matches()方法------返回true则注册Bean,返回false则不注册。
6.2 实战案例:存在Jedis类才注册RedisClient
java
// 自定义条件:类路径中存在Jedis类才注册Bean
public class JedisExistCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
try {
// 检查类路径中是否存在Jedis(第三方类)
Class.forName("redis.clients.jedis.Jedis");
return true; // 存在则满足条件
} catch (ClassNotFoundException e) {
return false; // 不存在则不满足条件
}
}
}
// 配置类,结合@Bean + @Conditional实现条件注册
@Configuration
public class ConditionalConfig {
@Bean
@Conditional(JedisExistCondition.class) // 满足条件才注册RedisClient
public RedisClient redisClient() {
RedisClient client = new RedisClient();
client.setHost("127.0.0.1");
client.setPort(6379);
return client;
}
}
扩展 :Spring Boot提供了大量现成的条件注解(如@ConditionalOnClass、@ConditionalOnMissingBean、@ConditionalOnProperty),无需自定义Condition,直接使用即可。例如:
java
// 存在Jedis类,且不存在RedisClient Bean时才注册
@Bean
@ConditionalOnClass(Jedis.class)
@ConditionalOnMissingBean
public RedisClient redisClient() {
// ...
}
四、所有注册方式对比与选型建议
结合10年实战经验,整理各方式的核心优势和适用场景,帮你快速选型:
| 注册方式 | 核心优势 | 适用场景 |
|---|---|---|
| XML配置() | 历史兼容,手动控制BeanDefinition | 老项目维护,学习原理 |
| @Component及其衍生注解 | 简单直观,自动扫描,开发效率高 | 应用层自定义Bean(Service/Controller/Dao) |
| @Configuration + @Bean | 自定义创建逻辑,支持第三方类 | 第三方组件注册,复杂初始化Bean |
| 编程式注册(BeanDefinitionRegistry) | 动态灵活,手动控制BeanDefinition | 简单动态场景,小型框架扩展 |
| @Import(普通类) | 快速批量注册,无需扫描 | 少量第三方类快速注册 |
| @Import(ImportSelector) | 动态选择Bean,支持条件逻辑 | 多环境适配,批量动态注册(如Spring Boot自动配置) |
| @Import(ImportBeanDefinitionRegistrar) | 完全自定义BeanDefinition,灵活性最高 | 框架开发,复杂BeanDefinition定制 |
| FactoryBean | 封装复杂创建逻辑,支持二级Bean | 第三方组件整合,动态代理对象创建 |
| @Conditional | 条件控制,避免无效Bean | 多环境适配,依赖检查,按需注册 |
五、Bean注册的完整逻辑与核心心法
Spring中所有Bean注册的核心都围绕BeanDefinition展开(除FactoryBean的二级Bean),不同方式的差异本质是"如何生成/注册BeanDefinition":
-
基础方式 (XML、@Component、@Bean):容器自动生成
BeanDefinition,开发者只需简单声明; -
高级方式 (@Import系列):开发者手动/动态生成
BeanDefinition,再注册到容器,灵活性更高; -
特殊方式 (FactoryBean):
BeanDefinition注册的是"工厂",最终使用的Bean由工厂直接生成(无需BeanDefinition)。
核心心法:
- 先明确场景:是简单业务Bean、第三方组件、动态条件注册,还是框架扩展?
- 优先选简单方式:应用层开发优先用
@Service/@Controller,第三方组件优先用@Bean; - 复杂场景用高级方式:动态条件用ImportSelector+@Conditional,复杂初始化用FactoryBean,框架开发用ImportBeanDefinitionRegistrar;
- 理解底层原理:所有方式最终都可追溯到
BeanDefinition和容器的生命周期,看懂源码的关键就在这里。