Spring Bean 的自动装配(Autowiring)模式:byType, byName, constructor 的底层实现

Spring Bean 自动装配:一场对象间的浪漫邂逅

各位看官,今天咱们聊聊 Spring 框架里一个特别有意思的功能:Bean 的自动装配。这玩意儿就像个老媒婆,专门负责撮合对象们,让他们自动找到彼此,省去了咱们手动 new 对象、setter 注入的麻烦。

想象一下,你写了一个类,里面需要用到另一个类的实例。如果没有自动装配,你得自己创建那个实例,然后用 setter 方法或者构造函数把它塞进去。这就像相亲,得自己到处打听,费劲巴拉地找对象,然后还得自己介绍认识。

但是有了自动装配,Spring 就像个老媒婆,它会根据你的需求,自动帮你找到合适的“对象”,然后帮你把它们“撮合”在一起。 这就省事多了,对吧?

Spring 提供了几种自动装配的模式,分别是 byTypebyNameconstructor。咱们今天就来深入了解一下这三种“撮合”方式的底层实现,看看 Spring 这个老媒婆到底是怎么工作的。

1. byType:类型匹配的“一见钟情”

byType 顾名思义,就是根据类型来匹配 Bean。 就像相亲时说, "我想找个医生", 然后媒婆就给你推荐所有医生一样。只要 Spring 容器里有一个类型匹配的 Bean,它就会自动注入到你的目标 Bean 中。

工作原理:

Spring 在启动时,会扫描所有 Bean 的定义,并建立一个 Bean 类型到 Bean 名称的映射表。 当需要进行 byType 自动装配时, Spring 会根据目标 Bean 中需要注入的属性的类型, 在映射表中查找是否有匹配的 Bean。 如果找到一个且仅有一个匹配的 Bean, 就会自动注入。

示例:

假设我们有两个类:CarDriverDriver 类需要一辆 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,并将其注入到 myDrivercar 属性中。 因为容器中只有一个 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。 如果找到,就会自动注入。

示例:

我们还是用 CarDriver 这两个类。这次,我们将 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),并将其注入到 myDrivercar 属性中。

重要:

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 的自动装配机制,并在实际开发中灵活运用。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注