Spring 提供了很多的實作AOP的方式:Spring 接口方式,schema配置方式和注解.
本文重點介紹Spring使用接口方式實作AOP. 研究使用接口方式實作AOP, 以了解為目的. 更好地了解spring使用動态代理實作AOP. 通常我們使用的更多的是使用注解的方式實作AOP
下面來看看如何實作接口方式的AOP
一. 環境準備
要在項目中使用Spring AOP 則需要在項目中導入除了spring jar包之外, 還需要引入aspectjrt.jar,aspectjweaver.jar,aopalliance.jar ,spring-aop-3.2.0.M2.jar和cglib.jar
二、Spring接口方式實作AOP步驟
使用Spring aop接口方式實作aop, 可以通過自定義通知來供Spring AOP識别. 常見的自己定義通知有:前置通知, 後置通知, 傳回通知, 異常通知, 環繞通知. 對應實作的接口是:
- 前置通知: MethodBeforeAdvice
- 後置通知: AfterAdvice
- 傳回通知:AfterReturningAdvice
- 異常通知:ThrowsAdvice
- 環繞通知:MethodInterceptor
實作步驟如下:
1. 業務接口實作
package com.lxl.www.aop.interfaceAop;
/**
* 使用接口方式實作AOP, 預設通過JDK的動态代理來實作. 非接口方式, 使用的是cglib實作動态代理
*
* 業務接口類-- 電腦接口類
*
* 定義三個業務邏輯方法
*/
public interface IBaseCalculate {
int add(int numA, int numB);
int sub(int numA, int numB);
int div(int numA, int numB);
int multi(int numA, int numB);
int mod(int numA, int numB);
}
2. 業務類
package com.lxl.www.aop.interfaceAop;//業務類,也是目标對象
import com.lxl.www.aop.Calculate;
import org.springframework.aop.framework.AopContext;
import org.springframework.stereotype.Service;
/**
* 業務實作類 -- 基礎電腦
*/
@Service
public class BaseCalculate implements IBaseCalculate {
@Override
public int add(int numA, int numB) {
System.out.println("執行目标方法: add");
return numA + numB;
}
@Override
public int sub(int numA, int numB) {
System.out.println("執行目标方法: sub");
return numA - numB;
}
@Override
public int multi(int numA, int numB) {
System.out.println("執行目标方法: multi");
return numA * numB;
}
@Override
public int div(int numA, int numB) {
System.out.println("執行目标方法: div");
return numA / numB;
}
@Override
public int mod(int numA, int numB) {
System.out.println("執行目标方法: mod");
int retVal = ((Calculate) AopContext.currentProxy()).add(numA, numB);
return retVal % numA;
}
}
3. 通知類
前置通知
package com.lxl.www.aop.interfaceAop;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* 定義前置通知
*/
@Component
public class BaseBeforeAdvice implements MethodBeforeAdvice {
/**
*
* @param method 切入的方法
* @param args 切入方法的參數
* @param target 目标對象
* @throws Throwable
*/
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("===========進入beforeAdvice()============");
System.out.println("目标對象:" + target);
System.out.println("方法名: "+method);
System.out.println("即将進入切入點方法");
}
}
後置通知
package com.lxl.www.aop.interfaceAop;
import org.aspectj.lang.annotation.AfterReturning;
import org.springframework.aop.AfterAdvice;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
public class BaseAfterReturnAdvice implements AfterReturningAdvice {
/**
*
* @param returnValue 切入點執行完方法的傳回值,但不能修改
* @param method 切入點方法
* @param args 切入點方法的參數數組
* @param target 目标對象
* @throws Throwable
*/
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("==========進入afterReturning()=========== \n");
System.out.println("切入點方法執行完成");
System.out.println("後置通知--目标對象:" + target);
System.out.println("後置通知--方法名: "+method);
System.out.println("後置通知--方法入參: "+ args.toString());
System.out.println("後置通知--方法傳回值: "+ returnValue);
}
}
異常通知
package com.lxl.www.aop.interfaceAop;
import org.springframework.aop.ThrowsAdvice;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Component
public class BaseAfterThrowsAdvice implements ThrowsAdvice {
/**
* @param method 可選:切入的方法
* @param args 可選:切入的方法的參數
* @param target 可選:目标對象
* @param throwable 必填 : 異常子類,出現這個異常類的子類,則會進入這個通知。
*/
public void afterThrowing(Method method, Object[] args, Object target, Throwable throwable) {
System.out.println("出錯啦");
}
}
環繞通知
package com.lxl.www.aop.interfaceAop;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* 環繞通知
*/
@Component
public class BaseAroundAdvice implements MethodInterceptor {
/**
* invocation :連接配接點
*/
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("===========around環繞通知方法 開始===========");
// 調用目标方法之前執行的動作
System.out.println("環繞通知--調用方法之前: 執行");
// 調用方法的參數
Object[] args = invocation.getArguments();
// 調用的方法
Method method = invocation.getMethod();
// 擷取目标對象
Object target = invocation.getThis();
System.out.println("輸入參數:" + args[0] + ";" + method + ";" + target);
// 執行完方法的傳回值:調用proceed()方法,就會觸發切入點方法執行
Object returnValue = invocation.proceed();
System.out.println("環繞通知--調用方法之後: 執行");
System.out.println("輸出參數:" + args[0] + ";" + method + ";" + target + ";" + returnValue);
System.out.println("===========around環繞通知方法 結束===========");
return returnValue;
}
}
4. 自定義切點
package com.lxl.www.aop.interfaceAop;
/**
* 切點
*
* 繼承NameMatchMethodPointcut類,來用方法名比對
*/
import org.springframework.aop.support.NameMatchMethodPointcut;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Component
public class Pointcut extends NameMatchMethodPointcut {
private static final long serialVersionUID = 3990456017285944475L;
@SuppressWarnings("rawtypes")
@Override
public boolean matches(Method method, Class targetClass) {
// 設定單個方法比對
this.setMappedName("add");
// 設定多個方法比對
String[] methods = { "add", "div" };
//也可以用“ * ” 來做比對符号
// this.setMappedName("get*");
this.setMappedNames(methods);
return super.matches(method, targetClass);
}
}
5. 配置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:p="http://www.springframework.org/schema/p"
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-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"
>
<!-- ==============================aop配置================================ -->
<!-- 聲明一個業務類 -->
<bean id="baseCalculate" class="com.lxl.www.aop.interfaceAop.BaseCalculate"/>
<!-- 聲明通知類 -->
<bean id="baseBefore" class="com.lxl.www.aop.interfaceAop.BaseBeforeAdvice"/>
<bean id="baseAfterReturn" class="com.lxl.www.aop.interfaceAop.BaseAfterReturnAdvice"/>
<bean id="baseAfterThrows" class="com.lxl.www.aop.interfaceAop.BaseAfterThrowsAdvice"/>
<bean id="baseAround" class="com.lxl.www.aop.interfaceAop.BaseAroundAdvice"/>
<!-- 指定切點比對類 -->
<bean id="pointcut" class="com.lxl.www.aop.interfaceAop.Pointcut"/>
<!-- 包裝通知,指定切點 -->
<bean id="matchBeforeAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="pointcut">
<ref bean="pointcut"/>
</property>
<property name="advice">
<ref bean="baseBefore"/>
</property>
</bean>
<!-- 使用ProxyFactoryBean 産生代理對象 -->
<bean id="businessProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 代理對象所實作的接口 ,如果有接口可以這樣設定 -->
<property name="proxyInterfaces">
<value>com.lxl.www.aop.interfaceAop.IBaseCalculate</value>
</property>
<!-- 設定目标對象 -->
<property name="target">
<ref bean="baseCalculate"/>
</property>
<!-- 代理對象所使用的攔截器 -->
<property name="interceptorNames">
<list>
<value>matchBeforeAdvisor</value>
<value>baseAfterReturn</value>
<value>baseAround</value>
</list>
</property>
</bean>
</beans>
6. 方法入口
package com.lxl.www.aop.interfaceAop;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class InterfaceMainClass{
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("aop/aop.xml");
BaseCalculate calculate = (BaseCalculate) context.getBean("baseCalculate");
calculate.add(1, 3);
}
}
三. 分析
各種類型通知的執行順序: 前置方法會在切入點方法之前執行,後置會在切入點方法執行之後執行,環繞則會在切入點方法執行前執行同僚方法結束也會執行對應的部分。主要是調用proceed()方法來執行切入點方法。來作為環繞通知前後方法的分水嶺
在xml 配置 businessProxy這個bean的時候,ProxyFactoryBean類中指定了,proxyInterfaces參數。這裡把他配置了IBaseCalculate接口。因為在項目開發過程中,往往業務類都會有對應的接口,以友善利用IOC解耦。但Spring AOP卻也能支援沒有接口的代理。這就是為什麼需要導入cglib.jar包了。看過spring的源碼,知道在目标切入對象如果有實作接口,spring會預設使用jdk動态代理來實作代理類。如果沒有接口,則會通過cglib來實作代理類。
這個業務類現在有 前置通知,後置通知,環繞三個通知同時作用,可能以及更多的通知進行作用。那麼這些通知的執行順序是怎麼樣的?就這個例子而言,同時實作了三個通知。在例 子xml中,則顯示執行before通知,然後執行around的前處理,執行切點方法,再執行return處理。最後執行around的後處理。經過測 試,知道spring 處理順序是按照xml配置順序依次處理通知,以隊列的方式存放前通知,以壓棧的方式存放後通知。是以是前通知依次執行,後通知到切入點執行完之後,從棧裡 在後進先出的形式把後通知執行。
在實作過程中發現通知執行對應目标對象的整個類中的方法,如何精确到某個方法,則需要定義一個切點比對的方式:spring提供了方法名比對或正則方式來比對。然後通過DefaultPointcutAdvisor來包裝通知,指定切點。
使用接口方式配置起來,可見代碼還是非常的厚重的,定義一個切面就要定義一個切面類,然而切面類中,就一個通知方法,着實沒有必要。是以Spring提供了,依賴aspectj的schema配置和基于aspectj 注解方式。這兩種方式非常簡單友善使用,也是項目中普遍的使用方式。
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiInBnauUjNzYjN1YTO10iN0AzM1AjM4AzNyYDMxIDMy0iNxkzN4ETMvwlNwEjMwIzLcZTM5cDOxEzLcd2bsJ2Lc12bj5ycn9Gbi52YuAjMwIzZtl2Lc9CX6MHc0RHaiojIsJye.jpg)