1. 程式人生 > >Spring AOP 之AspectJ註解和XML配置兩種實現(Maven構建)

Spring AOP 之AspectJ註解和XML配置兩種實現(Maven構建)

xml配置

1.介面和實現類

public interface UserManager {
    public String findUserById(int userId);
}
@Service
public class UserManagerImpl implements UserManager {
    @Override
    public String findUserById(int userId) {
        System.out.println("---------UserManagerImpl.findUserById()--------");
        if
(userId <= 0) { throw new IllegalArgumentException("該使用者不存在!"); } return "張三"; } }

2.單獨寫一個Advice通知類進行測試

public class XMLAdvice {

    /**
     * 在核心業務執行前執行 不能阻止核心業務的呼叫
     */
    private void doBefore(JoinPoint joinPoint) {
        System.out.println("---doBefore().invoke--"
); System.out.println(" 此處意在執行核心業務邏輯前,做一些安全性的判斷等等"); System.out.println(" 可通過joinPoint來獲取所需要的內容"); System.out.println("-----End of doBefore()------"); } /** * 手動控制呼叫核心業務邏輯,以及呼叫前和呼叫後的處理 * <p> * 注意:當核心業務拋異常後 立即退出 轉向AfterAdvice * 執行完AfterAdvice 再轉向Throwing Advice */
private Object doAround(ProceedingJoinPoint pjp) throws Throwable { System.out.println("-----doAround().invoke-----"); System.out.println(" 此處可以做類似於Before Advice的事情"); //呼叫核心邏輯 Object retVal = pjp.proceed(); System.out.println(" 此處可以做類似於After Advice的事情"); System.out.println("-----End of doAround()------"); return retVal; } /** * 核心業務邏輯退出後(包括正常執行結束和異常退出),執行此Advice */ private void doAfter(JoinPoint joinPoint) { System.out.println("-----doAfter().invoke-----"); System.out.println(" 此處意在執行核心業務邏輯之後,做一些日誌記錄操作等等"); System.out.println(" 可通過joinPoint來獲取所需要的內容"); System.out.println("-----End of doAfter()------"); } /** * 核心業務邏輯呼叫正常退出後,不管是否有返回值,正常退出後,均執行此Advice */ private void doReturn(JoinPoint joinPoint) { System.out.println("-----doReturn().invoke-----"); System.out.println(" 此處可以對返回值做進一步處理"); System.out.println(" 可通過joinPoint來獲取所需要的內容"); System.out.println("-----End of doReturn()------"); } /** * 核心業務邏輯呼叫異常退出後,執行此Advice,處理錯誤資訊 */ private void doThrowing(JoinPoint joinPoint, Throwable ex) { System.out.println("-----doThrowing().invoke-----"); System.out.println(" 錯誤資訊:" + ex.getMessage()); System.out.println(" 此處意在執行核心業務邏輯出錯時,捕獲異常,並可做一些日誌記錄操作等等"); System.out.println(" 可通過joinPoint來獲取所需要的內容"); System.out.println("-----End of doThrowing()------"); } }

3.在Spring配置檔案中配置

<bean id="xmlHandler" class="aop.XMLAdvice"/>
<aop:config>
        <aop:aspect id="aspect" ref="xmlHandler">
            <aop:pointcut id="pointUserMgr" expression="execution(* intergrate.service.*.find*(..))"/>

            <aop:before method="doBefore" pointcut-ref="pointUserMgr"/>
            <aop:around method="doAround" pointcut-ref="pointUserMgr"/>
            <aop:after method="doAfter" pointcut-ref="pointUserMgr"/>
            <aop:after-returning method="doReturn" pointcut-ref="pointUserMgr"/>
            <aop:after-throwing method="doThrowing" throwing="ex" pointcut-ref="pointUserMgr"/>
        </aop:aspect>
    </aop:config>

4.測試

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring-service.xml")
public class UserManagerImplTest {

    @Resource
    private UserManager userManager;

    @Test
    public void test() throws Exception {
        //正常執行
        String user = userManager.findUserById(1);
        System.out.println("user:" + user);

        System.out.println("=====華麗麗的分割線=======");

        //拋異常
        try {
            userManager.findUserById(0);
        } catch (Exception e) {
        }
    }
}

輸出結果

---doBefore().invoke--
 此處意在執行核心業務邏輯前,做一些安全性的判斷等等
 可通過joinPoint來獲取所需要的內容
-----End of doBefore()------
-----doAround().invoke-----
 此處可以做類似於Before Advice的事情
---------UserManagerImpl.findUserById()--------
 此處可以做類似於After Advice的事情
-----End of doAround()------
-----doAfter().invoke-----
 此處意在執行核心業務邏輯之後,做一些日誌記錄操作等等
 可通過joinPoint來獲取所需要的內容
-----End of doAfter()------
-----doReturn().invoke-----
 此處可以對返回值做進一步處理
 可通過joinPoint來獲取所需要的內容
-----End of doReturn()------
user:張三
=====華麗麗的分割線=======
---doBefore().invoke--
 此處意在執行核心業務邏輯前,做一些安全性的判斷等等
 可通過joinPoint來獲取所需要的內容
-----End of doBefore()------
-----doAround().invoke-----
 此處可以做類似於Before Advice的事情
---------UserManagerImpl.findUserById()--------
-----doAfter().invoke-----
 此處意在執行核心業務邏輯之後,做一些日誌記錄操作等等
 可通過joinPoint來獲取所需要的內容
-----End of doAfter()------
-----doThrowing().invoke-----
 錯誤資訊:該使用者不存在!
 此處意在執行核心業務邏輯出錯時,捕獲異常,並可做一些日誌記錄操作等等
 可通過joinPoint來獲取所需要的內容
-----End of doThrowing()------

值得注意的是Around與Before和After的執行順序。3者的執行順序取決於在xml中的配置順序

如果配置順序是aop:after -> aop:around ->aop:before,那麼before和after都會包含在around中。這種情況的產生是由於Around的特殊性,它可以做類似於Before和After的操作。當安全性的判斷不通過時,可以阻止核心業務邏輯的呼叫,這是Before做不到的。

AspectJ註解

介面和實現類

與上面相同

自定義AspceJAdvice

@Aspect
public class AspceJAdvice {

    /**
     * Pointcut
     * 定義Pointcut,Pointcut的名稱是aspectjMethod(),此方法沒有返回值和引數
     * 該方法就是一個標識 不進行呼叫
     */
    @Pointcut("execution(* find*(..))")
    private void aspectjMethod() {
    }

    /**
     * Before
     * 在核心業務執行前執行,不能阻止核心業務的呼叫
     */
    @Before("aspectjMethod()")
    public void beforeAdvice(JoinPoint joinPoint) {
        System.out.println("-----beforeAdvice().invoke-----");
        System.out.println(" 此處意在執行核心業務邏輯前,做一些安全性的判斷等等");
        System.out.println(" 可通過joinPoint來獲取所需要的內容");
        System.out.println("-----End of beforeAdvice()------");
    }

    /**
     * After
     * 核心業務邏輯退出後(包括正常執行結束和異常退出),執行此Advice
     */
    @After("aspectjMethod()")
    public void afterMethod(JoinPoint joinPoint) {
        System.out.println("-----afterAdvice().invoke-----");
        System.out.println(" 此處意在執行核心業務邏輯之後,做一些日誌記錄操作等等");
        System.out.println(" 可通過joinPoint來獲取所需要的內容");
        System.out.println("-----End of afterAdvice()------");
    }

    /**
     * Around
     * 手動控制呼叫核心業務邏輯,以及呼叫前和呼叫後的處理,
     * <p>
     * 注意:當核心業務拋異常後,立即退出,轉向AfterAdvice
     * 執行完AfterAdvice,再轉到ThrowingAdvice
     */
    @Around(value = "aspectjMethod()")
    public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("-----aroundAdvice().invoke-----");
        System.out.println(" 此處可以做類似於Before Advice的事情");
        //呼叫核心邏輯
        Object retVal = pjp.proceed();
        System.out.println(" 此處可以做類似於After Advice的事情");
        System.out.println("-----End of aroundAdvice()------");
        return retVal;
    }

    /**
     * AfterReturning
     * 核心業務邏輯呼叫正常退出後,不管是否有返回值,正常退出後,均執行此Advice
     */
    @AfterReturning(value = "aspectjMethod()", returning = "retVal")
    public void afterReturningAdvice(JoinPoint joinPoint, String retVal) {
        System.out.println("-----afterReturningAdvice().invoke-----");
        System.out.println("Return Value: " + retVal);
        System.out.println(" 此處可以對返回值做進一步處理");
        System.out.println(" 可通過joinPoint來獲取所需要的內容");
        System.out.println("-----End of afterReturningAdvice()------");
    }

    /**
     * 核心業務邏輯呼叫異常退出後,執行此Advice,處理錯誤資訊
     * <p>
     * 注意:執行順序在Around Advice之後
     */
    @AfterThrowing(value = "aspectjMethod()", throwing = "ex")
    public void afterThrowingAdvice(JoinPoint joinPoint, Exception ex) {
        System.out.println("-----afterThrowingAdvice().invoke-----");
        System.out.println(" 錯誤資訊:" + ex.getMessage());
        System.out.println(" 此處意在執行核心業務邏輯出錯時,捕獲異常,並可做一些日誌記錄操作等等");
        System.out.println(" 可通過joinPoint來獲取所需要的內容");
        System.out.println("-----End of afterThrowingAdvice()------");
    }
}

Spring配置檔案中配置

<bean id="aspcejHandler" class="aop.AspceJAdvice"/>

<aop:aspectj-autoproxy/>

測試

同上

輸出結果

-----aroundAdvice().invoke-----
 此處可以做類似於Before Advice的事情
-----beforeAdvice().invoke-----
 此處意在執行核心業務邏輯前,做一些安全性的判斷等等
 可通過joinPoint來獲取所需要的內容
-----End of beforeAdvice()------
---------UserManagerImpl.findUserById()--------
 此處可以做類似於After Advice的事情
-----End of aroundAdvice()------
-----afterAdvice().invoke-----
 此處意在執行核心業務邏輯之後,做一些日誌記錄操作等等
 可通過joinPoint來獲取所需要的內容
-----End of afterAdvice()------
-----afterReturningAdvice().invoke-----
Return Value: 張三
 此處可以對返回值做進一步處理
 可通過joinPoint來獲取所需要的內容
-----End of afterReturningAdvice()------
user:張三
=====華麗麗的分割線=======
-----aroundAdvice().invoke-----
 此處可以做類似於Before Advice的事情
-----beforeAdvice().invoke-----
 此處意在執行核心業務邏輯前,做一些安全性的判斷等等
 可通過joinPoint來獲取所需要的內容
-----End of beforeAdvice()------
---------UserManagerImpl.findUserById()--------
-----afterAdvice().invoke-----
 此處意在執行核心業務邏輯之後,做一些日誌記錄操作等等
 可通過joinPoint來獲取所需要的內容
-----End of afterAdvice()------
-----afterThrowingAdvice().invoke-----
 錯誤資訊:該使用者不存在!
 此處意在執行核心業務邏輯出錯時,捕獲異常,並可做一些日誌記錄操作等等
 可通過joinPoint來獲取所需要的內容
-----End of afterThrowingAdvice()------

通過測試的發現AroundAdvice、BeforeAdvice、AfterAdvice、ReturningAdvice的執行順序是根據註解的順序而定的

XML配置和註解配置優缺點

XML配置優點

  • xml 作為可擴充套件標記語言最大的優勢在於開發者能夠為軟體量身定製適用的標記,使程式碼更加通俗易懂。
  • 利用 xml 配置能使軟體更具擴充套件性。例如 Spring 將 class 間的依賴配置在 xml 中,最大限度地提升應用的可擴充套件性。
  • 具有成熟的驗證機制確保程式正確性。利用 Schema 或 DTD 可以對 xml 的正確性進行驗證,避免了非法的配置導致應用程式出錯。
    修改配置而無需變動現有程式。

XML配置缺點

  • 需要解析工具或類庫的支援。
  • 解析 xml 勢必會影響應用程式效能,佔用系統資源。
  • 配置檔案過多導致管理變得困難。
  • 編譯期無法對其配置項的正確性進行驗證,或要查錯只能在執行期。
  • IDE 無法驗證配置項的正確性無能為力。
  • 查錯變得困難。往往配置的一個手誤導致莫名其妙的錯誤。
  • 開發人員不得不同時維護程式碼和配置檔案,開發效率變得低下。
  • 配置項與程式碼間存在潛規則。改變了任何一方都有可能影響另外一方。

註解配置優點

  • 儲存在 class 檔案中,降低維護成本。
  • 無需工具支援,無需解析。
  • 編譯期即可驗證正確性,查錯變得容易。
  • 提升開發效率。

註解配置缺點

  • 若要對配置項進行修改,不得不修改 Java 檔案,重新編譯打包應用。
  • 配置項編碼在 Java 檔案中,可擴充套件性差。