javascript
5.4 Spring AOP
2019獨(dú)角獸企業(yè)重金招聘Python工程師標(biāo)準(zhǔn)>>>
5.4.1 ?從代理機(jī)制初探AOP
? ??? ??來看一個簡單的例子,當(dāng)需要在執(zhí)行某些方法時留下日志信息,可能會這樣寫:
import?java.util.logging.*; public?class?HelloSpeaker{ pirvate?Logger?logger=Logger.getLogger(this.getClass().getName());public?void?hello(String?name){logger.log(Level.INFO,?"hello?method?starts…");//?方法開始執(zhí)行時留下日志Sytem.out.println("hello,?"+name);?//?程序的主要功能Logger.log(Level.INFO,?"hello?method?ends…");//?方法執(zhí)行完畢時留下日志} }? ??? ??在HelloSpeaker類中,當(dāng)執(zhí)行hello()方法時,程序員希望該方法執(zhí)行開始與執(zhí)行完畢時都留下日志。最簡單的做法是用上面的程序設(shè)計,在方法執(zhí)行的前后加上日志動作。
? ??? ??可以使用代理(Proxy)機(jī)制來解決這個問題,有兩種代理方式:靜態(tài)代理(static proxy)和動態(tài)代理(dynamic proxy)。
? ??? ??在靜態(tài)代理的實(shí)現(xiàn)中,代理類與被代理的類必須實(shí)現(xiàn)同一個接口。在代理類中可以實(shí)現(xiàn)記錄等相關(guān)服務(wù),并在需要的時候再呼叫被代理類。這樣被代理類就可以僅僅保留業(yè)務(wù)相關(guān)的職責(zé)了。
? ??? ??舉個簡單的例子,首先定義一個IHello接口,IHello.java代碼如下:
public?interface?IHello{public?void?hello(String?name); }? ? ? ? 然后讓實(shí)現(xiàn)業(yè)務(wù)邏輯的HelloSpeaker類實(shí)現(xiàn)IHello接口,HelloSpeaker.java代碼如下:
public?class?HelloSpeaker?implements?IHello{public?void?hello(String?name){System.out.println("hello,"+name);} }? ??? ??可以看到,在HelloSpeaker類中沒有任何日志的代碼插入其中,日志服務(wù)的實(shí)現(xiàn)將被放到代理類中,代理類同樣要實(shí)現(xiàn)IHello接口。
HelloProxy.java代碼如下:
public?class?HelloProxy?implements?IHello{private?Logger?logger=Logger.getLogger(this.getClass().getName());private?IHello?helloObject;public?HelloProxy(IHello?helloObject){this.helloObject=helloObject;}public?void?hello(String?name){log("hello?method?starts…");//?日志服務(wù)helloObject.hello(name);//?執(zhí)行業(yè)務(wù)邏輯log("hello?method?ends…");//?日志服務(wù)}private?void?log(String?msg){logger.log(Level.INFO,msg);} }? ??? ??在HelloProxy類的hello()方法中,真正實(shí)現(xiàn)業(yè)務(wù)邏輯前后安排記錄服務(wù),可以實(shí)際撰寫一個測試程序來看看如何使用代理類。
public?class?ProxyDemo{public?static?void?main(String[]?args){IHello?proxy=new?HelloProxy(new?HelloSpeaker());proxy.hello("Justin");} }? ??? ??程序運(yùn)行結(jié)果:
hello,Justin5.4.2 ?動態(tài)代理
? ??? ??要實(shí)現(xiàn)動態(tài)代理,同樣需要定義所要代理的接口。
IHello.java代碼如下:
public?interface?IHello{public?void?hello(String?name); }? ? ? ? 然后讓實(shí)現(xiàn)業(yè)務(wù)邏輯的HelloSpeaker類實(shí)現(xiàn)IHello接口。
HelloSpeaker.java代碼如下:
public?class?HelloSpeaker?implements?IHello{public?void?hello(String?name){System.out.println("Hello,"+name);} }? ??? ??與上例不同的是,這里要實(shí)現(xiàn)不同的代理類:
import?java.lang.reflect.InvocationHandler; import?java.lang.reflect.Method; public?class?LogHandler?implements?InvocationHandler{private?Object?sub;public?LogHandler()?{}public?LogHandler(Object?obj){sub?=?obj;}public?Object?invoke(Object?proxy,?Method?method,?Object[]?args)throws?Throwable{System.out.println("before?you?do?thing");method.invoke(sub,?args);System.out.println("after?you?do?thing");return?null;} }? ??? ??寫一個測試程序,使用LogHandler來綁定被代理類。
ProxyDemo.java代碼如下:
import?java.lang.reflect.Proxy; public?class?ProxyDemo?{public?static?void?main(String[]?args)?{HelloSpeaker?helloSpeaker=new?HelloSpeaker();LogHandler?logHandler=new?LogHandler(helloSpeaker);Class?cls=helloSpeaker.getClass();IHello?iHello=(IHello)Proxy.newProxyInstance(cls.getClassLoader(),cls.getInterfaces(),logHandler);iHello.hello("Justin");} }? ??? ??程序運(yùn)行結(jié)果:
before?you?do?thing Hello,?Justin after?you?do?thing5.4.3 ?AOP術(shù)語與概念
1.cross-cutting concerns
? ??? ??在DynamicProxyDemo例子中,記錄的動作原先被橫切(Cross-cutting)到HelloSpeaker本身所負(fù)責(zé)的業(yè)務(wù)流程中。類似于日志這類的動作,如安全檢查、事務(wù)等服務(wù),在一個應(yīng)用程序中常被安排到各個類的處理流程之中。這些動作在AOP的術(shù)語中稱為cross-cutting concerns。如圖5.7所示,原來的業(yè)務(wù)流程是很單純的。?
?
圖5.7? 原來的業(yè)務(wù)流程
? ??? ??為了加入日志與安全檢查等服務(wù),類的程序代碼中被硬生生地寫入了相關(guān)的Logging、Security程序片段,如圖5.8所示。?
?
圖5.8? 加入各種服務(wù)的業(yè)務(wù)流程
2.Aspect
? ??? ??將散落在各個業(yè)務(wù)類中的cross-cutting concerns收集起來,設(shè)計各個獨(dú)立可重用的類,這種類稱為Aspect。例如,在動態(tài)代理中將日志的動作設(shè)計為LogHandler類,LogHandler類在AOP術(shù)語中就是Aspect的一個具體實(shí)例。在需要該服務(wù)的時候,縫合到應(yīng)用程序中;不需要服務(wù)的時候,也可以馬上從應(yīng)用程序中脫離。應(yīng)用程序中的可重用組件不用做任何的修改。例如,在動態(tài)代理中的HelloSpeaker所代表的角色就是應(yīng)用程序中可重用的組件,在它需要日志服務(wù)時并不用修改本身的程序代碼。
5.4.4 ?通知Advice
? ??? ??Spring提供了5種通知(Advice)類型:Interception Around、Before、After Returning、Throw 和Introduction。它們分別在以下情況被調(diào)用:
Interception Around Advice:在目標(biāo)對象的方法執(zhí)行前后被調(diào)用。
Before Advice:在目標(biāo)對象的方法執(zhí)行前被調(diào)用。
After Returning Advice:在目標(biāo)對象的方法執(zhí)行后被調(diào)用。
Throw Advice:在目標(biāo)對象的方法拋出異常時被調(diào)用。
Introduction Advice:一種特殊類型的攔截通知,只有在目標(biāo)對象的方法調(diào)用完畢后執(zhí)行。
創(chuàng)建一個Before Advice的Web項(xiàng)目,步驟如下。
① 創(chuàng)建一個Web項(xiàng)目,命名為“Spring_Advices”。
② 編寫Java類。
? ??? ??Before Advice會在目標(biāo)對象的方法執(zhí)行之前被呼叫。這個接口提供了獲取目標(biāo)方法、參數(shù)及目標(biāo)對象。
MethodBeforeAdvice接口的代碼如下:
import?java.lang.ref.*; import?java.lang.reflect.Method; public?interface?MethodBeforeAdvice{void?before(Method?method,?Object[]?args,?Object?target)?throws?Exception; }? ??? ??用實(shí)例來示范如何使用Before Advice。首先要定義目標(biāo)對象必須實(shí)現(xiàn)的接口IHello。
IHello.java代碼如下:
public?interface?IHello{public?void?hello(String?name); }? ??? ??接著定義一個HelloSpeaker,實(shí)現(xiàn)IHello接口。
HelloSpeaker.java代碼如下:
public?class?HelloSpeaker?implements?IHello{public?void?hello(String?name){System.out.println("Hello,"+name);} }? ??? ??在對HelloSpeader不進(jìn)行任何修改的情況下,想要在hello()方法執(zhí)行之前可以記錄一些信息。有一個組件,但沒有源代碼,可對它增加一些日志的服務(wù)。
LogBeforeAdvice.java代碼如下:
import?java.lang.reflect.*; import?java.util.logging.Level; import?java.util.logging.Logger; import?org.springframework.aop.MethodBeforeAdvice; public?class?LogBeforeAdvice?implements?MethodBeforeAdvice{private?Logger?logger=Logger.getLogger(this.getClass().getName());public?void?before(Method?method,Object[]?args,Object?target)?throws?Exception{logger.log(Level.INFO,?"method?starts…"+method);} }③ 添加Spring開發(fā)能力。
applicationContext.xml的代碼如下:
<?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-2.0.xsd"><bean?id="logBeforeAdvice"?class="LogBeforeAdvice"?/><bean?id="helloSpeaker"?class="HelloSpeaker"?/><bean?id="helloProxy"class="org.springframework.aop.framework.ProxyFactoryBean"><property?name="proxyInterfaces"><value>IHello</value></property><property?name="target"><ref?bean="helloSpeaker"?/></property><property?name="interceptorNames"><list><value>logBeforeAdvice</value></list></property></bean> </beans>④ 運(yùn)行程序,測試結(jié)果。
寫一個程序測試一下Before Advice的運(yùn)作。
SpringAOPDemo.java代碼如下:
import?org.springframework.context.ApplicationContext; import?org.springframework.context.support.FileSystemXmlApplicationContext; public?class?SpringAOPDemo{public?static?void?main(String[]?args){ApplicationContext?context=new?FileSystemXmlApplicationContext("/WebRoot/WEB-INF/classes/applicationContext.xml");IHello?helloProxy=(IHello)context.getBean("helloProxy");helloProxy.hello("Justin");} }程序運(yùn)行結(jié)果:
HelloSpeaker與LogBeforeAdvice是兩個獨(dú)立的類。對于HelloSpeaker來說,它不用知道LogBeforeAdvice的存在;而LogBeforeAdvice也可以運(yùn)行到其他類之上。HelloSpeaker與LogBeforeAdvice都可以重復(fù)使用。
5.4.5 ?切入點(diǎn)Pointcut
創(chuàng)建一個切入點(diǎn)Pointcut項(xiàng)目,步驟如下。
① 創(chuàng)建一個Web項(xiàng)目,命名為“Spring_Pointcut”。
② 編寫Java類。
IHello.java代碼如下:
public?interface?IHello{public?void?helloNewbie(String?name);public?void?helloMaster(String?name); }HelloSpeaker類實(shí)現(xiàn)IHello接口。HelloSpeaker.java代碼如下:
public?class?HelloSpeaker?implements?IHello{public?void?helloNewbie(String?name){System.out.println("Hello,?"+name+"newbie!?");}public?void?helloMaster(String?name){System.out.println("Hello,?"+name+"master!?");} }③ 添加Spring開發(fā)能力。
applicationContext.xml的代碼。
<?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-2.0.xsd"><bean?id="logBeforeAdvice"?class="LogBeforeAdvice"?/><bean?id="helloAdvisor"class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor"><property?name="mappedName"><value>hello*</value></property><property?name="advice"><ref?bean="logBeforeAdvice"?/></property></bean><bean?id="helloSpeaker"?class="HelloSpeaker"?/><bean?id="helloProxy"class="org.springframework.aop.framework.ProxyFactoryBean"><property?name="proxyInterfaces"><value>IHello</value></property><property?name="target"><ref?bean="helloSpeaker"?/></property><property?name="interceptorNames"><list><value>helloAdvisor</value></list></property></bean> </beans>④ 運(yùn)行程序,測試結(jié)果。
SpringAOPDemo.java代碼如下:
import?org.springframework.context.ApplicationContext; import?org.springframework.context.support.FileSystemXmlApplicationContext; public?class?SpringAOPDemo?{public?static?void?main(String[]?args)?{ApplicationContext?context?=?new?FileSystemXmlApplicationContext("/WebRoot/WEB-INF/classes/applicationContext.xml");IHello?helloProxy?=?(IHello)?context.getBean("helloProxy");helloProxy.helloNewbie("Justin");helloProxy.helloMaster("Tom");} }程序運(yùn)行結(jié)果:
附:目錄《JavaEE基礎(chǔ)實(shí)用教程》筆記說明
轉(zhuǎn)載于:https://my.oschina.net/jerrypan/blog/631893
總結(jié)
以上是生活随笔為你收集整理的5.4 Spring AOP的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 哈希工具主方法
- 下一篇: python创建虚拟串口