相信各位小夥伴在準備面試的時候,AOP都是沒法繞過的一個點,常常能看到動態代理、JDK動態代理、CGLIB動態代理這樣的字眼。其實動態代理是代理模式的一種。代理模式有靜態代理、強制代理、動態代理。因此在認識AOP以前須要瞭解代理模式。java
代理模式(Proxy Pattern):爲其餘對象提供一種代理以控制這個對象的訪問。面試
跳板機不論是對於運維老哥仍是對於咱們來說,都是平常的工做中不可或缺的一個工具。爲了保證生產服務器的安全,咱們是沒法經過xshell等工具直接進行鏈接的。若是須要操做生產的服務器,則須要經過跳板機。而且跳板機還能記錄咱們的操做,用來作安全審計。防止出現,某位老哥一氣之下反手就是個sudo rm -rf /*
直接涼涼。shell
咱們回過頭看看代理模式的定義:爲其餘對象提供一種代理以控制這個對象的訪問。實際上跳板機就是生產服務器的一個代理Proxy
,爲了實現控制生產服務器的訪問權限。你須要經過跳板機來操做生產服務器。數據庫
Proxy
的職責:Proxy
是對真實角色的應用,把全部抽象主題類定義的方法限制委託給真實主題角色實現。而且在真實主題角色處理完畢先後作到預處理和善後工做。你經過操做跳板機。跳板機將你輸入的命令在生產服務器上進行執行。而且能記錄下執行的命令和執行的結果。編程
Server設計模式
public interface Server { /** * 執行 * @param command 命令 */ void exec(String command); }
ProdServer安全
public class ProdServer implements Server { @Override public void exec(String command){ System.out.println(command + ":執行成功"); } }
JumpServer服務器
public class JumpServer { private Server server; public JumpServer(Server server) { this.server = server; } public void exec(String command){ System.out.println("xxx在:" + LocalDateTime.now() + " 執行了:" + command); server.exec(command); System.out.println("xxx在:" + LocalDateTime.now() + " 執行完了:"+ command + " 結果是XXX"); } }
Client網絡
public class Client { public static void main(String[] args) { JumpServer jumpServer = new JumpServer(); jumpServer.exec("pwd"); } }
運行結果框架
xxx在:2020-04-04T16:43:19.277 執行了:pwd pwd:執行成功 xxx在:2020-04-04T16:43:19.278 執行完了:pwd 結果是XXX
經過上面的代碼,簡單的實現了代理模式。在網絡上代理服務器設置分爲透明代理和普通代理。
當運維老哥給了一臺服務器的帳號和密碼,你成功登陸,並完成了相應的操做。你覺得給你的是生產的服務器。實際就多是個跳板機。爲了安全起見,是不可能將實際服務器的IP讓你知道的。很顯然跳板機對你來說就是透明的。
因此咱們調整一下代碼,將其變成普通代理
JumpServer
public class JumpServer { private Server server; public JumpServer(Server server) { this.server = server; } public void exec(String command){ server.exec(command); } }
Client
public class Client { public static void main(String[] args) { ProdServer prodServer = new ProdServer(); JumpServer jumpServer = new JumpServer(prodServer); jumpServer.exec("pwd"); } }
執行結果
xxx在:2020-04-04T16:52:23.282 執行了:pwd pwd:執行成功 xxx在:2020-04-04T16:52:23.283 執行完了:pwd 結果是XXX
對於現實狀況,咱們能夠經過不開放公網訪問的權限來實現,強制使用代理操做服務器。咱們能夠用代碼簡單的模擬下。
JumpServer
public class ProdServer implements Server { private JumpServer jumpServer; @Override public void exec(String command){ hasProxy(); System.out.println(command + ":執行成功"); } private void hasProxy(){ if(jumpServer == null){ throw new RuntimeException("請使用跳板機!"); } } public JumpServer setJumpServer() { this.jumpServer = new JumpServer(this); return this.jumpServer; } }
Client未設置跳板機
public class Client { public static void main(String[] args) { ProdServer prodServer = new ProdServer(); prodServer.exec("pwd"); } }
不設置跳板機運行結果
Exception in thread "main" java.lang.RuntimeException: 請使用跳板機! at proxy.pattern.tmp.ProdServer.hasProxy(ProdServer.java:21) at proxy.pattern.tmp.ProdServer.exec(ProdServer.java:15) at proxy.pattern.tmp.Client.main(Client.java:13)
Client設置跳板機
public class Client { public static void main(String[] args) { ProdServer prodServer = new ProdServer(); prodServer.setJumpServer().exec("pwd"); } }
運行結果
xxx在:2020-04-05T15:01:10.944 執行了:pwd pwd:執行成功 xxx在:2020-04-05T15:01:10.944 執行完了:pwd 結果是XXX
這個時候須要訪問生產服務器,就須要先設置跳板機了,才能進行操做。
對靜態代理來講,咱們須要手動生成代理類。可是若是須要代理的類太多了,那這個確定是不可取的。因此咱們可使用JDK動態代理來幫咱們完成工做。
JDK動態代理利用攔截器(攔截器必須實現InvocationHanlder
)加上反射機制生成一個實現代理接口的匿名類,在調用具體方法前調用InvokeHandler
來處理。
JumpServerInvocationHandler
public class JumpServerInvocationHandler implements InvocationHandler { private Object target; public JumpServerInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return method.invoke(target,args); } }
Client
public class Client { public static void main(String[] args) { ProdServer prodServer = new ProdServer(); JumpServerInvocationHandler handler = new JumpServerInvocationHandler(prodServer); ClassLoader classLoader = prodServer.getClass().getClassLoader(); Server proxy = (Server) Proxy.newProxyInstance(classLoader, new Class[]{Server.class}, handler); proxy.exec("pwd"); } }
測試結果
pwd:執行成功
Advice
public interface Advice { void exec(); }
BeforeAdvice
public class BeforeAdvice implements Advice { @Override public void exec() { System.out.println("執行前置通知"); } }
AfterAdvice
public class AfterAdvice implements Advice { @Override public void exec() { System.out.println("執行後置通知"); } }
JumpServerInvocationHandler
public class JumpServerInvocationHandler implements InvocationHandler { private Object target; public JumpServerInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 執行前置通知 new BeforeAdvice().exec(); Object ret = method.invoke(target, args); // 執行後置通知 new AfterAdvice().exec(); return ret; } }
Client
public class Client { public static void main(String[] args) { ProdServer prodServer = new ProdServer(); JumpServerInvocationHandler handler = new JumpServerInvocationHandler(prodServer); ClassLoader classLoader = prodServer.getClass().getClassLoader(); Server proxy = (Server) Proxy.newProxyInstance(classLoader, new Class[]{Server.class}, handler); proxy.exec("pwd"); } }
測試結果
執行前置通知 pwd:執行成功 執行後置通知
看到這裏有沒有點AOP的感受了。
CGLIB動態代理利用ASM開源包,對代理對象類的class文件加載進來,經過修改其字節碼生成子類來處理,須要使用MethodInterceptor
接口來進行實現。
JumpServerMethodInterceptor
public class JumpServerMethodInterceptor implements MethodInterceptor { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { new BeforeAdvice().exec(); Object ret = methodProxy.invokeSuper(obj, args); new AfterAdvice().exec(); return ret; } }
注意的點:是使用invokeSuper()
而不是invoke()
。
Client
public class Client { public static void main(String[] args) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(ProdServer.class); enhancer.setCallback(new JumpServerMethodInterceptor()); ProdServer proxy = (ProdServer) enhancer.create(); proxy.exec("pwd"); } }
測試結果
執行前置通知 pwd:執行成功 執行後置通知
經過上面的一大堆的篇幅介紹代理模式就是爲了能更加清晰的理解代理模式很是重要的一個應用場景AOP。
AOP(Aspect Oriented Programming)意爲:面向切面編程,經過預編譯方式和運行期間動態代理實現程序功能的統一維護的一種技術。
鏈接點(Joinpoint)
一個類或一段程序代碼擁有一些具備便捷性質的特定點。好比說類的某個方法調用前/調用後、方法拋出異常後。
切點(Pointcut)
在AOP中用於定位鏈接點。若是將鏈接點做爲數據庫中的記錄,切點即至關於查詢條件。切點和鏈接點不是一對一的關係,一個切點能夠匹配多個鏈接點。
加強(Advice)
加強是織入目標類鏈接點上的一段程序代碼。
在Spring中加強除了用於描述一段程序代碼外,還能夠擁有另外一個和鏈接點相關的信息,這即是執行點的方位。經過執行點方位信息和切點信息,就能夠找到特定的鏈接。正是由於加強即包含添加到鏈接點上的邏輯,包含定位鏈接點的方位信息,因此Spring提供的加強接口都是帶方位名的。如BeforeAdvice
、AfterAdvice
、AroundAdvice
。
目標對象(Target)
須要織入加強邏輯的目標類。好比說在使用AOP的時候配置的請求日誌輸出,目標對象就是對應的controller
.
引介(Introduction)
引介是一種特殊的加強,它爲類添加一些屬性和方法。這樣,即便一個業務類沒有本來沒有實現某個接口,經過AOP能夠動態的爲某些業務類添加接口和實現方法,讓業務類成爲這個接口的實現類。
織入(Weaving)
織入是將加強添加到目標類的具體鏈接點上的過程。AOP有3種織入方式:
代理(Proxy)
一個類被AOP織入加強後,就產生了一個結果類,它是融合了原類和加強邏輯的代理類。
切面(Proxy)
切面由切點和加強(引介)組成,它即包括很切邏輯的定義,也包括鏈接點的定義。SpringAOP就是負責實施切面的框架,他將切面所定義的橫切邏輯織入切面所指定的鏈接點中。
AOP翻譯過來是:面向切面編程是一種設計思想。主要由鏈接點,切點,加強、切面組成。AOP依託於代理模式進行實現,因此AOP擁有代理模式的特性。能夠在不改變原有類的狀況下,能動態的添加某些功能。因此說比較適合來實現,打印請求日誌,權限校驗,等功能。針對不一樣的場景,AOP能夠選擇使用JDK動態代理或CGLIB代理來實現。因爲CGLIB建立出來的代理類運行速度快於JDK動態代理,可是建立的過程太慢,因此能夠將其建立出來的代理類交由IOC容器進行管理,免去了重複建立代理沒必要要的性能開銷,來提升運行速度。
主要針對,AOP是什麼、由什麼組成、適合用場景、如何實現,不一樣實現的區別這些點去總結回答。
切面包含切點,切點和加強組成了切面。SpringAOP經過切面將邏輯特定織入切面所指定的鏈接點中。
jdk
動態代理只能對實現了接口的類生成代理,而不能針對類。cglib
是針對類實現代理,主要是對指定的類生成一個子類,覆蓋其中的方法。簡而言之就是JDK動態代理基於接口實現,cglib
基於類繼承。由於是繼承,因此該類或方法不能使用final進行修飾。
在性能上,有研究代表cglib
所建立的代理對象的性能要比jdk
建立的高10倍,可是呢cglib
建立代理對象時所花費的時間要比jdk
多8倍。因此單例的代理對象或者具備實例池的代理,無效頻繁的建立對象,比較適合採用cglib
,反正適合採用jdk
。
若是以爲對你有幫助,能夠多多評論,多多點贊哦,也能夠到個人主頁看看,說不定有你喜歡的文章,也能夠隨手點個關注哦,謝謝。