Spring Bean 自动装配:一场对象间的浪漫邂逅
各位看官,今天咱们聊聊 Spring 框架里一个特别有意思的功能:Bean 的自动装配。这玩意儿就像个老媒婆,专门负责撮合对象们,让他们自动找到彼此,省去了咱们手动 new
对象、setter
注入的麻烦。
想象一下,你写了一个类,里面需要用到另一个类的实例。如果没有自动装配,你得自己创建那个实例,然后用 setter
方法或者构造函数把它塞进去。这就像相亲,得自己到处打听,费劲巴拉地找对象,然后还得自己介绍认识。
但是有了自动装配,Spring 就像个老媒婆,它会根据你的需求,自动帮你找到合适的“对象”,然后帮你把它们“撮合”在一起。 这就省事多了,对吧?
Spring 提供了几种自动装配的模式,分别是 byType
、byName
和 constructor
。咱们今天就来深入了解一下这三种“撮合”方式的底层实现,看看 Spring 这个老媒婆到底是怎么工作的。
1. byType
:类型匹配的“一见钟情”
byType
顾名思义,就是根据类型来匹配 Bean。 就像相亲时说, "我想找个医生", 然后媒婆就给你推荐所有医生一样。只要 Spring 容器里有一个类型匹配的 Bean,它就会自动注入到你的目标 Bean 中。
工作原理:
Spring 在启动时,会扫描所有 Bean 的定义,并建立一个 Bean 类型到 Bean 名称的映射表。 当需要进行 byType
自动装配时, Spring 会根据目标 Bean 中需要注入的属性的类型, 在映射表中查找是否有匹配的 Bean。 如果找到一个且仅有一个匹配的 Bean, 就会自动注入。
示例:
假设我们有两个类:Car
和 Driver
。Driver
类需要一辆 Car
。
// Car.java
public class Car {
private String brand;
public Car(String brand) {
this.brand = brand;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
@Override
public String toString() {
return "Car{" +
"brand='" + brand + ''' +
'}';
}
}
// Driver.java
public class Driver {
private Car car;
public void setCar(Car car) {
this.car = car;
}
public Car getCar() {
return car;
}
@Override
public String toString() {
return "Driver{" +
"car=" + car +
'}';
}
}
现在,我们用 Spring 配置来声明这两个 Bean,并使用 byType
进行自动装配:
<!-- applicationContext.xml -->
<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="myCar" class="com.example.Car">
<constructor-arg value="BMW"/>
</bean>
<bean id="myDriver" class="com.example.Driver" autowire="byType"/>
</beans>
在上面的配置中,myDriver
Bean 的 autowire
属性设置为 byType
。 这意味着 Spring 会自动查找类型为 Car
的 Bean,并将其注入到 myDriver
的 car
属性中。 因为容器中只有一个 Car
类型的 Bean(myCar
),所以它会被自动注入。
底层实现 (简化版):
Spring 内部的实现大概是这样的(简化版,真正的实现要复杂得多):
public class SimpleBeanFactory {
private Map<String, Object> beanDefinitions = new HashMap<>(); // 存储 Bean 定义
public void registerBeanDefinition(String beanName, Object bean) {
beanDefinitions.put(beanName, bean);
}
public Object getBean(String beanName) {
return beanDefinitions.get(beanName);
}
public void autowireByType(Object bean) throws IllegalAccessException {
Class<?> beanClass = bean.getClass();
Field[] fields = beanClass.getDeclaredFields(); // 获取所有字段
for (Field field : fields) {
if (field.isAnnotationPresent(Autowired.class)) { // 假设有 @Autowired 注解
Class<?> fieldType = field.getType(); // 获取字段类型
Object dependency = findBeanByType(fieldType); // 查找匹配的 Bean
if (dependency != null) {
field.setAccessible(true); // 设置可访问性
field.set(bean, dependency); // 注入依赖
}
}
}
}
private Object findBeanByType(Class<?> type) {
List<Object> matchingBeans = new ArrayList<>();
for (Object bean : beanDefinitions.values()) {
if (type.isAssignableFrom(bean.getClass())) { // 判断类型是否匹配
matchingBeans.add(bean);
}
}
if (matchingBeans.size() == 1) {
return matchingBeans.get(0);
} else if (matchingBeans.size() > 1) {
throw new RuntimeException("找到多个匹配的 Bean,byType 自动装配失败");
} else {
return null; // 没有找到匹配的 Bean
}
}
// 假设的 @Autowired 注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Autowired {
}
public static void main(String[] args) throws IllegalAccessException {
SimpleBeanFactory beanFactory = new SimpleBeanFactory();
// 注册 Bean
Car car = new Car("Ford");
beanFactory.registerBeanDefinition("myCar", car);
Driver driver = new Driver();
beanFactory.registerBeanDefinition("myDriver", driver);
// 模拟自动装配
Driver driverBean = (Driver) beanFactory.getBean("myDriver");
driverBean.setCar(car); // 手动设置,模拟byType
System.out.println(driverBean);
//尝试模拟自动装配的底层实现
//将beanFactory中注册的car注入到driver中
Driver driver2 = (Driver) beanFactory.getBean("myDriver");
Field[] fields = driver2.getClass().getDeclaredFields();
for (Field field : fields){
if(field.getType() == Car.class){
field.setAccessible(true);
try {
field.set(driver2,car);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
System.out.println(driver2);
}
}
byType
的注意事项:
- 唯一性:
byType
要求容器中只能有一个匹配类型的 Bean。 如果有多个, Spring 会抛出NoUniqueBeanDefinitionException
异常。 这就像媒婆给你推荐了两个医生,你不知道选哪个。 - 接口和抽象类:
byType
可以匹配接口和抽象类的实现类。 只要容器中有实现类或子类, Spring 就能找到并注入。 - 优先级: 如果有多个匹配的 Bean,并且你使用了
@Primary
注解或者配置了<bean primary="true">
,那么 Spring 会优先选择被标记为 primary 的 Bean。
总结:
byType
是一种简单直接的自动装配方式,它适用于依赖关系明确且类型唯一的场景。 但是,如果容器中有多个相同类型的 Bean,就需要考虑使用其他自动装配方式,或者使用 @Primary
注解来指定优先级。
2. byName
:名称匹配的“指腹为婚”
byName
自动装配是根据 Bean 的名称来匹配的。 就像相亲时说, "我要找叫张三的", 然后媒婆就给你推荐所有叫张三的人一样。Spring 会查找与目标 Bean 中需要注入的属性名称相同的 Bean,如果找到,就会自动注入。
工作原理:
Spring 在启动时,会扫描所有 Bean 的定义,并建立一个 Bean 名称到 Bean 实例的映射表。 当需要进行 byName
自动装配时,Spring 会根据目标 Bean 中需要注入的属性的名称,在映射表中查找是否有匹配的 Bean。 如果找到,就会自动注入。
示例:
我们还是用 Car
和 Driver
这两个类。这次,我们将 Car
Bean 的 id 设置为 myAwesomeCar
,然后让 Driver
类的 car
属性的名称与 Car
Bean 的 id 相同。
// Car.java (不变)
// Driver.java (不变)
Spring 配置:
<!-- applicationContext.xml -->
<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="myAwesomeCar" class="com.example.Car">
<constructor-arg value="Mercedes"/>
</bean>
<bean id="myDriver" class="com.example.Driver" autowire="byName"/>
<property name="car" ref="myAwesomeCar" />
</beans>
在这个例子中,myDriver
Bean 的 autowire
属性设置为 byName
。 Spring 会查找一个 id 为 car
的 Bean(因为 Driver
类的 car
属性的名称为 car
),并将其注入到 myDriver
的 car
属性中。
重要:
byName
依赖于属性名称与 Bean 的名称的匹配。 如果属性名称与 Bean 名称不匹配,则不会进行自动装配。 上面例子中,如果 Driver
类中 car
属性改名为 vehicle
, 那么 byName
自动装配就会失败,因为 Spring 找不到 id 为 vehicle
的 Bean。
底层实现 (简化版):
public class SimpleBeanFactory {
private Map<String, Object> beanDefinitions = new HashMap<>(); // 存储 Bean 定义
public void registerBeanDefinition(String beanName, Object bean) {
beanDefinitions.put(beanName, bean);
}
public Object getBean(String beanName) {
return beanDefinitions.get(beanName);
}
public void autowireByName(Object bean) throws IllegalAccessException {
Class<?> beanClass = bean.getClass();
Field[] fields = beanClass.getDeclaredFields(); // 获取所有字段
for (Field field : fields) {
if (field.isAnnotationPresent(Autowired.class)) { // 假设有 @Autowired 注解
String fieldName = field.getName(); // 获取字段名称
Object dependency = getBean(fieldName); // 根据名称查找 Bean
if (dependency != null) {
field.setAccessible(true); // 设置可访问性
field.set(bean, dependency); // 注入依赖
}
}
}
}
// 假设的 @Autowired 注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Autowired {
}
public static void main(String[] args) throws IllegalAccessException {
SimpleBeanFactory beanFactory = new SimpleBeanFactory();
// 注册 Bean
Car car = new Car("Ford");
beanFactory.registerBeanDefinition("myCar", car);
Driver driver = new Driver();
beanFactory.registerBeanDefinition("myDriver", driver);
// 模拟自动装配 byName, 需要保证Driver中Car属性的名字要和Car的beanName一致
Driver driverBean = (Driver) beanFactory.getBean("myDriver");
Field[] fields = driverBean.getClass().getDeclaredFields();
for (Field field : fields){
if(field.getName().equals("car")){ //属性名要和beanName一致
Car car1 = (Car) beanFactory.getBean("myCar");
field.setAccessible(true);
try {
field.set(driverBean,car1);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
System.out.println(driverBean);
}
}
byName
的注意事项:
- 命名约定:
byName
依赖于属性名称和 Bean 名称的匹配,因此需要遵守一定的命名约定。 通常情况下,属性名称应该与 Bean 名称相同。 - 可读性:
byName
可以提高代码的可读性,因为可以通过属性名称直接看出依赖关系。
总结:
byName
是一种基于名称匹配的自动装配方式,它适用于需要显式指定依赖关系的场景。 它可以提高代码的可读性,但也需要遵守一定的命名约定。
3. constructor
:构造函数注入的“包办婚姻”
constructor
自动装配是通过构造函数来注入依赖的。 就像相亲时说, "我要求结婚对象必须是博士", 然后媒婆就给你推荐所有博士结婚对象一样。 Spring 会查找与目标 Bean 的构造函数参数类型匹配的 Bean,并将它们作为参数传递给构造函数。
工作原理:
Spring 会分析目标 Bean 的构造函数,找到所有参数的类型。 然后,它会在容器中查找与这些参数类型匹配的 Bean。 如果找到匹配的 Bean, Spring 会将它们按照构造函数参数的顺序传递给构造函数,从而创建目标 Bean 的实例。
示例:
我们修改 Driver
类,使用构造函数来注入 Car
:
// Driver.java
public class Driver {
private Car car;
public Driver(Car car) {
this.car = car;
}
public Car getCar() {
return car;
}
@Override
public String toString() {
return "Driver{" +
"car=" + car +
'}';
}
}
Spring 配置:
<!-- applicationContext.xml -->
<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="myCar" class="com.example.Car">
<constructor-arg value="Audi"/>
</bean>
<bean id="myDriver" class="com.example.Driver" autowire="constructor"/>
</beans>
在这个例子中,myDriver
Bean 的 autowire
属性设置为 constructor
。 Spring 会查找一个类型为 Car
的 Bean,并将其作为参数传递给 Driver
类的构造函数。
底层实现 (简化版):
public class SimpleBeanFactory {
private Map<String, Object> beanDefinitions = new HashMap<>(); // 存储 Bean 定义
public void registerBeanDefinition(String beanName, Object bean) {
beanDefinitions.put(beanName, bean);
}
public Object getBean(String beanName) {
return beanDefinitions.get(beanName);
}
public Object autowireConstructor(Class<?> beanClass) throws IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor<?>[] constructors = beanClass.getDeclaredConstructors();
Constructor<?> bestConstructor = null;
Object[] constructorArgs = null;
for (Constructor<?> constructor : constructors) {
Class<?>[] parameterTypes = constructor.getParameterTypes();
Object[] args = new Object[parameterTypes.length];
boolean match = true;
for (int i = 0; i < parameterTypes.length; i++) {
Class<?> parameterType = parameterTypes[i];
Object dependency = findBeanByType(parameterType); // 查找匹配的 Bean
if (dependency == null) {
match = false;
break;
}
args[i] = dependency;
}
if (match) {
bestConstructor = constructor;
constructorArgs = args;
break; // 找到匹配的构造函数,停止查找
}
}
if (bestConstructor != null) {
return bestConstructor.newInstance(constructorArgs); // 使用构造函数创建实例
} else {
try {
return beanClass.getDeclaredConstructor().newInstance();
} catch (NoSuchMethodException e){
throw new RuntimeException("没有找到合适的构造函数");
}
}
}
private Object findBeanByType(Class<?> type) {
List<Object> matchingBeans = new ArrayList<>();
for (Object bean : beanDefinitions.values()) {
if (type.isAssignableFrom(bean.getClass())) { // 判断类型是否匹配
matchingBeans.add(bean);
}
}
if (matchingBeans.size() == 1) {
return matchingBeans.get(0);
} else if (matchingBeans.size() > 1) {
throw new RuntimeException("找到多个匹配的 Bean,byType 自动装配失败");
} else {
return null; // 没有找到匹配的 Bean
}
}
public static void main(String[] args) throws IllegalAccessException, InstantiationException, InvocationTargetException {
SimpleBeanFactory beanFactory = new SimpleBeanFactory();
// 注册 Bean
Car car = new Car("Ford");
beanFactory.registerBeanDefinition("myCar", car);
//Driver driver = new Driver();
//beanFactory.registerBeanDefinition("myDriver", driver);
// 模拟 autowireConstructor
Driver driver = (Driver) beanFactory.autowireConstructor(Driver.class);
System.out.println(driver);
}
}
constructor
的注意事项:
- 构造函数参数类型:
constructor
依赖于构造函数参数类型与 Bean 类型的匹配。 如果没有匹配的 Bean, Spring 会抛出异常。 - 多个构造函数: 如果一个 Bean 有多个构造函数, Spring 会选择参数最多的构造函数进行自动装配。 如果有多个参数数量相同的构造函数, Spring 会选择第一个找到的构造函数。
总结:
constructor
是一种通过构造函数注入依赖的自动装配方式。 它可以确保 Bean 在创建时就拥有所有必需的依赖,从而提高代码的健壮性。
自动装配模式的比较:
模式 | 匹配方式 | 优点 | 缺点 | 适用场景 |
---|---|---|---|---|
byType |
类型 | 简单直接,配置量少 | 要求类型唯一,容易出现 NoUniqueBeanDefinitionException |
依赖关系明确且类型唯一的场景 |
byName |
名称 | 可读性高,可以显式指定依赖关系 | 依赖于命名约定,配置量相对较多 | 需要显式指定依赖关系,或者有多个相同类型的 Bean 需要区分的场景 |
constructor |
构造函数参数类型 | 确保 Bean 在创建时就拥有所有必需的依赖,代码健壮性高 | 如果没有合适的构造函数,或者有多个构造函数,可能会出现问题,配置量相对较多 | 需要确保 Bean 在创建时就拥有所有必需的依赖,并且希望通过构造函数来初始化 Bean 的场景 |
总结:
Spring 的自动装配功能极大地简化了 Bean 的依赖注入过程,提高了开发效率。 理解不同的自动装配模式的底层实现,可以帮助我们更好地选择合适的装配方式,并避免一些常见的错误。 就像了解媒婆的套路,才能更好地找到心仪的对象一样。 希望这篇文章能帮助你更好地理解 Spring 的自动装配机制,并在实际开发中灵活运用。