Spring 源码 十(Spring 容器和Spring mvc 容器的区别,Spring 中5个后置处理器,在9个不同地方的调用过程)

图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是一个优先级的关系。  

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当然,我很乐意回答您的问题。在 Spring ,Bean 后置处理器是一种特殊类型的 Bean,它可以在 Bean 初始化前后对 Bean 进行一些处理。下面是一个使用 Bean 后置处理器的例子: ```java @Component public class MyBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("Before initialization of bean: " + beanName); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("After initialization of bean: " + beanName); return bean; } } ``` 在这个例子,我们创建了一个名为 MyBeanPostProcessor 的 Bean 后置处理器,并实现了 BeanPostProcessor 接口的两个方法:postProcessBeforeInitialization 和 postProcessAfterInitialization。这两个方法分别在 Bean 初始化前和初始化后被调用。 我们可以使用 @Component 注解将 MyBeanPostProcessor 注册为一个 Spring Bean,并在需要使用的地方注入它: ```java @Configuration public class AppConfig { @Autowired private MyBeanPostProcessor myBeanPostProcessor; @Bean public MyBean myBean() { return new MyBean(); } } ``` 在这个例子,我们将 MyBeanPostProcessor 注入到 AppConfig ,并在 AppConfig 创建了一个名为 myBean 的 Bean。当 Spring 容器初始化时,MyBeanPostProcessor 将会被自动调用,并对 myBean 进行处理。 希望这个例子能够帮助您理解 Bean 后置处理器的使用方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值