結(jié)合源碼分析 Spring 容器與 SpringMVC 容器之間的關(guān)系
問(wèn)題
問(wèn)題描述:項(xiàng)目中發(fā)現(xiàn)玖像,自定義切面注解在 Controller 層正常工作诀拭,在 Service 層卻無(wú)法正常工作稠鼻。為了便于分析捺信,去掉代碼中的業(yè)務(wù)邏輯,只留下場(chǎng)景仁烹。
自定義注解耸弄,打印時(shí)間
/**
* Description: 自定義打印時(shí)間的注解
* Created by jiangwang3 on 2018/5/9.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface PrintTime {
}
注解解析器
/**
*Description:打印時(shí)間注解的解析器
* @author jiangwang
* @date 11:28 2018/5/14
*/
@Aspect
public class PrintTimeProcessor {
? ? private Logger LOGGER = LoggerFactory.getLogger(getClass());
? ? @Pointcut("@annotation(com.foo.service.annotation.PrintTime)")
? ? public void printTimePoint() {
? ? }
? ? @Around("printTimePoint()")
? ? public Object process(ProceedingJoinPoint jp) throws Throwable{
? ? ? ? System.out.println();
? ? ? ? LOGGER.error("開(kāi)始運(yùn)行程序。卓缰。计呈。Start==>");
? ? ? ? Object proceed = jp.proceed();
? ? ? ? LOGGER.error("結(jié)束啦,運(yùn)行結(jié)束==>");
? ? ? ? System.out.println();
? ? ? ? return proceed;
? ? }
}
Controller層
/**
* @author jiangwang
* @date? 2018/5/14
*/
@RestController
@RequestMapping(value = "/user")
public class UserController {
? ? private Logger logger = LoggerFactory.getLogger(getClass());
? ? @Resource
? ? private UserService userService;
? ? @RequestMapping(value = "/serviceAspect", method={RequestMethod.GET})
? ? public? String serviceAspect(){
? ? ? ? return userService.serviceAspect();
? ? }
? ? @RequestMapping(value = "/controllerAspect", method={RequestMethod.GET})
? ? @PrintTime
? ? public? String name(){
? ? ? ? logger.info("Controller層----測(cè)試切面");
? ? ? ? return "controllerAspect";
? ? }
}
Service層
/**
* @author jiangwang
* @date 11:34 2018/5/14
*/
@Service
public class UserService {
? ? private Logger logger = LoggerFactory.getLogger(getClass())
? ? @PrintTime
? ? public String serviceAspect(){
? ? ? ? logger.info("Service層---測(cè)試切面");
? ? ? ? return "serviceAspect";
? ? }
}
spring.xml 配置文件征唬,主要部分
<context:annotation-config />
<!-- 動(dòng)態(tài)代理開(kāi)啟 -->
<aop:aspectj-autoproxy proxy-target-class="true" />
<context:component-scan base-package="com.foo" >
? ? <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!-- 公共配置引入 -->
<import resource="classpath:spring/spring-config-dao.xml" />
springmvc.xml 配置文件捌显,主要部分
<mvc:annotation-driven />
<mvc:default-servlet-handler />
<!-- 動(dòng)態(tài)代理開(kāi)啟 -->
<aop:aspectj-autoproxy proxy-target-class="true" />
<!-- mvc controller -->
<context:component-scan base-package="com.foo.web.controller" use-default-filters="false">
? ? <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
? ? <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service" />
</context:component-scan>
<bean class="com.foo.service.processor.PrintTimeProcessor"/>
以上為主要代碼。項(xiàng)目運(yùn)行之后总寒,發(fā)現(xiàn)在 Service 層的注解切面未生效扶歪,而在 Controller 層正常。而當(dāng)我將 springmvc.xml 中的
<bean class="com.foo.service.processor.PrintTimeProcessor"/>
遷移至 spring.xml 中摄闸,發(fā)現(xiàn) Service 層與 Controller 層的注解切面均可正常運(yùn)行善镰。WHY???
從源碼的角度探究該問(wèn)題
由于源碼中的方法較長(zhǎng),所以只貼出重點(diǎn)且與主題相關(guān)的代碼贪薪。建議結(jié)合本地源碼一起看媳禁。
為了說(shuō)清楚這個(gè)問(wèn)題眠副,咱們先看一下Spring容器是如何實(shí)現(xiàn) Bean 自動(dòng)注入(簡(jiǎn)化版)
Web 項(xiàng)目的入口是 web.xml画切,所以咱們從它開(kāi)始。
web.xml 配置文件囱怕,主要部分
<!-- Spring Config -->
<listener>
? <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
? <param-name>contextConfigLocation</param-name>
? <param-value>classpath:spring/spring-config.xml</param-value>
</context-param>
<!-- SpringMvc Config -->
<servlet>
? <servlet-name>springMvc</servlet-name>
? <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
? <init-param>
? ? <param-name>contextConfigLocation</param-name>
? ? <param-value>classpath:spring/spring-mvc.xml</param-value>
? </init-param>
? <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
? <servlet-name>springMvc</servlet-name>
? <url-pattern>/*</url-pattern>
</servlet-mapping>
Spring 容器 Bean 加載流程
從 Spring 配置部分可以看出霍弹,ContextLoaderListener 監(jiān)聽(tīng)器是 Spring 容器的入口,進(jìn)入該文件:
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
? ? public ContextLoaderListener() {
? ? }
? ? public ContextLoaderListener(WebApplicationContext context) {
? ? ? ? super(context);
? ? }
? ? @Override
? ? public void contextInitialized(ServletContextEvent event) {
? ? ? ? initWebApplicationContext(event.getServletContext());
? ? }
? ? @Override
? ? public void contextDestroyed(ServletContextEvent event) {
? ? ? ? closeWebApplicationContext(event.getServletContext());
? ? ? ? ContextCleanupListener.cleanupAttributes(event.getServletContext());
? ? }
}
ContextLoaderListener?監(jiān)聽(tīng)器一共有四個(gè)方法娃弓,可以很容易地判斷出來(lái)典格,進(jìn)入該監(jiān)聽(tīng)器后,會(huì)進(jìn)入初始化方法:contextInitialized台丛。繼而進(jìn)入initWebApplicationContext?方法耍缴,方法注釋中“Initialize Spring’s web application context for the given servlet context”砾肺,明確表明了該方法的目的是初始化 Spring Web 應(yīng)用。這段代碼中有兩句話(huà)比較關(guān)鍵:
this.context = createWebApplicationContext(servletContext);
創(chuàng)建 Web 應(yīng)用容器防嗡,即創(chuàng)建了 Spring 容器变汪;
configureAndRefreshWebApplicationContext(cwac, servletContext);
配置并刷新Spring容器。后續(xù)發(fā)生的所有事蚁趁,都是從它開(kāi)始的裙盾。進(jìn)入,里面的重點(diǎn)代碼是:
wac.refresh();
refresh()?方法是spring容器注入bean的核心方法他嫡,每一行代碼都很重要番官。代碼結(jié)構(gòu)也非常優(yōu)美,每一行代碼背后都完成了一件事钢属,代碼結(jié)構(gòu)比較容易理解徘熔。由于內(nèi)容較多,只講里面跟主題相關(guān)的兩句話(huà):
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
獲取 Bean 工廠淆党,把你配置文件中的內(nèi)容近顷,放在 Bean 工廠中,留著后面創(chuàng)建 Bean 時(shí)用宁否。
finishBeanFactoryInitialization(beanFactory);
開(kāi)始創(chuàng)建 Bean窒升,即實(shí)現(xiàn) Spring 中的自動(dòng)注入功能。進(jìn)入該方法后慕匠,末尾有這么一句話(huà):
beanFactory.preInstantiateSingletons();
繼續(xù)跟進(jìn)饱须,貼出該方法中的重點(diǎn)代碼:
getBean(beanName);
我們?cè)?preInstantiateSingletons() 方法中,會(huì)發(fā)現(xiàn)有多個(gè)地方出現(xiàn)了 getBean() 方法台谊,究竟咱們貼出來(lái)的是哪一句蓉媳?無(wú)關(guān)緊要确徙。跟進(jìn)去之后影锈,
@Override
public Object getBean(String name) throws BeansException {
? ? return doGetBean(name, null, null, false);
}
這里調(diào)用了 doGetBean() 方法兽埃,Spring 中只要以do?命名的方法矫渔,都是真正干活的洲愤。重點(diǎn)代碼分段貼出分析:
// Eagerly check singleton cache for manually registered singletons.
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
? ? if (logger.isDebugEnabled()) {
? ? ? ? if (isSingletonCurrentlyInCreation(beanName)) {
? ? ? ? ? ? logger.debug("Returning eagerly cached instance of singleton bean '" + beanName +
? ? ? ? ? ? ? ? ? ? "' that is not fully initialized yet - a consequence of a circular reference");
? ? ? ? }
? ? ? ? else {
? ? ? ? ? ? logger.debug("Returning cached instance of singleton bean '" + beanName + "'");
? ? ? ? }
? ? }
? ? bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
直接獲取單例 Bean宰闰,若沒(méi)有取到辛块,繼續(xù)往下走:
// Check if bean definition exists in this factory.
BeanFactory parentBeanFactory = getParentBeanFactory();
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
? ? // Not found -> check parent.
? ? String nameToLookup = originalBeanName(name);
? ? if (args != null) {
? ? ? ? // Delegation to parent with explicit args.
? ? ? ? return (T) parentBeanFactory.getBean(nameToLookup, args);
? ? }
? ? else {
? ? ? ? // No args -> delegate to standard getBean method.
? ? ? ? return parentBeanFactory.getBean(nameToLookup, requiredType);
? ? }
}
這一段代碼單獨(dú)看浊猾,不知所云贼邓,里面提到了一個(gè)詞:Parent阶冈。暫且跳過(guò),后續(xù)會(huì)回來(lái)分析這一段塑径。繼續(xù):
// Create bean instance.
if (mbd.isSingleton()) {
? ? ? sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
? ? ? ? ? ? @Override
? ? ? ? ? ? public Object getObject() throws BeansException {
? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? return createBean(beanName, mbd, args);
? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? catch (BeansException ex) {
? ? ? ? ? ? ? ? ? ? ? // Explicitly remove instance from singleton cache: It might have been put there
? ? ? ? ? ? ? ? ? ? ? // eagerly by the creation process, to allow for circular reference resolution.
? ? ? ? ? ? ? ? ? ? ? // Also remove any beans that received a temporary reference to the bean.
? ? ? ? ? ? ? ? ? ? ? destroySingleton(beanName);
? ? ? ? ? ? ? ? ? ? ? throw ex;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? });
? ? ? ? bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
這段代碼中有 createBean女坑,咱們的目的是分析 Bean 的創(chuàng)建過(guò)程,此處出現(xiàn)了create统舀,毫不猶豫地跟進(jìn)匆骗,進(jìn)入實(shí)現(xiàn)類(lèi)中的方法劳景,有這么一句:
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
剛才咱們提了,Spring 中有do?命名的方法碉就,是真正干活的枢泰。跟進(jìn):
instanceWrapper = createBeanInstance(beanName, mbd, args);
這句話(huà)是初始化 Bean,即創(chuàng)建了?Bean铝噩,等價(jià)于調(diào)用了一個(gè)類(lèi)的空構(gòu)造方法衡蚂。此時(shí),已經(jīng)成功地創(chuàng)建了對(duì)象骏庸,下文需要做的是毛甲,給該對(duì)象注入需要的屬性;
populateBean(beanName, mbd, instanceWrapper);
填充?Bean 屬性具被,就是剛才咱們提的玻募,初始化一個(gè)對(duì)象后,只是一個(gè)空對(duì)象一姿,需要給它填充屬性七咧。跟進(jìn),看 Spring 是如何為對(duì)象注入屬性的叮叹,或者說(shuō)艾栋,看一下 Spring 是如何實(shí)現(xiàn) Bean 屬性的自動(dòng)注入:
pvs = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
繼續(xù)進(jìn)入 AutowiredAnnotationBeanPostProcessor 的 postProcessPropertyValues 方法:
metadata.inject(bean, beanName, pvs);
這句話(huà)中,出現(xiàn)了inject蛉顽,這個(gè)詞的意思是“注入”蝗砾。咱們可以斷定,Spring 的自動(dòng)注入携冤,八成跟它有關(guān)了悼粮。進(jìn)入該方法:
<code>element.inject(target, beanName, pvs); </code>
與上一句一樣,只是做了一些參數(shù)處理曾棕,并沒(méi)有開(kāi)始注入扣猫。繼續(xù)跟進(jìn)看:
Field field = (Field) this.member;
ReflectionUtils.makeAccessible(field);
field.set(target, getResourceToInject(target, requestingBeanName));
看到這里,大概明白了 Spring 是如何自動(dòng)注入了翘地。Java 反射相關(guān)的代碼申尤,通過(guò)反射的方式給 field 賦值。這里的field?是 Bean 中的某一個(gè)屬性子眶,例如咱們開(kāi)始時(shí)的 UserController 類(lèi)中的userService瀑凝。getResourceToInject序芦,獲取需要賦予的值了臭杰,其實(shí)這里會(huì)重新進(jìn)入 getBean 方法,獲取 Bean 值(例如 UserController 對(duì)象中需要注入 userService谚中。)渴杆,然后賦予 field寥枝。至此,Spring容器已經(jīng)初始化完成磁奖,Spring Bean注入的大概流程囊拜,咱們也已經(jīng)熟悉了”却睿回到開(kāi)始初始化 Spring 容器的地方冠跷,ContextLoader 類(lèi) initWebApplicationContext 方法,
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
初始化 Spring 容器之后身诺,將其放入了 servletContext 中蜜托。
咱們的問(wèn)題是,“在項(xiàng)目中霉赡,自定義切面注解在 Controller 層正常工作橄务,卻在 Service 層無(wú)法正常工作?”看完這個(gè)穴亏,其實(shí)并沒(méi)有解答該問(wèn)題蜂挪,咱們下面繼續(xù)看 SpringMVC Bean的加載流程,看完 SpringMVC 后嗓化,答案會(huì)自動(dòng)浮出水面棠涮。
SpringMVC 容器 Bean 加載流程
同樣,從 web.xml 中的?SpringMVC 配置出發(fā)刺覆,里面有 DispatcherServlet故爵,這是 SpringMVC 的入口,跟進(jìn)之后發(fā)現(xiàn)方法較多隅津,無(wú)法知道會(huì)執(zhí)行哪個(gè)方法诬垂。但是咱們要記住,DispatcherServlet?本質(zhì)上是一個(gè)?Servlet伦仍,通過(guò)它的繼承關(guān)系圖也可以證明:
DispatcherServlet繼承關(guān)系圖
看一下 Servlet 的接口:
public interface Servlet {
? ? public void init(ServletConfig config) throws ServletException;
? ? public ServletConfig getServletConfig();
? ? public void service(ServletRequest req, ServletResponse res)
? ? ? ? ? ? throws ServletException, IOException;
? ? public String getServletInfo();
? ? public void destroy();
}
從 Servlet 接口方法中可以看出结窘,Servlet 的入口是init?方法,層層跟進(jìn)(一定要根據(jù) DispatcherServlet 繼承圖跟進(jìn))充蓝,進(jìn)入到了 FrameworkServlet 的initServletBean()?方法隧枫,進(jìn)入方法,貼出重點(diǎn)代碼:
this.webApplicationContext = initWebApplicationContext();
字面理解谓苟,初始化?SpringMVC Web容器官脓,進(jìn)入探究:
WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
前面咱們提到,Spring 容器初始化完成之后涝焙,放入了servletContext?中卑笨。這里又從 servletContext 獲取到了Spring 容器;
wac = createWebApplicationContext(rootContext);
字面理解創(chuàng)建 Web 應(yīng)用容器仑撞,且參數(shù)是 Spring 容器赤兴。跟進(jìn)方法:
ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
創(chuàng)建web應(yīng)用容器妖滔,即咱們所理解的?SpringMVC容器在此創(chuàng)建了;
wac.setParent(parent);
這里是重點(diǎn)桶良,SpringMVC?容器將 Spring 容器設(shè)置成了自己的父容器座舍。
configureAndRefreshWebApplicationContext(wac);
這個(gè)方法剛才在分析 Spring Bean 加載流程時(shí),分析過(guò)了陨帆。其中有一段曲秉,前面說(shuō),“暫且跳過(guò)疲牵,后續(xù)會(huì)回來(lái)分析這一段”“痘耄現(xiàn)在開(kāi)始分析:
在 AbstractBeanFactory 類(lèi) doGetBean 方法,有這么一段:
// Check if bean definition exists in this factory.
BeanFactory parentBeanFactory = getParentBeanFactory();
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
? ? // Not found -> check parent.
? ? String nameToLookup = originalBeanName(name);
? ? if (args != null) {
? ? ? ? ? ? // Delegation to parent with explicit args.
? ? ? ? return (T) parentBeanFactory.getBean(nameToLookup, args);
? ? }
? ? else {
? ? ? ? // No args -> delegate to standard getBean method.
? ? ? ? return parentBeanFactory.getBean(nameToLookup, requiredType);
? ? }
}
這里其實(shí)是在獲取父容器中的 Bean瑰步,若獲取到矢洲,直接拿到 Bean,這個(gè)方法就結(jié)束了缩焦。結(jié)論:子容器可以使用父容器里的 Bean读虏,反之則不行。
現(xiàn)在來(lái)解答咱們的問(wèn)題
<bean class="com.foo.service.processor.PrintTimeProcessor"/>
當(dāng)上門(mén)這句話(huà)放在 springmvc.xml 中時(shí)袁滥,名為“printTimeProcessor”?的 Bean 會(huì)存在于 SpringMVC 容器盖桥,那么 Spring 容器是無(wú)法獲取它的。而 Service 層恰巧是存在于 Spring 容器中题翻,所以“printTimeProcessor”?切面對(duì) Service 層不起作用揩徊。而 Controller 層本身存在于 SpringMVC 容器,所以 Controller 層可以正常工作嵌赠。而當(dāng)它放在 spring.xml 中時(shí)塑荒,”printTimeProcessor” 是存在于 Spring 容器中,SpringMVC 容器是 Spring 容器的子容器姜挺,子容器可以獲取到父容器的 Bean齿税,所以 Controller 層與 Service 層都能獲取到該 Bean,所有都能正常使用它炊豪。
歡迎工作一到五年的Java工程師朋友們加入Java程序員開(kāi)發(fā): 854393687
群內(nèi)提供免費(fèi)的Java架構(gòu)學(xué)習(xí)資料(里面有高可用凌箕、高并發(fā)、高性能及分布式词渤、Jvm性能調(diào)優(yōu)牵舱、Spring源碼,MyBatis缺虐,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個(gè)知識(shí)點(diǎn)的架構(gòu)資料)合理利用自己每一分每一秒的時(shí)間來(lái)學(xué)習(xí)提升自己芜壁,不要再用"沒(méi)有時(shí)間“來(lái)掩飾自己思想上的懶惰!趁年輕,使勁拼沿盅,給未來(lái)的自己一個(gè)交代把篓!