Spring 是如何处理构造函数注入、Setter 方法注入、字段注入?

Spring 支持三种主要的依赖注入方式:构造函数注入、Setter 方法注入和字段注入。Spring 框架内部通过反射机制以及 BeanWrapper 接口等来实现这些注入方式。

1. 构造函数注入 (Constructor Injection):

  • 原理: Spring 通过调用 Bean 类的构造函数来创建 Bean 实例,并将依赖的 Bean 作为构造函数的参数传入。

  • 优点:

    • 强制依赖: 构造函数注入可以确保 Bean 在创建时就拥有所有必需的依赖,避免了部分初始化状态。
    • 不可变性: 可以将依赖声明为 final,保证依赖在 Bean 的生命周期内不会被修改。
    • 易于测试: 可以轻松地通过构造函数传递模拟对象 (Mock Objects) 进行单元测试。
  • 缺点:

    • 循环依赖问题: 如果存在循环依赖(A 依赖 B,B 又依赖 A),构造函数注入可能会导致问题(可以通过 Setter 注入或 @Lazy 注解解决)。
    • 构造函数参数过多: 如果依赖很多,构造函数参数列表可能会很长,不够优雅(可以使用构建器模式或参数对象来改善)。
  • XML 配置:

    <bean id="userService" class="com.example.UserService">
        <constructor-arg ref="userRepository"/>
    </bean>
    
    <bean id="userRepository" class="com.example.UserRepository"/>
    
  • Java 注解:

    @Service
    public class UserService {
    
        private final UserRepository userRepository;
    
        @Autowired // 可以省略 (从 Spring 4.3 开始,如果只有一个构造函数,@Autowired 可以省略)
        public UserService(UserRepository userRepository) {
            this.userRepository = userRepository;
        }
    }
    
    @Repository
    public class UserRepository {
    }
    
  • Java 配置类:

    @Configuration
    public class AppConfig {
    
        @Bean
        public UserService userService(UserRepository userRepository) {
            return new UserService(userRepository);
        }
    
        @Bean
        public UserRepository userRepository() {
            return new UserRepository();
        }
    }
    

2. Setter 方法注入 (Setter Injection):

  • 原理: Spring 通过调用 Bean 类的 Setter 方法来注入依赖。

  • 优点:

    • 灵活性: 可以在 Bean 创建后修改依赖。
    • 可选依赖: Setter 方法可以有默认值,或者可以不调用,因此可以支持可选的依赖。
    • 解决循环依赖: Setter 注入可以解决构造函数注入可能导致的循环依赖问题。
  • 缺点:

    • 可能部分初始化: Bean 在创建时可能没有所有必需的依赖,需要在调用 Setter 方法后才能完全初始化。
    • 可变性: 依赖可以被修改,可能导致意外的行为。
    • 测试稍复杂: 需要手动调用 Setter 方法来设置依赖。
  • XML 配置:

    <bean id="userService" class="com.example.UserService">
        <property name="userRepository" ref="userRepository"/>
    </bean>
    
    <bean id="userRepository" class="com.example.UserRepository"/>
    
  • Java 注解:

    @Service
    public class UserService {
    
        private UserRepository userRepository;
    
        @Autowired
        public void setUserRepository(UserRepository userRepository) {
            this.userRepository = userRepository;
        }
    }
    
    @Repository
    public class UserRepository {
    }
    
  • Java 配置类: (不常用,通常直接在 @Bean 方法中使用构造函数注入)

     @Configuration
     public class AppConfig {
    
       @Bean
       public UserService userService() {
           UserService userService = new UserService();
           userService.setUserRepository(userRepository()); // 手动调用 Setter 方法
           return userService;
       }
    
       @Bean
       public UserRepository userRepository() {
           return new UserRepository();
       }
    }
    

3. 字段注入 (Field Injection):

  • 原理: Spring 直接通过反射设置 Bean 的字段值。

  • 优点:

    • 简洁: 代码最简洁,不需要构造函数或 Setter 方法。
  • 缺点:

    • 破坏封装性: 直接操作字段,绕过了 Setter 方法可能存在的逻辑。
    • 难以测试: 无法通过构造函数或 Setter 方法注入模拟对象,测试困难。
    • 反射开销: 反射操作通常比直接调用方法要慢。
    • 与依赖注入容器紧耦合: 只能通过依赖注入容器注入依赖,无法手动注入。
    • 不可变性无法保证
    • 容易导致循环依赖
  • 不推荐使用字段注入。

  • XML 配置: (不支持,需要使用 <property>

  • Java 注解:

    @Service
    public class UserService {
    
        @Autowired
        private UserRepository userRepository; // 直接注入到字段
    }
    
    @Repository
    public class UserRepository {
    }
    
  • Java 配置类: (不支持,通常使用 @Bean 方法的构造函数注入)

Spring 内部如何实现:

  1. 解析 Bean 定义: Spring 解析 XML 配置、注解或 Java 配置类,生成 BeanDefinition 对象,其中包含了 Bean 的类名、构造函数参数、属性值、依赖关系等信息。

  2. 创建 Bean 实例: Spring 根据 BeanDefinition 中的信息,通过反射创建 Bean 的实例(通常是调用构造函数)。

  3. 属性填充 (Populate Bean): Spring 根据 BeanDefinition 中的属性配置,为 Bean 的属性赋值。

    • BeanWrapper: Spring 使用 BeanWrapper 接口及其实现类(如 BeanWrapperImpl)来访问和操作 Bean 的属性。BeanWrapper 内部使用反射来调用 Setter 方法或设置字段值。
    • PropertyAccessor: BeanWrapper 实现了 PropertyAccessor 接口,提供了更底层的属性访问方法。
    • 类型转换: 如果配置值的类型与属性的类型不匹配,Spring 会尝试进行类型转换(使用 TypeConverterConversionService 或自定义的 PropertyEditor)。
    • 自动装配: 如果启用了自动装配 (byType, byName, constructor),Spring 会根据类型或名称自动查找依赖的 Bean,并注入。
    • AutowiredAnnotationBeanPostProcessor: 这是一个 BeanPostProcessor,它负责处理 @Autowired@Value@Inject@Resource 等注解,实现依赖注入。 它实现了InstantiationAwareBeanPostProcessor接口, 会在属性注入阶段被调用.
  4. 依赖解析: Spring 容器会解析 Bean 之间的依赖关系,并确保依赖的 Bean 在需要时被创建和注入。

示例(Spring 内部处理流程的简化描述):

假设有以下 Java 类:

public class UserService {
    private UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public void setUserRepository(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

构造函数注入:

  1. Spring 解析 UserServiceBeanDefinition
  2. Spring 发现 UserService 有一个带参数的构造函数。
  3. Spring 从容器中查找类型为 UserRepository 的 Bean(假设已存在)。
  4. Spring 使用反射调用 UserService 的构造函数,并将 UserRepository 的实例作为参数传入。

Setter 方法注入:

  1. Spring 解析 UserServiceBeanDefinition
  2. Spring 创建 UserService 的实例(使用无参构造函数)。
  3. Spring 发现 UserService 有一个 setUserRepository 方法。
  4. Spring 从容器中查找类型为 UserRepository 的 Bean。
  5. Spring 使用 BeanWrapper 和反射调用 setUserRepository 方法,并将 UserRepository 的实例作为参数传入。

字段注入:

  1. Spring 解析 UserServiceBeanDefinition
  2. Spring 创建 UserService 的实例。
  3. AutowiredAnnotationBeanPostProcessor 检测到 userRepository 字段上有 @Autowired 注解。
  4. Spring 从容器中查找类型为 UserRepository 的 Bean。
  5. Spring 使用 BeanWrapper 和反射直接设置 userRepository 字段的值。

总结:

Spring 支持构造函数注入、Setter 方法注入和字段注入三种依赖注入方式。 推荐使用构造函数注入(强制依赖、不可变性、易于测试)。 Spring 内部使用反射、BeanWrapperAutowiredAnnotationBeanPostProcessor 等机制来实现依赖注入。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冰糖心书房

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值