曹工說Spring Boot原始碼(21)-- 為了讓大家理解Spring Aop利器ProxyFactory,我已經拼了
寫在前面的話
相關背景及資源:
曹工說Spring Boot原始碼(1)-- Bean Definition到底是什麼,附spring思維導圖分享
曹工說Spring Boot原始碼(2)-- Bean Definition到底是什麼,咱們對著介面,逐個方法講解
曹工說Spring Boot原始碼(3)-- 手動註冊Bean Definition不比遊戲好玩嗎,我們來試一下
曹工說Spring Boot原始碼(4)-- 我是怎麼自定義ApplicationContext,從json檔案讀取bean definition的?
曹工說Spring Boot原始碼(5)-- 怎麼從properties檔案讀取bean
曹工說Spring Boot原始碼(6)-- Spring怎麼從xml檔案裡解析bean的
曹工說Spring Boot原始碼(7)-- Spring解析xml檔案,到底從中得到了什麼(上)
曹工說Spring Boot原始碼(8)-- Spring解析xml檔案,到底從中得到了什麼(util名稱空間)
曹工說Spring Boot原始碼(9)-- Spring解析xml檔案,到底從中得到了什麼(context名稱空間上)
曹工說Spring Boot原始碼(10)-- Spring解析xml檔案,到底從中得到了什麼(context:annotation-config 解析)
曹工說Spring Boot原始碼(11)-- context:component-scan,你真的會用嗎(這次來說說它的奇技淫巧)
曹工說Spring Boot原始碼(12)-- Spring解析xml檔案,到底從中得到了什麼(context:component-scan完整解析)
曹工說Spring Boot原始碼(13)-- AspectJ的執行時織入(Load-Time-Weaving),基本內容是講清楚了(附原始碼)
曹工說Spring Boot原始碼(14)-- AspectJ的Load-Time-Weaving的兩種實現方式細細講解,以及怎麼和Spring Instrumentation整合
曹工說Spring Boot原始碼(15)-- Spring從xml檔案裡到底得到了什麼(context:load-time-weaver 完整解析)
曹工說Spring Boot原始碼(16)-- Spring從xml檔案裡到底得到了什麼(aop:config完整解析【上】)
曹工說Spring Boot原始碼(17)-- Spring從xml檔案裡到底得到了什麼(aop:config完整解析【中】)
曹工說Spring Boot原始碼(18)-- Spring AOP原始碼分析三部曲,終於快講完了 (aop:config完整解析【下】)
曹工說Spring Boot原始碼(19)-- Spring 帶給我們的工具利器,建立代理不用愁(ProxyFactory)
曹工說Spring Boot原始碼(20)-- 碼網恢恢,疏而不漏,如何記錄Spring RedisTemplate每次操作日誌
工程程式碼地址 思維導圖地址
工程結構圖:
概要
前面兩三篇,介紹了spring aop得以實現的利器:ProxyFactory。
ProxyFactory,全稱:org.springframework.aop.framework.ProxyFactory,spring帝國spring aop軍工廠boss,職責就是生產proxy,即,代理工廠。
通過下面幾行程式碼,就能生成一個代理物件,而且我們還加了了一個環繞通知:
@Test
public void createJdkDynamicProxyWithAdvisor() {
ProxyFactory proxyFactory = new ProxyFactory();
Performer performer = new Performer();
proxyFactory.setTarget(performer);
proxyFactory.addInterface(Perform.class);
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
advisor.setAdvice(new MethodInterceptor() {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Object result = invocation.proceed();
System.out.println("男孩唱完要行禮");
return result;
}
});
proxyFactory.addAdvisor(advisor);
Perform proxy = (Perform) proxyFactory.getProxy();
ProxyFactoryTest.log.info("proxy class:{}",proxy.getClass().getName());
proxy.sing();
}
輸出如下:
本講,我們來講講,背後的故事。
ProxyFactory如何構造
其一共有如下幾個過載的建構函式:
- 無參建構函式
- ProxyFactory(Object target) 指定target
- ProxyFactory(Class[] proxyInterfaces) 指定代理類要實現的介面
- ProxyFactory(Class proxyInterface, Interceptor interceptor) 指定代理類要實現的介面,以及一個切面
- ProxyFactory(Class proxyInterface, TargetSource targetSource) 指定代理類要實現的介面,以及一個targetSource,targetSource類似於一個targetFactory,通過其,間接獲取target
因為spring aop原始碼裡,預設就使用了的是無參建構函式,這裡我們也以無參建構函式來講解。
我們知道,建構函式呼叫時,如果這個類有父類,還得先呼叫父類的建構函式。恰巧,這個類就有父類:
其中,ProxyConfig沒有顯示定義的建構函式,所以只有預設的無參建構函式。所以,
會先呼叫ProxyConfig的無參建構函式;
呼叫AdvisedSupport的無參建構函式,如下:
/** * No-arg constructor for use as a JavaBean. */ public AdvisedSupport() { initMethodCache(); } /** * Initialize the method cache. */ private void initMethodCache() { this.methodCache = new ConcurrentHashMap<MethodCacheKey, List<Object>>(32); }
接下來,呼叫ProxyCreatorSupport的無參建構函式:
/** * Create a new ProxyCreatorSupport instance. */ public ProxyCreatorSupport() { this.aopProxyFactory = new DefaultAopProxyFactory(); }
呼叫ProxyFactory的無參建構函式
/** * Create a new ProxyFactory. */ public ProxyFactory() { }
其中,比較有的講的,主要是第三個步驟,即ProxyCreatorSupport的無參建構函式。
這一步呢,new了一個DefaultAopProxyFactory,不過,暫時還沒用到它。
構造完了,接下來,就是各種配置上場的時候了。
配置ProxyFactory
好歹這也是一響噹噹的工廠,但是吧,要生產啥呢?總得有個方向吧。你是一個ProxyFactory,代理工廠,你要代理誰?代理賣火車票,還是代理賣房呢?注意,這裡我說的是賣火車票,和賣房。
這說明啥,說明我屁股是坐在賣方的,是12306一方,是要賣房的一方。因為啥呢,因為我現在的target,是賣方,我是作為賣方的代表(即,代理)來出現的。
target很重要,這個直接決定了我們工廠的方向。比如,假設翻轉一下,代理買方。比如,現在中國人,有錢人很多,很多人就去國外買房,比如澳洲、日本、東南亞啥的,但是呢,你對當地不瞭解,所以,就催生了當地的一批華人,來幫助大陸中國人在那邊買房,此時,他們就是我們的代理。我們呢,就是他們的target。
ok,大家想必理解了,ProxyFactory要生產啥,主要還是要有個定位,看看屁股坐哪邊。所以,我們作為程式碼世界的王者,就要負責來定方向。
配置target
Performer performer = new Performer();
proxyFactory.setTarget(performer);
定了target,基本定了一半了。
當然,你也不可以不直接定target,定介面也行。
配置介面,即代理要具備的功能
proxyFactory.addInterface(Perform.class);
配置額外的切面(可選)
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
advisor.setAdvice(new MethodInterceptor() {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Object result = invocation.proceed();
Method method = invocation.getMethod();
if (method.getName().equals("sing")) {
System.out.println("男孩唱完要行禮");
}
return result;
}
});
proxyFactory.addAdvisor(advisor);
這個步驟是可選的,你也可以沒有切面,沒有的話,預設就是代理啥事都不幫你做,你讓他幫你分析房產,結果人只收錢不幹活。
我們這裡的切面,是在target唱歌完了之後,輸出一句話:要行禮。
額外的一些配置
當然了,作為一個齊備的工廠,還是要支援一些客戶的定製功能的。比如:
從ProxyConfig繼承來的一些方法
比如,有的客戶說,我要cglib建立代理,有的說,我要jdk。ok,這個就滿足你了。
再比如,isExposeProxy,這個可以把生成的代理通過一個api提供給你,你可以在target方法內,拿到代理物件。
從AdvisedSupport繼承來的功能
這個也簡單,基本就是我們前面那幾個配置的過載方法,增刪改查嘛。
從ProxyCreatorSupport繼承來的功能
這個嘛,基本就是擴充套件了一下,搞了點事件/監聽的機制,方便我們擴充套件。
ok,配也配好了,是不是該把代理物件給人家了。
根據配置,生成代理
我寫著寫著,發現這個東西,很像開一個煎餅店,比如根據客戶要求:要雞蛋、培根、雞排啥的(這個就是對應上面的配置部分);然後,這一步,我們作為店老闆,就開始去根據客戶的要求,煎煎餅!
Perform proxy = (Perform) proxyFactory.getProxy();
煎餅的過程如何,我們來看看:
public Object getProxy() {
return createAopProxy().getProxy();
}
是不是很簡單,其實,我們應該分為兩步來看:
public Object getProxy() {
/**
* AopProxy是一個介面,實現類有jdk動態代理、cglib兩種
*/
AopProxy aopProxy = createAopProxy();
return aopProxy.getProxy();
}
選擇客戶要求,選擇合適的煎鍋
這一步,就是對應:
/**
* AopProxy是一個介面,實現類有jdk動態代理、cglib兩種
*/
AopProxy aopProxy = createAopProxy();
因為我們這裡,AopProxy有兩種實現,要用哪一種,要根據之前的配置來,比如,指定了proxyTargetClass,那就是要用cglib;否則就用jdk 動態代理。
我們具體看看:
protected final synchronized AopProxy createAopProxy() {
if (!this.active) {
activate();
}
/**
* 其實這裡獲取的,就是之前建構函式時那個DefaultAopProxyFactory
*/
AopProxyFactory aopProxyFactory = getAopProxyFactory();
return aopProxyFactory.createAopProxy(this);
}
這裡,先獲取了AopProxyFactory,這裡呢,拿到的,就是之前我們建構函式時候那個。
/**
* Return the AopProxyFactory that this ProxyConfig uses.
*/
public AopProxyFactory getAopProxyFactory() {
return this.aopProxyFactory;
}
這裡拿到DefaultAopProxyFactory後,程式會呼叫其createAopProxy(this),且把當前物件都傳進去了,當前物件是誰?就是ProxyFactory代理工廠本廠。
具體的建立程式碼如下:
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
if (targetClass.isInterface()) {
return new JdkDynamicAopProxy(config);
}
return CglibProxyFactory.createCglibProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
注意看最上面的if判斷:
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config))
是不是,如果isProxyTargetClass為true,或者hasNoUserSuppliedProxyInterfaces,按裡面理解,沒有提供介面,則會走下面的邏輯,去用cglib建立代理。
因為我們這裡是提供了介面的,所以,會new一個:jdk的動態代理。
public JdkDynamicAopProxy(AdvisedSupport config) throws AopConfigException {
Assert.notNull(config, "AdvisedSupport must not be null");
if (config.getAdvisors().length == 0 && config.getTargetSource() == AdvisedSupport.EMPTY_TARGET_SOURCE) {
throw new AopConfigException("No advisors and no TargetSource specified");
}
this.advised = config;
}
這裡可以看到,建構函式很簡單,就是把代理工廠本廠的引用傳給他了。我們前面配了那麼多東西在ProxyFactory上,怎麼能說給人就給人?
廢話,不給JdkDynamicAopProxy,它怎麼建立代理呢?
JdkDynamicAopProxy揭祕
這個類,我直接給大家說,其實現了兩個介面:
代理介面:AopProxy
public interface AopProxy { /** * Create a new proxy object. * <p>Uses the AopProxy's default class loader (if necessary for proxy creation): * usually, the thread context class loader. * @return the new proxy object (never {@code null}) * @see Thread#getContextClassLoader() */ Object getProxy(); }
這個介面就是獲取代理物件。
java.lang.reflect.InvocationHandler介面
這個介面,熟悉jdk動態代理的就知道,攔截的邏輯就寫在這裡面。我們大概可以猜測,代理物件呼叫方法時,就會被攔截到這個方法裡面來處理。
生成代理物件
前面,我們已經講解了這一步了:
馬上就要呼叫getProxy來生成代理物件。
org.springframework.aop.framework.JdkDynamicAopProxy#getProxy(java.lang.ClassLoader)
public Object getProxy(ClassLoader classLoader) {
if (logger.isDebugEnabled()) {
logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
}
Class[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised);
findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
這裡就簡單的幾步:
獲取要代理的全部介面
Class[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised);
實際上,大家記得,我們前面只配了一個要代理的介面,但這個方法內部,還會給我們加上兩個介面。
org.springframework.aop.SpringProxy
這個是marker介面,空的,不用管,只是做個標記,框架會用到
org.springframework.aop.framework.Advised
這個介面,功能比較全,還是一些增刪改查的操作,物件吧,是那些切面、target啥的,這可以讓我們動態地修改生成的代理物件。
呼叫jdk方法,生成代理
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
這裡沒啥說的,唯一就是,第三個引數,傳了個this,這裡的this,就是JdkDynamicAopProxy它自己。前面我們也說了,它自己實現了java.lang.reflect.InvocationHandler。
呼叫代理物件的方法時,如何作用
我們再想想代理的作用,不就是幫我們乾點事嗎?那要怎麼幫我們target幹事呢?
注意,當我們拿到ProxyFactory的getProxy返回的物件時,其型別已經有點奇怪,你看上圖,它的型別是$Proxy5.
這是jdk動態生成的class。
所以,我們呼叫,實際上是在代理物件上進行呼叫,對他們進行呼叫,實際的邏輯會被跳轉到之前生成代理時,傳進去的那個invocationHandler物件的invoke裡面去。
這個頁面,熟悉吧,不用我多說了,但凡大家在service層加了事務,debug時,進去的就是這個地方。
方法的核心邏輯,大概如下:
Object retVal;
// 1. May be null. Get as late as possible to minimize the time we "own" the target,
// in case it comes from a pool.
target = targetSource.getTarget();
if (target != null) {
targetClass = target.getClass();
}
// 2. Get the interception chain for this method.
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
// Check whether we have any advice. If we don't, we can fallback on direct
// reflective invocation of the target, and avoid creating a MethodInvocation.
if (chain.isEmpty()) {
// 3. We can skip creating a MethodInvocation: just invoke the target directly
// Note that the final invoker must be an InvokerInterceptor so we know it does
// nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);
}
else {
// 4. We need to create a method invocation...
invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
// Proceed to the joinpoint through the interceptor chain.
retVal = invocation.proceed();
}
上面的程式碼,我標了號。
第一處,是獲取target,根據之前配置的targetSource來獲取,忘了的可以再翻一下;
第二處,根據當前要執行的method和class,判斷哪些切面(其實就是代理要幫我們做的事)是匹配的
第三處,如果切面集合為null,說明代理啥事不幹,所以只能直接呼叫target了
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);
第四處,如果切面不為null,說明代理有事要做,這裡就封裝了一個invocation,來呼叫切面集合。
這裡面有兩點要講,第二處和第四處。
第二處,獲取匹配的切面時,核心邏輯是,把切面裡的切點,和目標類、目標方法一一匹配,都匹配,就算;否則不算。
public List<Object> getInterceptorsAndDynamicInterceptionAdvice( Advised config, Method method, Class targetClass) { // This is somewhat tricky... we have to process introductions first, // but we need to preserve order in the ultimate list. List<Object> interceptorList = new ArrayList<Object>(config.getAdvisors().length); boolean hasIntroductions = hasMatchingIntroductions(config, targetClass); AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance(); // 講解點1 for (Advisor advisor : config.getAdvisors()) { if (advisor instanceof PointcutAdvisor) { // Add it conditionally. PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor; // 講解點2 if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(targetClass)) { MethodInterceptor[] interceptors = registry.getInterceptors(advisor); MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher(); // 講解點3 if (MethodMatchers.matches(mm, method, targetClass, hasIntroductions)) { interceptorList.addAll(Arrays.asList(interceptors)); } } } else { Interceptor[] interceptors = registry.getInterceptors(advisor); interceptorList.addAll(Arrays.asList(interceptors)); } } return interceptorList; }
這裡就三個講解點,
- 1是遍歷全部的切面
- 2是拿出切面中的切點的classMatcher,看看目標class是否匹配
- 3是拿出切面中的切點的methodMatcher,看看目標方法是否匹配
實際呼叫切面和目標方法
這裡用到了責任鏈模式,遞迴執行;其實也可以直接for迴圈的。
這裡new了一個ReflectiveMethodInvocation,這個其實就是一個wrapper,包裹了所有必要的引數,可以理解為大雜燴,主要是封裝一下,程式碼不那麼亂。
protected ReflectiveMethodInvocation( Object proxy, Object target, Method method, Object[] arguments, Class targetClass, List<Object> interceptorsAndDynamicMethodMatchers) { this.proxy = proxy; this.target = target; this.targetClass = targetClass; this.method = BridgeMethodResolver.findBridgedMethod(method); this.arguments = arguments; this.interceptorsAndDynamicMethodMatchers = interceptorsAndDynamicMethodMatchers; }
看,是不是,這裡面啥都有了,代理物件、目標物件、目標class,目標方法,方法引數,切面集合。
同時,這裡面還有個隱含的陣列下標:
/** * Index from 0 of the current interceptor we're invoking. * -1 until we invoke: then the current interceptor. */ private int currentInterceptorIndex = -1;
這玩意主要是用來遍歷切面集合的。
好了,接下來說下面這處:
else { // 這一步已經講解完了,拿到了ReflectiveMethodInvocation 物件 invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain); // Proceed to the joinpoint through the interceptor chain. retVal = invocation.proceed(); }
接下來,我們看看invocation.proceed();
public Object proceed() throws Throwable { //講解點1: if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) { return invokeJoinpoint(); } Object interceptorOrInterceptionAdvice = this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex); // 講解點2: if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) { // Evaluate dynamic method matcher here: static part will already have // been evaluated and found to match. InterceptorAndDynamicMethodMatcher dm = (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice; if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) { return dm.interceptor.invoke(this); } else { // Dynamic matching failed. // Skip this interceptor and invoke the next in the chain. return proceed(); } } else { // 講解點3: // It's an interceptor, so we just invoke it: The pointcut will have // been evaluated statically before this object was constructed. return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this); } }
- 講解點1:一開始進來的時候,之前說的那個下標為-1;判斷是否已經是切面集合的最後一個,我們這裡剛開始,所以會走到下面
- 講解點2:一般都不會走到這個分支裡,會直接跳到講解點3. 因為我們spring aop,一般都是基於方法的切面嘛。
- 講解點3:這裡,呼叫切面的invoke方法,傳進去了this。this是啥?就是ReflectiveMethodInvocation自己。
所以,大家看上圖就知道了,這裡形成了遞迴呼叫。
我思考了一下,之所以遞迴呼叫,而不是for迴圈,主要是要保證target的method先執行,執行完了,才能到我們這裡的切面來執行。
這樣邏輯才對。
當這裡遞迴呼叫進去時,因為我們只有一個切面,所以就開始執行連線點:
待到連線點執行完了後,會繼續執行我們切面的後續邏輯。
這就是和tomcat filter鏈類似的責任鏈模式。
總結
aop這塊,基本的東西是差不多了,大家有問題及時聯絡我