課程概要:
- Spring AOP的基本概念
- Spring AOP的增強類型
- Spring AOP的前置增強
- Spring AOP的後置增強
- Spring AOP的環繞增強
- Spring AOP的異常拋出增強
- Spring AOP的引介增強
一.Spring AOP增強的基本概念
Spring當中的專業術語-advice,翻譯成中文就是增強的意思。
所謂增強,其實就是向各個程序內部注入一些邏輯代碼從而增強原有程序的功能。
二.Spring AOP的增強類型
首先先了解一下增強接口的繼承關係
如上圖所示:
其中帶Spring標誌的是Spring定義的擴展增強接口
其中帶aopalliance標誌的是AOP聯盟所定義的接口
按照增加在目標類方法連接點的位置可以將增強劃分爲以下五類:
- 前置增強 (org.springframework.aop.BeforeAdvice) 表示在目標方法執行前來實施增強
- 後置增強 (org.springframework.aop.AfterReturningAdvice) 表示在目標方法執行後來實施增強
- 環繞增強 (org.aopalliance.intercept.MethodInterceptor) 表示在目標方法執行前後同時實施增強
- 異常拋出增強 (org.springframework.aop.ThrowsAdvice) 表示在目標方法拋出異常後來實施增強
- 引介增強 (org.springframework.aop.introductioninterceptor) 表示在目標類中添加一些新的方法和屬性
其中,引介增強是一種特殊的增強。他可以在目標類中添加屬性和方法,通過攔截定義一個接口,讓目標代理實現這個接口。他的連接點是類級別的,而前面的幾種則是方法級別的。
其中,環繞增強是AOP聯盟定義的接口,其他四種增強接口則是Spring定義的接口。
其實,AOP增強很簡單:
通過實現這些增強接口,在實現這些接口的方法當中定義橫切邏輯,然後通過配置Spring的配置文件就可以完成將增強織入到目標方法當中了。
補充:增強既包含了橫切邏輯同時又包含了部分連接點信息。
三.Spring AOP的前置增強
1.通過代碼實現增強
在Spring當中,僅支持方法級別的增強,利用MethodBeforeAdvice實現,表示在目標方法執行前實施增強。
示例演示:
對服務生的服務用語進行強制規範。我們假設服務生只需要幹兩件事情:1.歡迎顧客 2.對顧客提供服務
那麼我們創建的示例代碼的主要步驟如下:
- 創建業務接口類:Waiter.java
- 創建業務實現類:NativeWaiter.java
- 創建業務增強類:GreetingBeforeAdvice.java
- 創建增強測試類:TestAdvice.java
接下來我們分別在IDEA中創建相應的類。
服務生接口Waiter.java
-
public interface Waiter {
-
void greetTo(String name);
-
void serverTo(String name);
-
}
服務生實現類NativeWaiter.java
-
public class NativeWaiter implements Waiter{
-
public void greetTo(String name) {
-
System.out.println("greet to"+name+"...");
-
}
-
-
public void serverTo(String name) {
-
System.out.println("serving"+name+"...");
-
}
-
}
服務生業務增強類GreetingBeforeAdvice.java
-
public class GreetingBeforeAdvice implements MethodBeforeAdvice{
-
-
-
-
-
-
-
-
-
public void before(Method method, Object[] objects, Object o) throws Throwable {
-
String clientName=(String)objects[0];
-
System.out.println("How Are You! mr."+clientName);
-
}
-
}
增強測試類TestBeforeAdvice.java
-
public class TestBeforeAdvice {
-
public static void main(String[] args){
-
-
Waiter target=new NativeWaiter();
-
-
BeforeAdvice advice=new GreetingBeforeAdvice();
-
-
ProxyFactory factory=new ProxyFactory();
-
-
factory.setTarget(target);
-
-
factory.addAdvice(advice);
-
-
Waiter proxy=(Waiter)factory.getProxy();
-
-
proxy.greetTo("icarus");
-
proxy.serverTo("icarus");
-
-
}
-
}
程序運行結果:
How Are You! mr.icarus
greet toicarus...
How Are You! mr.icarus
servingicarus...
2.ProxyFactory介紹
其實ProxyFactory代理技術就是利用jdk代理或者cglib代理的技術,將增強應用到目標類當中。
Spring定義的AOP Proxy類具有兩個final類型的實現類,如下圖所示:
其中:
Cglib2AopProxy是使用cglib代理技術來創建代理
JdkDynamicAopProxy是使用jdk代理技術來創建代理
那麼使用JDK代理來實現上面的代碼則爲:
-
-
ProxyFactory factory=new ProxyFactory();
-
-
factory.setInterfaces(target.getClass().getInterfaces());
-
-
factory.setTarget(target);
-
-
factory.addAdvice(advice);
使用CGLib代理則爲:
-
ProxyFactory factory=new ProxyFactory();
-
-
factory.setInterfaces(target.getClass().getInterfaces());
-
-
factory.setOptimize(true);
-
-
factory.setTarget(target);
-
-
factory.addAdvice(advice);
可以觀察到,ProxyFactory通過addAdvice來增加一個增強。
用戶可以使用該方法增加多個增強,通過增強形成一個增強鏈,他們的調用順序和添加順序是一致的
3.通過配置文件實現增強
我們也可以通過配置文件來實現Spring的前置增強,並且大多數情況下都是使用配置文件方式。
首先我們介紹下ProxyFactory Bean配置文件當中常用的屬性:
- target:我們需要代理的目標對象
- proxyInterfaces:代理所要實現的接口,可以是多個接口
- interceptorNames:需要織入的目標對象的Bean的列表(增強類的Bean列表),使用Bean的名稱來指定。
- singleton:確定返回的代理是不是單實例的,系統默認返回的是單實例的。
- optimize:當值爲true時,強制使用cglib代理。當是singleton的實例時我們推薦使用cglib代理,當是其他作用域的時候,推薦使用JDK的代理。原因是cglib創建代理速度比較慢,但是運行效率高。JDK代理則剛好相反。
- proxyTargetClass:是否對類進行代理而不是對接口進行代理,當值爲true的時候使用cglib代理
接下來我們使用配置文件對上面的示例代碼進行配置:
-
<?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"
-
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
-
<bean id="gerrtingBefore" class="cn.lovepi.chapter07.aop.advice.GreetingBeforeAdvice"/>
-
<bean id="target" class="cn.lovepi.chapter07.aop.advice.NativeWaiter"/>
-
<bean id="waiter" class="org.springframework.aop.framework.ProxyFactoryBean"
-
p:proxyInterfaces="cn.lovepi.chapter07.aop.advice.Waiter"
-
p:interceptorNames="gerrtingBefore"
-
p:target-ref="target"
-
/>
-
-
</beans>
接下來我們創建對應的測試文件
-
public class TestBeforeAdviceByXml {
-
public static void main(String[] args){
-
String path="src/conf/conf-advice.xml";
-
ApplicationContext context=new FileSystemXmlApplicationContext(path);
-
Waiter waiter=context.getBean("waiter",Waiter.class);
-
waiter.greetTo("icarus");
-
waiter.serverTo("icarus");
-
-
}
-
}
可以看到輸出結果爲:
How Are You! mr.icarus
greet toicarus...
How Are You! mr.icarus
servingicarus...
和我們通過代碼實現增強的結果相同
四.Spring AOP的後置增強
後置增強在目標方法調用後執行,例如上面的例子中,在服務生每次服務後,也需要向客人問候,可以通過後置增強來實施這一要求,步驟如下:
- 創建業務接口類:Waiter.java
- 創建業務實現類:NativeWaiter.java
- 創建業務增強類:GreetingAfterAdvice.java
- 創建配置文件:conf-advice.xml
- 創建增強測試類:TestAdvice.java
接下來我們在IDEA中創建相應的代碼:
我們繼續使用上面的例子,由於Waiter.java和NativeWaiter.java已經創建好了
我們只需創建GreetingAfterAdvice.java
-
public class GreetingAfterAdvice implements AfterReturningAdvice{
-
-
-
-
-
-
-
-
-
public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
-
System.out.println("please enjoy youself!");
-
}
-
}
接下來我們修改對應的配置文件
首先得將後者增強類作爲bean配置到文件當中
<bean id="gerrtingAfter" class="cn.lovepi.chapter07.aop.advice.GreetingAfterAdvice"/>
接下來得在ProxyFactory Bean當中添加織入的bean
p:interceptorNames="gerrtingBefore,gerrtingAfter"
完整的配置文件如下:
-
<?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"
-
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
-
<bean id="gerrtingBefore" class="cn.lovepi.chapter07.aop.advice.GreetingBeforeAdvice"/>
-
<bean id="gerrtingAfter" class="cn.lovepi.chapter07.aop.advice.GreetingAfterAdvice"/>
-
<bean id="target" class="cn.lovepi.chapter07.aop.advice.NativeWaiter"/>
-
<bean id="waiter" class="org.springframework.aop.framework.ProxyFactoryBean"
-
p:proxyInterfaces="cn.lovepi.chapter07.aop.advice.Waiter"
-
p:interceptorNames="gerrtingBefore,gerrtingAfter"
-
p:target-ref="target"
-
/>
-
-
</beans>
測試文件和上面的保持不變,運行測試類,測試結果爲:
How Are You! mr.icarus
greet toicarus...
please enjoy youself!
How Are You! mr.icarus
servingicarus...
please enjoy youself!
五.Spring AOP的環繞增強
環繞增強允許在目標類方法調用前後織入橫切邏輯,它綜合實現了前置,後置增強兩者的功能,下面是我們用環繞增強同時實現上面的我們的示例。步驟如下:
- 創建業務接口類:Waiter.java
- 創建業務實現類:NativeWaiter.java
- 創建業務增強類:GreetingInterceptor.java
- 創建配置文件:conf-advice.xml
- 創建增強測試類:TestAdvice.java
接下來我們在IDEA中來實現。
首先創建GreetingInterceptor.java
-
public class GreetingInterceptor implements MethodInterceptor{
-
-
-
-
-
-
-
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
-
-
Object[] args=methodInvocation.getArguments();
-
-
String clickName= (String) args[0];
-
System.out.println("GreetingInterceptor:How are you!");
-
-
Object object=methodInvocation.proceed();
-
System.out.println("GreetingInterceptor: please enjoy youself!");
-
return object;
-
}
-
}
接下來在配置文件中對其進行配置:
-
<?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"
-
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
-
<bean id="gerrtingBefore" class="cn.lovepi.chapter07.aop.advice.GreetingBeforeAdvice"/>
-
<bean id="gerrtingAfter" class="cn.lovepi.chapter07.aop.advice.GreetingAfterAdvice"/>
-
<bean id="gerrtingAround" class="cn.lovepi.chapter07.aop.advice.GreetingInterceptor"/>
-
<bean id="target" class="cn.lovepi.chapter07.aop.advice.NativeWaiter"/>
-
<bean id="waiter" class="org.springframework.aop.framework.ProxyFactoryBean"
-
p:proxyInterfaces="cn.lovepi.chapter07.aop.advice.Waiter"
-
p:interceptorNames="gerrtingBefore,gerrtingAfter,gerrtingAround"
-
p:target-ref="target"
-
/>
-
-
</beans>
啓動測試類,觀察打印結果:
How Are You! mr.icarus
GreetingInterceptor:How are you!
greet toicarus...
GreetingInterceptor: please enjoy youself!
please enjoy youself!
How Are You! mr.icarus
GreetingInterceptor:How are you!
servingicarus...
GreetingInterceptor: please enjoy youself!
please enjoy youself!
可以看到,我們成功在示例中實現了前置增強,後者增強以及環繞增強。
六.Spring AOP的異常拋出增強
異常拋出增強表示在目標方法拋出異常後實施增強,最適合的場景是事務管理,比如當參與事事務的方法拋出異常後需要回滾事務。
異常拋出增強類需要實現ThrowsAdvice接口,ThrowsAdvice接口並沒有定義任何的方法,他只是一個標誌接口。
在運行期,Spring採用反射的機制來進行判斷。我們必須採用以下的形式來定義異常拋出的方法
public void afterThrowing(Method method,Object[] args,Object target,Throwable t)
其中:
方法名必須爲afterThrowing,方法入參中前三個入參是可選的,即要麼同時存在,要麼都沒有
最後一個入參是Throwable及其子類,必須得有。
也可以在異常增強類中定義多個方法,Spring會自動選擇匹配的方法來進行調用。
在類的繼承樹上,兩個類的距離越近,則兩個類的相似度越高
那麼當方法拋出異常時,會優先選取異常入參和拋出的異常相似度最高的afterThrowing方法。
接下來我們創建示例來演示一下,步驟如下:
- 創建業務實現類:ForumService.java
- 創建業務增強類:TransactionManager.java
- 創建配置文件:conf-advice.xml
- 創建增強測試類:TestAdvice.java
接下來我們在IDEA上分別創建對應的代碼:
首先,我們創建業務邏輯類ForumService
-
public class ForumService {
-
public void removeForum(){
-
-
throw new RuntimeException("removeForum:Exception...");
-
}
-
public void updateForum(){
-
-
throw new RuntimeException("updateForum:Exception...");
-
}
-
}
接下來我們創建增強類TransactionManager
-
public class TransactionManager implements ThrowsAdvice{
-
-
-
-
-
-
-
-
-
public void afterThrowing(Method method,Object[] args,Object target,Exception ex)throws Throwable{
-
System.out.println("method:"+method.getName());
-
System.out.println("拋出異常:"+ex.getMessage());
-
System.out.println("成功回滾事務");
-
}
-
}
接下來我們編寫對應的配置文件
-
<?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"
-
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
-
<bean id="forumServiceTarget" class="cn.lovepi.chapter07.aop.advice.ForumService"/>
-
<bean id="transactionManager" class="cn.lovepi.chapter07.aop.advice.TransactionManager"/>
-
<bean id="forumService" class="org.springframework.aop.framework.ProxyFactoryBean"
-
p:proxyTargetClass="true"
-
p:target-ref="forumServiceTarget"
-
p:interceptorNames="transactionManager"
-
/>
-
</beans>
創建相應的測試類進行測試
-
public static void testThrowAdvice(){
-
String path="src/conf/conf-advice.xml";
-
ApplicationContext context=new FileSystemXmlApplicationContext(path);
-
ForumService forumService=context.getBean("forumService",ForumService.class);
-
try {
-
forumService.removeForum();
-
}catch (Exception e){}
-
try {
-
forumService.updateForum();
-
}catch (Exception e){}
-
}
運行結果爲:
method:removeForum
拋出異常:removeForum:Exception...
成功回滾事務
method:updateForum
拋出異常:updateForum:Exception...
成功回滾事務
七.Spring AOP的引介增強
引介增強是一種比較特殊的增強類型,他不是在目標方法周圍織入增強,而是爲目標創建新的方法和屬性,所以他的連接點是類級別的而非方法級別的。通過引介增強我們可以爲目標類添加一個接口的實現即原來目標類未實現某個接口,那麼通過引介增強可以爲目標類創建實現某接口的代理。
接下來我們創建一個示例來演示下,步驟如下:
- 創建接口類:Monitorable.java
- 創建業務類:PerformanceMonitor.java
- 創建增強類:ControllablePerformanceMonitor.java
- 創建配置文件:conf-advice-introduce.xml
- 創建增強測試類:TestIntroduce.java
接下來我們在IDEA上分別創建對應的代碼:
首先創建性能監視接口Monitorable
-
public interface Monitorable {
-
void setMonitorActive(boolean active);
-
}
創建測試接口Testable
-
public interface Testable {
-
void test();
-
}
接下來創建業務類
-
public class PerformanceMonitor {
-
private static ThreadLocal<MethodPerformace> performaceRecord = new ThreadLocal<MethodPerformace>();
-
public static void begin(String method) {
-
System.out.println("begin monitor...");
-
MethodPerformace mp = performaceRecord.get();
-
if(mp == null){
-
mp = new MethodPerformace(method);
-
performaceRecord.set(mp);
-
}else{
-
mp.reset(method);
-
}
-
}
-
public static void end() {
-
System.out.println("end monitor...");
-
MethodPerformace mp = performaceRecord.get();
-
mp.printPerformace();
-
}
-
}
接下來創建增強類ControllablePerformanceMonitor
-
public class ControllablePerformaceMonitor
-
extends
-
DelegatingIntroductionInterceptor implements Monitorable, Testable {
-
private ThreadLocal<Boolean> MonitorStatusMap = new ThreadLocal<Boolean>();
-
public void setMonitorActive(boolean active) {
-
MonitorStatusMap.set(active);
-
}
-
public Object invoke(MethodInvocation mi) throws Throwable {
-
Object obj = null;
-
if (MonitorStatusMap.get() != null && MonitorStatusMap.get()) {
-
PerformanceMonitor.begin(mi.getClass().getName() + "."
-
+ mi.getMethod().getName());
-
obj = super.invoke(mi);
-
PerformanceMonitor.end();
-
} else {
-
obj = super.invoke(mi);
-
}
-
return obj;
-
}
-
public void test() {
-
-
System.out.println("dd");
-
}
-
}
接下來創建所要增強的方法類
-
public class ForumService {
-
-
public void removeTopic(int topicId) {
-
System.out.println("模擬刪除Topic記錄:"+topicId);
-
try {
-
Thread.currentThread().sleep(20);
-
} catch (Exception e) {
-
throw new RuntimeException(e);
-
}
-
-
}
-
-
public void removeForum(int forumId) {
-
System.out.println("模擬刪除Forum記錄:"+forumId);
-
try {
-
Thread.currentThread().sleep(40);
-
} catch (Exception e) {
-
throw new RuntimeException(e);
-
}
-
}
-
}
-
-
public class MethodPerformace {
-
private long begin;
-
private long end;
-
private String serviceMethod;
-
public MethodPerformace(String serviceMethod){
-
reset(serviceMethod);
-
}
-
public void printPerformace(){
-
end = System.currentTimeMillis();
-
long elapse = end - begin;
-
System.out.println(serviceMethod+"花費"+elapse+"毫秒。");
-
}
-
public void reset(String serviceMethod){
-
this.serviceMethod = serviceMethod;
-
this.begin = System.currentTimeMillis();
-
}
-
}
創建配置文件來將所設置的代碼組合起來:
-
<?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"
-
xsi:schemaLocation="http://www.springframework.org/schema/beans
-
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
-
-
<bean id="pmonitor" class="cn.lovepi.chapter07.aop.intorduce.ControllablePerformaceMonitor" />
-
<bean id="forumServiceTarget" class="cn.lovepi.chapter07.aop.intorduce.ForumService" />
-
<bean id="forumService" class="org.springframework.aop.framework.ProxyFactoryBean"
-
p:interfaces="cn.lovepi.chapter07.aop.intorduce.Monitorable"
-
p:target-ref="forumServiceTarget"
-
p:interceptorNames="pmonitor" p:proxyTargetClass="true" />
-
-
</beans>
創建對應的測試類
-
public class TestIntroduce {
-
public static void main(String[] args) {
-
testBeforeAdviceByCode();
-
}
-
-
private static void testBeforeAdviceByCode() {
-
String configPath = "src/conf/conf-advice-introduce.xml";
-
ApplicationContext ctx = new FileSystemXmlApplicationContext(configPath);
-
ForumService forumService = (ForumService)ctx.getBean("forumService");
-
forumService.removeForum(10);
-
forumService.removeTopic(1022);
-
Monitorable moniterable = (Monitorable)forumService;
-
moniterable.setMonitorActive(true);
-
forumService.removeForum(10);
-
forumService.removeTopic(1022);
-
}
-
-
}
程序運行結果爲:
模擬刪除Forum記錄:10
模擬刪除Topic記錄:1022
begin monitor...
模擬刪除Forum記錄:10
end monitor...
org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.removeForum花費40毫秒。
begin monitor...
模擬刪除Topic記錄:1022
end monitor...
org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.removeTopic花費20毫秒。
總結:
增強其實就是對原有的方法或類動態增加功能,可爲方法執行前後以及所拋出的異常進行邏輯處理。實現增強的方式有兩種:代碼方式和XML配置文件方式,建議在以後開發中使用後者,這樣可以避免代碼的耦合度,方便後期維護。