xylz,imxylz

          關(guān)注后端架構(gòu)、中間件、分布式和并發(fā)編程

             :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::
            111 隨筆 :: 10 文章 :: 2680 評論 :: 0 Trackbacks

          2 AOP 面向切面編程

          2.1 AOP入門

          在前面的章節(jié)主要講Guice的依賴注入,有了依賴注入的基礎(chǔ)后我們再來看Guice的AOP。我們先從一個例子入手,深入淺出的去理解Guice的AOP的原理和實現(xiàn)。

          首先我們定義服務(wù)Service,這個服務(wù)有一個簡單的方法sayHello,當然了我們有一個服務(wù)的默認實現(xiàn)ServiceImpl,然后使用@ImplementedBy將服務(wù)和默認實現(xiàn)關(guān)聯(lián)起來,同時將服務(wù)的實現(xiàn)標注為單例模式。

          1 @ImplementedBy(ServiceImpl.class)
          2 public interface Service {
          3     void sayHello();
          4 }

           

          在服務(wù)的實現(xiàn)ServiceImpl中,我們sayHello方法就是輸出一行信息,這行信息包含服務(wù)的類名,hashCode以及方法名稱和執(zhí)行的時間。


           1 @Singleton
           2 public class ServiceImpl implements Service {
           3 
           4     @Override
           5     @Named("log")
           6     public void sayHello() {
           7         System.out.println(String.format("[%s#%d] execute %s at %d"this.getClass().getSimpleName(),hashCode(),"sayHello",System.nanoTime()));
           8     }
           9 
          10 }
          11 

          接下來定義一個AOP的實現(xiàn)。在Aopalliance中(大家都認可的AOP聯(lián)盟)實現(xiàn)我們的方法攔截器。這個攔截器LoggerMethodInterceptor 也沒有做什么特別的事情,只是記錄些執(zhí)行的時間,當然了由于執(zhí)行時間比較短我們用納秒來描述(盡管不是那么精確)。

          在MethodInvocation中我們一定要調(diào)用proceed()方法,這樣我們的服務(wù)才能被執(zhí)行。當然了如果為了做某些控制我們就能決定是否調(diào)用服務(wù)代碼了。


           1 import static java.lang.System.out;
           2 
           3 import org.aopalliance.intercept.MethodInterceptor;
           4 import org.aopalliance.intercept.MethodInvocation;
           5 
           6 public class LoggerMethodInterceptor implements MethodInterceptor {
           7 
           8     @Override
           9     public Object invoke(MethodInvocation invocation) throws Throwable {
          10         String methodName = invocation.getMethod().getName();
          11         long startTime=System.nanoTime();
          12         out.println(String.format("before method[%s] at %s", methodName, startTime));
          13         Object ret = null;
          14         try {
          15             ret = invocation.proceed();
          16         } finally {
          17             long endTime=System.nanoTime();
          18             out.println(String.format(" after method[%s] at %s, cost(ns):%d", methodName, endTime,(endTime-startTime)));
          19         }
          20         return ret;
          21     }
          22 }
          23 

          最后才是我們的客戶端程序,注意在這里我們需要綁定一個攔截器,這個攔截器匹配任何類的帶有l(wèi)og注解的方法。所以這就是為什么我們服務(wù)的實現(xiàn)方法需要用log標注的原因了。


           1 public class AopDemo {
           2     @Inject
           3     private Service service;
           4 
           5     public static void main(String[] args) {
           6         Injector inj = Guice.createInjector(new Module() {
           7             @Override
           8             public void configure(Binder binder) {
           9                 binder.bindInterceptor(Matchers.any(),//
          10                         Matchers.annotatedWith(Names.named("log")),//
          11                         new LoggerMethodInterceptor());
          12             }
          13         });
          14         inj.getInstance(AopDemo.class).service.sayHello();
          15         inj.getInstance(AopDemo.class).service.sayHello();
          16         inj.getInstance(AopDemo.class).service.sayHello();
          17     }
          18 }
          19 

          我們的程序輸出了我們期望的結(jié)果。

          before method[sayHello] at 7811306067456
          [ServiceImpl$$EnhancerByGuice$$
          96717882#33353934] execute sayHello at 7811321912287
          after method[sayHello] at 
          7811322140825, cost(ns):16073369
          before method[sayHello] at 
          7811322315064
          [ServiceImpl$$EnhancerByGuice$$
          96717882#33353934] execute sayHello at 7811322425280
          after method[sayHello] at 
          7811322561835, cost(ns):246771
          before method[sayHello] at 
          7811322710141
          [ServiceImpl$$EnhancerByGuice$$
          96717882#33353934] execute sayHello at 7811322817521
          after method[sayHello] at 
          7811322952455, cost(ns):242314

           

          關(guān)于此結(jié)果有幾點說明。

          (1)由于使用了AOP我們的服務(wù)得到的不再是我們寫的服務(wù)實現(xiàn)類了,而是一個繼承的子類,這個子類應(yīng)該是在內(nèi)存中完成的。

          (2)除了第一次調(diào)用比較耗時外(可能guice內(nèi)部做了比較多的處理),其它調(diào)用事件為0毫秒(我們的服務(wù)本身也沒做什么事)。

          (3)確實完成了我們期待的AOP功能。

          我們的例子暫且說到這里,來看看AOP的相關(guān)概念。

          2.2 AOP相關(guān)概念

          老實說AOP有一套完整的體系,光是概念就有一大堆,而且都不容易理解。這里我們結(jié)合例子和一些場景來大致了解下這些概念。

          通知(Advice)

          所謂通知就是我們切面需要完成的功能。比如2.1例子中通知就是記錄方式執(zhí)行的耗時,這個功能我們就稱之為一個通知。

          比如說在很多系統(tǒng)中我們都會將操作者的操作過程記錄下來,但是這個記錄過程又不想對服務(wù)侵入太多,這樣就可以使用AOP來完成,而我們記錄日志的這個功能就是一個通知。通知除了描述切面要完成的工作外還需要描述何時執(zhí)行這個工作,比如是在方法的之前、之后、之前和之后還是只在有異常拋出時。

          連接點(Joinpoint)

          連接點描述的是我們的通知在程序執(zhí)行中的時機,這個時機可以用一個“點”來描述,也就是瞬態(tài)。通常我們這個瞬態(tài)有以下幾種:方法運行前,方法運行后,拋出異常時或者讀取修改一個屬性等等。總是我們的通知(功能)就是插入這些點來完成我們額外的功能或者控制我們的執(zhí)行流程。比如說2.1中的例子,我們的通知(時間消耗)不僅在方法執(zhí)行前記錄執(zhí)行時間,在方法的執(zhí)行后也輸出了時間的消耗,那么我們的連接點就有兩個,一個是在方法運行前,還有一個是在方法運行后。

          切入點(Pointcut)

          切入點描述的是通知的執(zhí)行范圍。如果通知描述的是“什么時候”做“什么事”,連接點描述有哪些“時候”,那么切入點可以理解為“什么地方”。比如在2.1例子中我們切入點是所有Guice容器管理的服務(wù)的帶有@Named(“log”)注解的方法。這樣我們的通知就限制在這些地方,這些地方就是所謂的切入點。

          切面(Aspect)

          切面就是通知和切入點的結(jié)合。就是說切面包括通知和切入點兩部分,由此可見我們所說的切面就是通知和切入點。通俗的講就是在什么時候在什么地方做什么事。

          引入(Introduction)

          引入是指允許我們向現(xiàn)有的類添加新的方法和屬性。個人覺得這個特性盡管很強大,但是大部分情況下沒有多大作用,因為如果一個類需要切面來增加新的方法或者屬性的話那么我們可以有很多更優(yōu)美的方式繞過此問題,而是在繞不過的時候可能就不是很在乎這個功能了。

          目標(Target)

          目標是被通知的對象,比如我們2.1例子中的ServiceImpl 對象。

          代理(Proxy)

          代理是目標對象被通知引用后創(chuàng)建出來新的對象。比如在2.1例子中我們拿到的Service對象都不是ServiceImpl本身,而是其包裝的子類ServiceImpl$$EnhancerByGuice$$96717882。

          織入(Weaving)

          所謂織入就是把切面應(yīng)用到目標對象來創(chuàng)建新的代理對象的過程。通常情況下我們有幾種實際來完成織入過程:

          編譯時:就是在Java源文件編程成class時完成織入過程。AspectJ就存在一個編譯器,運行在編譯時將切面的字節(jié)碼編譯到目標字節(jié)碼中。

          類加載時:切面在目標類加載到JVM虛擬機中時織入。由于是在類裝載過程發(fā)生的,因此就需要一個特殊的類裝載器(ClassLoader),AspectJ就支持這種特性。

          運行時:切面在目標類的某個運行時刻被織入。一般情況下AOP的容器會建立一個新的代理對象來完成目標對象的功能。事實上在2.1例子中Guice就是使用的此方式。

          Guice支持AOP的條件是:

          • 類必須是public或者package (default)
          • 類不能是final類型的
          • 方法必須是public,package或者protected
          • 方法不能使final類型的
          • 實例必須通過Guice的@Inject注入或者有一個無參數(shù)的構(gòu)造函數(shù)

          2.3 切面注入依賴

          如果一個切面(攔截器)也需要注入一些依賴怎么辦?沒關(guān)系,Guice允許在關(guān)聯(lián)切面之前將切面的依賴都注入。比如看下面的例子。

          我們有一個前置服務(wù),就是將所有調(diào)用的方法名稱輸出。


          1 @ImplementedBy(BeforeServiceImpl.class)
          2 public interface BeforeService {
          3 
          4     void before(MethodInvocation invocation);
          5 }
          6 

          1 public class BeforeServiceImpl implements BeforeService {
          2 
          3     @Override
          4     public void before(MethodInvocation invocation) {
          5         System.out.println("before method "+invocation.getMethod().getName());
          6     }
          7 }
          8 

          然后有一個切面,這個切面依賴前置服務(wù),然后輸出一條方法調(diào)用結(jié)束語句。

           1 public class AfterMethodInterceptor implements MethodInterceptor {
           2    @Inject
           3     private BeforeService beforeService;
           4     @Override
           5     public Object invoke(MethodInvocation invocation) throws Throwable {
           6         beforeService.before(invocation);
           7         Object ret = null;
           8         try {
           9             ret = invocation.proceed();
          10         } finally {
          11             System.out.println("after "+invocation.getMethod().getName());
          12         }
          13         return ret;
          14     }
          15 }

           

          在AopDemo2中演示了如何注入切面的依賴。在第9行,AfterMethodInterceptor 請求Guice注入其依賴。

           1 public class AopDemo2 {
           2     @Inject
           3     private Service service;
           4     public static void main(String[] args) {
           5         Injector inj = Guice.createInjector(new Module() {
           6             @Override
           7             public void configure(Binder binder) {
           8                 AfterMethodInterceptor after= new AfterMethodInterceptor();
           9                 binder.requestInjection(after);
          10                 binder.bindInterceptor(Matchers.any(),//
          11                         Matchers.annotatedWith(Names.named("log")),//
          12                         after);
          13             }
          14         });
          15         AopDemo2 demo=inj.getInstance(AopDemo2.class);
          16         demo.service.sayHello();
          17     }
          18 }

           

          盡管切面允許注入其依賴,但是這里需要注意的是,如果切面依賴仍然走切面的話那么程序就陷入了死循環(huán),很久就會堆溢出。

          2.4 Matcher

          Binder綁定一個切面的API是

          com.google.inject.Binder.bindInterceptor(Matcher<? super Class<?>>, Matcher<? super Method>, MethodInterceptor...)

          第一個參數(shù)是匹配類,第二個參數(shù)是匹配方法,第三個數(shù)組參數(shù)是方法攔截器。也就是說目前為止Guice只能攔截到方法,然后才做一些切面工作。

          對于Matcher有如下API:

          • com.google.inject.matcher.Matcher.matches(T)
          • com.google.inject.matcher.Matcher.and(Matcher<? super T>)
          • com.google.inject.matcher.Matcher.or(Matcher<? super T>)

          其中第2、3個方法我沒有發(fā)現(xiàn)有什么用,好像Guice不適用它們,目前沒有整明白。

          對于第一個方法,如果是匹配Class那么這里T就是一個Class<?>的類型,如果是匹配Method就是一個Method對象。不好理解吧。看一個例子。

           1 public class ServiceClassMatcher implements Matcher<Class<?>>{
           2     @Override
           3     public Matcher<Class<?>> and(Matcher<? super Class<?>> other) {
           4         return null;
           5     }
           6     @Override
           7     public boolean matches(Class<?> t) {
           8         return t==ServiceImpl.class;
           9     }
          10     @Override
          11     public Matcher<Class<?>> or(Matcher<? super Class<?>> other) {
          12         return null;
          13     }
          14 }

           

          在前面的例子中我們是使用的Matchers.any()對象匹配所有類而通過標注來識別方法,這里可以只匹配ServiceImpl類來控制服務(wù)運行流程。

          事實上Guice里面有一個Matcher的抽象類com.google.inject.matcher.AbstractMatcher<T>,我們只需要覆蓋其中的matches方法即可。

          大多數(shù)情況下我們只需要使用Matchers提供的默認類即可。Matchers中有如下API:

          • com.google.inject.matcher.Matchers.any():任意類或者方法
          • com.google.inject.matcher.Matchers.not(Matcher<? super T>):不滿足此條件的類或者方法
          • com.google.inject.matcher.Matchers.annotatedWith(Class<? extends Annotation>):帶有此注解的類或者方法
          • com.google.inject.matcher.Matchers.annotatedWith(Annotation):帶有此注解的類或者方法
          • com.google.inject.matcher.Matchers.subclassesOf(Class<?>):匹配此類的子類型(包括本身類型)
          • com.google.inject.matcher.Matchers.only(Object):與指定類型相等的類或者方法(這里是指equals方法返回true)
          • com.google.inject.matcher.Matchers.identicalTo(Object):與指定類型相同的類或者方法(這里是指同一個對象)
          • com.google.inject.matcher.Matchers.inPackage(Package):包相同的類
          • com.google.inject.matcher.Matchers.inSubpackage(String):子包中的類(包括此包)
          • com.google.inject.matcher.Matchers.returns(Matcher<? super Class<?>>):返回值為指定類型的方法

          通常只需要使用上面的方法或者組合方法就能滿足我們的需求。

          通過上面的學習可以看出,Guice的AOP還是很弱的,目前僅僅支持方法級別上的,另外靈活性也不是很高。

          上一篇:Google Guice 入門教程04 - 依賴注入(04)

          下一篇:Google Guice 入門教程06 – Web 和 Servlet



          ©2009-2014 IMXYLZ |求賢若渴
          posted on 2009-12-27 00:16 imxylz 閱讀(17773) 評論(2)  編輯  收藏 所屬分類: J2EE 、Google Guice

          評論

          # re: Google Guice 入門教程05 - AOP(面向切面編程) 2009-12-27 02:20 adultdy
          學習了?。。?!  回復  更多評論
            

          # re: Google Guice 入門教程05 - AOP(面向切面編程)[未登錄] 2012-06-01 15:35 peter
          很有幫助 非常感謝  回復  更多評論
            


          ©2009-2014 IMXYLZ
          主站蜘蛛池模板: 科技| 和硕县| 襄城县| 梁平县| 长治市| 定南县| 定远县| 泌阳县| 丰镇市| 高淳县| 清徐县| 汉沽区| 称多县| 类乌齐县| 饶平县| 湖州市| 若羌县| 内乡县| 科尔| 慈溪市| 北海市| 米林县| 阿巴嘎旗| 菏泽市| 民丰县| 南投县| 浮山县| 武宁县| 扶绥县| 贵南县| 邳州市| 蓬安县| 大英县| 平顶山市| 岑溪市| 泗水县| 山东省| 南开区| 巨鹿县| 怀集县| 汤阴县|