背景:

對dubbo 的consumer端進行統一監控,實現consumer的統一異常處理、前置provider服務的可用性校驗(若dubobo服務不可以發簡訊提醒)

思路:

(1)自定義annotation,僅作用在類、方法上。減少程式碼耦合性,consumer的類或方法只要增加自定義的註解即可。

(2)猶豫自定義的annotation有可能標註在服務類上,不一定只標註在controller上,所以用springmvc的interceptor有弊端,所以想到用spring的aop的環繞通知進行攔截處理

1.自定義annotation

<span style="font-family:SimSun;font-size:10px;">@Target({ TYPE, METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface Monitor {
    /**
     * 消費的服務
     * @return
     */
    String serviceName() default "";
    /**
     * 服務描述
     * @return
     */
    String desc() default "";
}</span>

1.annotation實現邏輯

<span style="font-family: Arial; font-size: 14px;">  </span><span style="font-family:SimSun;font-size:10px;"> //註解在類上或方法上
                Monitor monitor=mHandler.getMethodAnnotation(Monitor.class);
                if (beanClass.isAnnotationPresent(Monitor.class)||monitor!=null) {
                    String serviceName=monitor.serviceName();
                    if(StringUtils.isNotBlank(serviceName)){
                        EchoService echoService = null;
                        try {
                            echoService = (EchoService) SpringContextUtil.getBean(monitor.serviceName());
                        } catch (Exception e) {
                            log.error("不存在的bean,benName:"+serviceName,e);
                        }
                        try {
                            Object status = echoService.$echo("OK");
                            assert ("OK".equals(status.toString()));
                        } catch (Exception e) {
                            log.error("dubbo 呼叫失敗!請檢查是否存在此服務或dubbo是否正常!serviceName:"+serviceName,e);
                            //傳送簡訊通知 TODO
                        }
                    }
                }</span>


3. Spring AOP

以@AspectJ方式在Spring中實現AOP。由於@Aspect是基於註解的,因此要求支援註解的5.0版本以上的JDK。

要在專案中使用Spring AOP 則需要在專案中匯入除了spring jar包之外,還有aspectjweaver.jar,aspectjrt.jar 和cglib.jar 。

在Spring MVC基本上只需另外加上aspectjweaver.jar和cglib.jar就可以了

  1. <dependency>
  2.     <groupId>org.aspectj</groupId>
  3.     <artifactId>aspectjweaver</artifactId>
  4.     <version>1.7.1</version>
  5. </dependency>
  6. <dependency>
  7.     <groupId>cglib</groupId>
  8.     <artifactId>cglib</artifactId>
  9.     <version>2.2.2</version>
  10. </dependency>

好了,前提工作準備完成。


b.環繞增強的@AspectJ程式碼

<span style="font-family:SimSun;font-size:10px;">@Component
@Aspect
public class DubboMonitor {
    @Around(value = "execution(* dubbo.consumer.adapter.*.*(..)) && @annotation(monitor)")
    public Object aroundMethod(ProceedingJoinPoint pjd, Monitor monitor) {
        Object result = null;
        System.out.println(monitor.desc());
        try {
            System.out.println("前置通知");
            result = pjd.proceed();
            System.out.println("後置通知");
        } catch (Throwable e) {
            System.out.println("異常通知");
        }
        System.out.println("返回通知");
        return result;
    }
}</span>
  1. @Aspect
  2. publicclass ApiAspect {  
  3.     privatestaticfinal Logger logger = LoggerFactory.getLogger("com.xxx.log");  
  4.     //通過within匹配目標方法的class
  5.     @Around("within(com.xxx.api.*Controller)")  
  6.     public String arountAction(ProceedingJoinPoint pjp){  
  7.         //介面request引數檢查,
  8.         HttpServletRequest request = (HttpServletRequest) pjp.getArgs()[0];  
  9.         try {  
  10.             //TODO check
  11.         } catch (Exception e) {  
  12.             //TODO log & return
  13.         }  
  14.         String result = null;  
  15.         try {  
  16.             //執行目標方法
  17.             result = (String) pjp.proceed();  
  18.             //TODO log
  19.         } catch (Throwable e) {  
  20.             //TODO log & return
  21.         }  
  22.         return result;  
  23.     }  
  24. }  
c.Spring xml配置
  1. <aop:aspectj-autoproxy/>
  2. <beans:beanclass="com.xxx.api.ApiAspect"/>  (可以用@component註解代替此行配置)
  3. <context:component-scanbase-package="com.xxx.api.*"/>

這樣就實現了所需的功能 ,AOP的優點就是對目標方法程式碼不做任何改動,就可以實現前後處理,另外在b中也可以用@Before,@After等其它註解來實現不同的功能,下面就介紹下@AspectJ的詳細用法

4. @AspectJ的詳細用法

在Spring AOP中目前只有執行方法這一個連線點,Spring AOP支援的AspectJ切入點指示符如下:

一些常見的切入點的例子 
execution(public * * (. .))    任意公共方法被執行時,執行切入點函式。 
execution( * set* (. .))   任何以一個“set”開始的方法被執行時,執行切入點函式。 
execution( * com.demo.service.AccountService.* (. .))  當介面AccountService 中的任意方法被執行時,執行切入點函式。 
execution( * com.demo.service.*.* (. .))  當service 包中的任意方法被執行時,執行切入點函式。 within(com.demo.service.*)  在service 包裡的任意連線點。 within(com.demo.service. .*)  在service 包或子包的任意連線點。
this(com.demo.service.AccountService) 實現了AccountService 介面的代理物件的任意連線點。 
target(com.demo.service.AccountService) 實現了AccountService 介面的目標物件的任意連線點。 
args(java.io.Serializable)  任何一個只接受一個引數,且在執行時傳入引數實現了 Serializable 介面的連線點

增強的方式:

@Before:方法前執行

@AfterReturning:執行方法後執行

@AfterThrowing:Throw後執行

@After:無論方法以何種方式結束,都會執行(類似於finally)

@Around:環繞執行

@AspectJ語法基礎
@AspectJ使用JDK 5.0註解和正規的AspectJ 5的切點表示式語言描述切面,由於Spring只支援方法的連線點,所以Spring僅支援部分AspectJ的切點語言。在這節時,我們將對AspectJ切點表示式語言進行必要的學習。
切點表示式函式
    AspectJ 5的切點表示式由關鍵字和操作引數組成,如execution(* greetTo(..))的切點表示式,“execute”為關鍵字,而“* greetTo(..)”為操作引數。在這裡,execute代表目標類執行某一方法,而“* greetTo(..)”是描述目標方法的匹配模式串,兩者聯合起來所表示的切點匹配目標類greetTo()方法的連線點。為了描述方便,我們將 execution()稱作函式,而將匹配串“* greetTo(..)”稱作函式的入參。 
Spring支援9個@ApsectJ切點表示式函式,它們用不同的方式描述目標類的連線點,根據描述物件的不同,可以將它們大致分為4種類型: 
? 方法切點函式:通過描述目標類方法資訊定義連線點; 
? 方法入參切點函式:通過描述目標類方法入參的資訊定義連線點; 
? 目標類切點函式:通過描述目標類型別資訊定義連線點; 
? 代理類切點函式:通過描述目標類的代理類的資訊定義連線點; 
     這4種類型的切點函式,通過表 1進行說明: 
    表 1 切點函式

類別
函式
入參
說明
方法切點函式
execution()
方法
匹配模式串
表示滿足某一匹配模式的所有目標類方法連線點。如execution(* greetTo(..))表示所有目標類中的greetTo()方法。
@annotation()
方法注
解類名
表示標註了特定註解的目標方法連線點。如@annotation(com.baobaotao.anno.NeedTest)表示任何標註了@NeedTest註解的目標類方法。
方法入參切點函式
args()
類名
通過判別目標類方法執行時入參物件的型別定義指定連線點。如args(com.baobaotao.Waiter)表示所有有且僅有一個按型別匹配於Waiter的入參的方法。
@args()
型別注
解類名
通過判別目標方法的執行時入參物件的類是否標註特定註解來指定連線點。如@args(com.baobaotao.Monitorable)表示任何這樣的一個目標方法:它有一個入參且入參物件的類標註@Monitorable註解。
目標類切點函式
within()
類名匹配串
   表 示特定域下的所有連線點。如within(com.baobaotao.service.*)表示com.baobaotao.service包中的所有 連線點,也即包中所有類的所有方法,而within(com.baobaotao.service.*Service)表示在 com.baobaotao.service包中,所有以Service結尾的類的所有連線點。
target()
類名
   假如目標類按型別匹配於指定類,則目標類的所有連線點匹配這個切點。如通過target(com.baobaotao.Waiter)定義的切點,Waiter、以及Waiter實現類NaiveWaiter中所有連線點都匹配該切點。
@within()
型別註解類名
   假如目標類按型別匹配於某個類A,且類A標註了特定註解,則目標類的所有連線點匹配這個切點。
   如@within(com.baobaotao.Monitorable)定義的切點,假如Waiter類標註了@Monitorable註解,則Waiter以及Waiter實現類NaiveWaiter類的所有連線點都匹配。
@target()
型別註解類名
   目標類標註了特定註解,則目標類所有連線點匹配該切點。如@target(com.baobaotao.Monitorable),假如NaiveWaiter標註了@Monitorable,則NaiveWaiter所有連線點匹配切點。
代理類切點函式
this()
類名
 代理類按型別匹配於指定類,則被代理的目標類所有連線點匹配切點。這個函式比較難理解,這裡暫不舉例,留待後面詳解。

@AspectJ 除上表中所列的函式外,還有call()、initialization()、 preinitialization()、 staticinitialization()、 get()、 set()、handler()、 adviceexecution()、 withincode()、 cflow()、 cflowbelow()、 if()、 @this()以及@withincode()等函式,這些函式在Spring中不能使用,否則會丟擲IllegalArgumentException 異常。在不特別宣告的情況下,本書中所講@AspectJ函式均指表 1中所列的函式。