内容简介:最近项目在整合shiro权限认证模块时,给自己挖了一个深坑,也是分析了好久才定位到问题的所在,根本原因还是对spring相关的技术点掌握的不够娴熟。本文基于springboot 2.1.5进行分析。下面会用简单的Demo去还原问题的场景。简单将遇到的问题还原一下,这段代码中ShiroProperties 始终注入不到TestController中去。也就是配置文件application.properties:
前言
最近项目在整合shiro权限认证模块时,给自己挖了一个深坑,也是分析了好久才定位到问题的所在,根本原因还是对spring相关的技术点掌握的不够娴熟。本文基于springboot 2.1.5进行分析。下面会用简单的Demo去还原问题的场景。
示例
简单将遇到的问题还原一下,这段代码中ShiroProperties 始终注入不到TestController中去。也就是 shiroProperties
始终是null。
1@ConfigurationProperties(prefix = "test") 2@Data 3public class ShiroProperties { 4 5 private String name; 6 private List<String> chain; 7} 复制代码
1@Configuration 2@EnableConfigurationProperties(ShiroProperties.class) 3public class ShiroConfiguration { 4} 复制代码
1@Controller 2public class TestController { 3 4 @Autowired 5 private ShiroProperties shiroProperties; 6 7 @Bean 8 public Test test(){ 9 System.out.println(shiroProperties); 10 return new Test(); 11 } 12 13 @Bean 14 private TestProcessor testProcessor(){ 15 return new TestProcessor(); 16 } 17} 复制代码
1public class Test { 2} 复制代码
1public class TestProcessor implements 2 BeanFactoryPostProcessor, PriorityOrdered { 3 4 @Override 5 public void postProcessBeanFactory 6 (ConfigurableListableBeanFactory beanFactory) throws BeansException { 7 8 } 9 10 @Override 11 public int getOrder() { 12 return 0; 13 } 14} 复制代码
配置文件application.properties:
test.name=mars test.chain[0]=aa test.chain[1]=bb
初始定位
最开始问题定位为ShiroProperties 没有注册到容器中,所以注入时通过getBean无法取到相应的bean。但是Debug也会发现ShiroProperties 的BeanDefinition已经在容器中持有,所以这个方向基本被排除了。
重定位
重新定位问题,暂时定位在 populateBean
属性注入时初始化ShiroProperties 失败。后面debug查看了一下 populateBean
时的TestController的BeanDefinition,发现ShiroProperties 这个属性根本没有在依赖注入的范围内。所以压根就没初始化。
propertyValues
属性中。
@Autowired注入流程
例如@Autowired注解的属性会通过类AutowiredAnnotationBeanPostProcessor去处理。咱们简单看一下AutowiredAnnotationBeanPostProcessor的构造函数。
实例化的时候就会将Autowired和Value给缓存在一个 autowiredAnnotationTypes
集合中。在后面进行BeanDefinition合并的时候,会遍历 autowiredAnnotationTypes
集合,取出注解对应的字段(例如Autowired和Value对应的字段),最后存放到BeanDefinition的 propertyValues
属性中,供后面的 populateBean
调用时进行属性的注入。具体调用方法如下图:
上面的方法主要干了两个活:
shiroProperties propertyValues
所以这里咱们需要把思路往前推一下,需要判断出了什么问题才会导致TestController的 propertyValues
属性为空。beanDefinition的合并发生在 doCreateBean
方法中。如下图:
通过这个方法进去,咱们将所有的BeanPostProcessor打印出来,结果如下图所示, 并没有AutowiredAnnotationBeanPostProcessor这个后置处理器。这也就能解释咱们的ShiroProperties为啥注入不进去了 ,虽然找到了原因,但是这不是一个正常的结果,正常情况下依赖注入都是没问题的,毕竟依赖注入是Spring的核心三大板块之一。
这里咱们深入分析一下AutowiredAnnotationBeanPostProcessor这个类是啥时候注册到Spring容器的。
众所周知,在容器初始化的过程中,有一个关键性的方法 refresh
,在 refresh
方法调用过程中,会调用 invokeBeanFactoryPostProcessors
方法,所有的后置处理器都在这里进行初始化。咱们看看一共有多少个BeanPostProcessor:
从上图可以观察到正常情况下所有的postProcessorNames都是会被注册到Spring容器中的。但是这里有个例外,从上面的图片可以看出还有个testProcessor也在其中,这个是咱们自定义的BeanPostProcessor。Spring在注册这些BeanPostProcessor时会按一种规则去注册:
- 先注册实现了PriorityOrdered接口的BeanPostProcessor。
- 再注册实现了Ordered接口的BeanPostProcessor。
- 最后注册无顺序的BeanPostProcessor。
所以TestProcessor会在AutowiredAnnotationBeanPostProcessor之前进行注册,而TestProcessor是在TestController中的,所以说TestController作为TestProcessor的 factoryBean
,当然要先进行初始化,这样最后导致TestController在初始化时无法正常使用AutowiredAnnotationBeanPostProcessor的功能,然后使得ShiroProperties无法正常注入。
到这里一切疑惑迎刃而解~,针对上述问题,咱们可以另起一个无需初始化的PostProcessorConfig类去专门处理类似的BeanPostProcessor即可。
科普PriorityOrdered接口
看一下Spring对于PriorityOrdered这个接口的注释
Note: {@code PriorityOrdered} post-processor beans are initialized in a special phase, ahead of other post-processor beans. This subtly affects their autowiring behavior: they will only be autowired against beans which do not require eager initialization for type matching.
其实官方也有给咱们提示这一点的,用了这个接口的后置处理器将会影响依赖注入的过程,推荐放在不需要初始化的bean中进行装配。
PS:当然,这种情况一般也遇不到,只是最近在整合shiro时,shiro有提供一个LifecycleBeanPostProcessor 处理器去管理相关bean的生命周期,需要注册到Spring容器。然后就导致上面的情况,和LifecycleBeanPostProcessor 在的同一个类的@Autowired,@Value声明的字段均无法正常注入。
下面是关于这个问题可能涉及到的一些源码的分析。
源码分析
一般情况下,有两种组合可以使用。
- @ConfigurationProperties与@EnableConfigurationProperties进行配合使用。
- @ConfigurationProperties与@Configuration进行配合使用。
两则实现的目的一致,都是将@ConfigurationProperties注解的类交由Spring容器进行托管,在容器中注册BeanDefinition,以供后期bean的装配和获取。
下面的内容将以第一种组合展开进行讲解。我们可以先看看@EnableConfigurationProperties这个注解。
1@Target(ElementType.TYPE) 2@Retention(RetentionPolicy.RUNTIME) 3@Documented 4@Import(EnableConfigurationPropertiesImportSelector.class) 5public @interface EnableConfigurationProperties { 6 7 /** 8 * Convenient way to quickly register {@link ConfigurationProperties} annotated beans 9 * with Spring. Standard Spring Beans will also be scanned regardless of this value. 10 * @return {@link ConfigurationProperties} annotated beans to register 11 */ 12 Class<?>[] value() default {}; 13 14} 复制代码
这个注解上有@Import(EnableConfigurationPropertiesImportSelector.class)这样一个注解,对于@Import这个注解,感兴趣的朋友可以去看看ConfigurationClassParser中的 processImports
方法。大概流程就是调用EnableConfigurationPropertiesImportSelector中的 selectImports
方法,并将解析出来的所有类注册到容器中。
这里一共是将两个类注入到了容器中。他们都实现了ImportBeanDefinitionRegistrar接口,这个接口只有一个方法 registerBeanDefinitions
,看方法名也就略知一二了吧。
ConfigurationPropertiesBeanRegistrar
ConfigurationPropertiesBeanRegistrar这个类的主要目的是用来收集EnableConfigurationProperties 中value值指定的类,并将其注册到容器中。代码量并不多,我截取下关键性代码。
一共分为两步:
- getTypes:通过注解@EnableConfigurationProperties获取其value中的值。
- register:将上面获取的
Class[]
注册到Spring容器中。
ConfigurationPropertiesBindingPostProcessorRegistrar
ConfigurationPropertiesBindingPostProcessorRegistrar这个类注册了两个后置处理器。
从代码不难看出,上面的逻辑在一个容器中有且只会执行一次。执行过程中会注册一个实现BeanPostProcessor的bean后置处理器,还会注册一个实现BeanFactoryPostProcessor的beanFactory后置处理器。
- ConfigurationBeanFactoryMetadata:将beanFactory中所有的beandefinition都保存一份到
beansFactoryMetadata
集合中。 - ConfigurationPropertiesBindingPostProcessor:这个类是处理ConfigurationProperties的重点类,它将会帮我们解析配置文件里面的配置并赋值到bean中。
bind
方法中进行。解析过程略显复杂,这里不做过多说明。
触发点
前面介绍了两个ImportBeanDefinitionRegistrar接口的实现类,但是 registerBeanDefinitions
方法的触发点还未揭秘。了解过spring源码的同学想必对 refresh
这个方法应该不陌生。咱们从这里开始挖掘一波~
refresh中有一个方法 invokeBeanFactoryPostProcessors
。
这个是触发点的入口,一步步点进去,直到 PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors 方法,这里面会执行一些定义好的BeanFactoryPostProcessor中的 postProcessBeanDefinitionRegistry
方法,
咱们要关注的就是 ConfigurationClassPostProcessor 这个类。这个类其实干了很多活,包括前面提到的对于@Import这个注解的处理等等。
归纳成为三步,每一步都内嵌在前一步中:
- this.reader.loadBeanDefinitions(configClasses);
- loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);控制是否跳过一些bean的处理,例如咱们有时候会配置@ConditionalOnBean @ConditionalOnClass等等条件,若不满足,则直接跳过这些bean的处理。
- loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
1 private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) { 2 registrars.forEach((registrar, metadata) -> 3 registrar.registerBeanDefinitions(metadata, this.registry)); 4 } 复制代码
这一块就是咱们的 registerBeanDefinitions
方法触发的地方了。也与前面一块的讲解也就串起来了。
总结
解决问题的同时也是对自己成长的一种促进,这次源码分析补充了前面很多 强行看源码 时的一些疑惑点。毕竟spring盘子太大了,不一定所有的使用注意事项都会在官方文档加以注释,碰见了搜索引擎解决不了的问题还是得自己手撸源码------>O(∩_∩)O哈哈~。
End
以上所述就是小编给大家介绍的《Autowired无法正常注入的疑难杂症》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- TypeScript 疑难杂症
- 话说 Kubernetes 网络疑难杂症
- 疑难杂症篇之 ulimit
- TCP协议疑难杂症全景解析
- 十八问,认识Python序列,解决疑难杂症!
- PHP 疑难杂症:解决守护进程时 Redis 假死
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Learn Python the Hard Way
Zed A. Shaw / Addison-Wesley Professional / 2013-10-11 / USD 39.99
Master Python and become a programmer-even if you never thought you could! This breakthrough book and CD can help practically anyone get started in programming. It's called "The Hard Way," but it's re......一起来看看 《Learn Python the Hard Way》 这本书的介绍吧!