文章目录
- Spring的循环依赖
- 1.循环依赖的定义&&原因
- 2.循环依赖的场景
- 1.构造器注入引起循环依赖
- 2.Field属性setter注入的循环依赖
- 3.循环依赖解决思路
- 4.三级缓存
- 5.面试题[三级缓存]
- AOP源码深度剖析
- 概述
- Spring AOP的前世今生
- 实现机制
- **JDK 动态代理**
- **CGLIB 代理**
- 流程
- 总结
- MVC流程源码剖析
- Servlet生命周期
- DispatcherServlet 类图
- 源码剖析-根容器初始化【父容器】
- Web应用部署初始化过程 (Web Application Deployement)
- ContextLoaderListener的初始化过程
- ServletContextListener接口源码:
- 源码剖析-DispatcherServlet初始化【子容器&9大组件】
- DispatcherServlet类图
- 为什么需要多个IOC容器呢?
- DispatcherServlet初始化
1.Spring中核心组件
2.IOC流程:bean对象是如何创建出来的
3.Bean生命周期
4.循环依赖:循坏依赖为什么要通过三级缓存来解决
5.AOP
6.事务
7.MVC
Spring的循环依赖
1.循环依赖的定义&&原因
定义:一个或多个对象实例之间存在直接或间接的依赖关系,这种依赖关系构成了一个环形调用(闭环)
在Spring中,一个对象并不是简单new出来的,而是会经过一系列的Bean的生命周期,就是因为Bean的生命周期,所以才出现了循环依赖的问题
在Bean生命周期的属性赋值阶段,A依赖了B,从单例池中找B对象,没有则创建,创建B的途中依赖了A,此处形成了环形调用。
2.循环依赖的场景
1.构造器注入引起循环依赖
2.Field属性setter注入的循环依赖
对于以上两种场景的循环依赖,Spring下测试效果:
1.构造器注入引起的循环依赖(不能解决)
2.单例Bean的Setter注入产生的循环依赖(能解决)
补充:多例Bean的Setter注入产生的循环依赖(不能解决)
3.循环依赖解决思路
Spring的循环依赖的理论依据是:基于Java的引用传递
即:可以先实例化对象,实例化对象之后,在内存中就有了该对象的内存地址,我们就可以先从内存中获取到该对象而对象的具体属性,是可以延后设置的
核心:创建和属性赋值分开,达到对象的提前暴露效果
A创建时,先创建A原始对象放入缓存中
因为依赖于B,在创建B时需要A,从缓存中取A,缓存中有A则B创建完成,赋值给A
4.三级缓存
一级缓存
存放完整的Bean
Bean执行了一系列生命周期,存入单例池SingletonObjects中
二级缓存
存放半成品Bean
类似于A:{b=null},实例化但未赋值的状态,存入earlySingletonObjects
三级缓存
存放ObjectFactory对象
将Bean进行包装成ObjectFactory,存入singletonFactories
取对象时,可能取A的原始对象/A的代理对象
bean将A的地址传入进来,如果现在针对A进行AOP配置的话,A对象的方法增强,则在三级缓存中取A的时候,会提前生成A对象的代理对象proxy,如果没有配置则返回A的原始对象。
当有AOP配置时,Spring根据beanName在已注册的Advisor集合中找匹配到的拦截面,生成代理对象,把生成的代理对象存入二级缓存,并删除三级缓存: this.singletonFactories.remove(beanName)
5.面试题[三级缓存]
1.构造器注入引起的循环依赖能够解决吗?
不能。因为创建A对象时候是通过A的有参构造去构建的——new A(B),这个对象的创建和属性赋值是没有分开来的,所以没有办法进行A对象的提前暴露。
2.多例Bean对象setter注入产生的循环依赖能够解决吗?
不能。多例Bean的生命周期不由Spring管理
3.循环依赖,只有一级缓存,能够解决循环依赖吗?
单从循环依赖的角度,能解决,但使用过程会有问题,因为此时成品对象、半成品对象都存到一个map中,如果另一个请求需要调用A对象的B属性的方法,他并不知道此时A对象是成品对象还是半成品对象,如果是半成品对象,调用B属性方法(此时B为null),则会报空指针异常。
4.循环依赖,只有一级缓存、二级缓存,能够解决循环依赖吗?
如果不存在AOP代理的情况,是可以解决的。
如果对A进行了AOP配置,要生成A的代理对象,A在初始化通过BeanPostProcessor#after方法生成proxy代理对象并存储缓存,但B的A属性还是指向A的原始对象,这里就产生了问题。
针对以上问题解决思路:在A放缓存之前,判断是否需要生成代理对象,如果需要,则基于原始对象生成代理对象,再把代理对象存入缓存中(等同于违背了Spring的设计原则,无论Bean是否产生循环依赖问题,所有Bean对象产生代理对象的时机都提前了(所有的Bean都得多一个判断))
5.循环依赖,只有一级缓存、三级缓存,能够解决循环依赖吗?
简单的A、B循环依赖没问题,但如果此时出现复杂的循环依赖会出现问题:比如A、B、C相互依赖场景
B依赖于A,生成了A的代理对象Proxy1,C依赖于A,又生成了A的代理对象Proxy2,此时B、C引用了不同的A对象地址。
通过二级缓存解决这种现象:Spring先查二级缓存,发现有A对象代理生成,则不会再去生成新的A对象代理。
生成代理对象的时候有判断:该bean之前是否生成过代理,不重复生成,通过集合判断
6.三级缓存为什么存ObjectFactory?为什么需要三级缓存解决循环依赖问题?
本身违背了Spring设计原则,Spring很多拓展点设计都无法应用,只有产生循环依赖,才会把产生循环依赖的对象要生成的代理对象做一个提前操作,否则就把没有循环依赖,但需要产生代理对象按照Spring的设计规范生成
总结
三级缓存作用:在没有循环依赖的情况下,能够包装bean的初始化的最后阶段再生成代理对象,遵循Spring设计原则。
个人理解:三级缓存就是判断对象是否有AOP配置生成proxy对象,将生成代理对象提前了,但Spring找缓存对象时是按照顺序:一级缓存 -> 二级缓存 -> 三级缓存 查找的,不一定每个Bean都走到三级缓存,所以不违背Spring设计原则。
AOP源码深度剖析
概述
AOP(Aspect Orient Programming):面向切面编程;
用途:用于系统中的横切关注点,比如日志管理,事务管理;
实现:利用代理模式,通过代理对象对被代理的对象增加功能。
所以,关键在于AOP框架自动创建AOP代理对象,代理模式分为静态代理和动态代理;
框架:
AspectJ使用静态代理,编译时增强,在编译期生成代理对象;
SpringAOP使用动态代理,运行时增强,在运行时,动态生成代理对象;
Spring AOP的前世今生
目前 Spring AOP 一共有三种配置方式,Spring 做到了很好地向下兼容,所以可以放心使用。
- Spring 1.2 基于接口的配置:最早的 Spring AOP 是完全基于几个接口的
- Spring 2.0 schema-based 配置:Spring 2.0 以后使用 XML 的方式来配置,使用 命名空间
- Spring 2.0 @AspectJ 配置:使用注解的方式来配置,这种方式感觉是最方便的,还有,这里虽然叫做 @AspectJ,但是这个和 AspectJ 其实没啥关系。
要说明的是,这里介绍的 Spring AOP 是纯的 Spring 代码,和 AspectJ 没什么关系,但是 Spring 延用了 AspectJ 中的概念,包括使用了 AspectJ 提供的 jar 包中的注解,但是不依赖于其实现功能。
如 @Aspect、@Pointcut、@Before、@After 等注解都是来自于 AspectJ,但是功能的实现是纯 Spring AOP 自己实现的。
实现机制
Spring AOP 底层实现机制目前有两种:JDK 动态代理、CGLIB 动态字节码生成。在阅读源码前对这两种机制的使用有个认识,有利于更好的理解源码。
JDK 动态代理
Proxy.newProxyInstance()第三个参数,实现InvocationHandler接口重写invoke方法,在每次调用方法时,都会进入invoke方法实现增强。
Spring为Bean对象创建代理时,会判断当前Bean对象有没有实现接口,如果实现接口,使用JDK动态代理生成对象,否则使用CGLIB代理。
如果proxy-target-class=“true”,则再判断当前对象是否为接口对象,是接口的话依然使用JDK动态代理,不是接口使用CGLIB;如果proxy-target-class不设置或者为false,则默认为JDK动态代理
CGLIB 代理
new Enhance对象,设置属性,调用create()方法创建proxy对象,每次调用方法时,会进入intercept方法,可以在proxy.invokeSuper()前后进行逻辑增强。
流程
Spring对标签<aop:aspectj-autoproxy @EnableAspectJAutoProxy/>的解析(作用):
注册AnnotationAwareAspectJAutoProxyCreator后置处理器
AnnotationAwareAspectJAutoProxyCreator类图:实现了BeanPostProcessor
AnnotationAwareAspectJAutoProxyCreator 实现了几个重要的扩展接口(可能是在父类中实现):
1)实现了 BeanPostProcessor 接口:实现了 postProcessAfterInitialization 方法。
2)实现了 InstantiationAwareBeanPostProcessor 接口:实现了 postProcessBeforeInstantiation 方法。
3)实现了 SmartInstantiationAwareBeanPostProcessor 接口:实现了 predictBeanType 方法、getEarlyBeanReference 方法。
4)实现了 BeanFactoryAware 接口,实现了 setBeanFactory 方法。
对于 AOP 来说,postProcessAfterInitialization 是我们重点分析的内容,因为在该方法中,会对 bean 进行代理,该方法由父类 AbstractAutoProxyCreator 实现。
AbstractAutoProxyCreator#postProcessAfterInitialization
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
// 1.判断当前bean是否需要被代理,如果需要则进行封装
if (!this.earlyProxyReferences.contains(cacheKey)) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
AnnotationAwareAspectJAutoProxyCreator的BeanPostProcessor#after方法执行的时候,经过wrapIfNecessary方法来判断是否需要针对当前类生成代理对象;
wrapIfNecessary
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
// 1.判断当前bean是否在targetSourcedBeans缓存中存在(已经处理过),如果存在,则直接返回当前bean
if (beanName != null && this.targetSourcedBeans.contains(beanName)) {
return bean;
}
// 2.在advisedBeans缓存中存在,并且value为false,则代表无需处理
if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
return bean;
}
// 3.bean的类是aop基础设施类 || bean应该跳过,则标记为无需处理,并返回
if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
// Create proxy if we have advice.
// 4.获取当前bean的Advices和Advisors
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
// 5.如果存在增强器则创建代理
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
// 5.1 创建代理对象:这边SingletonTargetSource的target属性存放的就是我们原来的bean实例(也就是被代理对象),
// 用于最后增加逻辑执行完毕后,通过反射执行我们真正的方法时使用(method.invoke(bean, args))
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
// 5.2 创建完代理后,将cacheKey -> 代理类的class放到缓存
this.proxyTypes.put(cacheKey, proxy.getClass());
// 返回代理对象
return proxy;
}
// 6.标记为无需处理
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
getAdvicesAndAdvisorsForBean
@Override
protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, TargetSource targetSource) {
// 1.找到符合条件的Advisor
List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
if (advisors.isEmpty()) {
// 2.如果没有符合条件的Advisor,则返回null
return DO_NOT_PROXY;
}
return advisors.toArray();
}
getAdvicesAndAdvisorsForBean针对当前程序中所有切面类@Aspect进行解析,找到切面类中@Before/@After/@Around注解标注的方法,把这些方法创建成Advice对象,并且针对@Before(“pointcut()”)中的切点信息进行解析成Pointcut对象,将Advice对象和Pointcut对象进行封装,形成Advisor对象。
总结
1.标签<aop:aspectj-autoproxy @EnableAspectJAutoProxy/> 注册了一个AnnotationAwareAspectJAutoProxyCreator后置处理器,当代理对象生成的时候是调用AnnotationAwareAspectJAutoProxyCreator后置处理器的after方法生成。
2.创建代理对象时,after方法对于当前Bean对象去找对应的Advisor,如果有对应的Advisor,则生成代理对象(判断对象有没有实现接口,如果实现接口,采用JDK动态代理,否则采用CGLIB动态代理);当代理对象调用接口中任意方法时,都是执行底层的invoke方法,invoke方法执行时涉及拦截器链的依次执行(如果是后置通知/最终通知:先放行,再在方法返回时执行对应逻辑)。
MVC流程源码剖析
* 问题1:Spring和SpringMVC整合使用时,会创建一个容器还是两个容器(父子容器?)
答:会创建两个容器对象,并且是有父子容器关系,对于Spring容器主要管理业务层/持久层/事务层对象,对于SpringMVC容器主要负责Web层对象的维护,SpringMVC容器为子容器,Spring容器为父容器(其实就是设置了SpringMVC的一个parent属性)
* 问题2:DispatcherServlet初始化过程中做了什么?
答:在DispatcherServlet初始化init方法时,首先构建了子容器对象,并且根据spring-mvc.xml进行文件解析,这里关注<mvc:annotation-driven>标签(开启注解模式驱动),它向当前的BeanDefinitionMap中注册了一些Bean定义,其中有RequestMappingHandlerMapping、RequestMappingHandlerAdapter、ExceptionHandlerExceptionResolver,以支持对使用了@RequestMapping、@ExceptionHandler及其他注解的控制器方法的请求处理。
RequestMappingHandlerMapping:生命周期中,父类实现了initializingBean#AfterPropertiesSet,这里进行了映射关系的注册:
1.获取容器中所有的BeanName,根据BeanName获得对应的BeanType,判断有没有@Controller/@RequestMapping注解,如果有 说明是处理器(Handler)对象,将这些Bean封装到Map集合中(key:Method,value:RequestMappingInfo)(RequestMappingInfo是针对@RequestMapping注解的属性解析封装)
2.根据Map注册映射关系(key:RequestMappingInfo,value:HandlerMethod)(这里HandlerMethod是上一步Method的封装,比如还封装了这个Method的所在类的对象)
3.将RequestMappingInfo再封装到urlLookup中(key:url,value:RequestMappingInfo)
RequestMappingHandlerAdapter:在它的initializingBean#AfterPropertiesSet方法中:
初始化了参数解析器、返回值处理器
* 问题3:请求的执行流程是怎么样的?
答:
1.根据请求地址,定位到Controller中的方法,可能有拦截器拦截,所以返回的是执行器链,根据顺序:拦截器目标方法、拦截器后置方法执行;
2.(参数如何绑定)通过不同的参数解析器完成参数值的解析,再反射调用方法
3.(不同返回值如何处理)通过不同的返回值处理器将结果变为ModelAndView对象,再进行视图渲染,最终以转发的形式完成视图的跳转
SpringMVC是基于Servlet和Spring容器设计的Web框架
Servlet生命周期
1</> //将随着服务的启动进行实例化,并调用init方法且只调用一次。
ServletConfig 是一个和 Servlet 配置相关的接口:
在配置 Spring MVC 的 DispatcherServlet 时,会通过 ServletConfig 将配置文件的位置告知 DispatcherServlet。
例:
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
</servlet>
如上,标签内的配置信息最终会被放入 ServletConfig 实现类对象中。DispatcherServlet 通过 ServletConfig 接口中的方法,就能获取到 contextConfigLocation 对应的值。
DispatcherServlet 类图
请求入口:DispatcherServlet类的dpDispatch()方法 (找service()方法一步一步找到的核心方法)
源码剖析-根容器初始化【父容器】
Web应用部署初始化过程 (Web Application Deployement)
Web应用部署初始化
流程执行图:
可以发现,在tomcat
下web应用
的初始化流程是,先初始化listener
接着初始化filter
最后初始化servlet
,当我们清楚认识到Web应用
部署到容器后的初始化过程后,就可以进一步深入探讨SpringMVC
的启动过程。
ContextLoaderListener的初始化过程
首先定义了<context-param>
标签,用于配置一个全局变量,<context-param>
标签的内容读取后会被放进application
中,做为Web应用的全局变量使用,接下来创建listener
时会使用到这个全局变量,因此,Web应用在容器中部署后,进行初始化时会先读取这个全局变量,之后再进行上述讲解的初始化启动过程。
接着定义了一个ContextLoaderListener类
的listener
。查看ContextLoaderListener
的类声明源码如下图:
ServletContextListener接口源码:
public interface ServletContextListener extends java.util.EventListener {
void contextInitialized(javax.servlet.ServletContextEvent servletContextEvent);
void contextDestroyed(javax.servlet.ServletContextEvent servletContextEvent);
}
该接口只有两个方法contextInitialized
和contextDestroyed
,这里采用的是观察者模式,也称为为订阅-发布模式,实现了该接口的listener
会向发布者进行订阅,当Web应用
初始化或销毁时会分别调用上述两个方法。
继续看ContextLoaderListener
,该listener
实现了ServletContextListener
接口,因此在Web应用
初始化时会调用该方法,该方法的具体实现如下:
/**
* Initialize the root web application context.
*/
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
ContextLoaderListener
的contextInitialized()
方法直接调用了initWebApplicationContext()
方法,这个方法是继承自ContextLoader类
,通过函数名可以知道,该方法是用于初始化Web应用上下文,即IoC容器
,这里使用的是代理模式
源码剖析-DispatcherServlet初始化【子容器&9大组件】
DispatcherServlet类图
Web应用
启动的最后一个步骤就是创建和初始化相关Servlet
,我们配置了DispatcherServlet类
前端控制器,前端控制器作为中央控制器是整个Web应用
的核心,用于获取分发用户请求并返回响应。
其类图如下所示:
通过类图可以看出DispatcherServlet类
的间接父类实现了Servlet接口
,因此其本质上依旧是一个Servlet
为什么需要多个IOC容器呢?
答:父子容器类似于类的继承关系,子类可以访问父类中的成员变量,而父类不可访问子类的成员变量,同样的,子容器可以访问父容器中定义的Bean,但父容器无法访问子容器定义的Bean。
根IoC容器做为全局共享的IoC容器放入Web应用需要共享的Bean,而子IoC容器根据需求的不同,放入不同的Bean,这样能够做到隔离,保证系统的安全性。
DispatcherServlet类的子IoC容器创建过程,如果当前Servlet存在一个IoC容器则为其设置根IoC容器作为其父类,并配置刷新该容器,用于构造其定义的Bean,这里的方法与前文讲述的根IoC容器类似,同样会读取用户在web.xml中配置的中的值,用于查找相关的xml配置文件用于构造定义的Bean,这里不再赘述了。如果当前Servlet不存在一个子IoC容器就去查找一个,如果仍然没有查找到则调用 createWebApplicationContext()方法去创建一个,查看该方法的源码如下图所示:
protected WebApplicationContext createWebApplicationContext(@Nullable WebApplicationContext parent) {
return createWebApplicationContext((ApplicationContext) parent);
}
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
Class<?> contextClass = getContextClass();
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException(
"Fatal initialization error in servlet with name '" + getServletName() +
"': custom WebApplicationContext class [" + contextClass.getName() +
"] is not of type ConfigurableWebApplicationContext");
}
ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
wac.setEnvironment(getEnvironment());
wac.setParent(parent);
String configLocation = getContextConfigLocation();
if (configLocation != null) {
wac.setConfigLocation(configLocation);
}
configureAndRefreshWebApplicationContext(wac);
return wac;
}
该方法用于创建一个子IoC容器并将根IoC容器做为其父容器,接着进行配置和刷新操作用于构造相关的Bean。至此,根IoC容器以及相关Servlet的子IoC容器已经配置完成,子容器中管理的Bean一般只被该Servlet使用,因此,其中管理的Bean一般是“局部”的,如SpringMVC中需要的各种重要组件,包括Controller、Interceptor、Converter、ExceptionResolver等。
DispatcherServlet初始化
DispatcherServlet的内置组件及其作用。
如 @Aspect、@Pointcut、@Before、@After 等注解都是来自于 AspectJ,但是功能的实现是纯 Spring AOP 自己实现的。
颜色
颜色
颜色
颜色
颜色
颜色
颜色
颜色
颜色
颜色