转载

spring源码系列(五)——番外篇回答网友的问题

回答一下@椰大大�的问题;他在第四篇的文章评论里面留言了;但是他的问题比较复杂;为了把问题讲清楚就挪到这里来回答吧;

spring源码系列(五)——番外篇回答网友的问题

问题原文:

有个问题在网上找半天,问了一堆人也不会,只能留言请教你了。。。 为何@Autowired可以注入ApplicationContext, 一般来说,我们可以通过实现ApplicationContextAware接口来获取ApplicationContext的引用。但是根据官方文档,发现也可以通过 @Autowired来注入ApplicationContext,这是为什么呢?

问题1——去了哪里?

如果你提问我肯定回答;所以肯定还在CSDN。有空肯定奋笔疾书写完spring源码分析的博客

为何@Autowired可以注人 ApplicationContext对象?

这个问题比较复杂;得到ApplicationContext对象的方法多了去了,不止这两种,我就不一一例举了;比如extends WebApplicationObjectSupport也可以获取ApplicationContext对象; 而且可能你有两重意思 1、有了ApplicationContextAware已经得到ApplicationContext为什么还需要@Autowried? 2、 为何@Autowired可以注人 ApplicationContext对象,原理是什么? 我都回答一下吧——正文开始

获取AppliationContext的主流方式

为了适合更对spring还不那么熟悉的读者我先把主流的两种获取ApplicationContext对象的方式列出来; 第一种 ,在一个bean(注意一定得是bean,被spring容器管理的对象)当中通过@Autowired来注入ApplicationContext对象;代码如下:

spring源码系列(五)——番外篇回答网友的问题
@Component
public class X {
	@Autowired
	Y y;

	@Autowired
	ApplicationContext applicationContext;

	public X(){
		System.out.println("x 的构造方法");
	}
}

复制代码

第二种,通过实现ApplicationContextAware接口来获取AppliationContext对象;

@Comonpent
public class Util implements ApplicationContextAware{
	private static ApplicationContext applicationContext = null;
	public  void setApplicationContext(ApplicationContext applicationContext){
		Util.applicationContext =applicationContext;
	}

	//可以对getBean封装
	public static Object getBean(String name){
		sout("可能你需要干一点其他事吧");
    	return getApplicationContext().getBean(name);
  }
	//也可以提供一个静态方法返回applicationContext
	代码省略了
}

复制代码

两种获取ApplicationContext对象的方式有什么区别呢?

@Autowried和ApplicationContextAware都能实现得到一个ApplicationContext(再次说明,其实不止这两种方式;但是其他的方式可以忽略,全部归类为接口方式);那么这两种方式有什么区别呢?我认为没什么区别,无非是一个耦合是@Autowried这个注解;另一个耦合的是一个接口;现在的spring版本已经5.x了;如果你们公司使用的spring不支持注解那么就使用接口吧;

为什么需要注入这个ApplicationContext对象呢?

我列举一下官网上说的经典场景吧;

假设类A(单例的)需要注入一个类B(原型的);如果我们直接写会有问题;

比如你在A类当中的m()方法中返回b,那么无论你调用多少次a.m();返回的都是同一个b对象;就违背b的原型规则,应该在m方法中每次都返回一个新的b;所以某些场景下b不能直接注入;

错误的代码:

@Component
public class A{

	//注意B是原型的  scope=prototype
	@Autowried;
	B b;
	public B m(){

		//直接返回注入进来的b;肯定有问题
		//返回的永远是A实例化的时候注入的那个bean
		//违背的B设计成原型的初衷
		return b;
	}
}

复制代码

正确的写法

@Component
public class A{
	@Autowired
	ApplicationContext applicationContext;
	public B m(){
		//每次调用m都是通过spring容器去获取b
		//如果b是原型,每次拿到的都是原型b
		B b= applicationContext.getBean("b");
		return b;
	}
}

复制代码

当然这个不是我胡说,这是官网的上面的经典例子 官网参考: docs.spring.io/spring/docs…

In most application scenarios, most beans in the container are singletons. When a singleton bean needs to collaborate with another singleton bean or a non-singleton bean needs to collaborate with another non-singleton bean, you typically handle the dependency by defining one bean as a property of the other. A problem arises when the bean lifecycles are different. Suppose singleton bean A needs to use non-singleton (prototype) bean B, perhaps on each method invocation on A. The container creates the singleton bean A only once, and thus only gets one opportunity to set the properties. The container cannot provide bean A with a new instance of bean B every time one is needed.
spring源码系列(五)——番外篇回答网友的问题

读者可以自己打开这一章节;可以自行翻译一下;上面是笔者蹩脚的英文水平的理解;说不定你有更加深刻的理解也说不定;

再说一个大家常见的场景

比如A这个类不是被spring管理的对象;但是需要得到一个spring管理的B;这个时候当然不能用传统的@Autowired来注入B,因为A都不是bean,你在A里面写spring注解都不可能生效;只能获取ApplicationContext对象,然后调用getBean方法得到b;

当然可能还有更多场景需要用到这个对象就不再啰嗦了;

为什么@Autowired能够注入ApplicationContext对象?

比如上文中的X里面注入了一个Y;注入了一个ApplicationContext对象;为什么这个ApplicatonContext可以注入成功?这个问题其实很经典;可能有人会想当然的认为肯定可以注入啊;就像注入Y一样简单;假设你对spring有一点点了解你便知道Y之所以能够被注入是因为Y本身就存在spring容器当中;换句话说Y在单例池当中;那么ApplicationContext对象究竟在不在spring容器当中?或者在不在单例池当中呢?

要搞清这个问题的话只有一个办法 Debug It ;

首先我们假设ApplicationContext 这个对象也存在spring 容器 当中;其实如果你读过前面的博客就知道一个bean如果存在spring容器当中,大部分(有的是直接把对象put到单例池,故而没有BeanDefinition)的情况下会有一个与之对应的BeanDefinition对象;也存在容器当中;那么我们看看这个ApplicationContext的BeanDefinition对象有没有呢?

AnnotationConfigApplicationContext ac
			= new AnnotationConfigApplicationContext(App.class);

	String[] beanDefinitionNames = ac.getBeanDefinitionNames();
	//打印spring容器当中所有bean的bd
	for (String beanDefinitionName : beanDefinitionNames) {
		System.out.println(beanDefinitionName);
	}
}

复制代码

执行结果

spring源码系列(五)——番外篇回答网友的问题

运行结果是没有与之对应的beanDefinition对象存在容器;说穿了ApplicationContext这个对象不在 容器 当中;需要注意的是我这里说的 容器 这个概念特别庞大,这里不展开讲;其实按照我的理解ApplicationContext这个对象是在容器当中的;但是由于之前文章没有去系统的聊过什么叫做容器,那么大家就先按照自己的理解去看待容器;如果按照一般的理解,看到这里可以认为ApplicationContext不在容器当中吧(再次说明我理解的是在容器当中的);以后再来解释容器这个概念;

如果上面的结果不足以说服你,那么笔者再列出一个证据; 试想一下我们注入的这个ApplicationContext对象肯定单例的;如果每次注入的ApplicationContext都是一个新的那肯定不合理;ApplicationContext如果是单例的讲道理他会存在单例池当中;所以我们可以看看单例池是否存在这个对象;

spring源码系列(五)——番外篇回答网友的问题

可以看到连Y都被实例化并且存在单例池里面了(这也是Y之所以能够用@Autowried注入进来的原因),但是没有看到ApplicationContext对象;那么可以肯定ApplicationContext对象不在spring容器当中( 再再再再次 说明,其实笔者认为他是存在容器当中的;可能我对容器的理解可能更深一点吧);既然他不在容器当中,也就是和Y不一样;怎么注入进来的呢?

如果你系统的阅读过spring源码就会知道完成@Autowried这个注解功能的类是AutowiredAnnotationBeanPostProcessor这个后置处理器;说穿了对@Autowried这个注解的解析就是这个后置处理器;我们可以看看他是如何完成@Autowried解析、注入属性的;找到这个后置处理器当中完成属性注入的方法—— debug it;

postProcessProperties方法便是处理属性注入的方法;

spring源码系列(五)——番外篇回答网友的问题

图上可以看到X已经实例化成对象了(还不是bean),但是里面的属性都是null,因为他才刚刚开始来填充属性;调用 metadata.inject(bean, beanName, pvs); 继续完成填充属性—— debug it

metadata.inject(bean, beanName, pvs); 首先会遍历当前bean——当中所有需要注入的属性;也就是有两个Y 和 ApplicationContext; 所以第一次for循环注入的是y;就是从单例池当中获取一下Y;如果获取不到就实例化Y放到单例池然后返回;反射填充属性Y;完成注入;

spring源码系列(五)——番外篇回答网友的问题

当然我们关注的是ApplicationContext的注入;所以调试第二次循环——element==ApplicationContext的时候

spring源码系列(五)——番外篇回答网友的问题

完成ApplicationContext的步骤—— 多图警告: 1、调用 element.inject(target, beanName, pvs);

spring源码系列(五)——番外篇回答网友的问题

2、调用 beanFactory.resolveDependency 方法得到ApplicationContext对象;主要就是这里怎么得到的?他不在单例池当中为何可以获取到?

spring源码系列(五)——番外篇回答网友的问题

3、调用 doResolveDependency 方法获取ApplicationContext对象;

spring源码系列(五)——番外篇回答网友的问题

4、调用 findAutowireCandidates 获取ApplicationContext对象

spring源码系列(五)——番外篇回答网友的问题

5、关键代码了

spring源码系列(五)——番外篇回答网友的问题

贴一下主要代码吧

Map<String, Object> result = new LinkedHashMap<>(candidateNames.length);
	for (Map.Entry<Class<?>, Object> classObjectEntry : this.resolvableDependencies.entrySet()) {
		Class<?> autowiringType = classObjectEntry.getKey();
		if (autowiringType.isAssignableFrom(requiredType)) {
			Object autowiringValue = classObjectEntry.getValue();
			autowiringValue = AutowireUtils.resolveAutowiringValue(autowiringValue, requiredType);
			if (requiredType.isInstance(autowiringValue)) {
				result.put(ObjectUtils.identityToString(autowiringValue), autowiringValue);
				break;
			}
		}
	}

复制代码

对这段代码做一点解释吧

当代码执行到findAutowireCandidates的时候,传了一个特别重要的参数 Class requireType;就是当前注入的属性的类型——也就是ApplicationContext.class;

然后遍历了一个map——resolvableDependencies(关于这个map,下文有解释)

for (Map.Entry<Class<?>, Object> classObjectEntry : this.resolvableDependencies.entrySet()) {

复制代码

接着会把传过来的requireType和遍历出来的autowiringType——当前类型进行比较

if (autowiringType.isAssignableFrom(requiredType)) {

复制代码

如果传过来的requiredType和遍历出来Class对象相同则停止遍历,直接把当前遍历出来的对象返回作为注入属性的值,完成属性注入;

如果你调试代码可以看到这个map——resolvableDependencies一共就四个对象;

spring源码系列(五)——番外篇回答网友的问题

这个四个对象就包含了一个ApplicationContext,所以在@Autowired 注入ApplicationContext的时候这个for循环会进入,并且直接返回了map当中已经存在好的ApplicationContext对象以便完成属性的注入;但是如果普通bean的注入,比如X注入Y,这不会进入这个for循环;我们可以证明一下;

注入ApplicationContext的时候:

spring源码系列(五)——番外篇回答网友的问题

可以看到for循环已经进入,并且判断成功 autowiringValue已经有值了,进入if,break然后返回这个autowiringValue,完成属性注入;

再来看看普通属性y的注入和这个是否有差别呢?

spring源码系列(五)——番外篇回答网友的问题

这个时候就是x注入y,继续debug会发现整个for循环当中的if是不会进的;也就是相当于这个for循环没有任何作用;

spring源码系列(五)——番外篇回答网友的问题

可以看到findAutowireCandidates已经执行完了,但是需要注入的属性y还只是一个class,不是对象;后台也只打印了x的构造方法没有打印y的;说穿了到到此为止y还是没有找到;

但是如果需要注入的属性是ApplicationContext这里得到的就不一样,因为上面已经说了for循环里面已经返回了对象; 证明一下吧

spring源码系列(五)——番外篇回答网友的问题

那么现在Y(对象或者bean)是如何获得并返回的?

其实这个不是这里讨论的,我如果能够把spring源码系列文章写完会会写到属性注入的全部过程和原理的;

这里先给个基本结果吧

spring源码系列(五)——番外篇回答网友的问题

也就是上图这个代码这里把Y的对象或者叫做bean获取到返回出去完成属性注入;

说了这么多总结一下吧:

普通对象的注入如果注入的属性是单例,那么spring首先从单例池获取,如果获取不到直接实例化这个bean,放到单例池在返回,完成注入;如果是原型的每次注入就直接实例化这个原型bean返回完成注入;

ApplicationContext对象的注入不同,如果注入的属性是ApplicationContext类型,那么spring会先从resolvableDependencies这个map当中去找,如果找到直接返回一个ApplicationContext对象完成属性注入;

那么问题来了,resolvableDependencies这个map的作用是什么 他其实有一个javadoc

spring源码系列(五)——番外篇回答网友的问题
/** Map from dependency type to corresponding autowired value. */
	//缓存一些依赖类型通用自动注入的值
	private final Map<Class<?>, Object> resolvableDependencies = new ConcurrentHashMap<>(16);

复制代码

以我蹩脚的英文水平理解这个map 缓存一些依赖类型通用自动注入的值 ;也就是@Autowried的时候有一些通用或者常用的类型的值都存放这个map里面;

如果这个javadoc不是很详细可以参考另外一个;这个map其实spring没有开发给程序员使用,private的;那么可想而知他肯定提供了api来操作这个map;找到他,看看这个api的说明

spring源码系列(五)——番外篇回答网友的问题

Register a special dependency type with corresponding autowired value 注册一个特殊的依赖类型——通用的注入的值 换句话说比如你有一些通用的对象、可能会被别的bean注入,那么你可以调用这个方法把这写对象放到一个map当中——resolvableDependencies 下面还有更加详细的说明,意思说spring当中的一些工厂或者上下文对象他们在bean工厂里面不是定义为bean,这个时候如果别的bean需要注入,则可以把他们放到这个map当中;

那么spring什么时候把这四个对象放到这个map当中的呢?——spring容器初始化的时候 我先把调用链列出来,然后在截几个图说明一下: 0、main方法 1、org.springframework.context.support.AbstractApplicationContext#refresh 2、org.springframework.context.support.AbstractApplicationContext#prepareBeanFactory 3、org.springframework.beans.factory.config.ConfigurableListableBeanFactory#registerResolvableDependency

spring源码系列(五)——番外篇回答网友的问题
spring源码系列(五)——番外篇回答网友的问题
spring源码系列(五)——番外篇回答网友的问题

这就是为什么我们可以注入ApplicationContext的全部原因吧;

原文  https://juejin.im/post/5e8ef2db6fb9a03c77620659
正文到此结束
Loading...