Bean的自动注入及循环依赖问题
作者:先熬半个月
一、spring中的各种注入方式
对于是spring的注入前置知识、@Autowired、@Resource等的知识可以看其他文章,这里就不多说了
我们对Bean的注入,一般有下面几种方式:
1)、通过@Autowired、@Resource作用在属性上
2)、通过@Autowired、@Resource作用在方法上
3)、通过提供set方法+改变注入模型为自动注入
4)、通过BeanDefinition方式完成属性注入
我们先说前三种方式:
我们用下面的测试类来检验:
1、测试注解作用在属性上:
@Component public class AnnotationAutowiredFiledBeanTest { }
2、测试注解作用在方法上:
@Component public class AnnotationAutowiredMethodBeanTest { }
3、测试通过提供set方法+改变注入模型为自动注入
@Component public class AutowiredInjectByTypeMethodBeanTest { }
修改为注入模型类:
/** * 用来设置SpringBeanInfoTest类的属性注入为自动注入模式 * * */ @Component public class UpdateBeanInfoBeanFactoryPostProcessor implements BeanFactoryPostProcessor{ @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { AbstractBeanDefinition a = (AbstractBeanDefinition) beanFactory.getBeanDefinition("a"); a.setAutowireMode(2); //a.getPropertyValues().add("beanDefinitionPropertyValuesBeanTest",new BeanDefinitionPropertyValuesBeanTest()); } }
4、各种方式的注入类:
@Component("a") public class SpringBeanInfoTest { //@Autowired作用在属性上进行注入 @Autowired AnnotationAutowiredFiledBeanTest autowiredFiledBeanTest; //@Autowired作用在方法上进行注入 @Autowired public void setAnnotationAutowiredMethodBeanTest(AnnotationAutowiredMethodBeanTest annotationAutowiredMethodBeanTest){ System.out.println("AnnotationAutowiredMethodBeanTest=[{}]"+annotationAutowiredMethodBeanTest); } //使用自动注入(使用byType的模式) public void setAutowiredInjectByTypeMethodBeanTest(AutowiredInjectByTypeMethodBeanTest autowiredInjectByTypeMethodBeanTest){ System.out.println("AutowiredInjectByTypeMethodBeanTest=[{}]"+autowiredInjectByTypeMethodBeanTest); } //用来打印@Autowired作用在属性上进行注入是否成功 public void printf(){ System.out.println("autowiredFiledBeanTest=[{}]"+autowiredFiledBeanTest); } }
5、启动测试类:
配置类:
@Configuration @ComponentScan("com.spring.demo.introspect") public class App { }
启动类:
public class ApplicationTest { @Test public void testSpringInject() throws NoSuchFieldException, IntrospectionException { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.register(App.class); context.refresh(); context.getBean(SpringBeanInfoTest.class).printf(); } }
此时我们在UpdateBeanInfoBeanFactoryPostProcessor类中设置了SpringBeanInfoTest类的属性注入为自动注入(2,默认为0)。我们运行下看看注入情况:
此时发现三种情况都能进行注入
此时我们修改注入模型为默认的手动注入。
/** * 用来设置SpringBeanInfoTest类的属性注入为自动注入模式 * * */ @Component public class UpdateBeanInfoBeanFactoryPostProcessor implements BeanFactoryPostProcessor{ @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { AbstractBeanDefinition a = (AbstractBeanDefinition) beanFactory.getBeanDefinition("a"); //a.setAutowireMode(2); //a.getPropertyValues().add("beanDefinitionPropertyValuesBeanTest",new BeanDefinitionPropertyValuesBeanTest()); } }
结果打印
此时发现第三种方式无法注入了。
那么此时我们再来测试下第四种方式(通过BeanDefinition方式)
我们知道java内省机制最后都是讲解析出来的属性等转换为一个BeanInfo对象,然后所有属性等信息存在一个PropertyDescriptor数组中,而spring中在BeanDefinition中定义了一个MutablePropertyValues对象(propertyValues)中的一个List用来定义描述这个类的属性等,那么我们要往SpringBeanInfoTest中注入一个属性,此时就可以往这个List中存进我们要注入的对象即可。
(其实四种方式都是往propertyValues的List中添加属性内容,最后会对这个list进行统一的处理。只是前三种通过不同方式获取到属性内容,然后放进list,而第四种则是直接add进行)
我们先编写一个测试类(这里不用@Component):
public class BeanDefinitionPropertyValuesBeanTest { }
此时在UpdateBeanInfoBeanFactoryPostProcessor 类中进行BeanDefinition方式的注入:
/** * 用来设置SpringBeanInfoTest类的属性注入为自动注入模式 * * */ @Component public class UpdateBeanInfoBeanFactoryPostProcessor implements BeanFactoryPostProcessor{ @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { AbstractBeanDefinition a = (AbstractBeanDefinition) beanFactory.getBeanDefinition("a"); a.getPropertyValues().add("beanDefinitionPropertyValuesBeanTest",new BeanDefinitionPropertyValuesBeanTest()); } }
这里由于是手动的修改BeanDefinition对象,所以其注入模型并不会影响到这个是否生效。
然后在SpringBeanInfoTest中添加注入方法:
@Component("a") public class SpringBeanInfoTest { //@Autowired作用在属性上进行注入 @Autowired AnnotationAutowiredFiledBeanTest autowiredFiledBeanTest; //@Autowired作用在方法上进行注入 @Autowired public void setAnnotationAutowiredMethodBeanTest(AnnotationAutowiredMethodBeanTest annotationAutowiredMethodBeanTest){ System.out.println("AnnotationAutowiredMethodBeanTest=[{}]"+annotationAutowiredMethodBeanTest); } //使用自动注入(使用byType的模式) public void setAutowiredInjectByTypeMethodBeanTest(AutowiredInjectByTypeMethodBeanTest autowiredInjectByTypeMethodBeanTest){ System.out.println("AutowiredInjectByTypeMethodBeanTest=[{}]"+autowiredInjectByTypeMethodBeanTest); } //添加使用BeanDefinition方式进行属性注入 public void setBeanDefinitionPropertyValuesBeanTest(BeanDefinitionPropertyValuesBeanTest beanDefinitionPropertyValuesBeanTest){ System.out.println("BeanDefinitionPropertyValuesBeanTest=[{}]"+beanDefinitionPropertyValuesBeanTest); } //用来打印@Autowired作用在属性上进行注入是否成功 public void printf(){ System.out.println("autowiredFiledBeanTest=[{}]"+autowiredFiledBeanTest); } }
我们可以来看看是否生效:
可以看是可以进行注入的。
下面我们可以先简单的对这四种情况做个总结,后续再进行源码分析验证猜想:
1)、在一个属性上面加一个@Autowired注解
使用反射机制进行注入,可以看@Autowired源码,伪代码大概如下:
Class clazz = null; Field autowiredFiledBeanTest = clazz.getDeclaredField("autowiredFiledBeanTest"); autowiredFiledBeanTest.setAccessible(true); autowiredFiledBeanTest.set(this,getBean(AnnotationAutowiredFiledBeanTest.class));
2)、在一个方法上面加一个@Autowired注解
2.1如果注入模型是1、2 (自动注入),那么spring底层采用的是java的自省机制发现setter方法然后调用执行
* 也就是说方法上面的@Autowierd注解无用,直接走内省机制进行注入而不是通过解析@Autowierd进行注入
2.2如果注入模型为0(默认值,手动注入) 那么则是和在属性上面加注解差不多,底层查找所有加了@Autowired注解的方法,然后反射调用method.invoke()
3)、提供一个setter方法,继而把该bean的注入模型改成1、2 自动注入
* 3.1 注入模型是自动注入 则是java的内省机制
伪代码如下:
BeanInfo beanInfo = Introspector.getBeanInfo(SpringBeanInfoTest.class); PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors(); for (PropertyDescriptor propertyDescriptor : propertyDescriptors) { Method writeMethod = propertyDescriptor.getWriteMethod(); writeMethod.invoke(this,getBean(parma)) }
4)、使用BeanDefinition方式进行注入
不和注入模型有相关联,即所有情况都能生效
------------------------------------------源码验证
入口:refresh---》
finishBeanFactoryInitialization----》beanFactory.preInstantiateSingletons();---》
getBean---》doGetBean---》createBean-----》doCreateBean----》populateBean
我们进入populateBean方法看看:
//先从容器中获取bean,有则直接返回进行注入 //无则调用createBean创建需要进行注入的bean,放进单例池,最后再进行注入 protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) { if (bw == null) { if (mbd.hasPropertyValues()) { throw new BeanCreationException( mbd.getResourceDescription(), beanName, "Cannot apply property values to null instance"); } else { // Skip property population phase for null instance. return; } } // Give any InstantiationAwareBeanPostProcessors the opportunity to modify the // state of the bean before properties are set. This can be used, for example, // to support styles of field injection. if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof InstantiationAwareBeanPostProcessor) { InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp; if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) { return; } } } } //1、获取到MutablePropertyValues对象,里面的List<PropertyValue> propertyValueList封装着一些属性的定义 //这里现在只能获取到手动使用BeanDefinition动态添加的属性 PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null); //获取注入模型 int resolvedAutowireMode = mbd.getResolvedAutowireMode(); //2、注入模型为1或者2(自动注入),通过内省机制获取所有符合的属性(包括获取到使用了@Autowired注解的set),并getbean放进propertyValueList中 if (resolvedAutowireMode == AUTOWIRE_BY_NAME || resolvedAutowireMode == AUTOWIRE_BY_TYPE) { MutablePropertyValues newPvs = new MutablePropertyValues(pvs); // Add property values based on autowire by name if applicable. //获取到符合规则的属性(setter) //然后获取到该属性的bean,并加入到MutablePropertyValues中的List中 if (resolvedAutowireMode == AUTOWIRE_BY_NAME) { autowireByName(beanName, mbd, bw, newPvs); } // Add property values based on autowire by type if applicable. //获取到符合规则的属性(setter) // 然后获取到该属性的bean,并加入到MutablePropertyValues中的List中 if (resolvedAutowireMode == AUTOWIRE_BY_TYPE) { autowireByType(beanName, mbd, bw, newPvs); } //所有符合上面的属性都会加到这里 pvs = newPvs; } boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors(); boolean needsDepCheck = (mbd.getDependencyCheck() != AbstractBeanDefinition.DEPENDENCY_CHECK_NONE); //3、如果注入模型为0,手动注入,此时这里的propertyValueList只会存在我们手动使用BeanDefinition add进去的。 //那么到这里为止所有set方法都没被识别到(既不会在applyPropertyValues中执行了) //下面的循环则是去解析@Autowired作用的属性、方法(反射机制) //注意:如果该属性已经存在propertyValueList,这里则不会对其进行解析(即自动注入模型下@Autowired作用在方法的被忽略执行) PropertyDescriptor[] filteredPds = null; if (hasInstAwareBpps) { if (pvs == null) { pvs = mbd.getPropertyValues(); } //获取到所有BeanPostProcessor //如果是InstantiationAwareBeanPostProcessor,即处理@Autowired注解、@Resouce注解、@PostConstruct注解的BeanPostProcessor类型,则完成注入等操作 for (BeanPostProcessor bp : getBeanPostProcessors()) { //完成@Autowired作用在属性、方法上面的处理(使用反射) if (bp instanceof InstantiationAwareBeanPostProcessor) { InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp; //完成@Autowired作用在属性、方法上面的处理 PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName); if (pvsToUse == null) { if (filteredPds == null) { filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching); } pvsToUse = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName); if (pvsToUse == null) { return; } } pvs = pvsToUse; } } } if (needsDepCheck) { if (filteredPds == null) { filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching); } checkDependencies(beanName, mbd, filteredPds, pvs); } //4、处理propertyValueList中的所有还未执行的属性 //遍历属性名、对象,内省机制调用invoke方法执行set方法等 if (pvs != null) { applyPropertyValues(beanName, mbd, bw, pvs); } }
我们总结下:
1、先获取手动添加的propertyValueList的(通过BeanDefinition进行add的)
2、如果注入模型为自动注入,则通过内省机制获取所有符合的规则的属性(包含使用注解、未使用注解的),getBean获取到bean对象,并加入到propertyValueList中
3、通过反射获取使用@Autowired注解的属性,并解析执行。这里有几点需要注意
1)、解析包含@Autowired作用的属性和方法两种
2)、如果该属性存在propertyValueList则不进行解析(即当注入模型为自动注入时,@Autowired作用的方法上的方式在步骤2中使用内省机制进行获取,此时跳过第三步)
4、对propertyValueList的所有属性调用invoke方法执行
不同方式在不同注入模型下获取属性的方式不同(内省机制、反射机制)
到此这篇关于Bean的自动注入及循环依赖问题的文章就介绍到这了,更多相关Bean的自动注入内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!