javascript
再说 Spring AOP
什么是 AOP
AOP(Aspect-OrientedProgramming,面向方面編程),可以說是 OOP(Object-Oriented Programing,面向?qū)ο缶幊?#xff09;的補(bǔ)充和完善。OOP 引入封裝、繼承和多態(tài)性等概念來建立一種對象層次結(jié)構(gòu),用以模擬公共行為的一個(gè)集合。當(dāng)我們需要為分散的對象引入公共行為的時(shí)候,OOP 則顯得無能為力。也就是說,OOP 允許你定義從上到下的關(guān)系,但并不適合定義從左到右的關(guān)系。例如日志功能。日志代碼往往水平地散布在所有對象層次中,而與它所散布到的對象的核心功能毫無關(guān)系。對于其他類型的代碼,如安全性、異常處理和透明的持續(xù)性也是如此。這種散布在各處的無關(guān)的代碼被稱為橫切(cross-cutting)代碼,在 OOP 設(shè)計(jì)中,它導(dǎo)致了大量代碼的重復(fù),而不利于各個(gè)模塊的重用。
AOP 實(shí)現(xiàn)的關(guān)鍵在于 AOP 框架自動(dòng)創(chuàng)建的 AOP 代理,AOP 代理主要分為靜態(tài)代理和動(dòng)態(tài)代理,靜態(tài)代理的代表為 AspectJ;而動(dòng)態(tài)代理則以 Spring AOP 為代表。靜態(tài)代理是編譯期實(shí)現(xiàn),動(dòng)態(tài)代理是運(yùn)行期實(shí)現(xiàn),可想而知前者擁有更好的性能。
靜態(tài)代理是編譯階段生成 AOP 代理類,也就是說生成的字節(jié)碼就織入了增強(qiáng)后的 AOP 對象;動(dòng)態(tài)代理則不會(huì)修改字節(jié)碼,而是在內(nèi)存中臨時(shí)生成一個(gè) AOP 對象,這個(gè) AOP 對象包含了目標(biāo)對象的全部方法,并且在特定的切點(diǎn)做了增強(qiáng)處理,并回調(diào)原對象的方法。
Spring AOP 中的動(dòng)態(tài)代理主要有兩種方式,JDK 動(dòng)態(tài)代理和 CGLIB 動(dòng)態(tài)代理。JDK 動(dòng)態(tài)代理通過反射來接收被代理的類,并且要求被代理的類必須實(shí)現(xiàn)一個(gè)接口。JDK 動(dòng)態(tài)代理的核心是 InvocationHandler 接口和 Proxy 類。
如果目標(biāo)類沒有實(shí)現(xiàn)接口,那么 Spring AOP 會(huì)選擇使用 CGLIB 來動(dòng)態(tài)代理目標(biāo)類。CGLIB(Code Generation Library),是一個(gè)代碼生成的類庫,可以在運(yùn)行時(shí)動(dòng)態(tài)的生成某個(gè)類的子類,注意,CGLIB 是通過繼承的方式做的動(dòng)態(tài)代理,因此如果某個(gè)類被標(biāo)記為 final,那么它是無法使用 CGLIB 做動(dòng)態(tài)代理的,諸如 private 的方法也是不可以作為切面的。
我們分別通過實(shí)例來研究 AOP 的具體實(shí)現(xiàn)。
直接使用 Spring AOP
首先需要引入相關(guān)依賴,我這里是用了 SpringBoot 的相關(guān) starter
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId> </dependency>然后定義需要切入的接口和實(shí)現(xiàn)。為了簡單起見,定義一個(gè)接口Speakable和一個(gè)具體的實(shí)現(xiàn)類PersonSpring,只有兩個(gè)方法sayHi()和sayBye()。
public interface Speakable {void sayHi();void sayBye(); } @Service public class PersonSpring implements Speakable {@Overridepublic void sayHi() {try {Thread.currentThread().sleep(30);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Hi!!");}@Overridepublic void sayBye() {try {Thread.currentThread().sleep(30);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Bye!!");}}現(xiàn)在我們需要實(shí)現(xiàn)一個(gè)功能,記錄sayHi()和sayBye()的執(zhí)行時(shí)間。
我們定義了一個(gè)MethodMonitor類用來記錄 Method 執(zhí)行時(shí)間
光有這個(gè)類還是不夠的,希望有個(gè)靜態(tài)方法用起來更順手,像這樣
MonitorSession.start(methodName); doWork(); MonitorSession.end();說干就干,定義一個(gè)MonitorSession
public class MonitorSession {private static ThreadLocal<MethodMonitor> threadLocal = new ThreadLocal<>();public static void start(String methodName) {threadLocal.set(new MethodMonitor(methodName));}public static void end() {threadLocal.get().log();}}準(zhǔn)備工作都做完了, 接下來只需要我們做好切面的編碼
@Aspect @Component public class MonitorAdvice {@Pointcut("execution (* com.windmt.springaop.service.Speakable.*(..))")public void pointcut() {}@Around("pointcut()")public void around(ProceedingJoinPoint pjp) throws Throwable {MonitorSession.start(pjp.getSignature().getName());pjp.proceed();MonitorSession.end();}}如何使用?寫一個(gè)啟動(dòng)函數(shù)吧。
@SpringBootApplication public class SpringAopApplication {@Autowiredprivate Speakable personSpring;public static void main(String[] args) {SpringApplication.run(SpringAopApplication.class, args);}@Beanpublic CommandLineRunner commandLineRunner(ApplicationContext ctx) {return args -> {System.out.println("********** spring aop **********");personSpring.sayHi();personSpring.sayBye();System.exit(0);};}}運(yùn)行后輸出:
********** spring aop ********** sayHi monitor begin... Hi!! Method:sayHi, elapsedTime:48 millis sayHi monitor end. sayBye monitor begin... Bye!! Method:sayBye, elapsedTime:34 millis sayBye monitor end.JDK 動(dòng)態(tài)代理
剛剛的例子其實(shí)內(nèi)部實(shí)現(xiàn)機(jī)制就是 JDK 動(dòng)態(tài)代理,因?yàn)镻ersonSpring實(shí)現(xiàn)了一個(gè)接口。
為了不和第一個(gè)例子沖突,我們再定義一個(gè)PersonIndie來實(shí)現(xiàn)Speakable,實(shí)現(xiàn)和之前的完全一樣,只是注意這個(gè)實(shí)現(xiàn)是不帶 Spring Annotation 的,所以他不會(huì)被 Spring 托管。
重頭戲來了,我們需要利用InvocationHandler實(shí)現(xiàn)一個(gè)代理,讓它去包含Person這個(gè)對象。那么在運(yùn)行時(shí)實(shí)際上執(zhí)行的是這個(gè)代理的方法,然后代理再去執(zhí)行真正的方法。所以我們得以在執(zhí)行真正方法的前后做一些手腳。JDK 動(dòng)態(tài)代理是利用反射實(shí)現(xiàn),直接看代碼。
public class DynamicProxy implements InvocationHandler {private Object target;public DynamicProxy(Object obj) {this.target = obj;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {MonitorSession.start(method.getName());Object obj = method.invoke(this.target, args);MonitorSession.end();return obj;}public <T> T getProxy() {return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);}}通過getProxy可以得到這個(gè)代理對象,invoke就是具體的執(zhí)行方法,可以看到我們在執(zhí)行每個(gè)真正的方法前后都加了 Monitor。
再來一個(gè)工廠類來獲取 Person 代理對象
public class PersonProxyFactory {public static Speakable newJdkProxy() {DynamicProxy proxy = new DynamicProxy(new PersonIndie());return proxy.getProxy();}}具體使用
@SpringBootApplication public class SpringAopApplication {public static void main(String[] args) {SpringApplication.run(SpringAopApplication.class, args);}@Beanpublic CommandLineRunner commandLineRunner(ApplicationContext ctx) {return args -> {System.out.println("********** spring jdk proxy **********");Speakable person = PersonProxyFactory.newJdkProxy();person.sayHi();person.sayBye();System.exit(0);};}}運(yùn)行并輸出:
********** spring jdk proxy ********** sayHi monitor begin... Hi!! Method:sayHi, elapsedTime:35 millis sayHi monitor end. sayBye monitor begin... Bye!! Method:sayBye, elapsedTime:32 millis sayBye monitor end.CGLib 動(dòng)態(tài)代理
我們再新建一個(gè)Person來,這次不實(shí)現(xiàn)任何接口。
public class Person {public void sayHi() {try {Thread.currentThread().sleep(30);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Hi!!");}public void sayBye() {try {Thread.currentThread().sleep(30);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Bye!!");}}如果 Spring 識(shí)別到所代理的類沒有實(shí)現(xiàn) Interface,那么就會(huì)使用 CGLib 來創(chuàng)建動(dòng)態(tài)代理,原理實(shí)際上成為所代理類的子類。
public class CGLibProxy implements MethodInterceptor {private static volatile CGLibProxy instance;private CGLibProxy() {}public static CGLibProxy getInstance() {if (instance == null) {synchronized (CGLibProxy.class) {if (instance == null) {instance = new CGLibProxy();}}}return instance;}@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {MonitorSession.start(method.getName());Object obj = methodProxy.invokeSuper(o, objects);MonitorSession.end();return obj;}private Enhancer enhancer = new Enhancer();public <T> T getProxy(Class<T> clazz) {enhancer.setSuperclass(clazz);enhancer.setCallback(this);return (T) enhancer.create();}}類似的通過getProxy可以得到這個(gè)代理對象,intercep就是具體的執(zhí)行方法,可以看到我們在執(zhí)行每個(gè)真正的方法前后都加了 Monitor。
在工廠類中增加獲得 Person 代理類的方法
具體使用
Person person = PersonProxyFactory.newCGLibProxy(); person.sayHi(); person.sayBye();輸出結(jié)果
********** CGLib proxy ********** sayHi monitor begin... Hi!! Method:sayHi, elapsedTime:38 millis sayHi monitor end. sayBye monitor begin... Bye!! Method:sayBye, elapsedTime:35 millis sayBye monitor end.以上 code 都可以通過?Github?中獲取。
- 本文作者:?Yibo
- 本文鏈接:?https://windmt.com/2018/04/01/spring-aop-words-again/
- 版權(quán)聲明:?本博客所有文章除特別聲明外,均采用?CC BY-NC-SA 4.0?許可協(xié)議。轉(zhuǎn)載請注明出處!
總結(jié)
以上是生活随笔為你收集整理的再说 Spring AOP的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MySQL 优化原理(三)
- 下一篇: GO标准库—命令行参数解析FLAG