1. 程式人生 > >Spring-AOP配置切入點方式及配置各種型別增強

Spring-AOP配置切入點方式及配置各種型別增強

AOP(Aspect-Oriented Programming):面向切面程式設計
是一種通過預編譯方式和執行期動態代理實現在不修改原始碼的情況下給程式動態新增功能的技術

AOP相關jar包:
spring-aop-4.2.5.RELEASE.jar
aopalliance-1.0.jar
aspectjweaver-1.6.9.jar
cglib-nodep-2.1_3.jar

使用AOP之前先配置切入點:

    <aop:config>
        <!-- 配置切入點 -->
        <!-- execution()語法:execution (* com.xx.xx.impl..*.*(..))
        首先明白這個表示式是用來匹配方法的,各種條件是為了篩選整個專案的方法。
        (類的訪問修飾符
        第一個*表示方法返回值型別[*表示所有型別] 
        com.xx.xx.impl表示包路徑[*表示所有包]
        .[.表示當前包下所有類的方法,..表示當前包下及此包下所有子包中的所有類的方法] 
        第二個*表示類名[*表示所有類,可以匹配以X開頭或結尾如X*、*X、X*X的類名]
        第三個*表示方法名[*表示所有方法,可以匹配以X開頭或結尾的如X*、*X、X*X的方法名]
        (..)表示方法引數[..表示任何引數]
        )-->
<aop:pointcut expression="execution(public * com.bc.aop..*.*(..))" id="pointcut"/> </aop:config>

一、通過Advisor配置增強處理(不推薦)

  • 新建一個類MethodBeforeAdvice介面的before方法
/**前置增強*/
public class BeforeLogger implements MethodBeforeAdvice {

    private Logger logger = Logger.getLogger(BeforeLogger.class);

    @Override
public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable { logger.info("Method:" + arg0.getName()); logger.info("Object[]:" + Arrays.toString(arg1)); logger.info("Object:" + arg2.toString()); logger.info("這是BeforeLogger類的before方法!"); } }
  • 用來測試的切入點類
/**測試業務類*/
public class UserBiz {

    public String addUser(String uname, String pwd) {
        System.out.println("這是UserBiz類的addUser方法!");
        return "UserBiz類的addUser方法返回值";
    }
}
  • 使用<aop:advisor>配置切入點和前置增強方法
<aop:advisor advice-ref="beforeLogger" pointcut-ref="pointcut"/>
  • 相關實體bean配置略

  • 測試程式碼

        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserBiz user = (UserBiz) ac.getBean("userBiz01");
        user.addUser("張三", "李四");
        System.out.println("user:" + user.toString());
  • 執行結果

這裡寫圖片描述

  • 這種方式不推薦使用,其他增強略

二、通過<aop:aspect>配置實現增強

先介紹下用來訪問連線點上下文資訊的物件

AspectJ使用org.aspectj.lang.JoinPoint介面表示目標類連線點物件,如果是環繞增強時,使用org.aspectj.lang.ProceedingJoinPoint表示連線點物件,該類是JoinPoint的子介面。任何一個增強方法都可以通過將第一個入參宣告為JoinPoint訪問到連線點上下文的資訊。我們先來了解一下這兩個介面的主要方法:

1)JoinPoint
- java.lang.Object[] getArgs():獲取連線點方法執行時的入參列表;
- Signature getSignature() :獲取連線點的方法簽名物件;
- java.lang.Object getTarget() :獲取連線點所在的目標物件;
- java.lang.Object getThis() :獲取代理物件本身;

2)ProceedingJoinPoint
ProceedingJoinPoint繼承JoinPoint子介面,它新增了兩個用於執行連線點方法的方法:
- java.lang.Object proceed() throws java.lang.Throwable:通過反射執行目標物件的連線點處的方法;
- java.lang.Object proceed(java.lang.Object[] args) throws java.lang.Throwable:通過反射執行目標物件連線點處的方法,不過使用新的入參替換原來的入參。

接下來是各種增強測試,相關物件bean配置略

增強類程式碼

public class MyLogger {

    private Logger logger = Logger.getLogger(MyLogger.class);

    /**前置增強方法*/
    public void beforeLogger(JoinPoint jp) {
        logger.info("這是MyLogger類的before方法!");
        System.out.println("切入點方法入參列表:" + Arrays.toString(jp.getArgs()));
        System.out.println("切入點方法簽名物件:" + jp.getSignature());
        System.out.println("切入點所在目標物件:" + jp.getTarget());
        System.out.println("代理物件本身:" + jp.getThis());
    }

    /**後置增強方法*/
    public void afterReturning(JoinPoint jp, Object result) {
        logger.info("這是MyLogger類的after-returning方法!");
        System.out.println("切入點方法入參列表:" + Arrays.toString(jp.getArgs()));
        System.out.println("切入點方法簽名物件:" + jp.getSignature());
        System.out.println("切入點所在目標物件:" + jp.getTarget());
        System.out.println("代理物件本身:" + jp.getThis());
        System.out.println("切入點方法返回物件:" + result);
    }

    /**後置異常增強方法*/
    public void afterThrowing(JoinPoint jp, Exception e) {
        logger.info("這是MyLogger類的after-Throwing方法!");
        System.out.println("切入點方法入參列表:" + Arrays.toString(jp.getArgs()));
        System.out.println("切入點方法簽名物件:" + jp.getSignature());
        System.out.println("切入點所在目標物件:" + jp.getTarget());
        System.out.println("代理物件本身:" + jp.getThis());
        System.out.println("異常:" + e);
    }

    /**最終增強方法*/
    public void after(JoinPoint jp) {
        logger.info("這是MyLogger類的after方法!");
        System.out.println("切入點方法入參列表:" + Arrays.toString(jp.getArgs()));
        System.out.println("切入點方法簽名物件:" + jp.getSignature());
        System.out.println("切入點所在目標物件:" + jp.getTarget());
        System.out.println("代理物件本身:" + jp.getThis());
    }

    /**環繞增強方法*/
    public void aroundLogger(ProceedingJoinPoint jp) {
        logger.info("這是MyLogger類的around方法!");
        System.out.println("切入點方法入參列表:" + Arrays.toString(jp.getArgs()));
        System.out.println("切入點方法簽名物件:" + jp.getSignature());
        System.out.println("切入點所在目標物件:" + jp.getTarget());
        System.out.println("代理物件本身:" + jp.getThis());
        System.out.println("-------------------------------");
        try {
            System.out.println("執行切入點方法");
            jp.proceed();
            System.out.println("-------------------------------");
            System.out.println("執行切入點方法並改變引數");
            jp.proceed(new Object[]{7});
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }
}

切入點類程式碼

/**用於AOP測試的切入點類*/
public class UserInfo {

    public String beforeTest(int i) {
        System.out.println("這是UserInfo類的beforeTest方法!");
        return "beforeTest方法返回值";
    }

    public String afterReturningTest(int i) {
        System.out.println("這是UserInfo類的afterReturningTest方法!");
        return "afterReturningTest方法返回值";
    }

    public String afterThrowingTest(int i) {
        System.out.println("這是UserInfo類的afterThrowingTest方法!");
        throw new RuntimeException("afterThrowingTest方法丟擲的異常");
    }

    public String afterTest(int i) {
        System.out.println("這是UserInfo類的afterTest方法!");
        throw new RuntimeException("afterTest方法丟擲的異常");
    }

    public String aroundTest(int i) {
        System.out.println("這是UserInfo類的aroundTest方法!引數值:" + i);
        return "aroundTest方法返回值";
    }
}

applicationContext.xml配置

<!-- 配置多個增強方式 ref引用配置的增強類bean id-->
<aop:config>
    <!--配置切入點-->
    <aop:aspect ref="myLogger">
        <!--配置各種型別增強-->
    </aop:aspect>
</aop:config>
  • 前置增強

配置

<aop:before method="beforeLogger" pointcut-ref="pointcut"/>

測試程式碼

ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
UserInfo user = (UserInfo) ac.getBean("userInfo");
//前置增強測試
user.beforeTest(1);

控制檯

2016-06-02 15:50:45,127 INFO [com.bc.aop.demo02.MyLogger] - 這是MyLogger類的before方法!
切入點方法入參列表:[1]
切入點方法簽名物件:String com.bc.aop.demo02.UserInfo.beforeTest(int)
切入點所在目標物件:com.bc.aop.demo02.UserInfo@557caf28
代理物件本身:com.bc.aop.demo02.UserInfo@557caf28
這是UserInfo類的beforeTest方法!

結論:
從控制檯輸出來看,前置增強會在切入點方法執行之前執行,並可以通過JoinPoint物件獲取切入點方法的相關引數,如引數列表、方法簽名物件、切入點所在物件及代理物件本身。

  • 後置增強

配置

<aop:after-returning method="afterReturning" pointcut-ref="pointcut" returning="result"/>

測試程式碼

ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
UserInfo user = (UserInfo) ac.getBean("userInfo");
//後置增強測試
user.afterReturningTest(2);

控制檯

這是UserInfo類的afterReturningTest方法!
2016-06-02 15:56:08,334 INFO [com.bc.aop.demo02.MyLogger] - 這是MyLogger類的after-returning方法!
切入點方法入參列表:[2]
切入點方法簽名物件:String com.bc.aop.demo02.UserInfo.afterReturningTest(int)
切入點所在目標物件:com.bc.aop.demo02.UserInfo@139982de
代理物件本身:com.bc.aop.demo02.UserInfo@139982de
切入點方法返回物件:afterReturningTest方法返回值

結論
從控制檯輸出看出,後置增強會在切入點方法執行之後,再執行增強方法,因此除了可以像前置增強那樣獲取切入點方法的相關資訊,還可以獲取切入點方法的返回值,此外如果切入點方法沒有正常執行,如丟擲異常,則不會執行後置增強方法。

  • 後置異常增強

配置

<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="e"/>

測試程式碼

ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
UserInfo user = (UserInfo) ac.getBean("userInfo");
try {
//後置異常增強測試
user.afterThrowingTest(3);

控制檯

這是UserInfo類的afterReturningTest方法!
這是UserInfo類的afterThrowingTest方法!
2016-06-02 16:00:53,481 INFO [com.bc.aop.demo02.MyLogger] - 這是MyLogger類的after-Throwing方法!
切入點方法入參列表:[3]
切入點方法簽名物件:String com.bc.aop.demo02.UserInfo.afterThrowingTest(int)
切入點所在目標物件:com.bc.aop.demo02.UserInfo@385c9627
代理物件本身:com.bc.aop.demo02.UserInfo@385c9627
異常:java.lang.RuntimeException: afterThrowingTest方法丟擲的異常

結論
從控制檯輸出看出,後置異常增強會在切入點方法丟擲異常的時候執行異常增強方法,除了可以獲取方法相關資訊之外也可以獲取對應的異常資訊。

  • 最終增強

配置

<aop:after method="after" pointcut-ref="pointcut"/>

測試程式碼

ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
UserInfo user = (UserInfo) ac.getBean("userInfo");
try {
user.afterTest(4);
} catch (Exception e) {}

控制檯

這是UserInfo類的afterTest方法!
2016-06-02 16:06:15,169 INFO [com.bc.aop.demo02.MyLogger] - 這是MyLogger類的after方法!
切入點方法入參列表:[4]
切入點方法簽名物件:String com.bc.aop.demo02.UserInfo.afterTest(int)
切入點所在目標物件:com.bc.aop.demo02.UserInfo@7586beff
代理物件本身:com.bc.aop.demo02.UserInfo@7586beff

結論
從控制檯和切入點方法可以看出,最終增強無論切入點方法是否正常執行完畢,都會執行增強方法,因此不可以獲取返回值或者異常,只可以獲取和前置增強相同的資訊。

  • 環繞增強

配置

<aop:around method="aroundLogger" pointcut-ref="pointcut"/>

測試程式碼

ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
UserInfo user = (UserInfo) ac.getBean("userInfo");

//環繞增強測試
user.aroundTest(5);

控制檯

2016-06-02 16:12:00,054 INFO [com.bc.aop.demo02.MyLogger] - 這是MyLogger類的around方法!
切入點方法入參列表:[5]
切入點方法簽名物件:String com.bc.aop.demo02.UserInfo.aroundTest(int)
切入點所在目標物件:com.bc.aop.demo02.UserInfo@557caf28
代理物件本身:com.bc.aop.demo02.UserInfo@557caf28
-------------------------------
執行切入點方法
這是UserInfo類的aroundTest方法!引數值:5
-------------------------------
執行切入點方法並改變引數
這是UserInfo類的aroundTest方法!引數值:7

結論
環繞增強比較特殊,如果一個方法配置了環繞增強,那麼執行此方法只會執行環繞增強的方法,然後可以在環繞增強的方法中通過JoinPoint或ProceedingJoinPoint(只對環繞增強有效,其餘增強使用此物件會報錯)獲取切入點方法的相關資訊,同時,使用ProceedingJoinPoint還可以執行N次切入點方法,也可以改變切入點方法的引數數值(引數數量需要保持相同),因此,使用環繞增強,要想執行切入點方法需要在增強方法內呼叫ProceedingJoinPoint物件的proceed()方法來執行切入點方法。

三、使用註解方式配置AOP

首先先修改下applicationContext.xml配置檔案

<!-- 開啟切面註解 -->
<aop:aspectj-autoproxy proxy-target-class="true"/>
<!-- 掃描註解包 -->
<context:component-scan base-package="com.bc"/>

然後用上面的MyLogger類來改

@Aspect
@Component//必須有這個註解
public class MyLogger {

    private Logger logger = Logger.getLogger(MyLogger.class);

    //定義一個切入點
    @Pointcut("execution(public * com.bc.aop..*.*(..))")
    public void pointcut() {}

    /**前置增強方法*/
    @Before("pointcut()")
    //@Before("execution(public * com.bc.aop..*.*(..))")可以給方法單獨指定切入點
    public void beforeLogger(JoinPoint jp) {
        logger.info("這是MyLogger類的before方法!");
        System.out.println("切入點方法入參列表:" + Arrays.toString(jp.getArgs()));
        System.out.println("切入點方法簽名物件:" + jp.getSignature());
        System.out.println("切入點所在目標物件:" + jp.getTarget());
        System.out.println("代理物件本身:" + jp.getThis());
    }

    /**後置增強方法*/
    @AfterReturning(pointcut = "pointcut()", returning = "result")
    public void afterReturning(JoinPoint jp, Object result) {
        logger.info("這是MyLogger類的after-returning方法!");
        System.out.println("切入點方法入參列表:" + Arrays.toString(jp.getArgs()));
        System.out.println("切入點方法簽名物件:" + jp.getSignature());
        System.out.println("切入點所在目標物件:" + jp.getTarget());
        System.out.println("代理物件本身:" + jp.getThis());
        System.out.println("切入點方法返回物件:" + result);
    }

    /**後置異常增強方法*/
    @AfterThrowing(pointcut = "pointcut()", throwing = "e")
    public void afterThrowing(JoinPoint jp, Exception e) {
        logger.info("這是MyLogger類的after-Throwing方法!");
        System.out.println("切入點方法入參列表:" + Arrays.toString(jp.getArgs()));
        System.out.println("切入點方法簽名物件:" + jp.getSignature());
        System.out.println("切入點所在目標物件:" + jp.getTarget());
        System.out.println("代理物件本身:" + jp.getThis());
        System.out.println("異常:" + e);
    }

    /**最終增強方法*/
    @After("pointcut()")
    public void after(JoinPoint jp) {
        logger.info("這是MyLogger類的after方法!");
        System.out.println("切入點方法入參列表:" + Arrays.toString(jp.getArgs()));
        System.out.println("切入點方法簽名物件:" + jp.getSignature());
        System.out.println("切入點所在目標物件:" + jp.getTarget());
        System.out.println("代理物件本身:" + jp.getThis());
    }

    /**環繞增強方法*/
    @Around("pointcut()")
    public void aroundLogger(ProceedingJoinPoint jp) {
        logger.info("這是MyLogger類的around方法!");
        System.out.println("切入點方法入參列表:" + Arrays.toString(jp.getArgs()));
        System.out.println("切入點方法簽名物件:" + jp.getSignature());
        System.out.println("切入點所在目標物件:" + jp.getTarget());
        System.out.println("代理物件本身:" + jp.getThis());
        System.out.println("-------------------------------");
        try {
            System.out.println("執行切入點方法");
            jp.proceed();
            System.out.println("-------------------------------");
            System.out.println("執行切入點方法並改變引數");
            jp.proceed(new Object[]{7});
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }
}

切入點測試類程式碼

@Service("person")
public class Person {

    public String personTest(int i) {
        System.out.println("這是Person類的personTest方法,引數值:" + i);
        return "personTest方法返回值";
    }
}

測試程式碼

ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
Person person = (Person) ac.getBean("person");
person.personTest(1);

控制檯

2016-06-02 16:48:11,347 INFO [com.bc.aop.demo03.MyLogger] - 這是MyLogger類的around方法!
切入點方法入參列表:[1]
切入點方法簽名物件:String com.bc.aop.demo03.Person.personTest(int)
切入點所在目標物件:com.bc.aop.demo03.Person@1a4013
代理物件本身:com.bc.aop.demo03.Person@1a4013
-------------------------------
執行切入點方法
2016-06-02 16:48:11,367 INFO [com.bc.aop.demo03.MyLogger] - 這是MyLogger類的before方法!
切入點方法入參列表:[1]
切入點方法簽名物件:String com.bc.aop.demo03.Person.personTest(int)
切入點所在目標物件:com.bc.aop.demo03.Person@1a4013
代理物件本身:com.bc.aop.demo03.Person@1a4013
這是Person類的personTest方法,引數值:1
-------------------------------
執行切入點方法並改變引數
2016-06-02 16:48:11,371 INFO [com.bc.aop.demo03.MyLogger] - 這是MyLogger類的before方法!
切入點方法入參列表:[1]
切入點方法簽名物件:String com.bc.aop.demo03.Person.personTest(int)
切入點所在目標物件:com.bc.aop.demo03.Person@1a4013
代理物件本身:com.bc.aop.demo03.Person@1a4013
這是Person類的personTest方法,引數值:7
2016-06-02 16:48:11,372 INFO [com.bc.aop.demo03.MyLogger] - 這是MyLogger類的after方法!
切入點方法入參列表:[1]
切入點方法簽名物件:String com.bc.aop.demo03.Person.personTest(int)
切入點所在目標物件:com.bc.aop.demo03.Person@1a4013
代理物件本身:com.bc.aop.demo03.Person@1a4013
2016-06-02 16:48:11,372 INFO [com.bc.aop.demo03.MyLogger] - 這是MyLogger類的after-returning方法!
切入點方法入參列表:[1]
切入點方法簽名物件:String com.bc.aop.demo03.Person.personTest(int)
切入點所在目標物件:com.bc.aop.demo03.Person@1a4013
代理物件本身:com.bc.aop.demo03.Person@1a4013
切入點方法返回物件:null

註解的方式可以大大減少配置程式碼,但是個人也不是很喜歡這種侵入式配置,不利於後期維護