Spring中AOP相關的API及源碼解析

Spring中AOP相關的API及源碼解析html

本系列文章:java

讀源碼,咱們能夠從第一行讀起程序員

你知道Spring是怎麼解析配置類的嗎?web

配置類爲何要添加@Configuration註解?spring

談談Spring中的對象跟Bean,你知道Spring怎麼建立對象的嗎?微信

這篇文章,咱們來談一談Spring中的屬性注入 app

推薦閱讀:編輯器

Spring官網閱讀 | 總結篇ide

Spring雜談函數

本系列文章將會帶你一行行的將Spring的源碼吃透,推薦閱讀的文章是閱讀源碼的基礎!

由於本文會涉及到動態代理的相關內容,若是對動態代理不是很瞭解的話,參考文章:

動態代理學習(一)本身動手模擬JDK動態代理

動態代理學習(二)JDK動態代理源碼分析

前言

之因此寫這麼一篇文章主要是由於下篇文章將結束Spring啓動整個流程的分析,從解析配置到建立對象再到屬性注入最後再將建立好的對象初始化成爲一個真正意義上的Bean。由於下篇文章會設計到AOP,因此提早單獨將AOP的相關API及源碼作一次解讀,這樣能夠下降閱讀源碼的障礙,話很少說,咱們進入正文!

一個使用API建立代理的例子

在進入API分析前,咱們先經過兩個例子體會下如何使用API的方式來建立一個代理對象,對應示例以下:

  1. 定義通知
public class DmzAfterReturnAdvice implements AfterReturningAdvice {
 @Override  public void afterReturning(@Nullable Object returnValue, Method method, Object[] args, @Nullable Object target) throws Throwable {  System.out.println("after invoke method [" + method.getName() + "],aop afterReturning logic invoked");  } }  public class DmzAroundAdvice implements MethodInterceptor {  @Override  public Object invoke(MethodInvocation invocation) throws Throwable {  System.out.println("aroundAdvice invoked");  return invocation.proceed();  } }  public class DmzBeforeAdvice implements MethodBeforeAdvice {  @Override  public void before(Method method, Object[] args, Object target) throws Throwable {  System.out.println("before invoke method [" + method.getName() + "],aop before logic invoked");  } }  public class DmzIntroductionAdvice extends DelegatingIntroductionInterceptor implements Runnable {  @Override  public void run() {  System.out.println("running!!!!");  } } 複製代碼
  1. 切點
public class DmzPointcut implements Pointcut {
 @Override  @NonNull  public ClassFilter getClassFilter() {  // 在類級別上不進行攔截  return ClassFilter.TRUE;  }   @Override  @NonNull  public MethodMatcher getMethodMatcher() {  return new StaticMethodMatcherPointcut() {  @Override  public boolean matches(@NonNull Method method, Class<?> targetClass) {  // 對於toString方法不進行攔截  return !method.getName().equals("toString");  }  };  } } 複製代碼
  1. 目標類
public class DmzService {
 @Override  public String toString() {  System.out.println("dmzService toString invoke");  return "dmzService";  }   public void testAop(){  System.out.println("testAop invoke");  } } 複製代碼
  1. 測試代碼
public class Main {
 public static void main(String[] args) {   ProxyFactory proxyFactory = new ProxyFactory();   // 一個Advisor表明的是一個已經跟指定切點綁定了的通知  // 在這個例子中意味着環繞通知不會做用到toString方法上  Advisor advisor = new DefaultPointcutAdvisor(new DmzPointcut(), new DmzAroundAdvice());   // 添加一個綁定了指定切點的環繞通知  proxyFactory.addAdvisor(advisor);   // 添加一個返回後的通知  proxyFactory.addAdvice(new DmzAfterReturnAdvice());   // 添加一個方法執行前的通知  proxyFactory.addAdvice(new DmzBeforeAdvice());   // 爲代理類引入一個新的須要實現的接口--Runnable  proxyFactory.addAdvice(new DmzIntroductionAdvice());   // 設置目標類  proxyFactory.setTarget(new DmzService());   // 由於要測試代理對象本身定義的方法,因此這裏啓用cglib代理  proxyFactory.setProxyTargetClass(true);   // 建立代理對象  Object proxy = proxyFactory.getProxy();   // 調用代理類的toString方法,經過控制檯查看代理邏輯的執行狀況  proxy.toString();   if (proxy instanceof DmzService) {  ((DmzService) proxy).testAop();  }   // 判斷引入是否成功,並執行引入的邏輯  if (proxy instanceof Runnable) {  ((Runnable) proxy).run();  }  } }  複製代碼

這裏我就不將測試結果放出來了,你們能夠先自行思考這段程序將輸出什麼。接下來咱們就來分析上面這段程序中所涉及到的API,經過這些API的學習相信你們能夠完全理解上面這段代碼。

API介紹

Pointcut(切點)

對應接口定義以下:

public interface Pointcut {
  // ClassFilter,在類級別進行過濾  ClassFilter getClassFilter();   // MethodMatcher,在方法級別進行過濾  MethodMatcher getMethodMatcher();   // 一個單例對象,默認匹配全部  Pointcut TRUE = TruePointcut.INSTANCE;  } 複製代碼

切點的主要做用是定義通知所要應用到的類跟方法,上面的接口定義也很明顯的體現了這一點,咱們能夠將其拆分紅爲兩個部分

  • ClassFilter,接口定義以下:
public interface ClassFilter {
  boolean matches(Class<?> clazz);   ClassFilter TRUE = TrueClassFilter.INSTANCE;  } 複製代碼

ClassFilter的主要做用是在類級別上對通知的應用進行一次過濾,若是它的match方法對任意的類都返回true的話,說明在類級別上咱們不須要過濾,這種狀況下,通知的應用,就徹底依賴MethodMatcher的匹配結果。

  • MethodMatcher,接口定義以下:
public interface MethodMatcher {
  boolean matches(Method method, @Nullable Class<?> targetClass);   boolean isRuntime();   boolean matches(Method method, @Nullable Class<?> targetClass, Object... args);   MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;  } 複製代碼

MethodMatcher中一共有三個核心方法

  • matches(Method method, @Nullable Class<?> targetClass),這個方法用來判斷當前定義的切點跟目標類中的指定方法是否匹配,它能夠在建立代理的時候就被調用,從而決定是否須要進行代理,這樣就能夠避免每次方法執行的時候再去作判斷
  • isRuntime(),若是這個方法返回true的話,意味着每次執行方法時還須要作一次匹配
  • matches(Method method, @Nullable Class<?> targetClass, Object... args),當以前的 isRuntime方法返回true時,會調用這個方法再次進行一次判斷,返回false的話,意味這個不對這個方法應用通知

Advice(通知)

環繞通知(Interception Around Advice)

接口定義以下:

public interface MethodInterceptor extends Interceptor {
  Object invoke(MethodInvocation invocation) throws Throwable; } 複製代碼

在上面接口定義的invoke方法中,MethodInvocation就是當前執行的方法,當咱們調用invocation.proceed就是在執行當前的這個方法,基於此,咱們能夠在方法的執行先後去插入咱們自定義的邏輯,好比下面這樣

// 執行前的邏輯
doSomeThingBefore(); Object var = invocation.proceed; doSomeThingAfter(); // 執行後的邏輯 retrun var; 複製代碼

前置通知(Before Advice)

public interface MethodBeforeAdvice extends BeforeAdvice {
  void before(Method m, Object[] args, Object target) throws Throwable; } 複製代碼

跟環繞通知不一樣的是,這個接口中定義的方法的返回值是void,因此前置通知是沒法修改方法的返回值的。

若是在前置通知中發生了異常,那麼會直接終止目標方法的執行以及打斷整個攔截器鏈的執行

後置通知(After Returning Advice)

public interface AfterReturningAdvice extends Advice {
  void afterReturning(Object returnValue, Method m, Object[] args, Object target)  throws Throwable; } 複製代碼

後置通知相比較於前置通知,主要有如下幾點不一樣

  • 後置通知能夠訪問目標方法的返回值,可是不能修改
  • 後置通知是在方法執行完成後執行

異常通知(Throws Advice)

public interface ThrowsAdvice extends AfterAdvice {
 } 複製代碼

異常通知中沒有定義任何方法,它更像一個標記接口。咱們在定義異常通知時須要實現這個接口,同時方法的簽名也有要求

  1. 方法名稱必須是 afterThrowing
  2. 方法的參數個數必須是1個或者4個,以下:
public class OneParamThrowsAdvice implements ThrowsAdvice {
  // 若是隻有一個參數,那麼這個參數必須是要進行處理的異常  public void afterThrowing(RemoteException ex) throws Throwable {  // Do something with remote exception  } }  public class FourParamThrowsAdvice implements ThrowsAdvice {   // 若是定義了四個參數,那麼這四個參數分別是  // 1.m:目標方法  // 2.args:執行目標方法所須要的參數  // 3.target:目標對象  // 4.ex:具體要處理的異常  // 而且參數類型必須按照這個順序定義  public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {  // Do something with all arguments  } } 複製代碼

咱們能夠在一個異常通知中定義多個方法,在後續的源碼分析中咱們會發現,這些方法最終會被註冊成對應的異常的handler,像下面這樣

public static class CombinedThrowsAdvice implements ThrowsAdvice {
  public void afterThrowing(RemoteException ex) throws Throwable {  // Do something with remote exception  }   public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {  // Do something with all arguments  } } 複製代碼

引入通知(Introduction Advice)

引入通知的主要做用是可讓生成的代理類實現額外的接口。例如在上面的例子中,咱們爲DmzService建立一個代理對象,同時爲其定義了一個引入通知

public class DmzIntroductionAdvice extends DelegatingIntroductionInterceptor implements Runnable {
 @Override  public void run() {  System.out.println("running!!!!");  } } 複製代碼

在這個引入通知中,咱們爲其引入了一個新的須要實現的接口Runnable,同時通知自己做爲這個接口的實現類。

經過這個引入通知,咱們能夠將生成的代理類強轉成Runnable類型而後執行其run方法,同時,run方法也會被前面定義的前置通知,後置通知等攔截。

爲了更好的瞭解引入通知,咱們來須要瞭解下DelegatingIntroductionInterceptor這個類。見名知意,這個類就是一個委託引入攔截器,由於咱們要爲代理類引入新的接口,由於着咱們要提供具體的實現的邏輯,而具體的實現的邏輯就能夠被委託給這個DelegatingIntroductionInterceptor

咱們能夠看看它的源碼

public class DelegatingIntroductionInterceptor extends IntroductionInfoSupport  implements IntroductionInterceptor {   // 實際實現了引入邏輯的類  @Nullable  private Object delegate;   // 對外提供了一個帶參的構造函數,經過這個構造函數咱們能夠傳入一個  // 具體的實現類  public DelegatingIntroductionInterceptor(Object delegate) {  init(delegate);  }  // 對子類暴露了一個空參的構造函數,默認將自身做爲實現了引入邏輯的委託類  // 咱們上面的例子中就是使用的這種方法  protected DelegatingIntroductionInterceptor() {  init(this);  }   // 對這個類進行初始化,要經過實際的實現類來找到具體要實現的接口  private void init(Object delegate) {  Assert.notNull(delegate, "Delegate must not be null");  this.delegate = delegate;   // 找到delegate全部實現的接口  implementInterfacesOnObject(delegate);   // 由於咱們可能會將DelegatingIntroductionInterceptor自己做爲委託者  // Spring的設計就是不對外暴露這兩個接口  // 若是將其暴露,意味着咱們能夠將代理類強轉成這種類型  suppressInterface(IntroductionInterceptor.class);  suppressInterface(DynamicIntroductionAdvice.class);  }   // 引入通知自己也是基於攔截器實現的,當執行一個方法時須要判斷這個方法  // 是否是被引入的接口中定義的方法,若是是的話,那麼不能調用目標類的方法  // 而要調用委託類的方法  public Object invoke(MethodInvocation mi) throws Throwable {  if (isMethodOnIntroducedInterface(mi)) {  Object retVal = AopUtils.invokeJoinpointUsingReflection(this.delegate, mi.getMethod(), mi.getArguments());  // 這裏是處理一種特殊狀況,方法的返回值是this的時候  // 這裏應該返回代理類  if (retVal == this.delegate && mi instanceof ProxyMethodInvocation) {  Object proxy = ((ProxyMethodInvocation) mi).getProxy();  if (mi.getMethod().getReturnType().isInstance(proxy)) {  retVal = proxy;  }  }  // 其他狀況下直接將委託類的執行結果返回  return retVal;  }  // 執行到這裏說明不是引入的方法,這是Spring提供了一個擴展邏輯  // 正常來講這個類只會處理引入的邏輯,經過這個方法能夠對目標類中的方法作攔截  // 不經常使用  return doProceed(mi);  }   protected Object doProceed(MethodInvocation mi) throws Throwable {  return mi.proceed();  }  } 複製代碼

經過查看這個類的源碼咱們能夠發現,所謂的引入其實就是在方法執行的時候加了一層攔截,當判斷這個方法是被引入的接口提供的方法的時候,那麼就執行委託類中的邏輯而不是目標類中的方法

關於通知的總結

經過上文的分析咱們能夠發現,通知總共能夠分爲這麼幾類

  1. 普通的通知(前置,後置,異常等,沒有實現 MethodInterceptor接口)
  2. 環繞通知(實現了 MethodInterceptor接口)
  3. 引入通知(須要提供額外的引入的信息,實現了 MethodInterceptor接口)

上面的分類並不標準,只是爲了方便你們記憶跟理解,雖然咱們普通的通知沒有直接實現MethodInterceptor接口,但其實它的底層也是依賴於攔截器來完成的,你們能夠看看下面這個類

class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable {
  @Override  public boolean supportsAdvice(Advice advice) {  return (advice instanceof MethodBeforeAdvice);  }   // 根據傳入的一個前置通知,建立一個對應的攔截器  @Override  public MethodInterceptor getInterceptor(Advisor advisor) {  MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice();  return new MethodBeforeAdviceInterceptor(advice);  }  }  public class MethodBeforeAdviceInterceptor implements MethodInterceptor, BeforeAdvice, Serializable {   private final MethodBeforeAdvice advice;   public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) {  Assert.notNull(advice, "Advice must not be null");  this.advice = advice;  }   // 實際上仍是利用攔截器,在方法執行前調用了通知的before方法完成了前置通知  @Override  public Object invoke(MethodInvocation mi) throws Throwable {  this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());  return mi.proceed();  }  } 複製代碼

Advisor (綁定通知跟切點)

一個Advisor實際上就是一個綁定在指定切點上的通知。在前面的例子咱們能夠發現,有兩種添加通知的方式

// 一個Advisor表明的是一個已經跟指定切點綁定了的通知
// 在這個例子中意味着環繞通知不會做用到toString方法上 Advisor advisor = new DefaultPointcutAdvisor(new DmzPointcut(), new DmzAroundAdvice());  // 添加一個綁定了指定切點的環繞通知 proxyFactory.addAdvisor(advisor);  // 添加一個返回後的通知 proxyFactory.addAdvice(new DmzAfterReturnAdvice()); 複製代碼

一種是直接添加了一個Advisor,還有一種是添加一個Advice,後者也會被轉換成一個Advisor而後再進行添加,沒有指定切點的通知是沒有任何意義的

public void addAdvice(Advice advice) throws AopConfigException {
 int pos = this.advisors.size();  // 默認添加到集合的最後一個位置  addAdvice(pos, advice); }  // 這個方法添加通知 public void addAdvice(int pos, Advice advice) throws AopConfigException {  Assert.notNull(advice, "Advice must not be null");   // 若是是一個引入通知,那麼構建一個DefaultIntroductionAdvisor  // DefaultIntroductionAdvisor會匹配全部類  if (advice instanceof IntroductionInfo) {  addAdvisor(pos, new DefaultIntroductionAdvisor(advice, (IntroductionInfo) advice));  }  // 不能直接添加一個不是IntroductionInfo的DynamicIntroductionAdvice(動態引入通知)  else if (advice instanceof DynamicIntroductionAdvice) {  throw new AopConfigException("DynamicIntroductionAdvice may only be added as part of IntroductionAdvisor");  }  else {  // 若是是普通的通知,那麼會建立一個DefaultPointcutAdvisor  // DefaultPointcutAdvisor所定義的切點會匹配全部類以及全部方法  addAdvisor(pos, new DefaultPointcutAdvisor(advice));  } } 複製代碼

ProxyCreatorSupport

這個類的主要做用是爲建立一個AOP代理對象提供一些功能支持,經過它的getAopProxyFactory能獲取一個建立代理對象的工廠。

// 這裏我只保留了這個類中的關鍵代碼
public class ProxyCreatorSupport extends AdvisedSupport {   private AopProxyFactory aopProxyFactory;   // 空參構造,默認會建立一個DefaultAopProxyFactory  // 經過這個ProxyFactory能夠建立一個cglib代理或者jdk代理  public ProxyCreatorSupport() {  this.aopProxyFactory = new DefaultAopProxyFactory();  }   // 經過這個方法能夠建立一個具體的代理對象  protected final synchronized AopProxy createAopProxy() {  if (!this.active) {  activate();  }  // 實際就是使用DefaultAopProxyFactory來建立一個代理對象  // 能夠看到在調用createAopProxy方法時,傳入的參數是this  // 這是由於ProxyCreatorSupport自己就保存了建立整個代理對象所須要的配置信息  return getAopProxyFactory().createAopProxy(this);  } } 複製代碼
image-20200701231849588
image-20200701231849588

另外經過上面的UML類圖還能看到,ProxyCreatorSupport繼承了AdvisedSupportAdvisedSupport繼承了ProxyConfig

ProxyConfig

其中ProxyConfig是全部的AOP代理工廠的父類,它包含了建立一個AOP代理所須要的基礎的通用的一些配置信息

// 這裏省略了一些getter跟setter方法
public class ProxyConfig implements Serializable {   // 是否開啓cglib代理,默認不開啓使用jdk動態代理  private boolean proxyTargetClass = false;   // 是否啓用優化,默認爲false,按照官網對這個參數的解釋  // 這個優化是針對cglib,若是設計爲true的話,會作一些侵入性的優化  // 是否開啓在jdk代理的狀況下沒有影響  // 官網中特意說明了,除非對cglib的優化很是瞭解,不然不要開啓這個參數  private boolean optimize = false;   // 生成的代理類是否須要實現Advised接口,這個接口能夠向外提供操做通知的方法  // 若是爲false會實現  // 爲true的話,不會實現  boolean opaque = false;   // 是否將當前的配置類暴露到一個線程上下文中,若是設置爲true的話  // 能夠經過AopContext.currentProxy()來獲取到當前的代理對象  boolean exposeProxy = false;   // 標誌着是否凍結整個配置,若是凍結了,那麼配置信息將不容許修改  private boolean frozen = false; } 複製代碼

AdvisedSupport

當咱們爲某個對象建立代理時,除了須要上面的ProxyConfig提供的一些基礎配置外,起碼還須要知道

  1. 須要執行的通知是哪些?
  2. 目標對象是誰?
  3. 建立出來的代理須要實現哪些接口?

而這些配置信息是由AdvisedSupport提供的,AdvisedSupport自己實現了Advised接口,Advised接口定義了管理通知的方法。


在瞭解了上面的API後咱們來看看Spring提供了幾種建立AOP代理的方式

  1. ProxyFactoryBean
  2. ProxyFactory
  3. Auto-proxy

ProxyFactoryBean的方式建立AOP代理

使用示例

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">   <bean class="com.dmz.spring.initalize.service.DmzService" name="dmzService"/>   <bean id="aroundAdvice" class="com.dmz.spring.initalize.aop.advice.DmzAroundAdvice"/>   <bean id="dmzProxy"  class="org.springframework.aop.framework.ProxyFactoryBean">  <!---->  <property name="proxyInterfaces" value="java.lang.Runnable"/>  <property name="proxyTargetClass" value="true"/>  <property name="target" ref="dmzService"/>  <property name="interceptorNames">  <list>  <value>aroundAdvice</value>  </list>  </property>  </bean>  </beans> 複製代碼
// 目標類
public class DmzService {  @Override  public String toString() {  System.out.println("dmzService toString invoke");  return "dmzService";  }   public void testAop(){  System.out.println("testAop invoke");  } }  // 通知 public class DmzAroundAdvice implements MethodInterceptor {  @Override  public Object invoke(MethodInvocation invocation) throws Throwable {  System.out.println("aroundAdvice invoked");  return invocation.proceed();  } }  public class SourceMain {  public static void main(String[] args) {  ClassPathXmlApplicationContext cc =  new ClassPathXmlApplicationContext("application-init.xml");  DmzService dmzProxy = ((DmzService) cc.getBean("dmzProxy"));  dmzProxy.testAop();  } } 複製代碼

ProxyFactoryBean介紹

跟普通的FactoryBean同樣,這個類的主要做用就是經過getObject方法可以獲取一個Bean,不一樣的是這個類獲取到的是代理後的Bean。

咱們查看這個類的繼承關係能夠發現

image-20200701181049929
image-20200701181049929

這個類除了實現了FactoryBean接口以及一些Aware接口外,額外還繼承了ProxyCreatorSupport類。它是一個factoryBean,因此咱們重點就關注它的getObject方法便可。

public Object getObject() throws BeansException {
 // 初始化通知鏈  // 這裏主要就是將在XML中配置的通知添加到  // AdvisedSupport管理的配置中去  initializeAdvisorChain();  if (isSingleton()) {  // 若是是單例的,那麼獲取一個單例的代理對象  return getSingletonInstance();  }  else {  if (this.targetName == null) {  logger.warn("Using non-singleton proxies with singleton targets is often undesirable. " +  "Enable prototype proxies by setting the 'targetName' property.");  }  // 若是是原型的,獲取一個原型的代理對象  return newPrototypeInstance();  } } 複製代碼

關於這段代碼就不作過多分析了,它其實就兩步(不論是哪一種方式建立代理,都分爲這兩步)

  1. 完善建立代理須要的配置信息
  2. 建立代理

其中配置信息分爲兩部分,其一是AppConfig管理的通用的配置信息,其二是AdvisedSupport管理的通知信息。通用的配置信息咱們能夠直接在XML中配置,例如在上面的例子中咱們就配置了proxyTargetClass屬性,而通知信息即便咱們在XML中配置了也還須要作一層轉換,在前面咱們也提到過了,全部的Advice都會被轉換成Advisor添加到配置信息中。

ProxyFactory的方式建立AOP代理

使用示例(略,見開頭)

ProxyFactory介紹

image-20200702084419039
image-20200702084419039

從上面咱們能夠看出,ProxyFactory也繼承自ProxyCreatorSupport,從以前的例子咱們也能感覺到,使用它的API來建立一個代理對象也是要先去設置相關的配置信息,最後再調用建立代理的方法

咱們以後要分析的自動代理內部就是經過建立了一個ProxyFactory來獲取代理對象的。

咱們能夠對比下ProxyFactoryBeanProxyFactory在建立代理對象時的代碼

  • ProxyFactory
public Object getProxy() {
 // 調用了ProxyCreatorSupport的createAopProxy()方法建立一個AopProxy對象  // 而後調用AopProxy對象的getProxy方法  return createAopProxy().getProxy(); } 複製代碼
  • ProxyFactoryBean
private synchronized Object getSingletonInstance() {
 if (this.singletonInstance == null) {  this.targetSource = freshTargetSource();  if (this.autodetectInterfaces && getProxiedInterfaces().length == 0 && !isProxyTargetClass()) {  Class<?> targetClass = getTargetClass();  if (targetClass == null) {  throw new FactoryBeanNotInitializedException("Cannot determine target class for proxy");  }  setInterfaces(ClassUtils.getAllInterfacesForClass(targetClass, this.proxyClassLoader));  }  super.setFrozen(this.freezeProxy);  // 重點就看這裏  // 這裏調用了ProxyCreatorSupport的createAopProxy()方法建立一個AopProxy對象  // 而getProxy方法就是調用建立的AopProxy的getProxy方法  this.singletonInstance = getProxy(createAopProxy());  }  return this.singletonInstance; }  protected Object getProxy(AopProxy aopProxy) {  return aopProxy.getProxy(this.proxyClassLoader); } 複製代碼

綜上,咱們能夠得出結論,不論是經過哪一種方式建立AOP代理,核心代碼就一句

createAopProxy().getProxy()
複製代碼

這句代碼也是咱們接下來源碼分析的重點

Auto-proxy(實現自動AOP代理)

自動代理機制的實現其實很簡單,就是經過Bean的後置處理器,在建立Bean的最後一步對Bean進行代理,並將代理對象放入到容器中。

實現自動代理的核心類就是AbstractAutoProxyCreator。咱們來看看它的繼承關係

image-20200702103750263
image-20200702103750263

爲了更好的體會自動代理的做用,咱們對它的三個具體的實現類來進行分析,分別是

  1. BeanNameAutoProxyCreator
  2. DefaultAdvisorAutoProxyCreator
  3. AnnotationAwareAspectJAutoProxyCreator

BeanNameAutoProxyCreator

使用示例

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">   <bean class="com.dmz.spring.initalize.service.DmzService" name="dmzService"/>   <bean id="aroundAdvice" class="com.dmz.spring.initalize.aop.advice.DmzAroundAdvice"/>   <bean id="beforeAdvice" class="com.dmz.spring.initalize.aop.advice.DmzBeforeAdvice"/>   <!--使用很簡單,只要配置一個BeanNameAutoProxyCreator便可-->  <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator" name="autoProxyCreator">  <!--使用cglib代理-->  <property name="proxyTargetClass" value="true"/>  <!--對全部以dmz開頭的bean進行自動代理-->  <property name="beanNames" value="dmz*"/>  <!--添加兩個通知-->  <property name="interceptorNames">  <list>  <value>beforeAdvice</value>  <value>aroundAdvice</value>  </list>  </property>  </bean>  </beans> 複製代碼
public class SourceMain {
 public static void main(String[] args) {  ClassPathXmlApplicationContext cc =  new ClassPathXmlApplicationContext("application-init.xml");  DmzService dmzProxy = ((DmzService) cc.getBean("dmzService"));  dmzProxy.testAop();  } } // 程序打印: // before invoke method [testAop],aop before logic invoked // aroundAdvice invoked // testAop invoke 複製代碼

DefaultAdvisorAutoProxyCreator

使用示例

在上面例子的基礎上咱們要修改配置文件,以下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">   <bean class="com.dmz.spring.initalize.service.DmzService" name="dmzService"/>   <bean id="aroundAdvice" class="com.dmz.spring.initalize.aop.advice.DmzAroundAdvice"/>   <bean id="beforeAdvice" class="com.dmz.spring.initalize.aop.advice.DmzBeforeAdvice"/>   <bean class="org.springframework.aop.support.DefaultPointcutAdvisor" id="dmzBeforeAdvisor">  <property name="advice" ref="beforeAdvice"/>  </bean>   <bean class="org.springframework.aop.support.DefaultPointcutAdvisor" id="dmzAroundAdvisor">  <property name="advice" ref="aroundAdvice"/>  </bean>   <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"  id="advisorAutoProxyCreator">  <!--這兩個參數標明瞭咱們要使用全部以dmz開頭的Advisor類型的通知  這裏必須配置是Advisor,不能是Advice或者interceptor,  能夠看到DefaultAdvisorAutoProxyCreator跟BeanNameAutoProxyCreator的區別在於  BeanNameAutoProxyCreator須要指定要被代理的bean的名稱,  而DefaultAdvisorAutoProxyCreator不須要,它會根據咱們傳入的Advisor  獲取到須要被代理的切點  -->  <property name="usePrefix" value="true"/>  <property name="advisorBeanNamePrefix" value="dmz"/>   <property name="proxyTargetClass" value="true"/>  </bean> </beans> 複製代碼

測試代碼就不放了,你們能夠自行測試,確定是沒問題的

AnnotationAwareAspectJAutoProxyCreator

咱們正常在使用AOP的時候都會在配置類上添加一個@EnableAspectJAutoProxy註解,這個註解幹了什麼事呢?

實際就是向容器中註冊了一個AnnotationAwareAspectJAutoProxyCreator

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME) @Documented // 這裏導入了一個類 @Import(AspectJAutoProxyRegistrar.class) public @interface EnableAspectJAutoProxy {   boolean proxyTargetClass() default false;   boolean exposeProxy() default false;  } 複製代碼

經過@EnableAspectJAutoProxy導入了一個AspectJAutoProxyRegistrar,這個類會向容器中註冊一個AnnotationAwareAspectJAutoProxyCreator,對應源碼以下:

class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
 @Override  public void registerBeanDefinitions(  AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {   // 在這裏完成的註冊  // 最終會調用到AopUtils的registerAspectJAnnotationAutoProxyCreatorIfNecessary方法  // 完成AnnotationAwareAspectJAutoProxyCreator這個bd的註冊  AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);   // 解析註解的屬性  // proxyTargetClass:爲true的話開啓cglib代理,默認爲jdk代理  // exposeProxy:是否將代理對象暴露到線程上下文中  AnnotationAttributes enableAspectJAutoProxy =  AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);  if (enableAspectJAutoProxy != null) {  if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {  AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);  }   if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {  AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);  }  }  }  } 複製代碼

前面已經說過了,自動代理機制實際上就是Spring在內部new了一個ProxyFactory,經過它建立了一個代理對象。對應的代碼就在AbstractAutoProxyCreator中的createProxy方法內,源碼以下:

protected Object createProxy(Class<?> beanClass, @Nullable String beanName,  @Nullable Object[] specificInterceptors, TargetSource targetSource) {   if (this.beanFactory instanceof ConfigurableListableBeanFactory) {  AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);  }  // 看到了吧,這裏建立了一個proxyFactory  ProxyFactory proxyFactory = new ProxyFactory();  proxyFactory.copyFrom(this);   if (!proxyFactory.isProxyTargetClass()) {  if (shouldProxyTargetClass(beanClass, beanName)) {  proxyFactory.setProxyTargetClass(true);  }  else {  evaluateProxyInterfaces(beanClass, proxyFactory);  }  }   Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);  proxyFactory.addAdvisors(advisors);  proxyFactory.setTargetSource(targetSource);  customizeProxyFactory(proxyFactory);   proxyFactory.setFrozen(this.freezeProxy);  if (advisorsPreFiltered()) {  proxyFactory.setPreFiltered(true);  }  // 經過proxyFactory來建立一個代理對象  return proxyFactory.getProxy(getProxyClassLoader()); } 複製代碼

關於這個類的執行流程在下篇文章中我再詳細介紹,接下來咱們要分析的就是具體建立AOP代理的源碼了。對應的核心源碼就是咱們以前所提到的

createAopProxy().getProxy();
複製代碼

這行代碼分爲兩步,咱們逐步分析

  1. 調用 AopProxyFactorycreateAopProxy()方法獲取一個 AopProxy對象
  2. 調用 AopProxy對象的 getProxy()方法

核心源碼分析

createAopProxy方法分析

AopProxyFactory在Spring中只有一個默認的實現類,就是DefaultAopProxyFactory,它的對應的createAopProxy的是實現代碼以下:

public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
  // 就是經過AOP相關的配置信息來決定究竟是使用cglib代理仍是jdk代理  @Override  public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {  // 若是開啓了優化,或者ProxyTargetClass設置爲true  // 或者沒有提供代理類須要實現的接口,那麼使用cglib代理  // 在前面分析參數的時候已經說過了  // 默認狀況下Optimize都爲false,也不建議設置爲true,由於會進行一些侵入性的優化  // 除非你對cglib的優化很是瞭解,不然不建議開啓  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.");  }  // 須要注意的是,若是須要代理的類自己就是一個接口  // 或者須要被代理的類自己就是一個經過jdk動態代理生成的類  // 那麼無論如何設置都會使用jdk動態代理  if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {  return new JdkDynamicAopProxy(config);  }  return new ObjenesisCglibAopProxy(config);  }  // 不然都是jdk代理  else {  return new JdkDynamicAopProxy(config);  }  }   // 判斷是否提供代理類須要實現的接口  private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {  Class<?>[] ifcs = config.getProxiedInterfaces();  return (ifcs.length == 0 || (ifcs.length == 1 && SpringProxy.class.isAssignableFrom(ifcs[0])));  }  } 複製代碼

getProxy方法分析

從對createAopProxy方法的分析能夠看到,咱們要麼執行的是ObjenesisCglibAopProxy中的getProxy方法,要麼就是JdkDynamicAopProxygetProxy方法,兩者的區別在於一個是經過cglib的方式生成代理對象,然後者則是經過jdk的方式生成動態代理。

這裏我只分析一個JdkDynamicAopProxy,首先咱們來看看這個類的繼承關係

但願你以前已經閱讀過

原創 動態代理學習(一)本身動手模擬JDK動態代理

原創 動態代理學習(二)JDK動態代理源碼分析

image-20200702154037428
image-20200702154037428

能夠看到這個類自己就是一個InvocationHandler,這意味着當調用代理對象中的方法時,最終會調用到JdkDynamicAopProxyinvoke方法。

因此對於這個類咱們起碼應該關注兩個方法

  1. getProxy方法
  2. invoke方法

getProxy方法源碼以下:

public Object getProxy(@Nullable ClassLoader classLoader) {
 if (logger.isDebugEnabled()) {  logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());  }  // 這裏獲取到代理類須要實現的全部的接口  Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);  // 須要明確是否在接口定義了hashCode以及equals方法  // 若是接口中沒有定義,那麼在調用代理對象的equals方法的時候  // 若是兩個對象相等,那麼意味着它們的目標對象,通知以及實現的接口都相同  findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);  return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this); } 複製代碼

咱們再來看看究竟是怎麼獲取到須要實現的接口的

static Class<?>[] completeProxiedInterfaces(AdvisedSupport advised, boolean decoratingProxy) {
 // 第一步:獲取在配置中指定的須要實現的接口  Class<?>[] specifiedInterfaces = advised.getProxiedInterfaces();   // 第二步:若是沒有指定須要實現的接口,可是須要代理的目標類自己就是一個接口  // 那麼將其添加到代理類須要實現的接口的集合中  // 若是目標類自己不是一個接口,可是是通過jdk代理後的一個類  // 那麼獲取這個代理後的類全部實現的接口,並添加到須要實現的接口集合中  if (specifiedInterfaces.length == 0) {  Class<?> targetClass = advised.getTargetClass();  if (targetClass != null) {  if (targetClass.isInterface()) {  advised.setInterfaces(targetClass);  }  else if (Proxy.isProxyClass(targetClass)) {  advised.setInterfaces(targetClass.getInterfaces());  }  specifiedInterfaces = advised.getProxiedInterfaces();  }  }   // 第三步:爲代理類添加三個默認須要實現的接口,分別是  // 1.SpringProxy,一個標記接口,表明這個類是經過Spring的AOP代理生成的  // 2.Advised,提供了管理通知的方法  // 3.DecoratingProxy,用戶獲取到真實的目標對象  // 這個真實對象指的是在嵌套代理的狀況下會獲取到最終的目標對象  // 而不是指返回這個ProxyFactory的target  boolean addSpringProxy = !advised.isInterfaceProxied(SpringProxy.class);  boolean addAdvised = !advised.isOpaque() && !advised.isInterfaceProxied(Advised.class);  boolean addDecoratingProxy = (decoratingProxy && !advised.isInterfaceProxied(DecoratingProxy.class));  int nonUserIfcCount = 0;  if (addSpringProxy) {  nonUserIfcCount++;  }  if (addAdvised) {  nonUserIfcCount++;  }  if (addDecoratingProxy) {  nonUserIfcCount++;  }  Class<?>[] proxiedInterfaces = new Class<?>[specifiedInterfaces.length + nonUserIfcCount];  System.arraycopy(specifiedInterfaces, 0, proxiedInterfaces, 0, specifiedInterfaces.length);  int index = specifiedInterfaces.length;  if (addSpringProxy) {  proxiedInterfaces[index] = SpringProxy.class;  index++;  }  if (addAdvised) {  proxiedInterfaces[index] = Advised.class;  index++;  }  if (addDecoratingProxy) {  proxiedInterfaces[index] = DecoratingProxy.class;  }  return proxiedInterfaces; } 複製代碼

invoke方法分析

在確認了須要實現的接口後,直接調用了jdk的動態代理方法,這個咱們就不作分析了,接下來咱們來看看Spring是如何將通知應用到代理對象上的,對應的要分析的代碼就是JdkDynamicAopProxyinvoke方法,源碼以下:

// 這個方法的代碼稍微有點長,代碼也比較難,但願你們能耐心看完
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  Object oldProxy = null;  boolean setProxyContext = false;   TargetSource targetSource = this.advised.targetSource;  Object target = null;   try {  // 首先處理的是hashCode跟equals方法  // 若是接口中沒有定義這兩個方法,那麼會調用本類中定義的equals方法  // 前面咱們也說過了,只有當兩個類的目標對象,通知以及實現的接口都相等的狀況下  // equals纔會返回true  // 若是接口中定義了這兩個方法,那麼最終會調用目標對象中的方法  if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {  return equals(args[0]);  }  else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {  return hashCode();  }   // 也就是說咱們調用的是DecoratingProxy這個接口中的方法  // 這個接口中只定義了一個getDecoratedClass方法,用於獲取到  // 最終的目標對象,在方法實現中會經過一個while循環來不斷接近  // 最終的目標對象,直到獲得的目標對象不是一個被代理的對象纔會返回  else if (method.getDeclaringClass() == DecoratingProxy.class) {  return AopProxyUtils.ultimateTargetClass(this.advised);  }   // 說明調用的是Advised接口中的方法,這裏只是單純的進行反射調用  else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&  method.getDeclaringClass().isAssignableFrom(Advised.class)) {   return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);  }   Object retVal;   // 說明須要將代理類暴露到線程上下文中  // 調用AopContext.setCurrentProxy方法將其放入到一個threadLocal中  if (this.advised.exposeProxy) {  oldProxy = AopContext.setCurrentProxy(proxy);  setProxyContext = true;  }   // 接下來就是真正的執行代理邏輯了  target = targetSource.getTarget();  Class<?> targetClass = (target != null ? target.getClass() : null);   // 先獲取整個攔截器鏈  List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);   // 若是沒有進行攔截,直接反射調用方法  if (chain.isEmpty()) {  Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);  retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);  }   // 不然開始執行整個鏈條  else {  MethodInvocation invocation =  new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);  retVal = invocation.proceed();  }   // 這裏是處理一種特殊狀況,就是當執行的方法返回值爲this的狀況  // 這種狀況下,須要返回當前的代理對象而不是目標對象  Class<?> returnType = method.getReturnType();  if (retVal != null && retVal == target &&  returnType != Object.class && returnType.isInstance(proxy) &&  !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {  retVal = proxy;  }  else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {  throw new AopInvocationException(  "Null return value from advice does not match primitive return type for: " + method);  }  return retVal;  }  finally {  if (target != null && !targetSource.isStatic()) {  targetSource.releaseTarget(target);  }  if (setProxyContext) {  AopContext.setCurrentProxy(oldProxy);  }  } }  複製代碼

在上面整個流程中,咱們抓住核心的兩步

  1. 獲取整個攔截器鏈
  2. 開始在攔截器鏈上執行方法

咱們先看第一步,對應源碼以下:

public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, @Nullable Class<?> targetClass) {
 MethodCacheKey cacheKey = new MethodCacheKey(method);  List<Object> cached = this.methodCache.get(cacheKey);  if (cached == null) {  // 調用了advisorChainFactory的getInterceptorsAndDynamicInterceptionAdvice方法  cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(  this, method, targetClass);  this.methodCache.put(cacheKey, cached);  }  return cached; } 複製代碼
public List<Object> getInterceptorsAndDynamicInterceptionAdvice(  Advised config, Method method, @Nullable Class<?> targetClass) {   List<Object> interceptorList = new ArrayList<Object>(config.getAdvisors().length);   Class<?> actualClass = (targetClass != null ? targetClass : method.getDeclaringClass());   // 是否有引入通知  boolean hasIntroductions = hasMatchingIntroductions(config, actualClass);   AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();   // 獲取到全部的通知  for (Advisor advisor : config.getAdvisors()) {  // 除了引入通知外,能夠認爲全部的通知都是一個PointcutAdvisor  if (advisor instanceof PointcutAdvisor) {  PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;  // config.isPreFiltered:表明的是配置已通過濾好了,是能夠直接應用的  // 這句代碼的含義就是配置是預過濾的或者在類級別上是匹配的  if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) {  // 接下來要判斷在方法級別上是否匹配  MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();  if (MethodMatchers.matches(mm, method, actualClass, hasIntroductions)) {  // 將通知轉換成對應的攔截器  // 有些通知自己就是攔截器,例如環繞通知  // 有些通知須要經過一個AdvisorAdapter來適配成對應的攔截器  // 例如前置通知,後置通知,異常通知等  // 其中MethodBeforeAdvice會被適配成MethodBeforeAdviceInterceptor  // AfterReturningAdvice會被適配成AfterReturningAdviceInterceptor  // ThrowAdvice會被適配成ThrowsAdviceInterceptor  MethodInterceptor[] interceptors = registry.getInterceptors(advisor);   // 若是是動態的攔截,會建立一個InterceptorAndDynamicMethodMatcher  // 動態的攔截意味着須要根據具體的參數來決定是否進行攔截  if (mm.isRuntime()) {  for (MethodInterceptor interceptor : interceptors) {  interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptor, mm));  }  }  else {  interceptorList.addAll(Arrays.asList(interceptors));  }  }  }  }  else if (advisor instanceof IntroductionAdvisor) {  // 說明是引入通知  IntroductionAdvisor ia = (IntroductionAdvisor) advisor;  if (config.isPreFiltered() || ia.getClassFilter().matches(actualClass)) {  // 前文咱們有提到過,引入通知實際就是經過一個攔截器  // 將方法交由引入的類執行而不是目標類  Interceptor[] interceptors = registry.getInterceptors(advisor);  interceptorList.addAll(Arrays.asList(interceptors));  }  }  else {  // 可能會擴展出一些通知,通常不會  Interceptor[] interceptors = registry.getInterceptors(advisor);  interceptorList.addAll(Arrays.asList(interceptors));  }  }   return interceptorList; } 複製代碼

在構建好攔截器鏈後,接下來就是真正執行方法了,對應代碼就是

// 先建立一個MethodInvocation
MethodInvocation invocation =  new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain); // 開始在攔截器鏈上執行這個方法 retVal = invocation.proceed(); 複製代碼

最後的關鍵代碼就落在了ReflectiveMethodInvocationproceed方法

public Object proceed() throws Throwable {
  // 知足這個條件,說明執行到了最後一個攔截器,那麼直接反射調用目標方法  if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {  return invokeJoinpoint();  }   // 獲取到下一個要執行的攔截器  Object interceptorOrInterceptionAdvice =  this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);  if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {  // 前面構建攔截器鏈的時候咱們能夠看到,動態的攔截的話會建立一個InterceptorAndDynamicMethodMatcher  InterceptorAndDynamicMethodMatcher dm =  (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;  if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {  return dm.interceptor.invoke(this);  }  else {  // 若是匹配失敗了,執行攔截器鏈中的下一個攔截邏輯  return proceed();  }  }  else {  // 調用攔截器中的invoke方法,能夠看到這裏將this做爲參數傳入了  // 因此咱們在攔截器中調用 MethodInvocation的proceed時又會進行入當前這個方法  // 而後去執行鏈條中的下一個攔截器   return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);  } } 複製代碼

總結

本文主要是爲下篇文章作準備,下篇文章將會結束整個IOC流程的分析,IOC的最後一步即是爲Bean建立代理。本文已經分析了代理的具體建立邏輯,在下篇文章中咱們主要結合Spring的啓動流程來看一看Spring是如何將通知添加到建立代理的配置信息中去的。

關於整個IOC跟AOP的模塊還會有兩篇文章,一篇用於結束整個IOC流程,另一篇專門探討Spring中循環依賴的解決。完成這兩篇文章中,接下來打算用5到7篇文章對Spring的事務管理進行分析!

若是個人文章能幫到你,記得點個贊哈~!

若是本文對你有幫助的話,記得點個贊吧!也歡迎關注個人公衆號,微信搜索:程序員DMZ,或者掃描下方二維碼,跟着我一塊兒認認真真學Java,踏踏實實作一個coder。

公衆號

我叫DMZ,一個在學習路上匍匐前行的小菜鳥!

相關文章
相關標籤/搜索