原文出處: Fooisart
項目完整源碼github地址
結合源碼分析Spring容器與Springmvc容器之間的關系
問題
問題描述:項目中發(fā)現(xiàn)研侣,自定義切面注解在Controller層正常工作乱投,在Service層卻無法正常工作。為了便于分析亭螟,去掉代碼中的業(yè)務邏輯,只留下場景。
自定義注解,打印時間
/**
* Description: 自定義打印時間的注解
* Created by jiangwang3 on 2018/5/9.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface PrintTime {
}
注解解析器
/**
*Description:打印時間注解的解析器
* @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("開始運行程序酌住。店归。。Start==>");
Object proceed = jp.proceed();
LOGGER.error("結束啦赂韵,運行結束==>");
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層----測試切面");
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層---測試切面");
return "serviceAspect";
}
}
spring.xml配置文件,主要部分
<context:annotation-config />
<!-- 動態(tà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 />
<!-- 動態(tà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"/>
以上為主要代碼祭示,項目運行之后,發(fā)現(xiàn)在Service層的注解切面未生效谴古,而在Controller層正常质涛。而當我將springmvc.xml中的
<bean class="com.foo.service.processor.PrintTimeProcessor"/>
遷移至spring.xml中,發(fā)現(xiàn)Service層與Controller層的注解切面均可正常運行掰担。WHY???
從源碼的角度探究該問題
由于源碼中的個方法較長汇陆,所以只貼出重點且與主題相關的代碼。建議結合本地源碼一起看带饱。
為了說清楚這個問題毡代,咱們先看一下Spring容器是如何實現(xiàn)bea自動注入(簡化版)
web項目的入口是web.xml,所以咱們從它開始勺疼。
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)聽器是Spring容器的入口酪耕,進入該文件:
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)聽器一共有四個方法,可以很容易地判斷出來轨淌,進入該監(jiān)聽器后迂烁,會進入初始化方法:contextInitialized。繼而進入initWebApplicationContext方法递鹉,方法注釋中“Initialize Spring's web application context for the given servlet context”盟步,明確表明了該方法的目的是初始化spring web應用。這段代碼中有兩句話比較關鍵:
this.context = createWebApplicationContext(servletContext);
創(chuàng)建web 應用容器躏结,即創(chuàng)建了Spring容器址芯;
configureAndRefreshWebApplicationContext(cwac, servletContext);
配置并刷新Spring容器。后續(xù)發(fā)生的所有事窜觉,都是從它開始的谷炸。進入,里面的重點代碼是:
wac.refresh();
refresh()方法是spring容器注入bean的核心方法禀挫,每一行代碼都很重要旬陡。代碼結構也非常優(yōu)美,每一行代碼背后都完成了一件事语婴,代碼結構比較容易理解描孟。由于內容較多驶睦,只講里面跟主題相關的兩句話:
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
獲取bean工廠,把你配置文件中的內容匿醒,放在bean工廠中场航,留著后面創(chuàng)建bean時用。
finishBeanFactoryInitialization(beanFactory);
開始創(chuàng)建bean廉羔,即實現(xiàn)spring中的自動注入功能溉痢。進入該方法后,末尾有這么一句話:
beanFactory.preInstantiateSingletons();
繼續(xù)跟進憋他,貼出該方法中的重點代碼:
getBean(beanName);
我們在preInstantiateSingletons()方法中孩饼,會發(fā)現(xiàn)有多個地方出現(xiàn)了getBean()方法,究竟咱們貼出來的是哪一句竹挡?無關緊要镀娶。跟進去之后,
@Override
public Object getBean(String name) throws BeansException {
return doGetBean(name, null, null, false);
}
這里調用了doGetBean()方法揪罕,spring中只要以do命名的方法梯码,都是真正干活的。重點代碼分段貼出分析:
// 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好啰,若沒有取到忍些,繼續(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);
}
}
這一段代碼單獨看,不知所云坎怪,里面提到了一個詞:Parent罢坝。暫且跳過,后續(xù)會回來分析這一段搅窿。繼續(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)建過程,此處出現(xiàn)了create男应,毫不猶豫地跟進闹司,進入實現(xiàn)類中的方法,有這么一句:
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
剛才咱們提了沐飘,spring中有do命名的方法游桩,是真正干活的。跟進:
instanceWrapper = createBeanInstance(beanName, mbd, args);
這句話是初始化bean耐朴,即創(chuàng)建了bean借卧,等價于調用了一個類的空構造方法。此時筛峭,已經(jīng)成功地創(chuàng)建了對象铐刘,下文需要做的是,給該對象注入需要的屬性影晓;
populateBean(beanName, mbd, instanceWrapper);
填充bean屬性镰吵,就是剛才咱們提的檩禾,初始化一個對象后,只是一個空對象疤祭,需要給它填充屬性盼产。跟進,看spring是如何為對象注入屬性的勺馆,或者說戏售,看一下spring是如何實現(xiàn)bean屬性的自動注入:
pvs = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
繼續(xù)進入AutowiredAnnotationBeanPostProcessor的postProcessPropertyValues方法:
metadata.inject(bean, beanName, pvs);
這句話中,出現(xiàn)了inject谓传,這個詞的意思是"注入"蜈项。咱們可以斷定芹关,spring的自動注入续挟,八成跟它有關了。進入該方法:
element.inject(target, beanName, pvs);
與上一句一樣侥衬,只是做了一些參數(shù)處理诗祸,并沒有開始注入。繼續(xù)跟進看:
Field field = (Field) this.member;
ReflectionUtils.makeAccessible(field);
field.set(target, getResourceToInject(target, requestingBeanName));
看到這里轴总,大概明白了spring是如何自動注入了直颅。java反射相關的代碼,通過反射的方式給field賦值怀樟。這里的field是bean中的某一個屬性功偿,例如咱們開始時的UserController 類中的userService。getResourceToInject往堡,獲取需要賦予的值了械荷,其實這里會重新進入getBean方法,獲取bean值(例如UserController對象中需要注入userService虑灰。)吨瞎,然后賦予field。至此穆咐,spring容器已經(jīng)初始化完成颤诀,spring bean注入的大概流程,咱們也已經(jīng)熟悉了对湃⊙陆校回到開始初始化Spring容器的地方,ContextLoader類initWebApplicationContext方法拍柒,
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
初始化spring容器之后归露,將其放入了servletContext中。
咱們的問題是斤儿,"在項目中剧包,自定義切面注解在Controller層正常工作恐锦,卻在Service層無法正常工作?"看完這個,其實并沒有解答該問題疆液,咱們下面繼續(xù)看springmvc bean的加載流程一铅,看完springmvc后,答案會自動浮出水面堕油。
Springmvc容器Bean加載流程
同樣潘飘,從web.xml中的springmvc配置出發(fā),里面有DispatcherServlet掉缺,這是springmvc的入口卜录,跟進之后發(fā)現(xiàn)方法較多,無法知道會執(zhí)行哪個方法眶明。但是咱們要記住艰毒,DispatcherServlet本質上是一個servlet,通過它的繼承關系圖也可以證明:
[圖片上傳失敗...(image-4585b5-1545214369387)]
看一下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ù)DispatcherServlet繼承圖跟進),進入到了FrameworkServlet的initServletBean()方法蜀肘,進入方法绊汹,貼出重點代碼:
this.webApplicationContext = initWebApplicationContext();
字面理解,初始化springmvc web容器扮宠,進入探究:
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
前面咱們提到西乖,spring容器初始化完成之后,放入了servletContext中坛增。這里又從servletContext獲取到了spring容器获雕;
wac = createWebApplicationContext(rootContext);
字面理解創(chuàng)建web應用容器,且參數(shù)是spring容器轿偎。跟進方法:
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
創(chuàng)建web應用容器典鸡,即咱們所理解的Springmvc容器在此創(chuàng)建了;
wac.setParent(parent);
這里是重點坏晦,Springmvc容器將Spring容器設置成了自己的父容器萝玷。
configureAndRefreshWebApplicationContext(wac);
這個方法剛才在分析spring bean加載流程時,分析過了昆婿。其中有一段球碉,前面說,"暫且跳過仓蛆,后續(xù)會回來分析這一段"≌龆現(xiàn)在開始分析:
在AbstractBeanFactory類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);
}
}
這里其實是在獲取父容器中的bean,若獲取到豆拨,直接拿到bean直奋,這個方法就結束了。結論:子容器可以使用父容器里的bean施禾,反之脚线,則不行。
現(xiàn)在來解答咱們的問題
<bean class="com.foo.service.processor.PrintTimeProcessor"/>
當上門這句話放在springmvc.xml中時弥搞,名為"printTimeProcessor"的bean會存在于Springmvc容器邮绿,那么Spring容器是無法獲取它的。而Service層恰巧是存在于Spring容器中攀例,所以"printTimeProcessor"切面對Service層不起作用船逮。而Controller層本身存在于Springmvc容器,所以Controller層可以正常工作粤铭。而當它放在spring.xml中時挖胃,"printTimeProcessor"是存在于Spring容器中,Springmvc容器是Spring容器的子容器承耿,子容器可以獲取到父容器的bean冠骄,所以Controller層與Service層都能獲取到該bean伪煤,所有都能正常使用它加袋。