如果你觉得内容对你有帮助的话,不如给个赞,鼓励一下更新😂。
本文内容来自《小马哥讲Spring核心编程思想》,基本涵盖了工作中用到的注入相关的概念,所涵盖的知识点较多。
依赖注入的模式和类型
依赖注入的模式
手动模式 – 配置或者编程的方式,提前安排注入规则
- XML 资源配置元信息
- Java 注解配置元信息,比如@Autowired、@Resource
- API 配置元信息
自动模式 – 实现方提供依赖自动关联的方式,按照內建的注入规则
- Autowiring(自动绑定)
依赖注入的类型
依赖注入类型 | 配置元数据举例 |
---|---|
Setter 方法 | |
构造器 | |
字段 | @Autowired User user; |
方法 | @Autowired public void user(User user) { … } |
接口回调 | class MyBean implements BeanFactoryAware { … } |
构造器和Setter注入的利与弊
使用构造器注入的好处:
- 保证依赖不可变(final关键字)
- 保证依赖不为空(省去了我们对其检查)
- 保证返回客户端(调用)的代码的时候是完全初始化的状态
- 避免了循环依赖
- 避免了和容器的高度耦合,提升了代码的可复用性
总结: 构造器注入适用于强制对象注入;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