一、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() + "方法結束執行。"); } } }
