AspectJ:Java社區中最完整、最流行的AOP框架。
在Spring2.0以上版本中,可以使用基於AspectJ注解或基於XML配置的AOP。
在Spring中2啟用AspectJ注解支持:
1、要在 Spring應用中使用AspectJ注解,需要添加spring-aspect、aspectj-weaver、aopalliance依賴
2、將aop Schema添加到<beans>根元素
3、要在SpringIOC容器中啟用AspectJ注解支持,只要在Bean配置文件中定義一個空的XML元素:<aop:aspectj-autoproxy>
4、在Spring IOC容器偵測到Bean配置文件中的<aop:aspectj-autoproxy>元素時,會自動與AspectJ切面匹配的Bean創建代理。
一、基於注解的方式
1、依賴
2、在配置中加入aop的命名空間
3、基於注解的方式:
①在配置文件中加入:<aop:aspectj-autoproxy>
配置文件:
<?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.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="aopImpl"/> <!--使AspectJ注解起作用:自動為匹配的類生成代理對象--> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> </beans>
②把橫切關注點的代碼抽象到切面的類中
切面首先是一個IOC容器中的Bean,即加入@Component注解
切面還需要加入@AspectJ注解
③在類中聲明各種通知:
1、聲明一個方法
2、在方法前加入通知
@AspectJ支持的5中通知:
—@Before:前置通知在方法執行前執行
—@After:后置通知,在方法執行后執行
—@AfterReturning:返回通知,在方法返回結果之后執行
—@AfterThrowing:異常通知,在方法拋出異常后執行
—@Around:環繞通知,圍繞着方法執行
④我們可以在通知方法中聲明一個類型為JoinPoint參數,然后就能訪問鏈接細節,如方法名稱和參數值
切面類:
package aopImpl; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; import java.util.Arrays; import java.util.List; /** * 把這個類聲明為一個切面: * 1、需要把該類放入到容器中(就是加上@Component注解) * 2、再聲明為一個切面(加上@AspectJ注解) * * @author chenpeng * @date 2018/6/3 23:20 */ @Aspect @Component public class LoggingAspect { //聲明該方法為一個前置通知:在目標方法開始之前執行 //execution中是AspectJ表達式 @Before(value = "execution(* aopImpl.ArithmeticCalculatorImpl.*(int ,int ))") public void beforeMethod(JoinPoint joinPoint){ String methodName = joinPoint.getSignature().getName(); List<Object> args = Arrays.asList(joinPoint.getArgs()); System.out.println("beforeMethod "+methodName+" and "+args); } }
在使用的時候報錯:因為動態代理不能用接口的實現類來轉換Proxy的實現類,它們是同級,應該用共同的接口來轉換。
改為這樣:
import aopImpl.ArithmeticCalculator; import aopImpl.ArithmeticCalculatorImpl; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * @author chenpeng * @date 2018/6/3 23:16 */ public class aopImplTest { public static void main(String[] args) { //1、創建SpringIOC容器 ApplicationContext context = new ClassPathXmlApplicationContext("aopImpl.xml"); //2、從IOC容器中國獲取Bean的實例 ArithmeticCalculator arithmeticCalculator = (ArithmeticCalculator) context.getBean("ArithmeticCalculatorImpl"); //3、使用Bean int result = arithmeticCalculator.add(3,6); System.out.println(result); } }
其他通知:
package aopImpl; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; import java.util.Arrays; import java.util.List; /** * 把這個類聲明為一個切面: * 1、需要把該類放入到容器中(就是加上@Component注解) * 2、再聲明為一個切面(加上@AspectJ注解) * * @author chenpeng * @date 2018/6/3 23:20 */ @Aspect @Component public class LoggingAspect { //聲明該方法為一個前置通知:在目標方法開始之前執行 //execution中是AspectJ表達式 @Before(value = "execution(* aopImpl.ArithmeticCalculatorImpl.*(int ,int ))") public void beforeMethod(JoinPoint joinPoint){ String methodName = joinPoint.getSignature().getName(); List<Object> args = Arrays.asList(joinPoint.getArgs()); System.out.println("beforeMethod "+methodName+" start with "+args); } //后置通知,就是在目標方法執行之后(無論是否發生異常)執行的通知 //后置通知中不能訪問目標方法的返回結果 @After(value = "execution(* aopImpl.ArithmeticCalculatorImpl.*(int ,int ))") public void afterMethod(JoinPoint joinPoint){ String methodName = joinPoint.getSignature().getName(); List<Object> args = Arrays.asList(joinPoint.getArgs()); System.out.println("afterMethod "+methodName+" end with "+args); } //返回通知,在方法正常結束之后執行的代碼 //返回通知是可以訪問到方法的返回值的 @AfterReturning(value = "execution(* aopImpl.ArithmeticCalculatorImpl.*(int ,int ))",returning = "result") public void afterReturning(JoinPoint joinPoint,Object result){ String methodName = joinPoint.getSignature().getName(); List<Object> args = Arrays.asList(joinPoint.getArgs()); System.out.println("afterReturning "+methodName+" end with "+result); } //返回異常通知,返回拋出異常的時候執行的通知,可以獲得返回的異常 //可以訪問到異常對象,且可以指定在出現特定異常的時候再執行通知代碼 @AfterThrowing(value = "execution(* aopImpl.ArithmeticCalculatorImpl.*(int ,int ))",throwing = "ex") public void afterThrowing(JoinPoint joinPoint,Exception ex){ String methodName = joinPoint.getSignature().getName(); List<Object> args = Arrays.asList(joinPoint.getArgs()); System.out.println("afterThrowing "+methodName+" end with "+ ex ); } //環繞通知需要攜帶ProceedingJoinPoint類型的參數 //環繞通知類似於動態代理的全過程,這個類型ProceedingJoinPoint的參數可以決定是否執行目標方法 //且環繞通知必須有返回值,返回值即為目標方法返回值 @Around(value = "execution(* aopImpl.ArithmeticCalculatorImpl.*(int ,int ))") public void around(ProceedingJoinPoint proceedingJoinPoint){ Object result = null; String methodName = proceedingJoinPoint.getSignature().getName(); Object[] args = proceedingJoinPoint.getArgs(); //執行目標方法 try { //前置通知 System.out.println("beforeMethod "+methodName+" start with "+args); result = proceedingJoinPoint.proceed(); //返回通知 System.out.println("afterMethod "+methodName+" end with "+result); } catch (Throwable throwable) { //異常通知 System.out.println("afterThrowing "+methodName+" exception with "+ throwable ); throwable.printStackTrace(); } //后置通知 System.out.println("afterMethod "+methodName+" end with "+args); //System.out.println("around "+proceedingJoinPoint); return; } }
對於切面的優先級
可以在類上使用注解@Order(1),括號中的數字越小,優先級越高
@Order(1)
重用切面的切點表達式:使用@Pointcut
/** * 定義一個方法,用於聲明切入點表達式 *一般的,該方法中不需要添加其他的代碼 * 使用@Pointcut來聲明切入點表達式 * 后面的其他通知直接使用方法名來引用切入點表達式 */ @Pointcut(value = "execution(* aopImpl.ArithmeticCalculatorImpl.*(int ,int ))") public void declareJoinPointExpress(){ } //聲明該方法為一個前置通知:在目標方法開始之前執行 //execution中是AspectJ表達式 @Before(value = "declareJoinPointExpress()") public void beforeMethod(JoinPoint joinPoint){ String methodName = joinPoint.getSignature().getName(); List<Object> args = Arrays.asList(joinPoint.getArgs()); System.out.println("beforeMethod "+methodName+" start with "+args); }
其他切面類中使用:
package aopImpl; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import java.util.Arrays; /** * @author chenpeng * @date 2018/6/4 8:09 */ //可以使用@Order指定切面的優先級,值越小優先級越高 @Order(1) @Component @Aspect public class ValidationAspect { @Before(value = "LoggingAspect.declareJoinPointExpress()") public void validateArgs(JoinPoint joinPoint){ System.out.println("validateArgs"+ Arrays.asList(joinPoint.getArgs())); } }
二、基於XML配置文件的方式
除了使用@AspectJ注解聲明切面,Spring還支持在Bean的配置文件中聲明切面,這種聲明是通過aop Schema中的XML元素完成的。
正常情況下,基於注解的聲明要優於基於XML的聲明,通過@AspectJ注解,可以與Aspect切面相兼容,而基於XML配置則是Spring專有的。優於@AspectJ得到越來越多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:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!--配置bean--> <bean id="arithmeticCalculatorImpl" class="XML.ArithmeticCalculatorImpl"/> <!--配置切面的bean--> <bean id="LoggingAspect" class="XML.LoggingAspect"/> <bean id="ValidationAspect" class="XML.ValidationAspect"/> <!--配置AOP--> <aop:config> <!--配置切點表達式--> <aop:pointcut id="pointcut" expression="execution(* XML.ArithmeticCalculatorImpl.*(..))"/> <!--配置切面及通知--> <aop:aspect ref="LoggingAspect" order="2"> <aop:before method="beforeMethod" pointcut-ref="pointcut"/> <aop:after method="afterMethod" pointcut-ref="pointcut"/> <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="ex"/> <aop:after-returning method="afterReturning" pointcut-ref="pointcut" returning="result"/> </aop:aspect> <aop:aspect ref="ValidationAspect" order="1"> <aop:before method="validateArgs" pointcut-ref="pointcut"/> </aop:aspect> </aop:config> </beans>