深入理解Spring的依赖查找和依赖注入【建议收藏】

导读:本篇文章讲解 深入理解Spring的依赖查找和依赖注入【建议收藏】,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

如果你觉得内容对你有帮助的话,不如给个赞,鼓励一下更新😂。

本文内容来自《小马哥讲Spring核心编程思想》,基本涵盖了工作中用到的注入相关的概念,所涵盖的知识点较多。

依赖注入的模式和类型

依赖注入的模式

手动模式 – 配置或者编程的方式,提前安排注入规则

  • XML 资源配置元信息
  • Java 注解配置元信息,比如@Autowired、@Resource
  • API 配置元信息

自动模式 – 实现方提供依赖自动关联的方式,按照內建的注入规则

  • Autowiring(自动绑定)

依赖注入的类型

依赖注入类型 配置元数据举例
Setter 方法
构造器
字段 @Autowired User user;
方法 @Autowired public void user(User user) { … }
接口回调 class MyBean implements BeanFactoryAware { … }

构造器和Setter注入的利与弊

使用构造器注入的好处:

  1. 保证依赖不可变(final关键字)
  2. 保证依赖不为空(省去了我们对其检查)
  3. 保证返回客户端(调用)的代码的时候是完全初始化的状态
  4. 避免了循环依赖
  5. 避免了和容器的高度耦合,提升了代码的可复用性

总结: 构造器注入适用于强制对象注入;Setter 注入适合于可选对象注入;并且构造器注入在构造过程中可以保证线程的安全

自动绑定(Autowiring)的模式

模式 说明
no 默认值,未激活 Autowiring,需要手动指定依赖注入对象。
byName 根据被注入属性的名称作为 Bean 名称进行依赖查找,并将对象设置到该属性。
byType 根据被注入属性的类型作为依赖类型进行查找,并将对象设置到该属性。
constructor 特殊 byType 类型,用于构造器参数。

延迟依赖查找、延迟依赖注入

Bean 延迟依赖查找接口

  • org.springframework.beans.factory.ObjectFactory
  • org.springframework.beans.factory.ObjectProvider

Bean 延迟依赖注入也是这两个:

  • 使用 API ObjectFactory 延迟注入
  • 使用 API ObjectProvider 延迟注入(推荐)

限定注入@Qualifier

  • 通过Bean名称限定
  • 通过分组限定

实现@Autowired和@Resource等注解的后置处理器

  • AutowiredAnnotationBeanPostProcessor
    • 处理 @Autowired 以及 @Value 注解
    • 在postProcessMergedBeanDefinition方法中查找自动注入的元信息来封装注入元素信息
    • 在postProcessProperties方法中实现属性和方法注入
  • CommonAnnotationBeanostProcessor(生命周期注解也是在这里面实现的)
    • (条件激活)处理 JSR-250 注解,如 @PostConstruct 等

@Autowired注入过程

所有方法都在AutowiredAnnotationBeanPostProcessor#类里

  • 调用postProcessProperties()方法(spring 5.1之后才是这个方法名,5.1之前是postProcessPropertyValues)
    • postProcessProperties方法会比bean的setXX()方法先调用
    • findAutowiringMetadata()方法会找出一个bean加了@Autowired注解的字段(包括父类的),并且该方法做了缓存
    • xml配置的bean与bean之间是可以有继承关系的,有另一个周期(不是autowired的流程)是把配置super bean的属性合并到当前bean,之后会调用后置方法postProcessMergedBeanDefinition,该方法也会调用一次findAutowiringMetadata
    • 经测试,postProcessMergedBeanDefinition会比postProcessProperties先执行,因此调用postProcessProperties时都是直接拿缓存
  • 调用inject方法, 底层调用的是AutowiredFieldElement和AutowiredMethodElement的inject方法。(获得对应的bean,然后通过反射注入到类的字段上)
  • inject方法会调用DefaultListableBeanFactory#resolveDependency方法,这方法会根据@Autowired字段信息来匹配出符合条件的bean

Spring Bean的来源

依赖查找的来源

来源 配置元数据
Spring BeanDefinition
@Bean public User user(){…}
BeanDefinitionBuilder
单例对象 API实现

依赖注入的来源

来源 配置元数据
Spring BeanDefinition
@Bean public User user(){…}
BeanDefinitionBuilder
单例对象 API实现
非Spring容器管理对象
外部化配置作为依赖来源

Spring容器管理和游离对象

来源 配置元数据 生命周期管理 配置元信息 使用场景
Spring BeanDefinition 依赖查找、依赖注入
单例对象 依赖查找、依赖注入
ResolvableDependency 依赖注入

Spring BeanDefinition 作为依赖来源

  • 元数据:BeanDefinition
  • 注册:
    • 命名方式:BeanDefinitionRegistry#registerBeanDefinition(String,BeanDefinition)
    • 非命名方式: BeanDefinitionReaderUtils#registerWithGeneratedName(AbstractBeanDefinition,Be anDefinitionRegistry)
    • 配置类方式:AnnotatedBeanDefinitionReader#register(Class…)
  • 类型:延迟和非延迟
  • 顺序:Bean 生命周期顺序按照注册顺序
  • 举例:
    • 实现ImportBeanDefinitionRegistrar
    • 实现BeanDefinitionRegistryPostProcessor

单例对象作为依赖来源

  • 要素
    • 来源:外部普通Java 对象(不一定是POJO)
    • 注册:SingletonBeanRegistry#registerSingleton
  • 限制
    • 无生命周期管理
    • 无法实现延迟初始化Bean

非Spring容器管理对象作为依赖来源

  • 要素
    • 注册:ConfigurableListableBeanFactory#registerResolvableDependency
  • 限制
    • 无生命周期管理
    • 无法实现延迟初始化Bean
    • 无法通过依赖查找

registerResolvableDependency和registerSingleton的区别:
前者是注册可以解析的依赖关系,当注入的类型为dependencyType的时候,注入autowiredValue,并将注入类型与注入值的关系存储在map中。常用于解决在Spring框架内部自动装配的时候如果一个接口有多个实现类,并且都已经放到IOC中去了,那么自动装配的时候就会出异常,因为spring不知道把哪个实现类注入进去的问题。

外部化配置作为依赖来源

  • 要素
    • 类型:非常规Spring对象依赖来源
  • 限制
    • 无生命周期管理
    • 无法实现延迟初始化Bean
    • 无法通过依赖查找

Spring 依赖处理的过程

Spring依赖处理是依赖注入的一个环节,就是说在注入过程中我们把这个对象的依赖来进行解析,然后利用反射进行赋值注入。

  • 入口 – DefaultListableBeanFactory#resolveDependency
  • 依赖描述符 – DependencyDescriptor
  • 自定绑定候选对象处理器 – AutowireCandidateResolver

bean的属性填充populateBean方法最终会走到DefaultListableBeanFactory#resolveDependency方法,最后还是会走到AbstractBeanFactory#getBean方法中去获取Bean

如何解决单例Bean中需要注入Prototype作用域Bean的问题

在大多数应用场景中,容器中大部分bean都是singleton。当一个单例bean需要与另一个单例bean协作或一个非单例bean需要与另一个非单例bean协作时,通常是将一个bean定义为另一个bean的属性来处理依赖关系。当bean的生命周期不同时,就会出现问题。可能是在A的每个方法调用上,假设单例bean A需要使用非单例(原型)bean B。容器只会创建单例bean A一次,因此只有一次机会来设置属性。容器无法在每次进行A方法调用时,都为bean A提供一个全新的bean B实例。(注:也就是多例可以调用单例,但是单例无法调用多例
解决方案是放弃一些控制翻转。通过实现 ApplicationContextAware 接口,从而使bean A能够拿到容器的上下文 ,并在每次bean A 需要bean B时,通过调用容器上下文的 getBean("B") 方法来请求得到(通常是新创建的)bean B实例。

使用ApplicationContextAware回调接口解决

public class CommandManager implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    public Object process(Map commandState) {
        // grab a new instance of the appropriate Command
        Command command = createCommand();
        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState);
        return command.execute();
    }

    protected Command createCommand() {
        // notice the Spring API dependency!
        return this.applicationContext.getBean("command", Command.class);
    }

    public void setApplicationContext(
            ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

但是以上方法并不可取,因为业务代码会使用Spring内部的上下文,也就是会和Spring Framework耦合到一起

方法注入

查找方法注入是容器覆盖受容器管理的bean上的方法并返回容器中另一个命名的bean的查找结果的能力。查找通常涉及一个原型bean。Spring框架通过使用CGLIB库中的字节码生成来动态生成覆盖该方法的子类。
方法注入条件:

  • 为了使这动态子类起作用,Spring容器的子类class不能用 final 修饰,且重写的方法也不能用 final 修饰。
  • 对具有抽象方法的类进行单元测试,需要你自己创建这个类的子类并对 abstract 方法进行实现。
  • 组件扫描也需要具体的方法,这需要具体的类去支持。
  • 进一步的关键限制是查找方法对工厂方法不起作用,尤其不适用于配置类中的 @Bean 方法,因为在这种情况下,容器不负责创建实例,所以不能动态地创建运行时生成的子类。

在包含注入方法的客户端类(本例中 CommandManager)中,要求注入的方法具有以下签名:

<public|protected> [abstract] <return-type> theMethodName(no-arguments);

如果方法是 abstract 的,则动态生成的子类将实现该方法。否则,动态生成的子类将覆盖原始类中定义的具体方法。
注解版本实现:

public abstract class CommandManager {

    public Object process(Object commandState) {
        Command command = createCommand();
        command.setState(commandState);
        return command.execute();
    }

    @Lookup("myCommand")
    protected abstract Command createCommand();
}

XML实现:

<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype">
    <!-- inject dependencies here as required -->
</bean>

<!-- commandProcessor uses statefulCommandHelper -->
<bean id="commandManager" class="fiona.apple.CommandManager">
    <lookup-method name="createCommand" bean="myCommand"/>
</bean>

静态@Bean方法和实例@Bean方法的区别

设置方法为static,是因为bean定义的一个周期性问题:

  • 如果是非static,那么这个bean的构建是依赖于声明类的这个bean来处理的。
  • 如果是static,那么它就脱离了这个实现,变成了一个独立的bean,所以如果你需要bean初始化或提前初始化,那么可以选择性的标注成static。

具体细节在ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForBeanMethod方法中,如下:

if (metadata.isStatic()) {
    // static @Bean method
    beanDef.setBeanClassName(configClass.getMetadata().getClassName());
    beanDef.setFactoryMethodName(methodName);
}
else {
    // instance @Bean method
    beanDef.setFactoryBeanName(configClass.getBeanName());
    beanDef.setUniqueFactoryMethodName(methodName);
}

@Bean方法处理的时机

详细请看ConfigurationClassPostProcessor类 => ConfigurationClassParser​

怎么实现不注入Spring容器,却也完成依赖注入?

本着”减轻”Spring容器”负担”的目的,”手动”精细化控制Spring内的Bean组件。像有的这种解析器其实是完全没必要放进容器内的,需要什么组件让容器帮你完成注入即可,自己就没必要放进去

@Configuration
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {

    @Autowired
    private ApplicationContext applicationContext;
    
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        CurrUserArgumentResolver resolver = new CurrUserArgumentResolver();
        // 利用工厂给容器外的对象注入所需组件
        applicationContext.getAutowireCapableBeanFactory().autowireBean(resolver);
        argumentResolvers.add(resolver);
    }
}

本姿势的技巧是利用了AutowireCapableBeanFactory巧妙完成了给外部对象赋能,从而即使自己并不是容器内的Bean,也能自由注入、使用容器内Bean的能力(同样可以随意使用@Autowired注解了~),这种方式是侵入性最弱的。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/15191.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!