這篇文章準備從Java的代理機制講到Spring的AOP。php
代理模式是很常見的一種設計模式,代理一詞拆開來看就是代爲受理,那顯然是要涉及到請求被代理的委託方,提供代理的代理方,以及想要經過代理來實際聯繫委託方的客戶三個角色。舉個生活中很常見的例子,各路的明星都會有個本身的經紀人來替本身打點各類各樣的事情,這種場景下,明星自己是委託方,經紀人是代理方,明星把本身安排演出、出席見面會的時間安排權利委託給經紀人,這樣當各個商家做爲客戶想要請明星來代言時,就只能經過經紀人來進行。這樣明星自己不用暴露身份,而經濟人也能夠在溝通中告知商家明星出席活動時要吃什麼飯,作什麼車的一些要求,省去了明星本身操心這些雞毛蒜皮小事兒。另外一方面,當經紀人也能夠給多個明星提供服務,這樣商家只接觸一個經紀人,能夠聯繫到不一樣的明星,找個適合本身公司的人選。
經過上面的例子,代理模式的優勢就顯而易見了:java
優勢一:能夠隱藏委託類的實現;程序員
優勢二:能夠實現客戶與委託類間的解耦,在不修改委託類代碼的狀況下可以作一些額外的處理。spring
Java程序員都應該知道,Java經過Java編譯器將.java源文件編譯成.class字節碼文件,這種.class文件是二進制文件,內容是隻有JVM虛擬機可以識別的機器碼,JVM虛擬機讀取字節碼文件,取出二進制數據,加載到內存中,解析.class文件內的信息,生成對應的Class對象,進而使Class對象建立類的具體實例來進行調用實現具體的功能。express
上圖說明了Java加載字節碼的流程,可是Java的強大在於不只僅能夠加載在編譯期生成好的字節碼,還能夠在運行期系統中,遵循Java編譯系統組織.class文件的格式和結構,生成相應的二進制數據,而後再把這個二進制數據加載轉換成對應的類,這樣,就完成了在代碼中,動態建立一個類的能力了,以下圖流程。編程
下面舉一個動態生成類的實例,經過Javassist實現,Javassist是一個開源的分析、編輯和建立Java字節碼的類庫,咱們可使用Javasisst工具在運行時動態建立字節碼並加載類,以下代碼:設計模式
/** * Created by zhoujunfu on 2018/9/6. */
public class JavassistDemo {
public static void main(String[] args) {
makeNewClass();
}
public static Class<?> makeNewClass() {
try {
// 獲取ClassPool
ClassPool pool = ClassPool.getDefault();
// 建立Student類
CtClass ctClass = pool.makeClass("com.fufu.aop.Student");
// 建立Student類成員變量name
CtField name = new CtField(pool.get("java.lang.String"), "name", ctClass);
// 設置name爲私有
name.setModifiers(Modifier.PRIVATE);
// 將name寫入class
ctClass.addField(name, CtField.Initializer.constant("")); //寫入class文件
//增長set方法,名字爲"setName"
ctClass.addMethod(CtNewMethod.setter("setName", name));
//增長get方法,名字爲getname
ctClass.addMethod(CtNewMethod.getter("getName", name));
// 添加無參的構造體
CtConstructor cons = new CtConstructor(new CtClass[] {}, ctClass);
cons.setBody("{name = \"Brant\";}"); //至關於public Sclass(){this.name = "brant";}
ctClass.addConstructor(cons);
// 添加有參的構造體
cons = new CtConstructor(new CtClass[] {pool.get("java.lang.String")}, ctClass);
cons.setBody("{$0.name = $1;}"); //第一個傳入的形參$1,第二個傳入的形參$2,至關於public Sclass(String s){this.name = s;}
ctClass.addConstructor(cons);
//反射調用新建立的類
Class<?> aClass = ctClass .toClass();
Object student = aClass.newInstance();
Method getter = null;
getter = student.getClass().getMethod("getName");
System.out.println(getter.invoke(student));
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
複製代碼
介紹靜態和動態加載字節碼的兩種方式,是爲了引出下面關於兩種代理方式的介紹,代理機制經過代理類建立的時間不一樣分爲了靜態代理和動態代理:
靜態代理:代理類在編譯階段生成,程序運行前就已經存在,那麼這種代理方式被成爲靜態代理,這種狀況下的代理類一般都是咱們在Java代碼中定義的。
動態代理:代理類在程序運行時建立,也就是說,這種狀況下,代理類並非在Java代碼中定義的,而是在運行時根據咱們在Java代碼中的「指示」動態生成的。bash
目前,靜態代理主要有AspectJ靜態代理、JDK靜態代理技術、而動態代理有JDK動態代理、Cglib動態代理技術,而Spring Aop是整合使用了JDK動態代理和Cglib動態代理兩種技術,下面咱們結合實例一步一步介紹全部的概念。app
對於AspectJ,咱們只會進行簡單的瞭解,爲後續理解打下基礎,如今只須要知道下面這一句定義:框架
AspectJ是一個Java實現的面向切面的框架,它擴展了Java語言。AspectJ有自定義的語法,因此它有一個專門的編譯器用來生成遵照Java字節編碼規範的Class文件。
注意上面定義中的「專門的編譯器」這個描述,能夠看出AspectJ是典型的靜態代理技術,由於是在編譯時期就生成了代理類,而使用AspectJ也確定須要指定特定的編譯器,下面咱們用AspectJ來實現上面的明星和經紀人的模型。
首先在maven工程中引入AspectJ依賴:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.9</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>1.8.9</version>
</dependency>
複製代碼
而後在idea中將javac編譯器改成acj編譯器來支持AspectJ語法:
將明星的表演抽象成一個ShowService接口,包括了唱歌、跳舞的功能
public interface ShowService {
// 歌唱表演
void sing(String songName);
// 舞蹈表演
void dance();
}
複製代碼
明星類實現了ShowService接口:
package com.fufu.aop;
/**
* Created by zhoujunfu on 2018/9/6.
* 明星類
*/
public class Star implements ShowService{
private String name;
@Override
public void sing(String songName) {
System.out.println(this.name + " sing a song: " + songName);
}
@Override
public void dance() {
System.out.println(this.name + "dance");
}
public Star(String name) {
this.name = name;
}
public Star() {
}
public static void main(String[] args) {
Star star = new Star("Eminem");
star.sing("Mockingbird");
}
}
複製代碼
用AspectJ語法實現一個代理AgentAspectJ:
package com.fufu.aop;
/**
* Created by zhoujunfu on 2018/9/7.
*/
public aspect AgentAspectJ {
/**
* 定義切點
*/
pointcut sleepPointCut():call(* Star.sing(..));
/**
* 定義切點
*/
pointcut eatPointCut():call(* Star.eat(..));
/**
* 定義前置通知
*
* before(參數):鏈接點函數{
* 函數體
* }
*/
before():sleepPointCut(){
getMoney();
}
/**
* 定義後置通知
* after(參數):鏈接點函數{
* 函數體
* }
*/
after():sleepPointCut(){
writeReceipt();
}
private void getMoney() {
System.out.println("get money");
}
private void writeReceipt() {
System.out.println("write receipt");
}
}
複製代碼
建立一個Star並運行方法:
public static void main(String[] args) {
Star star = new Star("Eminem");
star.sing("Mockingbird");
}
複製代碼
輸出:
get money
Eminem sing a song: Mockingbird
write receipt
複製代碼
能夠看到Star的sing()方法先後輸出了咱們在AgentAspectJ中定義的前置通知和後置通知,因此是AspectJ在編譯期間,根據AgentAspectJ代碼中定義的代碼,生成了加強的Star類,而咱們實際調用時,就會實現代理類的功能。具體的AspectJ語法咱們不深究,只須要知道pointcut是定義代理要代理的切入點,這裏是定義了兩個pointcut,分別是Star類的sing()方法和dance()方法。而before()和after()分別能夠定義具體在切入點先後須要的額外操做。
總結一下,AspctJ就是用特定的編譯器和語法,對類實現編譯期加強,實現靜態代理技術,下面咱們看JDK靜態代理。
一般狀況下, JDK靜態代理更多的是一種設計模式,JDK靜態代理的代理類和委託類會實現同一接口或是派生自相同的父類,代理模式的基本類圖入下:
咱們接着經過把上面的明星和經紀人的例子寫成代碼來實現一個JDK靜態代理模式。
經紀人類也實現了ShowService接口,持有了一個明星對象來提供真正的表演,並在各項表演的先後加入了經紀人須要處理的事情,如收錢、開發票等:
package com.fufu.aop;
/** * Created by zhoujunfu on 2018/9/6. * 經紀人 */
public class Agent implements ShowService{
private Star star;
public Agent(Star star) {
this.star = star;
}
private void getMoney() {
System.out.println("get money");
}
private void writeReceipt() {
System.out.println("write receipt");
}
@Override
public void sing(String songName) {
// 唱歌開始前收錢
getMoney();
// 明星開始唱歌
star.sing(songName);
// 唱歌結束後開發票
writeReceipt();
}
@Override
public void dance() {
// 跳舞開始前收錢
getMoney();
// 明星開始跳舞
star.dance();
// 跳舞結束後開發票
writeReceipt();
}
}
複製代碼
經過經紀人來請明星表演:
public static void main(String[] args) {
Agent agent = new Agent(new Star("Eminem"));
agent.sing("Mockingbird");
}
複製代碼
輸出:
get money
Eminem sing a song: Mockingbird
write receipt
複製代碼
以上就是一個典型的靜態代理的實例,很簡單可是也能說明問題,咱們來看看靜態代理的優缺點:
優勢: 業務類能夠只關注自身邏輯,能夠重用,經過代理類來增長通用的邏輯處理。
缺點: 1.代理對象的一個接口只服務於一種類型的對象,若是要代理的類不少,勢必要爲每個類都進行代理,靜態代理在程序規模稍大時就沒法勝任了。
2.若是接口增長一個方法,除了全部實現類須要實現這個方法外,全部代理類也須要實現此方法。增長了代碼維護的複雜度
另外,若是要按照上述的方法使用代理模式,那麼真實角色(委託類)必須是事先已經存在的,並將其做爲代理對象的內部屬性。可是實際使用時,一個真實角色必須對應一個代理角色,若是大量使用會致使類的急劇膨脹;此外,若是事先並不知道真實角色(委託類),該如何使用代理呢?這些問題能夠經過Java的動態代理類來解決。
動態代理類的源碼是在程序運行期間由JVM根據反射等機制動態的生成,因此不存在代理類的字節碼文件。代理類和委託類的關係是在程序運行時肯定。
想弄明白動態代理類實現的思路是什麼,咱們還需用從靜態代理的存在的問題入手,由於畢竟動態代理是爲了解決靜態代理存在問題而出現的,回過頭來看靜態代理的問題:
動態代理如何解決:
也就是說,動態代理類仍是和實現類實現相同的接口,可是動態代理類是根據實現類實現的接口動態生成,不須要使用者關心,另外動態代理類的全部方法調用,統一交給InvocationHandler,不用處理實現類每一個接口的每一個方法。
在這種模式之中:代理Proxy和RealSubject應該實現相同的功能,這一點至關重要。(我這裏說的功能,能夠理解爲某個類的public方法)
在面向對象的編程之中,若是咱們想要約定Proxy和RealSubject能夠實現相同的功能,有兩種方式:
a.一個比較直觀的方式,就是定義一個功能接口,而後讓Proxy 和RealSubject來實現這個接口。
b.還有比較隱晦的方式,就是經過繼承。由於若是Proxy繼承自RealSubject,這樣Proxy則擁有了RealSubject的功能,Proxy還能夠經過重寫RealSubject中的方法,來實現多態。
其中JDK中提供的建立動態代理的機制,是以a這種思路設計的,而cglib則是以b思路設計的。
先來看一個具體的例子,仍是以上邊明星和經紀人的模型爲例,這樣方便對比理解:
將明星的表演抽象成一個ShowService接口,包括了唱歌、跳舞的功能:
package com.fufu.aop;
/** * Created by zhoujunfu on 2018/9/6. */
public interface ShowService {
// 歌唱表演
void sing(String songName);
// 舞蹈表演
void dance();
}
複製代碼
明星類實現了ShowService接口:
package com.fufu.aop;
/** * Created by zhoujunfu on 2018/9/6. * 明星類 */
public class Star implements ShowService{
private String name;
@Override
public void sing(String songName) {
System.out.println(this.name + " sing a song: " + songName);
}
@Override
public void dance() {
System.out.println(this.name + "dance");
}
public Star(String name) {
this.name = name;
}
public Star() {
}
}
複製代碼
實現一個代理類的請求處理器,處理對具體類的全部方法的調用:
package com.fufu.aop;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/** * Created by zhoujunfu on 2018/9/7. */
public class InvocationHandlerImpl implements InvocationHandler {
ShowService target;
public InvocationHandlerImpl(ShowService target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 表演開始前收錢
getMoney();
// 明星開始唱歌
Object invoke = method.invoke(target, args);
// 表演結束後開發票
writeReceipt();
return invoke;
}
private void getMoney() {
System.out.println("get money");
}
private void writeReceipt() {
System.out.println("write receipt");
}
}
複製代碼
經過JDK動態代理機制實現一個動態代理:
package com.fufu.aop;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
/**
* Created by zhoujunfu on 2018/9/7.
*/
public class JDKProxyDemo {
public static void main(String[] args) {
// 1.建立被代理的具體類
Star star = new Star("Eminem");
// 2.獲取對應的ClassLoader
ClassLoader classLoader = star.getClass().getClassLoader();
// 3.獲取被代理對象實現的全部接口
Class[] interfaces = star.getClass().getInterfaces();
// 4.設置請求處理器,處理全部方法調用
InvocationHandler invocationHandler = new InvocationHandlerImpl(star);
/**
* 5.根據上面提供的信息,建立代理對象 在這個過程當中,
* a.JDK會經過根據傳入的參數信息動態地在內存中建立和.class文件等同的字節碼
* b.而後根據相應的字節碼轉換成對應的class,
* c.而後調用newInstance()建立實例
*/
Object o = Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
ShowService showService = (ShowService)o;
showService.sing("Mockingbird");
}
}
複製代碼
咱們從代理的建立入手,看看JDK的動態代理都作了什麼:
Object o = Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
複製代碼
而對於InvocationHandler,咱們須要實現下列的invoke方法:
public Object invoke(Object proxy, Method method, Object[] args)
複製代碼
在調用代理對象中的每個方法時,在代碼內部,都是直接調用了InvocationHandler的invoke方法,而invoke方法根據代理類傳遞給本身的method參數來區分是什麼方法。
能夠看出,Proxy.newProxyInstance()方法生成的對象也是實現了ShowService接口的,因此能夠在代碼中將其強制轉換爲ShowService來使用,和靜態代理到達了一樣的效果。咱們能夠用下面代碼把生成的代理類的字節碼保存到磁盤裏,而後反編譯看看JDK生成的動態代理類的結構。
package com.fufu.aop;
import sun.misc.ProxyGenerator;
import java.io.FileOutputStream;
import java.io.IOException;
/** * Created by zhoujunfu on 2018/9/7. */
public class ProxyUtils {
public static void main(String[] args) {
Star star = new Star("Eminem");
generateClassFile(star.getClass(), "StarProxy");
}
public static void generateClassFile(Class clazz, String proxyName) {
//根據類信息和提供的代理類名稱,生成字節碼
byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces());
String paths = clazz.getResource(".").getPath();
System.out.println(paths);
FileOutputStream out = null;
try {
//保留到硬盤中
out = new FileOutputStream(paths + proxyName + ".class");
out.write(classFile);
out.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
複製代碼
反編譯StarPoxy.class文件後獲得:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
import com.fufu.aop.ShowService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
// 動態代理類StarPoxy實現了ShowService接口
public final class StarProxy extends Proxy implements ShowService {
// 加載接口中定義的全部方法
private static Method m1;
private static Method m3;
private static Method m4;
private static Method m2;
private static Method m0;
//構造函數接入InvocationHandler,也就是持有了InvocationHandler對象h
public StarProxy(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
// 自動生成的sing()方法,實際調用InvocationHandler對象h的invoke方法,傳入m3參數對象表明sing()方法
public final void sing(String var1) throws {
try {
super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
//同理生成dance()方法
public final void dance() throws {
try {
super.h.invoke(this, m4, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
// 加載接口中定義的全部方法
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
m3 = Class.forName("com.fufu.aop.ShowService").getMethod("sing", new Class[]{Class.forName("java.lang.String")});
m4 = Class.forName("com.fufu.aop.ShowService").getMethod("dance", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
複製代碼
經過上面反編譯後的代碼能夠看出,JDK生成的動態代理類實現和具體類相同的接口,並持有InvocationHandler對象(InvocationHandler對象又持有具體類),調用動態代理類中方法,會觸發傳入InvocationHandler的invoke()方法,經過method參數,來區分調用的是什麼具體的方法,具體以下圖所示:
JDK中提供的生成動態代理類的機制有個鮮明的特色是:
某個類必須有實現的接口,而生成的代理類也只能代理某個類接口定義的方法,好比:若是上面例子的Star實現了繼承自ShowService接口的方法外,另外實現了方法play(),則在產生的動態代理類中不會有這個方法了!更極端的狀況是:若是某個類沒有實現接口,那麼這個類就不能用JDK產生動態代理了!
幸虧咱們有cglib,「CGLIB(Code Generation Library),是一個強大的,高性能,高質量的Code生成類庫,它能夠在運行期擴展Java類與實現Java接口。」
cglib 建立某個類A的動態代理類的模式是:
1.查找A上的全部非final 的public類型的方法定義;
2.將這些方法的定義轉換成字節碼;
3.將組成的字節碼轉換成相應的代理的class對象;
4.實現 MethodInterceptor接口,用來處理對代理類上全部方法的請求(這個接口和JDK動態代理InvocationHandler的功能和角色是同樣的)
有了上邊JDK動態代理的例子,cglib的理解起來就簡單了,仍是先以實例說明,ShowService接口和Star類都複用以前的不變:
實現 MethodInterceptor接口:
package com.fufu.aop;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/** * Created by zhoujunfu on 2018/9/7. */
public class MethodInterceptorImpl implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
// 表演開始前收錢
getMoney();
// 明星開始唱歌
Object invoke = methodProxy.invokeSuper(o, objects);
// 表演結束後開發票
writeReceipt();
return invoke;
}
private void getMoney() {
System.out.println("get money");
}
private void writeReceipt() {
System.out.println("write receipt");
}
}
複製代碼
建立動態代理:
package com.fufu.aop;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
/** * Created by zhoujunfu on 2018/9/7. */
public class CglibProxyDemo {
public static void main(String[] args) {
Star star = new Star("Eminem");
MethodInterceptor methodInterceptor = new MethodInterceptorImpl();
//cglib 中增強器,用來建立動態代理
Enhancer enhancer = new Enhancer();
//設置要建立動態代理的類
enhancer.setSuperclass(star.getClass());
// 設置回調,這裏至關因而對於代理類上全部方法的調用,都會調用CallBack,而Callback則須要實行intercept()方法進行攔截
enhancer.setCallback(methodInterceptor);
ShowService showService = (ShowService) enhancer.create();
showService.sing("Mockingbird");
}
}
複製代碼
經過以上實例能夠看出,Cglib經過繼承實現動態代理,具體類不須要實現特定的接口,並且代理類能夠調用具體類的非接口方法,更加靈活。
AOP的具體概念就再也不說了,網上一搜一大把,這篇文章主要介紹Spring AOP低層使用的代理技術,由於平時在使用Spring AOP時,不少人都是copy配置,對上面介紹的這些技術概念並不清楚。
Spring AOP採用的是動態代理,在運行期間對業務方法進行加強,因此不會生成新類,對於動態代理技術,Spring AOP提供了對JDK動態代理的支持以及CGLib的支持,然而何時用哪一種代理呢?
一、若是目標對象實現了接口,默認狀況下會採用JDK的動態代理實現AOP
二、若是目標對象實現了接口,能夠強制使用CGLIB實現AOP
三、若是目標對象沒有實現了接口,必須採用CGLIB庫,spring會自動在JDK動態代理和CGLIB之間轉換
目前來看,Spring貌似和AspectJ沒半毛錢關係,那爲何在許多應用了Spring AOP的項目中都出現了@AspectJ的註解呢?Spring是應用的動態代理,怎麼會還和AspectJ有關係呢,緣由是Spring AOP基於註解配置的狀況下,須要依賴於AspectJ包的標準註解,可是不須要額外的編譯以及AspectJ的織入器,而基於XML配置不須要,因此Spring AOP只是複用了AspectJ的註解,並無其餘依賴AspectJ的地方。
當Spring須要使用@AspectJ註解支持時,須要在Spring配置文件中以下配置:
<aop:aspectj-autoproxy/>
複製代碼
而關於第二點強制使用CGLIB,能夠經過在Spring的配置文件以下配置實現:
<aop:aspectj-autoproxy proxy-target-class="true"/>
複製代碼
proxy-target-class屬性值決定是基於接口的仍是基於類的代理被建立。若是proxy-target-class 屬性值被設置爲true,那麼基於類的代理將起做用(這時須要cglib庫)。若是proxy-target-class屬值被設置爲false或者這個屬性被省略,那麼標準的JDK 基於接口的代理。
因此,雖然使用了Aspect的Annotation,可是並無使用它的編譯器和織入器。其實現原理是JDK動態代理或Cglib,在運行時生成代理類。
已經寫了這麼多了,下面再貼兩個Spring AOP的demo代碼吧,分別是基於XML和註解的:
切面類:
package com.fufu.spring.aop;
import org.springframework.stereotype.Component;
/** * Created by zhoujunfu on 2018/9/7. * 基於XML的Spring AOP */
@Component
public class AgentAdvisorXML {
public void getMoney() {
System.out.println("get money");
}
public void writeReceipt() {
System.out.println("write receipt");
}
}
複製代碼
配置文件:
<?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.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="star" class="com.fufu.proxy.Star">
<property name="name" value="Eminem"/>
</bean>
<bean id="agentAdvisorXML" class="com.fufu.spring.aop.AgentAdvisorXML"/>
<!--Spring基於Xml的切面-->
<aop:config>
<!-- 定義切點函數 -->
<aop:pointcut id="singPointCut" expression="execution(* com.fufu.proxy.Star.sing(..))"/>
<!-- 定義切面 order 定義優先級,值越小優先級越大-->
<aop:aspect ref="agentAdvisorXML" order="0">
<!--前置通知-->
<aop:before method="getMoney" pointcut-ref="singPointCut"/>
<!--後置通知-->
<aop:after method="writeReceipt" pointcut-ref="singPointCut"/>
</aop:aspect>
</aop:config>
</beans>
複製代碼
測試類:
package com.fufu.spring.aop;
import com.fufu.proxy.ShowService;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/** * Created by zhoujunfu on 2018/9/7. */
public class Main {
public static void main(String[] args) {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-aop.xml");
Object star = applicationContext.getBean("star");
ShowService showService = (ShowService)star;
showService.sing("Mockingbird");
}
}
複製代碼
切面類:
package com.fufu.spring.aop;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
/** * Created by zhoujunfu on 2018/9/7. * 基於註解的Spring AOP */
@Aspect
@Component
public class AgentAdvisor {
@Before(value = "execution(* com.fufu.proxy.ShowService.sing(..))")
public void getMoney() {
System.out.println("get money");
}
@After(value = "execution(* com.fufu.proxy.ShowService.sing(..))")
public void writeReceipt() {
System.out.println("write receipt");
}
}
複製代碼
配置文件:
<?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.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.fufu.proxy, com.fufu.spring.aop"/>
<aop:aspectj-autoproxy proxy-target-class="true"/>
</beans>
複製代碼
測試類:
package com.fufu.spring.aop;
import com.fufu.proxy.ShowService;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* Created by zhoujunfu on 2018/9/7.
*/
public class Main {
public static void main(String[] args) {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-aop-annotation.xml");
Object star = applicationContext.getBean("star");
ShowService showService = (ShowService)star;
showService.sing("Mockingbird");
}
}
複製代碼
以上內容,雖然比較淺顯易懂,可是能夠對Java代理機制和Spring AOP會有一個全面的理解,若有錯誤,歡迎指正。