AOP簡介和測試
AOP簡介
AOP,即面向切面程式設計,它是Spring中的兩個重要內容之一。它是為了把邏輯程式碼和處理瑣碎事務的程式碼分離開,以便能夠分離複雜度。
設想這樣一種需求:要實現一個計算器,除了能夠進行加減乘除運算之外,還有兩個功能,一是日誌功能,即能夠記錄程式的執行情況;二是驗證功能,即對要計算的引數進行驗證。
傳統的實現方法是在每一個方法上都新增日誌和驗證功能,例如,對於日誌功能,實現程式碼如下:
這將導致的問題有:
1. 程式碼混亂:越來越多的非業務需求(日誌和驗證等)加入後,原有的業務方法急劇膨脹,每個方法在處理核心邏輯的同時還必須兼顧其他多個關注點。
2. 程式碼分散: 以日誌需求為例,只是為了滿足這個單一需求,就不得不在多個模組(方法)裡多次重複相同的日誌程式碼,如果日誌需求發生變化,必須修改所有模組。
這正是AOP可以解決的問題,AOP的主要程式設計物件是切面(Aspect),切面可以模組化橫切關注點,例如,上述例子中的日誌和驗證功能都可以被模組化到特定的切面類中。AOP解決上述問題的原理如下圖所示:
AOP中的相關術語如下:
現在我們以上述問題為例,來簡單測試Spring中的AOP功能。
首先新建對應的介面和類:
//計算器介面
public interface ArithmeticCalculator {
int add(int i, int j);
int sub(int i, int j);
int mul(int i, int j);
int div(int i, int j);
}
//計算器實現類
@Component("arithmeticCalculator")
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {
@Override
public int add(int i, int j) {
int result = i + j;
return result;
}
@Override
public int sub(int i, int j) {
int result = i - j;
return result;
}
@Override
public int mul(int i, int j) {
int result = i * j;
return result;
}
@Override
public int div(int i, int j) {
int result = i / j;
return result;
}
}
Spring中有兩種方式用以實現AOP,一種是基於AspectJ註解的方式,另一種是基於XML配置檔案的方式,下面逐一介紹。
基於AspectJ註解的方式
首先匯入AspectJ的jar包:
編寫分別負責日誌和驗證功能的兩個切面類:
//日誌切面
//@Order指明切面的優先順序,值越小優先順序越高
@Order(2)
//通過新增 @Aspect 註解宣告一個 bean 是一個切面
@Aspect
@Component
public class LoggingAcpect {
/**
* 定義一個方法, 用於宣告切入點表示式. 一般地, 該方法中再不需要添入其他的程式碼.
* 使用 @Pointcut 來宣告切入點表示式.
* 後面的其他通知直接使用方法名來引用當前的切入點表示式.
*/
@Pointcut("execution(* com.MySpring.aop.annotation.*.*(..))")
public void declareJointPointExpression(){}
/**
* 前置通知:
* 在 com.MySpring.aop.annotation 包下的每一個類的每一個方法開始之前執行一段程式碼
*/
@Before("declareJointPointExpression()")
public void beforeMethod(JoinPoint joinpoint){
String methodName = joinpoint.getSignature().getName();
Object[] args = joinpoint.getArgs();
System.out.println("the method "+methodName+" begins with "+Arrays.asList(args));
}
/**
* 後置通知:
* 在 com.MySpring.aop.annotation 包下的每一個類的每一個方法開始後執行一段程式碼
* 無論這段程式碼是否丟擲異常
*/
@After("declareJointPointExpression()")
public void afterMethod(JoinPoint joinpoint){
String methodName = joinpoint.getSignature().getName();
Object[] args = joinpoint.getArgs();
System.out.println("the method "+methodName+" ends");
}
/**
* 返回通知:
* 在方法法正常結束受執行的程式碼
* 返回通知是可以訪問到方法的返回值的!
*/
@AfterReturning(value="declareJointPointExpression()",returning="result")
public void afterReturning(JoinPoint joinpoint,Object result){
String methodName = joinpoint.getSignature().getName();
Object[] args = joinpoint.getArgs();
System.out.println("the method "+methodName+" ends with result "+result);
}
/**
* 異常通知:
* 在目標方法出現異常時會執行的程式碼.
* 可以訪問到異常物件; 且可以指定在出現特定異常時在執行通知程式碼
*/
@AfterThrowing(value="declareJointPointExpression()",throwing="e")
public void afterThrowing(JoinPoint joinpoint,Exception e){
String methodName = joinpoint.getSignature().getName();
Object[] args = joinpoint.getArgs();
System.out.println("the method "+methodName+" occurs exception "+e);
}
}
//驗證切面
@Order(1)
@Aspect
@Component
public class ValidationAspect {
@Pointcut("execution(* com.MySpring.aop.annotation.*.*(..))")
public void declareJointPointExpression(){}
@Before("declareJointPointExpression()")
public void validateArgs(JoinPoint joinPoint){
Object[] args = joinPoint.getArgs();
System.out.println("-->validate args "+Arrays.asList(args));
}
}
編寫spring配置檔案applicationContext-aop-annotation.xml:
<?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"
xmlns:context="http://www.springframework.org/schema/context"
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-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
<!-- 配置自動掃描的包 -->
<context:component-scan base-package="com.MySpring.aop"></context:component-scan>
<!-- 配置自動為匹配 aspectJ 註解的 Java 類生成代理物件 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
編寫測試類:
public class Test {
@org.junit.Test
public void test() {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext-aop-annotation.xml");
ArithmeticCalculator arithmeticCalculator = (ArithmeticCalculator) ctx.getBean("arithmeticCalculator");
System.out.println("result is "+arithmeticCalculator.add(100, 200));
System.out.println("result is "+arithmeticCalculator.div(100, 0));
}
}
執行結果:
![image_1b51tt9lh1cl9f228c3i6u34s1t.png-26.2kB][5]
基於XML配置檔案的方式
首先新建介面和類,和上面類似,只是沒有AspectJ的註解:
//日誌切面
public class LoggingAcpect {
public void beforeMethod(JoinPoint joinpoint){
String methodName = joinpoint.getSignature().getName();
Object[] args = joinpoint.getArgs();
System.out.println("the method "+methodName+" begins with "+Arrays.asList(args));
}
public void afterMethod(JoinPoint joinpoint){
String methodName = joinpoint.getSignature().getName();
Object[] args = joinpoint.getArgs();
System.out.println("the method "+methodName+" ends");
}
public void afterReturning(JoinPoint joinpoint,Object result){
String methodName = joinpoint.getSignature().getName();
Object[] args = joinpoint.getArgs();
System.out.println("the method "+methodName+" ends with result "+result);
}
public void afterThrowing(JoinPoint joinpoint,Exception e){
String methodName = joinpoint.getSignature().getName();
Object[] args = joinpoint.getArgs();
System.out.println("the method "+methodName+" occurs exception "+e);
}
}
//驗證切面
public class ValidationAspect {
public void validateArgs(JoinPoint joinPoint){
Object[] args = joinPoint.getArgs();
System.out.println("-->validate args "+Arrays.asList(args));
}
}
編寫spring配置檔案applicationContext-aop-xml.xml:
<?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"
xmlns:context="http://www.springframework.org/schema/context"
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-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
<!-- 配置自動掃描的包 -->
<context:component-scan base-package="com.MySpring.aop.xml"></context:component-scan>
<!-- 配置切面的 bean. -->
<bean id="loggingAspect" class="com.MySpring.aop.xml.LoggingAcpect"></bean>
<bean id="validationAspect" class="com.MySpring.aop.xml.ValidationAspect"></bean>
<!-- 配置 AOP -->
<aop:config>
<!-- 配置切點表示式 -->
<aop:pointcut expression="execution(* com.MySpring.aop.xml.*.*(..))"
id="pointcut" />
<!-- 配置切面及通知 -->
<aop:aspect ref="loggingAspect" order="2">
<aop:before method="beforeMethod" pointcut-ref="pointcut" />
<aop:after method="afterMethod" pointcut-ref="pointcut" />
<aop:after-returning method="afterReturning"
pointcut-ref="pointcut" returning="result" />
<aop:after-throwing method="afterThrowing"
pointcut-ref="pointcut" throwing="e" />
</aop:aspect>
<aop:aspect ref="validationAspect" order="1">
<aop:before method="validateArgs" pointcut-ref="pointcut" />
</aop:aspect>
</aop:config>
</beans>
編寫測試類:
public class Test {
@org.junit.Test
public void test() {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext-aop-xml.xml");
ArithmeticCalculator arithmeticCalculator = (ArithmeticCalculator) ctx.getBean("arithmeticCalculator");
System.out.println("result is "+arithmeticCalculator.add(100, 200));
System.out.println("result is "+arithmeticCalculator.div(100, 0));
}
}
執行結果: