回答一下@椰大大�的问题;他在第四篇的文章评论里面留言了;可是他的问题比较复杂;为了把问题讲清楚就挪到这里来回答吧;html
有个问题在网上找半天,问了一堆人也不会,只能留言请教你了。。。 为什么@Autowired能够注入ApplicationContext, 通常来讲,咱们能够经过实现ApplicationContextAware接口来获取ApplicationContext的引用。可是根据官方文档,发现也能够经过 @Autowired来注入ApplicationContext,这是为何呢?java
若是你提问我确定回答;因此确定还在CSDN。有空确定奋笔疾书写完spring源码分析的博客ios
这个问题比较复杂;获得ApplicationContext对象的方法多了去了,不止这两种,我就不一一例举了;好比extends WebApplicationObjectSupport也能够获取ApplicationContext对象; 并且可能你有两重意思 一、有了ApplicationContextAware已经获得ApplicationContext为何还须要@Autowried? 二、 为什么@Autowired能够注人 ApplicationContext对象,原理是什么? 我都回答一下吧——正文开始程序员
为了适合更对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对象;api
@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不支持注解那么就使用接口吧;缓存
我列举一下官网上说的经典场景吧;bash
假设类A(单例的)须要注入一个类B(原型的);若是咱们直接写会有问题;app
好比你在A类当中的m()方法中返回b,那么不管你调用多少次a.m();返回的都是同一个b对象;就违背b的原型规则,应该在m方法中每次都返回一个新的b;因此某些场景下b不能直接注入;ide
错误的代码:
@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的步骤—— 多图警告: 一、调用element.inject(target, beanName, pvs);
二、调用beanFactory.resolveDependency
方法获得ApplicationContext对象;主要就是这里怎么获得的?他不在单例池当中为什么能够获取到?
三、调用doResolveDependency
方法获取ApplicationContext对象;
四、调用findAutowireCandidates
获取ApplicationContext对象
五、关键代码了
贴一下主要代码吧
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方法 一、org.springframework.context.support.AbstractApplicationContext#refresh 二、org.springframework.context.support.AbstractApplicationContext#prepareBeanFactory 三、org.springframework.beans.factory.config.ConfigurableListableBeanFactory#registerResolvableDependency
这就是为何咱们能够注入ApplicationContext的所有缘由吧;