回答一下@椰大大�的问题;他在第四篇的文章评论里面留言了;但是他的问题比较复杂;为了把问题讲清楚就挪到这里来回答吧;
有个问题在网上找半天,问了一堆人也不会,只能留言请教你了。。。 为何@Autowired可以注入ApplicationContext, 一般来说,我们可以通过实现ApplicationContextAware接口来获取ApplicationContext的引用。但是根据官方文档,发现也可以通过 @Autowired来注入ApplicationContext,这是为什么呢?
如果你提问我肯定回答;所以肯定还在CSDN。有空肯定奋笔疾书写完spring源码分析的博客
这个问题比较复杂;得到ApplicationContext对象的方法多了去了,不止这两种,我就不一一例举了;比如extends WebApplicationObjectSupport也可以获取ApplicationContext对象; 而且可能你有两重意思 1、有了ApplicationContextAware已经得到ApplicationContext为什么还需要@Autowried? 2、 为何@Autowired可以注人 ApplicationContext对象,原理是什么? 我都回答一下吧——正文开始
为了适合更对spring还不那么熟悉的读者我先把主流的两种获取ApplicationContext对象的方式列出来; 第一种 ,在一个bean(注意一定得是bean,被spring容器管理的对象)当中通过@Autowired来注入ApplicationContext对象;代码如下:
@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 代码省略了 } 复制代码
@Autowried和ApplicationContextAware都能实现得到一个ApplicationContext(再次说明,其实不止这两种方式;但是其他的方式可以忽略,全部归类为接口方式);那么这两种方式有什么区别呢?我认为没什么区别,无非是一个耦合是@Autowried这个注解;另一个耦合的是一个接口;现在的spring版本已经5.x了;如果你们公司使用的spring不支持注解那么就使用接口吧;
我列举一下官网上说的经典场景吧;
假设类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.
读者可以自己打开这一章节;可以自行翻译一下;上面是笔者蹩脚的英文水平的理解;说不定你有更加深刻的理解也说不定;
再说一个大家常见的场景
比如A这个类不是被spring管理的对象;但是需要得到一个spring管理的B;这个时候当然不能用传统的@Autowired来注入B,因为A都不是bean,你在A里面写spring注解都不可能生效;只能获取ApplicationContext对象,然后调用getBean方法得到b;
当然可能还有更多场景需要用到这个对象就不再啰嗦了;
比如上文中的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); } } 复制代码
运行结果是没有与之对应的beanDefinition对象存在容器;说穿了ApplicationContext这个对象不在 容器 当中;需要注意的是我这里说的 容器 这个概念特别庞大,这里不展开讲;其实按照我的理解ApplicationContext这个对象是在容器当中的;但是由于之前文章没有去系统的聊过什么叫做容器,那么大家就先按照自己的理解去看待容器;如果按照一般的理解,看到这里可以认为ApplicationContext不在容器当中吧(再次说明我理解的是在容器当中的);以后再来解释容器这个概念;
如果上面的结果不足以说服你,那么笔者再列出一个证据; 试想一下我们注入的这个ApplicationContext对象肯定单例的;如果每次注入的ApplicationContext都是一个新的那肯定不合理;ApplicationContext如果是单例的讲道理他会存在单例池当中;所以我们可以看看单例池是否存在这个对象;
可以看到连Y都被实例化并且存在单例池里面了(这也是Y之所以能够用@Autowried注入进来的原因),但是没有看到ApplicationContext对象;那么可以肯定ApplicationContext对象不在spring容器当中( 再再再再次 说明,其实笔者认为他是存在容器当中的;可能我对容器的理解可能更深一点吧);既然他不在容器当中,也就是和Y不一样;怎么注入进来的呢?
如果你系统的阅读过spring源码就会知道完成@Autowried这个注解功能的类是AutowiredAnnotationBeanPostProcessor这个后置处理器;说穿了对@Autowried这个注解的解析就是这个后置处理器;我们可以看看他是如何完成@Autowried解析、注入属性的;找到这个后置处理器当中完成属性注入的方法—— debug it;
postProcessProperties方法便是处理属性注入的方法;
图上可以看到X已经实例化成对象了(还不是bean),但是里面的属性都是null,因为他才刚刚开始来填充属性;调用 metadata.inject(bean, beanName, pvs);
继续完成填充属性—— debug it
metadata.inject(bean, beanName, pvs);
首先会遍历当前bean——当中所有需要注入的属性;也就是有两个Y 和 ApplicationContext; 所以第一次for循环注入的是y;就是从单例池当中获取一下Y;如果获取不到就实例化Y放到单例池然后返回;反射填充属性Y;完成注入;
当然我们关注的是ApplicationContext的注入;所以调试第二次循环——element==ApplicationContext的时候
完成ApplicationContext的步骤—— 多图警告: 1、调用 element.inject(target, beanName, pvs);
2、调用 beanFactory.resolveDependency
方法得到ApplicationContext对象;主要就是这里怎么得到的?他不在单例池当中为何可以获取到?
3、调用 doResolveDependency
方法获取ApplicationContext对象;
4、调用 findAutowireCandidates
获取ApplicationContext对象
5、关键代码了
贴一下主要代码吧
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一共就四个对象;
这个四个对象就包含了一个ApplicationContext,所以在@Autowired 注入ApplicationContext的时候这个for循环会进入,并且直接返回了map当中已经存在好的ApplicationContext对象以便完成属性的注入;但是如果普通bean的注入,比如X注入Y,这不会进入这个for循环;我们可以证明一下;
注入ApplicationContext的时候:
可以看到for循环已经进入,并且判断成功 autowiringValue已经有值了,进入if,break然后返回这个autowiringValue,完成属性注入;
再来看看普通属性y的注入和这个是否有差别呢?
这个时候就是x注入y,继续debug会发现整个for循环当中的if是不会进的;也就是相当于这个for循环没有任何作用;
可以看到findAutowireCandidates已经执行完了,但是需要注入的属性y还只是一个class,不是对象;后台也只打印了x的构造方法没有打印y的;说穿了到到此为止y还是没有找到;
但是如果需要注入的属性是ApplicationContext这里得到的就不一样,因为上面已经说了for循环里面已经返回了对象; 证明一下吧
那么现在Y(对象或者bean)是如何获得并返回的?
其实这个不是这里讨论的,我如果能够把spring源码系列文章写完会会写到属性注入的全部过程和原理的;
这里先给个基本结果吧
也就是上图这个代码这里把Y的对象或者叫做bean获取到返回出去完成属性注入;
说了这么多总结一下吧:
普通对象的注入如果注入的属性是单例,那么spring首先从单例池获取,如果获取不到直接实例化这个bean,放到单例池在返回,完成注入;如果是原型的每次注入就直接实例化这个原型bean返回完成注入;
ApplicationContext对象的注入不同,如果注入的属性是ApplicationContext类型,那么spring会先从resolvableDependencies这个map当中去找,如果找到直接返回一个ApplicationContext对象完成属性注入;
那么问题来了,resolvableDependencies这个map的作用是什么 他其实有一个javadoc
/** 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的说明
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
这就是为什么我们可以注入ApplicationContext的全部原因吧;