Spring依赖查找和依赖注入详解

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 等

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://zrrd.net.cn/2058.html

发表评论

登录后才能评论
咨询电话
联系电话:0451-81320577

地址:哈尔滨市松北区中小企业总部基地13F

微信咨询
微信咨询
QQ咨询
分享本页
返回顶部