Java:由浅入深揭开 AOP 实现原理
點擊上方?好好學java?,選擇?星標?公眾號
重磅資訊、干貨,第一時間送達 今日推薦:干掉 Navicat:這個 IDEA 的兄弟真香!個人原創100W+訪問量博客:點擊前往,查看更多作者:馬佩
juejin.im/post/5bf4fc84f265da611b57f906
概述:
最近在開發中遇到了一個剛好可以用AOP實現的例子,就順便研究了AOP的實現原理,把學習到的東西進行一個總結。文章中用到的編程語言為kotlin,需要的可以在IDEA中直接轉為java。
這篇文章將會按照如下目錄展開:
AOP簡介
代碼中實現舉例
AOP實現原理
部分源碼解析
1. AOP簡介
附上我歷時三個月總結的?Java 面試 + Java 后端技術學習指南,筆者這幾年及春招的總結,github 1.1k star,拿去不謝!
下載方式
1.?首先掃描下方二維碼
2.?后臺回復「Java面試」即可獲取
相信大家或多或少的了解過AOP,都知道它是面向切面編程,在網上搜索可以找到很多的解釋。這里我用一句話來總結:AOP是能夠讓我們在不影響原有功能的前提下,為軟件橫向擴展功能。
那么橫向擴展怎么理解呢,我們在WEB項目開發中,通常都遵守三層原則,包括控制層(Controller)->業務層(Service)->數據層(dao),那么從這個結構下來的為縱向,它具體的某一層就是我們所說的橫向。我們的AOP就是可以作用于這某一個橫向模塊當中的所有方法。
我們在來看一下AOP和OOP的區別:AOP是OOP的補充,當我們需要為多個對象引入一個公共行為,比如日志,操作記錄等,就需要在每個對象中引用公共行為,這樣程序就產生了大量的重復代碼,使用AOP可以完美解決這個問題。
接下來介紹一下提到AOP就必須要了解的知識點:
切面:攔截器類,其中會定義切點以及通知
切點:具體攔截的某個業務點。
通知:切面當中的方法,聲明通知方法在目標業務層的執行位置,通知類型如下:
前置通知:@Before 在目標業務方法執行之前執行
后置通知:@After 在目標業務方法執行之后執行
返回通知:@AfterReturning 在目標業務方法返回結果之后執行
異常通知:@AfterThrowing 在目標業務方法拋出異常之后
環繞通知:@Around 功能強大,可代替以上四種通知,還可以控制目標業務方法是否執行以及何時執行
2. 代碼中實現舉例
上面已經大概的介紹了AOP中需要了解的基本知識,也知道了AOP的好處,那怎么在代碼中實現呢?給大家舉個例子:我們現在有個學校管理系統,已經實現了對老師和學生的增刪改,又新來個需求,說是對老師和學生的每次增刪改做一個記錄,到時候校長可以查看記錄的列表。
那么問題來了,怎么樣處理是最好的解決辦法呢?這里我羅列了三種解決辦法,我們來看下他的優缺點。
最簡單的就是第一種方法,我們直接在每次的增刪改的函數當中直接實現這個記錄的方法,這樣代碼的重復度太高,耦合性太強,不建議使用。
其次就是我們最長使用的,將記錄這個方法抽離出來,其他的增刪改調用這個記錄函數即可,顯然代碼重復度降低,但是這樣的調用還是沒有降低耦合性。
這個時候我們想一下AOP的定義,再想想我們的場景,其實我們就是要在不改變原來增刪改的方法,給這個系統增加記錄的方法,而且作用的也是一個層面的方法。這個時候我們就可以采用AOP來實現了。
我們來看下代碼的具體實現:
1,首先我定義了一個自定義注解作為切點
@Target(AnnotationTarget.FUNCTION)?? //注解作用的范圍,這里聲明為函數 @Order(Ordered.HIGHEST_PRECEDENCE)?? //聲明注解的優先級為最高,假設有多個注解,先執行這個 annotation?class?Hanler(val?handler:?HandlerType)?? //自定義注解類,HandlerType是一個枚舉類型,里面定義的就是學生和老師的增刪改操作,在這里就不展示具體內容了2,接下來就是要定義切面類了
@Aspect???//該注解聲明這個類為一個切面類 @Component class?HandlerAspect{@Autowiredprivate?lateinit?var?handlerService:?HandlerService@AfterReturning("@annotation(handler)")???//當有函數注釋了注解,將會在函數正常返回后在執行我們定義的方法 fun?hanler(hanler:?Hanler)?{handlerService.add(handler.operate.value)???//這里是真正執行記錄的方法 } }3,最后就是我們本來的業務方法了
/** *?刪除學生方法 */ @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接口。這個接口就是學生(被代理類),和班長(代理類)的公共接口,他們都有交作業的行為。這樣,學生交作業就可以讓班長來代理執行。
/***?創建person接口*/ public?interface?Person?{//交作業void?giveTask(); }Student類實現Person接口,Student可以具體實施交作業這個行為。
public?class?Student?implements?Person?{private?String?name;public?Student(String?name)?{this.name?=?name;}public?void?giveTask()?{System.out.println(name?+?"交語文作業");} }StudentsProxy類,這個類也實現了Person接口,但是還另外持有一個學生類對象,那么他可以代理學生類對象執行交作業的行為。
/***?學生代理類,也實現了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();} }下面測試一下,看代理模式如何使用:
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接口:
/***?創建person接口*/ public?interface?Person?{//交作業void?giveTask(); }接下來是創建需要被代理的實際類,也就是學生類:
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方法。
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;} }那么接下來我們就可以具體的創建代理對象了。
/***?代理類*/ public?class?ProxyTest?{public?static?void?main(String[]?args)?{//創建一個實例對象,這個對象是被代理的對象Person?linqian?=?new?Student("林淺");//創建一個與代理對象相關聯的InvocationHandlerInvocationHandler?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對應的proxyreturn?proxyFactory.getProxy(getProxyClassLoader());}2,創建何種類型的Proxy?JDKProxy還是CGLIBProxy?
????public?Object?getProxy(ClassLoader?classLoader)?{return?createAopProxy().getProxy(classLoader);}//?createAopProxy()方法就是決定究竟創建何種類型的proxyprotected?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.如果目標類是接口類(目標對象實現了接口),則直接使用JDKproxyif?(targetClass.isInterface()?||?Proxy.isProxyClass(targetClass))?{return?new?JdkDynamicAopProxy(config);}//?3.其他情況則使用CGLIBproxyreturn?new?ObjenesisCglibAopProxy(config);}else?{return?new?JdkDynamicAopProxy(config);}}3,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);}4,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的methodif?(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;}}}那么到了這里,我要講的內容就差不多結束了,如果有什么不對的,或者有什么疑惑,歡迎大家指點
最后,再附上我歷時三個月總結的?Java 面試 + Java 后端技術學習指南,筆者這幾年及春招的總結,github 1.1k star,拿去不謝!
下載方式
1.?首先掃描下方二維碼
2.?后臺回復「Java面試」即可獲取
總結
以上是生活随笔為你收集整理的Java:由浅入深揭开 AOP 实现原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: leetcode的回溯算法题目用这个模板
- 下一篇: try catch 有多烦人,我就有多暴