Spring使用注解實現AOP


一、AspectJ概述

AspectJ是一個面向切面的框架,它擴展了Java語言、定義了AOP語法,能夠在編譯期提供代碼的織入,它提供了一個專門的編譯期用來生成遵守字節編碼規范的Class文件。

@Aspect是AspectJ 5新增的功能,使用JDK 5.0注解技術和正規的AspectJ切點表達式語言描述切面。因此在使用@Aspect之前,需要保證所使用的JDK是5.0或更高版本,否則將無法使用注解技術。

Spring通過集成AspectJ實現了以注解的方式定義切面,大大減輕了配置文件的工作量。此外,因為Java的反射機制無法獲取方法參數名,Spring還需要利用輕量級的字節碼處理框架asm(已集成在Spring Core模塊中)處理@AspectJ中所描述的方法參數名。

二、@Aspect(定義切面)、@Before(前置增強)、@AfterReturning(后置增強)注解配置切面

1、使用注解定義切面以實現日志切面功能,如下

package edu.cn.service;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Arrays;

@Aspect public class UserServiceLogger {
    private static final Logger log = LoggerFactory.getLogger(UserServiceLogger.class);

    @Before("execution(* edu.cn.service.UserService.*(..))")
    public void before(JoinPoint jp){
        log.info("調用" + jp.getTarget() + "的" + jp.getSignature().getName()
                + "方法。方法入參:" + Arrays.toString(jp.getArgs()));
    }

    @AfterReturning(pointcut = "execution(* edu.cn.service.UserService.*(..))", returning = "returnValue")
    public void afterReturning(JoinPoint jp, Object returnValue){
        log.info("調用" + jp.getTarget() + "的" + jp.getSignature().getName()
                + "方法。方法返回值:" + returnValue);
    }

} 

@Aspect等注解在aspectjweaver依賴下。在上述代碼中,使用@Aspect注解將UserServiceLogger定義為切面,並且使用@Before注解將before()方法定義為前置增強,使用@AfterReturning方法將afterReturning()方法定於為后置增強

為了能夠獲得當前連接點的信息,在增強方法中添加了JoinPoint類型的參數,Spring會自動注入該實例。

對於后置增強,還可以定義一個參數用於接受目標方法的返回值。需要注意的是,必須在@AfterReturning注解中通過returning屬性指定該參數的名稱,Spring會將目標方法的返回值賦值給指定名稱的參數。

1.1、對於相同的切入點要求,可以統一定義,以便於重用和維護,如下

package edu.cn.service;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Arrays;

@Aspect
public class UserServiceLogger {
    private static final Logger log = LoggerFactory.getLogger(UserServiceLogger.class);
    
    @Pointcut("execution(* edu.cn.service.UserService.*(..))")
    public void pointcut(){}

    @Before("pointcut()")
    public void before(JoinPoint jp){
        log.info("調用" + jp.getTarget() + "的" + jp.getSignature().getName()
                + "方法。方法入參:" + Arrays.toString(jp.getArgs()));
    }

    @AfterReturning(pointcut = "pointcut()", returning = "returnValue")
    public void afterReturning(JoinPoint jp, Object returnValue){
        log.info("調用" + jp.getTarget() + "的" + jp.getSignature().getName()
                + "方法。方法返回值:" + returnValue);
    }

}

切入點表達式可以使用@Pointcut注解來表示,而切入點簽名則需要通過一個普通的方法定義來提供,如上述代碼中的pointcut()方法,作為切入點簽名的方法必須返回void類型。定義好切入點后,就可以使用“pointcut()”簽名進行引用

2、切面定義完后,還需要在Spring配置文件中完成織入工作,如下

只需在配置文件中添加<aop:aspectj-autoproxy/>元素,就可以啟用對於@AspectJ注解的支持,Spring將自動為匹配的Bean創建代理

為了注冊定義好的切面,還需在Spring配置文件中聲明UserServiceLogger的一個實例。如果不需要被其他Bean引用,可以不指定id屬性

<?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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd 
            http://www.springframework.org/schema/aop 
            http://www.springframework.org/schema/aop/spring-aop.xsd">

<context:component-scan base-package="service,dao"/> <bean class="edu.cn.service.UserServiceLogger"></bean> <aop:aspectj-autoproxy/> </beans>

三、@AfterThrowing(異常拋出增強)、@After(最終增強)、@Around(環繞增強)注解進行增強的配置

1、@AfterThrowing(異常拋出增強)

使用@AfterThrowing注解可以定義異常拋出增強。如果需要獲取拋出的異常,可以為增強方法聲明相關類型的參數,並通過@AfterThrowing注解的throwing屬性指定該參數名稱,Spring會為其注入從目標方法拋出的異常實例,如下

package edu.cn.service;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Aspect
public class ErrorLogger {
    private static final Logger log = LoggerFactory.getLogger(ErrorLogger.class);

    @AfterThrowing(pointcut = "execution(* edu.cn.service.UserService.*(..))", throwing = "e")
    public void afterThrowing(JoinPoint jp, RuntimeException e){
        log.error(jp.getSignature().getName() + "方法方法異常:" + e);
    }
}

2、@After(最終增強)

使用@After注解可以定義最終增強

package edu.cn.service;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Aspect
public class AfterLogger {
    private static final Logger log = LoggerFactory.getLogger(AfterLogger.class);
    
    @After("execution(* edu.cn.service.UserService.*(..))")
    public void afterLogger(JoinPoint jp){
        log.info(jp.getSignature().getName() + "方法結束執行。");
    }
}

3、@Around(環繞增強)

使用@Around注解可以定義環繞增強。通過為增強方法聲明ProceedingJoinPoint類型的參數,可以獲得連接點信息。通過它的proceed()方法可以調用真正的目標方法,從而實現對連接點的完全控制。

package edu.cn.service;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Arrays;

@Aspect
public class AroundLogger {
    private static final Logger log = LoggerFactory.getLogger(AroundLogger.class);
    
    @Around("execution(* edu.cn.service.UserService.*(..))")
    public Object aroundLogger(ProceedingJoinPoint jp) throws Throwable {
        log.info("調用" + jp.getTarget() + "的" + jp.getSignature().getName() 
                + "方法。方法入參:" + Arrays.toString(jp.getArgs()));
        try {
            Object result = jp.proceed();
            log.info("調用" + jp.getTarget() + "的" + jp.getSignature().getName()
            + "方法。方法返回值:" + result);
        } catch (Throwable e) {
            log.error(jp.getSignature().getName() + "方法發生異常:" + e);
            throw e;
        }finally {
            log.info(jp.getSignature().getName() + "方法結束執行。");
        }
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM