Spring AOP 的幾個知識點

轉自 http://my.oschina.net/sniperLi/blog/491854

一. 什麼是AOP

AOP是OOP的延續,是Aspect Oriented Programming的縮寫,意思是面向切面編程。可以通過預編譯方式和運行期動態代理實現在不修改源代碼的情況下給程序動態統一添加功能的一種技術。AOP實際是GoF設計模式的延續,設計模式孜孜不倦追求的是調用者和被調用者之間的解耦,AOP可以說也是這種目標的一種實現。

我們現在做的一些非業務,如:日誌、事務、安全等都會寫在業務代碼中(也即是說,這些非業務類橫切於業務類),但這些代碼往往是重複,複製——粘貼式的代碼會給程序的維護帶來不便,AOP就實現了把這些業務需求與系統需求分開來做。這種解決的方式也稱代理機制。

二. AOP的相關概念

  • 切面(Aspect):官方的抽象定義爲“一個關注點的模塊化,這個關注點可能會橫切多個對象”,在本例中,“切面”就是類TestAspect所關注的具體行爲,例如,AServiceImpl.barA()的調用就是切面TestAspect所關注的行爲之一。“切面”在ApplicationContext中來配置。

  • 連接點(Joinpoint) :程序執行過程中的某一行爲,例如,UserService.get的調用或者UserService.delete拋出異常等行爲。

  • 通知(Advice) :“切面”對於某個“連接點”所產生的動作,例如,TestAspect中對com.spring.service包下所有類的方法進行日誌記錄的動作就是一個Advice。其中,一個“切面”可以包含多個“Advice”,例如ServiceAspect。

  • 切入點(Pointcut) :匹配連接點的斷言,在AOP中通知和一個切入點表達式關聯。例如,TestAspect中的所有通知所關注的連接點,都由切入點表達式execution(* com.spring.service..(..))來決定。

  • 目標對象(Target Object) :被一個或者多個切面所通知的對象。例如,AServcieImpl和BServiceImpl,當然在實際運行時,Spring AOP採用代理實現,實際AOP操作的是TargetObject的代理對象。

  • AOP代理(AOP Proxy) :在Spring AOP中有兩種代理方式,JDK動態代理和CGLIB代理。默認情況下,TargetObject實現了接口時,則採用JDK動態代理,例如,AServiceImpl;反之,採用CGLIB代理,例如,BServiceImpl。強制使用CGLIB代理需要將 的 proxy-target-class屬性設爲true。

  • 通知(Advice)類型:

    • 前置通知(Before advice):在某連接點(JoinPoint)之前執行的通知,但這個通知不能阻止連接點前的執行。ApplicationContext中在裏面使用元素進行聲明。例如,TestAspect中的doBefore方法。

    • 後置通知(After advice):當某連接點退出的時候執行的通知(不論是正常返回還是異常退出)。ApplicationContext中在裏面使用元素進行聲明。例如,ServiceAspect中的returnAfter方法,所以Teser中調用UserService.delete拋出異常時,returnAfter方法仍然執行。

    • 返回後通知(After return advice):在某連接點正常完成後執行的通知,不包括拋出異常的情況。ApplicationContext中在裏面使用元素進行聲明。

    • 環繞通知(Around advice):包圍一個連接點的通知,類似Web中Servlet規範中的Filter的doFilter方法。可以在方法的調用前後完成自定義的行爲,也可以選擇不執行。ApplicationContext中在裏面使用元素進行聲明。例如,ServiceAspect中的around方法。

    • 拋出異常後通知(After throwing advice):在方法拋出異常退出時執行的通知。ApplicationContext中在裏面使用元素進行聲明。例如,ServiceAspect中的returnThrow方法。

注:可以將多個通知應用到一個目標對象上,即可以將多個切面織入到同一目標對象。

使用Spring AOP可以基於兩種方式,一種是比較方便和強大的註解方式,另一種則是中規中矩的xml配置方式。

三. 基於註解的AOP

使用註解配置Spring AOP總體分爲兩步:

  1. 第一步是在xml文件中聲明激活自動掃描組件功能,同時激活自動代理功能(同時在xml中添加一個UserService的普通服務層組件,來測試AOP的註解功能)。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:p="http://www.springframework.org/schema/p" xmlns:cache="http://www.springframework.org/schema/cache"
    xmlns:jpa="http://www.springframework.org/schema/data/jpa"

    xsi:schemaLocation="http://www.springframework.org/schema/beans     
          http://www.springframework.org/schema/beans/spring-beans-4.2.xsd     
          http://www.springframework.org/schema/context     
          http://www.springframework.org/schema/context/spring-context-4.2.xsd     
          http://www.springframework.org/schema/aop     
          http://www.springframework.org/schema/aop/spring-aop-4.2.xsd     
          http://www.springframework.org/schema/tx      
          http://www.springframework.org/schema/tx/spring-tx-4.2.xsd  
          http://www.springframework.org/schema/cache   
          http://www.springframework.org/schema/cache/spring-cache-4.2.xsd  
          http://www.springframework.org/schema/data/jpa  
          http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">

    <context:annotation-config />

    <context:component-scan base-package="com.ninelephas.meerkat" />


    <!-- 激活自動代理功能 -->
    <aop:aspectj-autoproxy proxy-target-class="true" />


</beans> 
  1. 第二步是爲Aspect切面類添加註解:
/**
 * @Title: AspectTestComponent.java
 * @Package com.ninelephas.meerkat.aspect
 * @Description: TODO
 *               Copyright: Copyright (c) 2016
 *               Company:九象網絡科技(上海)有限公司
 * 
 * @author "徐澤宇"
 * @date 2016年7月27日 下午11:44:51
 * @version V1.0.0
 */

package com.ninelephas.meerkat.tests.aop;

import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

/**
 * @ClassName: AspectTestComponent
 * @Description: TODO
 * @author Comsys-"徐澤宇"
 * @date 2016年7月27日 下午11:44:51
 *
 */
@Component("com.ninelephas.meerkat.tests.aop.AspectBean")
@Aspect
public class AspectBean {
    private static final Logger logger = Logger.getLogger(AspectBean.class);



    @Around("execution(* com.ninelephas.meerkat.tests.aop.BusinessBean.testService(..))")
    public void before(JoinPoint joinPoint) {
        logger.debug("before(JoinPoint) - start"); //$NON-NLS-1$

        logger.info("切面被執行: before " + joinPoint);

        logger.debug("before(JoinPoint) - end"); //$NON-NLS-1$
    }
}
  1. 業務service
/**
 * @Title: BusinessBean.java
 * @Package com.ninelephas.meerkat.tests.aop
 * @Description: TODO
 * Copyright: Copyright (c) 2016
 * Company:九象網絡科技(上海)有限公司
 * 
 * @author "徐澤宇"
 * @date 2016年8月1日 下午2:21:03
 * @version V1.0.0
 */

package com.ninelephas.meerkat.tests.aop;

import org.apache.log4j.Logger;
import org.springframework.stereotype.Component;

/**
  * @ClassName: BusinessBean
  * @Description: TODO
  * @author Comsys-"徐澤宇"
  * @date 2016年8月1日 下午2:21:03
  *
  */


@Component("com.ninelephas.meerkat.tests.aop.BusinessBean")
public class BusinessBean {
    /**
     * @author 徐澤宇
     * @version 1.0.0
     * @date
     */
    private static final Logger logger = Logger.getLogger(BusinessBean.class);

    public void testService(){
        logger.debug("testService() - start"); //$NON-NLS-1$


        logger.debug("testService() - end"); //$NON-NLS-1$
    }

}
  1. 測試代碼:
/**
 * @Title: AopTestBean.java
 * @Package com.ninelephas.meerkat.tests.aop
 * @Description: TODO
 * Copyright: Copyright (c) 2016
 * Company:九象網絡科技(上海)有限公司
 * 
 * @author "徐澤宇"
 * @date 2016年7月27日 下午1:10:58
 * @version V1.0.0
 */

package com.ninelephas.meerkat.tests.aop;

import org.apache.log4j.Logger;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
  * @ClassName: AopTestBean
  * @Description: TODO
  * @author Comsys-"徐澤宇"
  * @date 2016年7月27日 下午1:10:58
  *
  */

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:META-INF/applicationContext.xml" })
public class JunitTestBean extends AbstractJUnit4SpringContextTests{
    /**
     * @author 徐澤宇
     * @version 1.0.0
     * @date
     */
    private static final Logger logger = Logger.getLogger(JunitTestBean.class);

    @Autowired
    BusinessBean businessBean;

    @Test
    public  void businessMethodOne(){
        logger.debug("businessMethodOne() - start"); //$NON-NLS-1$

        businessBean.testService();

        logger.debug("businessMethodOne() - end"); //$NON-NLS-1$
    }

}
  1. 測試結果:
 [meerkat] 2016-08-01 14:47:01,892 -com.ninelephas.meerkat.tests.aop.JunitTestBean.businessMethodOne(JunitTestBean.java:46) DEBUG - businessMethodOne() - start
[meerkat] 2016-08-01 14:47:01,904 -com.ninelephas.meerkat.tests.aop.AspectBean.before(AspectBean.java:38) DEBUG - before(JoinPoint) - start
[meerkat] 2016-08-01 14:47:01,906 -com.ninelephas.meerkat.tests.aop.AspectBean.before(AspectBean.java:40) INFO  - 切面被執行: before execution(void com.ninelephas.meerkat.tests.aop.BusinessBean.testService())
[meerkat] 2016-08-01 14:47:01,906 -com.ninelephas.meerkat.tests.aop.AspectBean.before(AspectBean.java:42) DEBUG - before(JoinPoint) - end
[meerkat] 2016-08-01 14:47:01,906 -com.ninelephas.meerkat.tests.aop.JunitTestBean.businessMethodOne(JunitTestBean.java:50) DEBUG - businessMethodOne() - end

四.些通用切入點表達式的例子。

  • 任意公共方法的執行:
execution(public * *(..))
  • 任何一個名字以“set”開始的方法的執行:
execution(* set*(..))
  • AccountService接口定義的任意方法的執行:
execution(* com.xyz.service.AccountService.*(..))
  • 在service包中定義的任意方法的執行:
execution(* com.xyz.service.*.*(..))
  • 在service包或其子包中定義的任意方法的執行:
execution(* com.xyz.service..*.*(..))
  • 在service包中的任意連接點(在Spring AOP中只是方法執行):
within(com.xyz.service.*)
  • 在service包或其子包中的任意連接點(在Spring AOP中只是方法執行):
within(com.xyz.service..*)
  • 實現了AccountService接口的代理對象的任意連接點 (在Spring AOP中只是方法執行):
this(com.xyz.service.AccountService
  • ‘this’在綁定表單中更加常用:- 請參見後面的通知一節中瞭解如何使得代理對象在通知體內可用。
    實現AccountService接口的目標對象的任意連接點 (在Spring AOP中只是方法執行):
target(com.xyz.service.AccountService
  • ‘target’在綁定表單中更加常用:- 請參見後面的通知一節中瞭解如何使得目標對象在通知體內可用。
    任何一個只接受一個參數,並且運行時所傳入的參數是Serializable 接口的連接點(在Spring AOP中只是方法執行)
args(java.io.Serializable
  • ‘args’在綁定表單中更加常用:- 請參見後面的通知一節中瞭解如何使得方法參數在通知體內可用。
    請注意在例子中給出的切入點不同於 execution(* *(java.io.Serializable)): args版本只有在動態運行時候傳入參數是Serializable時才匹配,而execution版本在方法簽名中聲明只有一個 Serializable類型的參數時候匹配。
    目標對象中有一個 @Transactional 註解的任意連接點 (在Spring AOP中只是方法執行)
@target(org.springframework.transaction.annotation.Transactional
  • ‘@target’在綁定表單中更加常用:- 請參見後面的通知一節中瞭解如何使得註解對象在通知體內可用。
    任何一個目標對象聲明的類型有一個 @Transactional 註解的連接點 (在Spring AOP中只是方法執行):
@within(org.springframework.transaction.annotation.Transactional
  • ‘@within’在綁定表單中更加常用:- 請參見後面的通知一節中瞭解如何使得註解對象在通知體內可用。
    任何一個執行的方法有一個 @Transactional 註解的連接點 (在Spring AOP中只是方法執行)
@annotation(org.springframework.transaction.annotation.Transactional
  • ‘@annotation’在綁定表單中更加常用:- 請參見後面的通知一節中瞭解如何使得註解對象在通知體內可用。
    任何一個只接受一個參數,並且運行時所傳入的參數類型具有@Classified 註解的連接點(在Spring AOP中只是方法執行)
@args(com.xyz.security.Classified
  • ‘@args’在綁定表單中更加常用:- 請參見後面的通知一節中瞭解如何使得註解對象在通知體內可用。
    任何一個在名爲’tradeService’的Spring bean之上的連接點 (在Spring AOP中只是方法執行):
bean(tradeService)
  • 任何一個在名字匹配通配符表達式’*Service’的Spring bean之上的連接點 (在Spring AOP中只是方法執行):
bean(*Service
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章