Bean的自动注入及循环依赖问题

 更新时间:2023年03月23日 14:35:20   作者:先熬半个月  
本文详细介绍了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的自动注入内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java_int、double型数组常用操作工具类(分享)

    Java_int、double型数组常用操作工具类(分享)

    下面小编就为大家带来一篇Java_int、double型数组常用操作工具类(分享)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-08-08
  • arthas jprofiler做复杂链路的调用分析

    arthas jprofiler做复杂链路的调用分析

    这篇文章主要为大家介绍了arthas jprofiler做复杂链路的调用分析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-06-06
  • Springboot开发OAuth2认证授权与资源服务器操作

    Springboot开发OAuth2认证授权与资源服务器操作

    这篇文章主要介绍了Springboot开发OAuth2认证授权与资源服务器操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-06-06
  • Springboot 如何使用@Async整合线程池

    Springboot 如何使用@Async整合线程池

    这篇文章主要介绍了Springboot 使用@Async整合线程池的操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-06-06
  • SpringCloud-Alibaba-Sentinel-配置持久化策略详解

    SpringCloud-Alibaba-Sentinel-配置持久化策略详解

    这篇文章主要介绍了SpringCloud-Alibaba-Sentinel-配置持久化策略,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-03-03
  • SpringSecurity自定义Form表单使用方法讲解

    SpringSecurity自定义Form表单使用方法讲解

    这篇文章主要介绍了Spring Security自定义Form表单使用方法,虽然 Spring Security 提供了默认的登录表单,实际项目里肯定是不可以直接使用的,当然 Spring Security 也提供了自定义登录表单的功能
    2023-01-01
  • java常见报错:Array Out of Bounds两种解决办法

    java常见报错:Array Out of Bounds两种解决办法

    这篇文章主要给大家介绍了关于java报错Array Out of Bounds的两种解决办法,Array out of bounds错误表示你尝试访问数组中不存在的索引,即索引小于零或者大于等于数组的大小,文中通过代码将解决的办法介绍的非常详细,需要的朋友可以参考下
    2024-08-08
  • Java实现数组翻转的实现代码

    Java实现数组翻转的实现代码

    这篇文章主要介绍了Java实现数组翻转的实现代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-09-09
  • pom文件中${project.basedir}的使用

    pom文件中${project.basedir}的使用

    这篇文章主要介绍了pom文件中${project.basedir}的使用,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-03-03
  • java中Base64字符串出现不合法字符的问题解决

    java中Base64字符串出现不合法字符的问题解决

    非法的base64数据可能导致编码或解码过程出错,本文主要介绍了java中Base64字符串出现不合法字符的问题解决,具有一定的参考价值,感兴趣的可以了解一下
    2024-06-06

最新评论