問題描述:項目中發現,自定義切面註解在 Controller 層正常工做,在 Service 層卻沒法正常工做。爲了便於分析,去掉代碼中的業務邏輯,只留下場景。html
1
2
3
4
5
6
7
8
9
|
/**
* Description: 自定義打印時間的註解
* Created by jiangwang3 on 2018/5/9.
*/
@Retention
(RetentionPolicy.RUNTIME)
@Target
({ElementType.METHOD})
@Documented
public
@interface
PrintTime {
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
/**
*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;
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
/**
* @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"
;
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
/**
* @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"
;
}
}
|
1
2
3
4
5
6
7
8
|
<
context:annotation-config
/>
<!-- 動態代理開啓 -->
<
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"
/>
|
1
2
3
4
5
6
7
8
9
10
|
<
mvc:annotation-driven
/>
<
mvc:default-servlet-handler
/>
<!-- 動態代理開啓 -->
<
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"
/>
|
以上爲主要代碼。項目運行以後,發如今 Service 層的註解切面未生效,而在 Controller 層正常。而當我將 springmvc.xml 中的java
1
|
<
bean
class
=
"com.foo.service.processor.PrintTimeProcessor"
/>
|
遷移至 spring.xml 中,發現 Service 層與 Controller 層的註解切面都可正常運行。WHY???web
因爲源碼中的方法較長,因此只貼出重點且與主題相關的代碼。建議結合本地源碼一塊兒看。spring
Web 項目的入口是 web.xml,因此我們從它開始。express
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
<!-- 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 配置部分能夠看出,ContextLoaderListener 監聽器是 Spring 容器的入口,進入該文件:spring-mvc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
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 監聽器一共有四個方法,能夠很容易地判斷出來,進入該監聽器後,會進入初始化方法:contextInitialized。繼而進入 initWebApplicationContext 方法,方法註釋中 bash
,明確代表了該方法的目的是初始化 Spring Web 應用。這段代碼中有兩句話比較關鍵:架構
1
|
this
.context = createWebApplicationContext(servletContext);
|
建立 Web 應用容器,即建立了 Spring 容器;mvc
1
|
configureAndRefreshWebApplicationContext(cwac, servletContext);
|
配置並刷新Spring容器。後續發生的全部事,都是從它開始的。進入,裏面的重點代碼是:app
1
|
wac.refresh();
|
refresh() 方法是spring容器注入bean的核心方法,每一行代碼都很重要。代碼結構也很是優美,每一行代碼背後都完成了一件事,代碼結構比較容易理解。因爲內容較多,只講裏面跟主題相關的兩句話:
1
|
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
|
獲取 Bean 工廠,把你配置文件中的內容,放在 Bean 工廠中,留着後面建立 Bean 時用。
1
|
finishBeanFactoryInitialization(beanFactory);
|
開始建立 Bean,即實現 Spring 中的自動注入功能。進入該方法後,末尾有這麼一句話:
1
|
beanFactory.preInstantiateSingletons();
|
繼續跟進,貼出該方法中的重點代碼:
1
|
getBean(beanName);
|
咱們在 preInstantiateSingletons() 方法中,會發現有多個地方出現了 getBean() 方法,究竟我們貼出來的是哪一句?可有可無。跟進去以後,
1
2
3
4
|
@Override
public
Object getBean(String name)
throws
BeansException {
return
doGetBean(name,
null
,
null
,
false
);
}
|
這裏調用了 doGetBean() 方法,Spring 中只要以 do 命名的方法,都是真正幹活的。重點代碼分段貼出分析:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
// 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,若沒有取到,繼續往下走:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
// 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。
繼續:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
// 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 的建立過程,此處出現了 create,堅決果斷地跟進,進入實現類中的方法,有這麼一句:
1
|
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
|
剛纔我們提了,Spring 中有 do 命名的方法,是真正幹活的。跟進:
1
|
instanceWrapper = createBeanInstance(beanName, mbd, args);
|
這句話是初始化 Bean,即建立了 Bean,等價於調用了一個類的空構造方法。此時,已經成功地建立了對象,下文須要作的是,給該對象注入須要的屬性;
1
|
populateBean(beanName, mbd, instanceWrapper);
|
填充 Bean 屬性,就是剛纔我們提的,初始化一個對象後,只是一個空對象,須要給它填充屬性。跟進,看 Spring 是如何爲對象注入屬性的,或者說,看一下 Spring 是如何實現 Bean 屬性的自動注入:
1
|
pvs = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
|
繼續進入 AutowiredAnnotationBeanPostProcessor 的 postProcessPropertyValues 方法:
1
|
metadata.inject(bean, beanName, pvs);
|
這句話中,出現了 inject,這個詞的意思是「注入」。我們能夠判定,Spring 的自動注入,八成跟它有關了。進入該方法:
<code>element.inject(target, beanName, pvs); </code>複製代碼
與上一句同樣,只是作了一些參數處理,並無開始注入。繼續跟進看:
1
2
3
|
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容器已經初始化完成,Spring Bean注入的大概流程,我們也已經熟悉了。回到開始初始化 Spring 容器的地方,ContextLoader 類 initWebApplicationContext 方法,
1
|
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,
this
.context);
|
初始化 Spring 容器以後,將其放入了 servletContext 中。
我們的問題是,「在項目中,自定義切面註解在 Controller 層正常工做,卻在 Service 層沒法正常工做?」看完這個,其實並無解答該問題,我們下面繼續看 SpringMVC Bean的加載流程,看完 SpringMVC 後,答案會自動浮出水面。
一樣,從 web.xml 中的 SpringMVC 配置出發,裏面有 DispatcherServlet,這是 SpringMVC 的入口,跟進以後發現方法較多,沒法知道會執行哪一個方法。可是我們要記住,DispatcherServlet 本質上是一個 Servlet,經過它的繼承關係圖也能夠證實:
DispatcherServlet繼承關係圖
看一下 Servlet 的接口:
1
2
3
4
5
6
7
8
|
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 方法,層層跟進(必定要根據 DispatcherServlet 繼承圖跟進),進入到了 FrameworkServlet 的 initServletBean() 方法,進入方法,貼出重點代碼:
1
|
this
.webApplicationContext = initWebApplicationContext();
|
字面理解,初始化 SpringMVC Web容器,進入探究:
1
|
WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
|
前面我們提到,Spring 容器初始化完成以後,放入了 servletContext 中。這裏又從 servletContext 獲取到了 Spring 容器;
1
|
wac = createWebApplicationContext(rootContext);
|
字面理解建立 Web 應用容器,且參數是 Spring 容器。跟進方法:
1
|
ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
|
建立web應用容器,即我們所理解的 SpringMVC 容器在此建立了;
1
|
wac.setParent(parent);
|
這裏是重點,SpringMVC 容器將 Spring 容器設置成了本身的父容器。
1
|
configureAndRefreshWebApplicationContext(wac);
|
這個方法剛纔在分析 Spring Bean 加載流程時,分析過了。其中有一段,前面說,
。如今開始分析:
在 AbstractBeanFactory 類 doGetBean 方法,有這麼一段:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
// 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,反之則不行。
1
|
<
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,全部都能正常使用它
歡迎學Java和大數據的朋友們加入java架構交流: 855835163
加羣連接:jq.qq.com/?_wv=1027&a…羣內提供免費的架構資料還有:Java工程化、高性能及分佈式、高性能、深刻淺出。高架構。性能調優、Spring,MyBatis,Netty源碼分析和大數據等多個知識點高級進階乾貨的免費直播講解 能夠進來一塊兒學習交流哦