AOP如何實現及實現原理


概述:

最近在開發中遇到了一個剛好可以用AOP實現的例子,就順便研究了AOP的實現原理,把學習到的東西進行一個總結。文章中用到的編程語言為kotlin,需要的可以在IDEA中直接轉為java。 這篇文章將會按照如下目錄展開:

  • AOP簡介
  • 代碼中實現舉例
  • AOP實現原理
  • 部分源碼解析

1. AOP簡介

相信大家或多或少的了解過AOP,都知道它是面向切面編程,在網上搜索可以找到很多的解釋。這里我用一句話來總結:AOP是能夠讓我們在不影響原有功能的前提下,為軟件橫向擴展功能。 那么橫向擴展怎么理解呢,我們在WEB項目開發中,通常都遵守三層原則,包括控制層(Controller)->業務層(Service)->數據層(dao),那么從這個結構下來的為縱向,它具體的某一層就是我們所說的橫向。我們的AOP就是可以作用於這某一個橫向模塊當中的所有方法。

我們在來看一下AOP和OOP的區別:AOP是OOP的補充,當我們需要為多個對象引入一個公共行為,比如日志,操作記錄等,就需要在每個對象中引用公共行為,這樣程序就產生了大量的重復代碼,使用AOP可以完美解決這個問題。

接下來介紹一下提到AOP就必須要了解的知識點:

  • 切面:攔截器類,其中會定義切點以及通知
  • 切點:具體攔截的某個業務點。
  • 通知:切面當中的方法,聲明通知方法在目標業務層的執行位置,通知類型如下:
    1. 前置通知:@Before 在目標業務方法執行之前執行
    2. 后置通知:@After 在目標業務方法執行之后執行
    3. 返回通知:@AfterReturning 在目標業務方法返回結果之后執行
    4. 異常通知:@AfterThrowing 在目標業務方法拋出異常之后
    5. 環繞通知:@Around 功能強大,可代替以上四種通知,還可以控制目標業務方法是否執行以及何時執行

2. 代碼中實現舉例

上面已經大概的介紹了AOP中需要了解的基本知識,也知道了AOP的好處,那怎么在代碼中實現呢?給大家舉個例子:我們現在有個學校管理系統,已經實現了對老師和學生的增刪改,又新來個需求,說是對老師和學生的每次增刪改做一個記錄,到時候校長可以查看記錄的列表。那么問題來了,怎么樣處理是最好的解決辦法呢?這里我羅列了三種解決辦法,我們來看下他的優缺點。

-最簡單的就是一種方法,我們直接在每次的增刪改的函數當中直接實現這個記錄的方法,這樣代碼的重復度太高,耦合性太強,不建議使用。

 

-其次就是我們最長使用的,將記錄這個方法抽離出來,其他的增刪改調用這個記錄函數即可,顯然代碼重復度降低,但是這樣的調用還是沒有降低耦合性。

-這個時候我們想一下AOP的定義,再想想我們的場景,其實我們就是要在不改變原來增刪改的方法,給這個系統增加記錄的方法,而且作用的也是一個層面的方法。這個時候我們就可以采用AOP來實現了。

我們來看下代碼的具體實現:

  1. 首先我定義了一個自定義注解作為切點
@Target(AnnotationTarget.FUNCTION) //注解作用的范圍,這里聲明為函數 @Order(Ordered.HIGHEST_PRECEDENCE) //聲明注解的優先級為最高,假設有多個注解,先執行這個 annotation class Hanler(val handler: HandlerType) //自定義注解類,HandlerType是一個枚舉類型,里面定義的就是學生和老師的增刪改操作,在這里就不展示具體內容了 復制代碼
  1. 接下來就是要定義切面類了
@Aspect //該注解聲明這個類為一個切面類 @Component class HandlerAspect{ @Autowired private lateinit var handlerService: HandlerService @AfterReturning("@annotation(handler)") //當有函數注釋了注解,將會在函數正常返回后在執行我們定義的方法 fun hanler(hanler: Hanler) { handlerService.add(handler.operate.value) //這里是真正執行記錄的方法 } } 復制代碼
  1. 最后就是我們本來的業務方法了
/** * 刪除學生方法 */ @Handler(operate= Handler.STUDENT_DELETE) //當執行到刪除學生方法時,切面類就會起作用了,當學生正常刪除后就會執行記錄方法,我們就可以看到記錄方法生成的數據 fun delete(id:String) { studentService.delete(id) } 復制代碼

3. AOP實現原理

我們現在了解了代碼中如何實現,那么AOP實現的原理是什么呢?之前看了一個博客說到,提到AOP大家都知道他的實現原理是動態代理,顯然我之前就是不知道的,哈哈,但是相信閱讀文章的你們一定是知道的。

講到動態代理就不得不說代理模式了, 代理模式的定義:給某一個對象提供一個代理,並由代理對象控制對原對象的引用。代理模式包含如下角色:subject:抽象主題角色,是一個接口。該接口是對象和它的代理共用的接口; RealSubject:真實主題角色,是實現抽象主題接口的類; Proxy:代理角色,內部含有對真實對象RealSubject的引用,從而可以操作真實對象。代理對象提供與真實對象相同的接口,以便代替真實對象。同時,代理對象可以在執行真實對象操作時,附加其他的操作,相當於對真實對象進行封裝。如下圖所示:

那么代理又分為靜態代理和動態代理,這里寫兩個小的demo,動態代理采用的就是JDK代理。舉個例子就是現在一個班上的學生需要交作業,現在由班長代理交作業,那么班長就是代理,學生就是被代理的對象。

3.1 靜態代理

首先,我們創建一個Person接口。這個接口就是學生(被代理類),和班長(代理類)的公共接口,他們都有交作業的行為。這樣,學生交作業就可以讓班長來代理執行。

/** * Created by Mapei on 2018/11/7 * 創建person接口 */ public interface Person { //交作業 void giveTask(); } 復制代碼

Student類實現Person接口,Student可以具體實施交作業這個行為。

/** * Created by Mapei on 2018/11/7 */ public class Student implements Person { private String name; public Student(String name) { this.name = name; } public void giveTask() { System.out.println(name + "交語文作業"); } } 復制代碼

StudentsProxy類,這個類也實現了Person接口,但是還另外持有一個學生類對象,那么他可以代理學生類對象執行交作業的行為。

/** * Created by Mapei on 2018/11/7 * 學生代理類,也實現了Person接口,保存一個學生實體,這樣就可以代理學生產生行為 */ public class StudentsProxy implements Person{ //被代理的學生 Student stu; public StudentsProxy(Person stu) { // 只代理學生對象 if(stu.getClass() == Student.class) { this.stu = (Student)stu; } } //代理交作業,調用被代理學生的交作業的行為 public void giveTask() { stu.giveTask(); } } 復制代碼

下面測試一下,看代理模式如何使用:

/** * Created by Mapei on 2018/11/7 */ public class StaticProxyTest { public static void main(String[] args) { //被代理的學生林淺,他的作業上交有代理對象monitor完成 Person linqian = new Student("林淺"); //生成代理對象,並將林淺傳給代理對象 Person monitor = new StudentsProxy(linqian); //班長代理交作業 monitor.giveTask(); } } 復制代碼

運行結果:

 

 

這里並沒有直接通過林淺(被代理對象)來執行交作業的行為,而是通過班長(代理對象)來代理執行了。這就是代理模式。代理模式就是在訪問實際對象時引入一定程度的間接性,這里的間接性就是指不直接調用實際對象的方法,那么我們在代理過程中就可以加上一些其他用途。比如班長在幫林淺交作業的時候想告訴老師最近林淺的進步很大,就可以輕松的通過代理模式辦到。在代理類的交作業之前加入方法即可。這個優點就可以運用在spring中的AOP,我們能在一個切點之前執行一些操作,在一個切點之后執行一些操作,這個切點就是一個個方法。這些方法所在類肯定就是被代理了,在代理過程中切入了一些其他操作。

3.2 動態代理

動態代理和靜態代理的區別是,靜態代理的的代理類是我們自己定義好的,在程序運行之前就已經變異完成,但是動態代理的代理類是在程序運行時創建的。相比於靜態代理,動態代理的優勢在於可以很方便的對代理類的函數進行統一的處理,而不用修改每個代理類中的方法。比如我們想在每個代理方法之前都加一個處理方法,我們上面的例子中只有一個代理方法,如果還有很多的代理方法,就太麻煩了,我們來看下動態代理是怎么去實現的。

首先還是定義一個Person接口:

/** * Created by Mapei on 2018/11/7 * 創建person接口 */ public interface Person { //交作業 void giveTask(); } 復制代碼

接下來是創建需要被代理的實際類,也就是學生類:

/** * Created by Mapei on 2018/11/7 */ public class Student implements Person { private String name; public Student(String name) { this.name = name; } public void giveTask() { System.out.println(name + "交語文作業"); } } 復制代碼

創建StuInvocationHandler類,實現InvocationHandler接口,這個類中持有一個被代理對象的實例target。InvocationHandler中有一個invoke方法,所有執行代理對象的方法都會被替換成執行invoke方法。

/** * Created by Mapei on 2018/11/7 */ public class StuInvocationHandler<T> implements InvocationHandler { //invocationHandler持有的被代理對象 T target; public StuInvocationHandler(T target) { this.target = target; } /** * proxy:代表動態代理對象 * method:代表正在執行的方法 * args:代表調用目標方法時傳入的實參 */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("代理執行" +method.getName() + "方法"); Object result = method.invoke(target, args); return result; } } 復制代碼

那么接下來我們就可以具體的創建代理對象了。

/** * Created by Mapei on 2018/11/7 * 代理類 */ public class ProxyTest { public static void main(String[] args) { //創建一個實例對象,這個對象是被代理的對象 Person linqian = new Student("林淺"); //創建一個與代理對象相關聯的InvocationHandler InvocationHandler stuHandler = new StuInvocationHandler<Person>(linqian); //創建一個代理對象stuProxy來代理linqian,代理對象的每個執行方法都會替換執行Invocation中的invoke方法 Person stuProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, stuHandler); //代理執行交作業的方法 stuProxy.giveTask(); } } 復制代碼

我們執行代理測試類,首先我們創建了一個需要被代理的學生林淺,將林淺傳入stuHandler中,我們在創建代理對象stuProxy時,將stuHandler作為參數,那么所有執行代理對象的方法都會被替換成執行invoke方法,也就是說,最后執行的是StuInvocationHandler中的invoke方法。所以在看到下面的運行結果也就理所當然了。

 

 

那么到這里問題就來了,為什么代理對象執行的方法都會通過InvocationHandler中的invoke方法來執行,帶着這個問題,我們需要看一下動態代理的源碼,對他進行簡單的分析。

上面我們使用Proxy類的newProxyInstance方法創建了一個動態代理對象,看一下他的源碼:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { Objects.requireNonNull(h); final Class<?>[] intfs = interfaces.clone(); final SecurityManager sm = System.getSecurityManager(); if (sm != null) { checkProxyAccess(Reflection.getCallerClass(), loader, intfs); } /* * Look up or generate the designated proxy class. */ Class<?> cl = getProxyClass0(loader, intfs); /* * Invoke its constructor with the designated invocation handler. */ try { if (sm != null) { checkNewProxyPermission(Reflection.getCallerClass(), cl); } final Constructor<?> cons = cl.getConstructor(constructorParams); final InvocationHandler ih = h; if (!Modifier.isPublic(cl.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { cons.setAccessible(true); return null; } }); } return cons.newInstance(new Object[]{h}); } catch (IllegalAccessException|InstantiationException e) { throw new InternalError(e.toString(), e); } catch (InvocationTargetException e) { Throwable t = e.getCause(); if (t instanceof RuntimeException) { throw (RuntimeException) t; } else { throw new InternalError(t.toString(), t); } } catch (NoSuchMethodException e) { throw new InternalError(e.toString(), e); } } 復制代碼

然后,我們需要重點關注Class<?> cl = getProxyClass0(loader, intfs)這句代碼,這里產生了代理類,這個類就是動態代理的關鍵,由於是動態生成的類文件,我們將這個類文件打印到文件中。

        byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", Student.class.getInterfaces()); String path = "/Users/mapei/Desktop/okay/65707.class"; try{ FileOutputStream fos = new FileOutputStream(path); fos.write(classFile); fos.flush(); System.out.println("代理類class文件寫入成功"); }catch (Exception e) { System.out.println("寫文件錯誤"); } 復制代碼

對這個class文件進行反編譯,我們看看jdk為我們生成了什么樣的內容:

import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; import proxy.Person; public final class $Proxy0 extends Proxy implements Person { private static Method m1; private static Method m2; private static Method m3; private static Method m0; /** *注意這里是生成代理類的構造方法,方法參數為InvocationHandler類型,看到這,是不是就有點明白 *為何代理對象調用方法都是執行InvocationHandler中的invoke方法,而InvocationHandler又持有一個 *被代理對象的實例,就可以去調用真正的對象實例。 */ public $Proxy0(InvocationHandler paramInvocationHandler) throws { super(paramInvocationHandler); } //這個靜態塊本來是在最后的,我把它拿到前面來,方便描述 static { try { //看看這兒靜態塊兒里面的住giveTask通過反射得到的名字m3,其他的先不管 m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") }); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); m3 = Class.forName("proxy.Person").getMethod("giveTask", new Class[0]); m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); return; } catch (NoSuchMethodException localNoSuchMethodException) { throw new NoSuchMethodError(localNoSuchMethodException.getMessage()); } catch (ClassNotFoundException localClassNotFoundException) { throw new NoClassDefFoundError(localClassNotFoundException.getMessage()); } } /** * *這里調用代理對象的giveMoney方法,直接就調用了InvocationHandler中的invoke方法,並把m3傳了進去。 *this.h.invoke(this, m3, null);我們可以對將InvocationHandler看做一個中介類,中介類持有一個被代理對象,在invoke方法中調用了被代理對象的相應方法。通過聚合方式持有被代理對象的引用,把外部對invoke的調用最終都轉為對被代理對象的調用。 */ public final void giveTask() throws { try { this.h.invoke(this, m3, null); return; } catch (Error|RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } } 復制代碼

看完了動態代理的源碼,我們接下來就要看一下Spring中AOP實現的源碼是怎樣的?

4. 部分源碼解析

aop創建代理的源碼分析

  1. 看一下bean如何被包裝為proxy
       	protected Object createProxy( Class<?> beanClass, String beanName, Object[] specificInterceptors, TargetSource targetSource) { if (this.beanFactory instanceof ConfigurableListableBeanFactory) { AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass); } // 1.創建proxyFactory,proxy的生產主要就是在proxyFactory做的 ProxyFactory proxyFactory = new ProxyFactory(); proxyFactory.copyFrom(this); if (!proxyFactory.isProxyTargetClass()) { if (shouldProxyTargetClass(beanClass, beanName)) { proxyFactory.setProxyTargetClass(true); } else { evaluateProxyInterfaces(beanClass, proxyFactory); } } // 2.將當前bean適合的advice,重新封裝下,封裝為Advisor類,然后添加到ProxyFactory中 Advisor[] advisors = buildAdvisors(beanName, specificInterceptors); for (Advisor advisor : advisors) { proxyFactory.addAdvisor(advisor); } proxyFactory.setTargetSource(targetSource); customizeProxyFactory(proxyFactory); proxyFactory.setFrozen(this.freezeProxy); if (advisorsPreFiltered()) { proxyFactory.setPreFiltered(true); } // 3.調用getProxy獲取bean對應的proxy return proxyFactory.getProxy(getProxyClassLoader()); } 復制代碼
  1. 創建何種類型的Proxy?JDKProxy還是CGLIBProxy?
	public Object getProxy(ClassLoader classLoader) { return createAopProxy().getProxy(classLoader); } // createAopProxy()方法就是決定究竟創建何種類型的proxy protected final synchronized AopProxy createAopProxy() { if (!this.active) { activate(); } // 關鍵方法createAopProxy() return getAopProxyFactory().createAopProxy(this); } // createAopProxy() public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { // 1.config.isOptimize()是否使用優化的代理策略,目前使用與CGLIB // config.isProxyTargetClass() 是否目標類本身被代理而不是目標類的接口 // hasNoUserSuppliedProxyInterfaces()是否存在代理接口 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."); } // 2.如果目標類是接口類(目標對象實現了接口),則直接使用JDKproxy if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) { return new JdkDynamicAopProxy(config); } // 3.其他情況則使用CGLIBproxy return new ObjenesisCglibAopProxy(config); } else { return new JdkDynamicAopProxy(config); } } 復制代碼
  1. getProxy()方法
   final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable// JdkDynamicAopProxy類結構,由此可知,其實現了InvocationHandler,則必定有invoke方法,來被調用,也就是用戶調用bean相關方法時,此invoke()被真正調用 // getProxy() 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, true); findDefinedEqualsAndHashCodeMethods(proxiedInterfaces); // JDK proxy 動態代理的標准用法 return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this); } 復制代碼
  1. invoke()方法法
    //使用了JDK動態代理模式,真正的方法執行在invoke()方法里,看到這里在想一下上面動態代理的例子,是不是就完全明白Spring源碼實現動態代理的原理了。 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { MethodInvocation invocation; Object oldProxy = null; boolean setProxyContext = false; TargetSource targetSource = this.advised.targetSource; Class<?> targetClass = null; Object target = null; try { // 1.以下的幾個判斷,主要是為了判斷method是否為equals、hashCode等Object的方法 if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) { // The target does not implement the equals(Object) method itself. return equals(args[0]); } else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) { // The target does not implement the hashCode() method itself. return hashCode(); } else if (method.getDeclaringClass() == DecoratingProxy.class) { // There is only getDecoratedClass() declared -> dispatch to proxy config. return AopProxyUtils.ultimateTargetClass(this.advised); } else if (!this.advised.opaque && method.getDeclaringClass().isInterface() && method.getDeclaringClass().isAssignableFrom(Advised.class)) { // Service invocations on ProxyConfig with the proxy config... return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args); } Object retVal; if (this.advised.exposeProxy) { // Make invocation available if necessary. oldProxy = AopContext.setCurrentProxy(proxy); setProxyContext = true; } // 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.獲取當前bean被攔截方法鏈表 List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); // 3.如果為空,則直接調用target的method if (chain.isEmpty()) { Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args); retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse); } // 4.不為空,則逐一調用chain中的每一個攔截方法的proceed,這里的一系列執行的原因以及proceed執行的內容,我 在這里就不詳細講了,大家感興趣可以自己去研讀哈 else { // 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(); } ... return retVal; } } } 復制代碼

那么到了這里,我要講的內容就差不多結束了,如果有什么不對的,或者有什么疑惑,歡迎大家指點!

總結:

aop代理

1.代理由proxyFactory生成

2.bean封裝成advice添加到proxyFactroy中

3.config.isOptimize()是否使用優化的代理策略,目前使用與CGLIB // config.isProxyTargetClass() 是否目標類本身被代理而不是目標類的接口 // hasNoUserSuppliedProxyInterfaces()是否存在代理接口

4.是接口類型的為jdk代理,其他是cglib代理

5.jdk代理調用invoke方法,去除equals、hashcode等object方法

6.jdk代理獲取當前bean被攔截方法鏈表chain

7.chain為空直接反射調用target的method

8.chain不為空,則逐一調用chain中的每一個攔截方法的proceed


轉載自:鏈接:https://juejin.im/post/5bf4fc84f265da611b57f906


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM