图1
图2-0 onStartup()
问题来了,ac.refresh() 的作用是什么? ,这行代码是spring最核心的地方。图4 。14行初始化spring环境,注册配置类。创建一个dispatcherServlet类,然后把这个spring环境交给dispatcherServelet 。dispatcherServelet就和spring环境关联起来。例如dispatcherServelete有两个bean,通过这种方式放进spring中。然后把dispatcherServlet 注册到一个容器中(tomcat,jeety),这个之所以被调用到是借用了servlet 3.0技术,图2-1的tomcat一启动就会调用这个类。
图2-1
图2-3
图2-4
图2-5
图3
图3为配置spring MVC 的dispatcherServlet 官网。
图4
图5
图2-3 test运行的结果是这样的,报jspServlet的 异常。为什么回报这个异常呢,因为tomcat.addWebApp()方法。回让spring 把该项目当成一个web项目去运行。就需要去加载一个JspServlet类。加载不到就会报这个异常。但是这个异常不影响。
但是整个tomcat已经正常启动。controller也能正常访问。
图6
图7
我们可以看到controller是可以正常访问的,但是返回的是一个map,因为没有对于的转换map的类。所以报错图6异常。
图8
如果我们把图2-1改成tomcat.addContext() 就不会报找不到JspServlet了,因为启动的时候不会当成一个web运行了,所以启动的时候也就不去找jsp依赖了。但是又有一个问题就是,既然不是web项目了,也就不会调用图2-0 onStartup() 方法了。那怎么办呢,只能把onStartup()方法的内容拷贝到图8 形成图9
图9
这样JspServlet 找不到的异常解决了。但是 不能转化map的异常还是存在(图6),这个是什么原因呢,原因是没有一个转化map的转化器导致的。传统的springMvc是如何解决的呢,通过配置xml。图10
图10
图11
如果我把controller 改成返回 string 类型的, 可以正常的返回的。这是为什么呢。因为在spring Mvc中维护了很多的httpMessageConverters 的list。因为这个string 已经存在这个list中了。例如在spring中已经存在5,6了转换器了,spring回匹配一个最合适的转换器使用。因为字符串是一种简单的类型,所以他就内置了一个。如果换成map。我们就需要添加一个。怎么添加呢,图12
图12
通过这种方式spring 把这个转化器的list传给我们让我们从新改造使用。但是当我们重新启动tomcat的时候发现这个方法没有执行。why?依然报 map转化的问题。以为这个方法不能凭空spring就回调。要想使spring回调必须要加一个注解。
图13
加上注解以后就可以正常回调了。但是启动的时候报异常了。图14
图14
为甚?因为在执行图9 ac.register(AppConfig.class) ; ac.refresh();的时候要解析AppConfig类 的@EnableWebMvc 注解,但是这个注解要执行一些方法需要 servletContext类,但是此时的DispatcherServlet还没有创建。所以就报错了。咋办。就把ac.refersh()去掉。但是这个是spring 的核心方法,去掉可以吗,图15。那么谁来示例化,谁又来解析注解,启动就不会启动起来。
图15
图16
然而奇迹竟然发生了,不单能够正常启动,map类型也能解析返回了,所有的问题都解决了。
图17
当我们的tomcat 启动的时候回调用DispatcherServlet的Init方法();
图18
我们发现在dispacherServlet的父类中有init()方法。
图19
图20
图21
图22
图21
图22
代码走到这里我们终于明白了,其实spring Mvc内部也调用了refresh()方法。所以开头所提的问题?spring 的容器和spring mvc 的容器的区别,本身这个问题是没有任何意义的。因为底层都是调用spring的refresh()方法。那么问题又来了,最早不是执行了两遍的refresh() 方法吗,没有问题吗,没有问题啊,因为getBean的流程了,因为spring 在new bean的时候要先get一遍的。所以不会示例化两遍。
图23
同过这个断点我们能知道spring mvc 和spring 存的都是同一个容器singletonObject。
回顾spirng周期的整个过程。
图24
图25
图26
图27
示例化对象。
图28
图29
图30
图31
问题来了当执行createBean()方法时候,执行了几次后置处理器,答案是9次。
第一次调用后置处理器 图35
图32
第三次调用后置处理器 图40
第四次调用后置处理器。图41
populateBean()方法 第五次调用,第6次调用后置处理器。图42。initializeBean方法里执行第七次调用后置处理器,第8次调用后置处理器 图43
图33
第7次调用后置处理器。图
图34
理解BeanPostProcessor 指的是单纯实现BeanPostProcessor类方法的类,比如BeanPostProcessor有两个方法,a() ,b().这两个方法BeanPostProcessor单纯实现的这两个方法的类。 假如有yyy实现了BeanPostProcessor,或者有xxxx extends BeanPostProcessor ,扩展了BeanPostProcessor 的两个方法 m1,m2,m3。 那么说BeanPostProcessor后置处理器贯穿了整个spring bean的实例化前后(其中这个BeanPostProcessor 包括 yyy,xxx,BeanPostProcessor),但是说 贯穿了spring bean的初始化过程,指的就是单纯的BeanPostProcessor 不包括yyy,xxx等。
图35
第一次调用后置处理器。
图36
间接实现 BeanPostProcessors接口的类,InstantiationAwareBeanPostProcessor 的类。
图37
图38
第二次调用后置处理器,
图39
第二次调用后置处理器。
图40
第三次调用后置处理器。
图41
第四次调用后置处理器。
图42
第五次调用后置处理器。
第6次调用后置处理器。
图43
第7次调用后置处理器。
图49
第8次后置处理器调用。
图50
图51
第7和第8解释:
什么是初始化,过程 new CityService -》调用构造方法() -> 执行BeanPostProcessor类的postProcessBeforeInitialization方法->执行 加了@PostConstruct 注解的方法->BeanPostProcessor类的postProcessAfterInitialization()方法。BeanPostProcessor是贯穿bean初始化的过程。注意单纯的BeanPostProcessor类,不包括子类。 而BeanPostProcessor及其子类是贯穿bean的示例化过程。
什么是开闭原则。开是扩展,闭是修改。什么是后置处理器? spring 内置的对外的一些扩展点。
图52
Spring 执行的第1个InstantiationAwareBeanPostProcessor 后置处理器的作用。图35
图53
图53源码解释了,图52的文字的。简单的说,在bean初始化前调用applyBeanPostProcessorsBeforeInstantiation()方法如果不为空就直接调用applyBeanPostProcessorsAfterInitialization()方法,返回bean,不在进行后续方法逻辑,比如自动装配,构造方法示例化等等都不执行了。但是回执行BeanPostProcessor的 postProcessAfterInitialization();
图54 举例证明
postProcessBeforeInstantiation()方法如果返回的bean不为空直接返回该对象,不在进行后续处理。使用场景,比如有时候我们想要对一个对象做代理,代理完后直接返回,不想在做其他spirng的处理,属性注入,或者实例化的等操作。如果返回null会正常执行spring的后续流程。 postProcessAfterInstantiation()这个方法判断是不是要自动注入属性。 要不要对属性进行修改。 postProcessPropertyValues()方法完成了属性的填充。
图55
图56
图57
如果我们把postProcessAfterInstantiation()这个方法改为返回false。是不是真的LubanDao 就不注入了呢.
图58
执行结果为 LubanDao为空所以报了空指针异常,确实是没有注入。如果我们从信息把postProcessAfterInstantiation()这个方法改为返回ftrue.
图59
能够正常执行。
图60
如果我们报postProcessBeforeInstantiation()方法修改为返回一个代理对象而不是空的时候。
图61
可以看到结果打印的是luban。因为CityService被代理了,那么toString()方法也被代理了,所以打印的是luban
图62
用这个方法来验证 postProcessBeforeInstantiation(); 返回不为空的bean 还要必须 BeanPostProcessor的 postProcessAfterInitialization();
的方法其他的方法都不会执行了。 期望的的结果应该是打印after,和 对象。
图63
与预期的一致。忽略proxy。
这个的应用场景是什么呢,Aop。
spring 的后置处理器分为两种,一种是BeanFactoryPostProcessor ,在spring 的初始化过程中率先执行。注解的解析,类的解析。把类型转换层bd。解析类的作用域。第二种后置处理器BeanPostProcessor。提供了很多扩展。其中有一InstantiationAwareBeanPostProcessor
图64
图65
第二次执行后置处理器.如果推断的构造方法不为空,用推断的构造方法,如果为空用默认的构造方法。
SmartInstantiationAwareBeanPostProcessor类的determineCandidateConstructors()方法。这个方法的作用是推断构造方法。推断采用哪个构造方法。
第三次执行后置处理器
第三次后置处理器,postProcessMergedBeanDefinition() 方法的作用是缓存注解信息。
第四次执行后置处理器。
第四次执行后置处理器调用SmartIntatiationAwareBeanPostProcessor类的getEarlyBeanReference方法,来解决循环依赖问题。getEarlyBeanReference 这个方法拿到一个提取暴露的对象。注意是对象不是bean(因为bean是一定在spring容器中的并且由spring产生的)
第5次执行后置处理器, 执行InstantiationAwareBeanPostProcessor 类postProcessAfterInstantiation()方法。判断你的bean的属性是否需要填充。
第6次执行后置处理器, InstantiationAwareBeanPostProcessor 类的 postProcessPropertyValues()方法。属性填充=属性注入。
第七次执行后置处理器
BeanPostProcessor.postProcessBeforeInitialization()方法。
第八次调用后置处理器。
BeanPostProcessor.postProcessAfterInitialization()方法。
第9次 bean销毁的.
总结图.
第一次调用后置处理器 使用场景演示
图66
图67
第一次调用后置处理器 使用场景演示
图68
打印的结果。
等于true 证明lubanAspectj这个类也在spring容器中。
对lubanAspectj这个类进行断点跟踪,这个类是不需要增强的,
advisedBeans
判断lubanAspectj要不要增强,判断这个类里面是不是包含Advisor,Pointcut等注解。如果这个类包括这些注解,则会 advisedBeans 这个map中。那么在后续调用BeanPostProcessor的postProcessAfterIniialization(); 会判断advisedBeans的值来忽略这个类型。
我们来看citService 这个类这个时候的bp是
最终这个也是cityService返回为null.并且不会再advisedBeans中放入值。
第二次调用后置处理器 详细说明
提供了两个构造方法,打印结果告诉我们 使用的是默认构造方法。
如果我们给有参构造器加上@Autowired注解那么,他使用的构造方法就是有参构造方法。为什么呢,?因为这个都是第二个调用后置处理器所处理的。
当我们对cityService进行断点跟踪,首先是演示第一个场景不带 @Autoried 注解的。推断出来的的构造器方法是空的。如果推断出来的构造方法是空spring会使用默认的构造方法。为什么推断出来的是空的的呢, 因为cityService 有两个构造方法,两个构造方法都是可以的,spring也晕了,干脆就给你返回一个null。
当我们加了@Autowired注解时。
通过断点我们知道最终 在AutowiredAnnotationBeanPostProcessor后置处理器进行的构造器推断。
AutowiredAnnotationBeanPostProcessor类的determineCandidateConstructors方法 。lookUp 不用关注。
先从缓存中获取构造函数。获取为空
从这个类里获取所有的声明的构造方法,有两个。
定义了四个变量candidates(默认设置和获取声明式构造器的个数),requiredConStructor(必须的构造器),defaultConstructor(默认的),primaryConstructor.(特殊的表达式采用不重要)。nonSyntheticConstructors(混合的)。286行判断是不是混合方法。如果不是 nonSyntheticConstructors 加1,当第二个构造函数执行了,nonSyntheticConstructors 为2 因为这两个都不是混合型的。证明是可用的构造函数。
判断属性,方法,是不是混合类
292行找这个类是不是加了@Autowired 注解 ,@Value 注解 。如果是第一个默认构造方法没有参数的。ann 等于null .。拿出构造方法所在的类和遍历出的构造方法的类是不是相等。一般是相等的。第二个有参构造器是加了@Autorwired注解,所以ann不等于空,当ann不等于null的时候返回的key=required,value=true 的值。 判断 295行 userClass!=beanClass 就是。如果这个类被代理了,获取他的父类,判断父类是不是加@Autowired,或者@Value注解。
313行把ann的值取出为true, 第二构造函数的话会走到这个分支。
如果ann==null 并且 构造器的参数个数为0 。第一个构造器默认的构造函数满足这个 就把 defaultConstructor 赋值为当前的的这个构造函数。如果是第二个构造函数,ann为null。required=true。 就会把当前的构造函数赋值给requiredConstructor变量。最后把这个当前的构造器放入到candidates集合中。
如果是第二个构造函数,cadidates中有第二个有参的构造函数,所以candidateConstructors中有一个构造函数就是第二个。
如果是第二个构造函数,最终返回 含有第二个构造函数的 candidateConstructors。
最终推断出只有一个构造方法。
问题又来了。如果我们把第二个构造方法的@Autowired注解去掉。推断构造函数为什么为空啊。如果@Autowired去掉,那么 ann =null ,candidates集合就会为空。所以 最终会走下图 candidateConstructors为空。其实我们可以总结理解为:spring 把找到合适的构造器就放到一个集合中,这个集合是candidates。最终返回这个集合。
AnnotationAttributes是什么啊,就是@Autowired,@Value的注解的值 如下图 ;annotationAttributes就是 类型map 的 (required,true)
判断该类的父类是不是包含cglib。
插播一条广告;
@Autowired 按类型,@Resource 按名称 。spring 的注入方式 (先查找 对象,然后再填充对象属性) ,那么查找有几种方式?填充属性又有几种方式呢, 查找有两种方式,通过类型,和通过名称。填充属性有几种方式呢,有三种方式,1 是通过构造器,2,通过set方法,3,是通过反射 field.set().
图65
举例证明
图66
更改
更改query方法
运行结果
通过这个测试发现没有调用set方法。 service是构造器打印的,lubanDao全类名是query()方法打印的。证明@Autowired与set方法没有关系,是通过反射的filed.set()方法注入的。
这种事通过构造器注入的。在实例化的过程中会推断最合适的构造器方法来注入。
结果
如果上述改成这样是不是就可以使用set注入了。
这种方式也能完成set注入。并且set方法和 setxxxx后缀没有任何关系。及时改成xxxx也可以正常执行。并且和属性也没有关系,及时去掉也不影响。
现在问题来了,如果@Autowired 加载属性上和加载方法上实现的逻辑一样吗,答案是不一样的,加载属性上是通过反射 找到这个类的属性filed.set()注入。那么加载方法上呢,通过反射获取类的set()方法来完成注入。
图67
如果我们把属性和方法的@Autoried 注解都去掉的话,dao就没有办法注入进来。有没有别的办法把dao注入进来呢,不用@Autoried注解或者其他注解。
图68
通过这种方式也可以完美的注入。 就是运用了自动装配的模型。如是是 by_type。 就会不用加任何注解,就可以调用set方法完成注入。这样就和spring没有任何依赖了,就是CityService 的@Compont注解也可以去掉。通过什么逻辑执行的呢,首先在初始化时,调用refresh()方法执行beanFactoryPostProcesss后置处理器,然后把cityService 的bd 拿出来,然后更改注入模型为by_type 。spring 就会查询cityService的set方法。调用。也可以把注入模型改成1那么他就使用构造器注入。 by_type 的时候,set方法有什么要求呢,第一要以set开头,第二参数类型必须在spring环境中能找到。by_type和by_name有时候可以相互转化,比如by_type 如果找不到就使用by_name,by_name如果找不到就用by_type是一个优先级的关系。
图