代理是基本的設(shè)計(jì)模式之一,是為了提供額外或不同的操作而插入的用以代替實(shí)際的"對(duì)象"的對(duì)象。代理對(duì)象通常繼承自實(shí)際對(duì)象或?qū)?shí)際對(duì)象作為自己的成員變量,因此能夠在提供額外操作的同時(shí)與"實(shí)際對(duì)象"通信并調(diào)用其原有的功能。
代理模式定義:給某一個(gè)對(duì)象提供一個(gè)代理,并由代理對(duì)象控制對(duì)原對(duì)象的引用。
根據(jù)創(chuàng)建代理類的不同可以分為靜態(tài)代理和動(dòng)態(tài)代理。
靜態(tài)代理
程序員創(chuàng)建或特定代碼生成工具自動(dòng)生成源代碼,在對(duì)其編譯。在程序運(yùn)行前,代理類.class字節(jié)碼文件就已經(jīng)存在。
interface IStar {
void dance();
String sing(String song,String song_text);
}
@Data
@AllArgsConstructor
public class BigStar implements IStar {
private String starName;
@Override
public void dance() {
System.out.println(starName+"正在跳舞~");
}
@Override
public String sing(String song,String song_text) {
System.out.println(this.starName+"正在唱歌:《"+song+"》");
return song_text;
}
}
public class ProxyUtil implements IStar{
private IStar star;
public ProxyUtil(IStar star){
this.star = star;
}
@Override
public void dance() {
System.out.println("代理安排跳舞產(chǎn)地");
this.star.dance();
}
@Override
public String sing(String song, String song_text) {
System.out.println("代理安排唱歌產(chǎn)地");
String text = this.star.sing(song,song_text);
System.out.println("歌詞:"+text);
return "謝謝";
}
}
public class Test {
public static void main(String[] args) {
//創(chuàng)建一個(gè)阿yueyue的代理
ProxyUtil 阿yueyue的代理 = new ProxyUtil(new BigStar("阿yueyue"));
阿yueyue的代理.dance();
阿yueyue的代理.sing("沈園外","歌詞1111");
//創(chuàng)建一個(gè)虞書欣的代理
ProxyUtil 虞書欣的代理 = new ProxyUtil(new BigStar("虞書欣"));
虞書欣的代理.dance();
虞書欣的代理.sing("如果愛忘了","歌詞2222");
}
}
上例是一個(gè)靜態(tài)代理的實(shí)現(xiàn),一個(gè)委托類對(duì)應(yīng)一個(gè)代理類,代理類在編譯期間就已經(jīng)確定。如果沒有使用接口,代理類可以通過繼承委托類實(shí)現(xiàn)靜態(tài)代理。
動(dòng)態(tài)代理
代理類在程序運(yùn)行時(shí)利用反射機(jī)制動(dòng)態(tài)創(chuàng)建而成,主要分為jdk動(dòng)態(tài)代理和cglib動(dòng)態(tài)代理。
jdk實(shí)現(xiàn)動(dòng)態(tài)代理需要實(shí)現(xiàn)類通過接口定義業(yè)務(wù)方法,對(duì)于沒有接口的實(shí)現(xiàn)類,可以使用cglib代理。
cglib采用了非常底層的字節(jié)碼技術(shù),原理是通過字節(jié)碼技術(shù)為一個(gè)類創(chuàng)建一個(gè)子類(繼承的方式),
并在子類中使用方法攔截技術(shù)攔截所以父類方法的調(diào)用,順勢(shì)織入橫切邏輯,兩種代理都是實(shí)現(xiàn)Spring Aop的基礎(chǔ)。
jdk動(dòng)態(tài)代理
JDK 動(dòng)態(tài)代理類的字節(jié)碼在程序運(yùn)行時(shí)由 Java 反射機(jī)制動(dòng)態(tài)生成,無需手工編寫它的源代碼。
動(dòng)態(tài)代理類不僅簡(jiǎn)化了編程工作,而且提高了軟件系統(tǒng)的可擴(kuò)展性,因?yàn)?Java 反射機(jī)制可以生成任意類型的動(dòng)態(tài)代理類。
java.lang.reflect 包中的Proxy類和InvocationHandler接口提供了生成動(dòng)態(tài)代理類的能力。
一、定義業(yè)務(wù)接口以及實(shí)現(xiàn)
public interface IUserService {
void login(String userName,String userPwd);
void register(String userName);
void download(String bookName);
}
public class UserService implements IUserService {
@Override
public void login(String userName,String userPwd) {
System.out.println("正在執(zhí)行登錄操作~");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("登錄完成,賬號(hào):"+userName+",密碼:"+userPwd);
}
@Override
public void register(String userName) {
System.out.println("正在執(zhí)行注冊(cè)操作~");
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("用戶名"+userName+"注冊(cè)完成");
}
@Override
public void download(String bookName) {
System.out.println("正在執(zhí)行下載操作~");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("《"+bookName+"》" +"下載完成。");
}
}
二、自定義創(chuàng)建代理的類,在該類里調(diào)用Proxy的靜態(tài)方法創(chuàng)建一個(gè)代理類
public class DefineProxy {
private Object target;
public DefineProxy(Object target){
this.target = target;
}
public Object createProxy(){
return Proxy.newProxyInstance(target.getClass().getClassLoader(), this.target.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("開始計(jì)算方法 "+method.getName()+" 的執(zhí)行時(shí)間~");
long start = System.currentTimeMillis();
Object invoke = method.invoke(target, args);
long end = System.currentTimeMillis();
System.out.println("執(zhí)行時(shí)間為:"+(end-start)+"毫秒。");
return invoke;
}
});
}
}
① DefineProxy構(gòu)造方法傳入的target對(duì)象是即將被代理的類的對(duì)象,最終還是需要使用這個(gè)對(duì)象來執(zhí)行業(yè)務(wù)操作。
② Proxy的靜態(tài)方法newProxyInstance參數(shù)
//public static Object newProxyInstance(ClassLoader loader,
// Class<?>[] interfaces,
// InvocationHandler h)
- loader:類加載器,使用本類的類加載器即可,或者target對(duì)象的類加載器也行。
- interfaces:接口數(shù)組,表示被代理類實(shí)現(xiàn)的接口,因可能會(huì)實(shí)現(xiàn)多個(gè)接口,所以這里是數(shù)組的形式。
- InvocationHandler h:核心,在這里創(chuàng)建一個(gè)內(nèi)部實(shí)現(xiàn)類實(shí)現(xiàn)InvocationHandler接口,重寫invoke方法
invoke里的參數(shù)proxy是代理類的實(shí)例,method是代理類的實(shí)例執(zhí)行的方法,args則是執(zhí)行的方法里面的參數(shù),
我們可以在這里對(duì)執(zhí)行核心業(yè)務(wù)邏輯前后增加代碼。method.invoke(target, args)這里是用了反射的原理讓target對(duì)象去執(zhí)行method方法。
三、通過代理調(diào)用方法
public class Test {
public static void main(String[] args) {
// IUserService proxy = (IUserService)ProxyFactory.jdk_getProxyInstance(new UserService());
IUserService proxy = (IUserService)new DefineProxy(new UserService()).createProxy();
proxy.login("anbanyu","czw520kdd");
proxy.register("anbanyu");
proxy.download("小而美:持續(xù)盈利的經(jīng)營(yíng)法則");
}
}
cglib 動(dòng)態(tài)代理
JDK 中提供的生成動(dòng)態(tài)代理類的機(jī)制有個(gè)鮮明的特點(diǎn)是:某個(gè)類必須有實(shí)現(xiàn)的接口,如果某個(gè)類沒有實(shí)現(xiàn)接口,那么這個(gè)類就不能通過 JDK 產(chǎn)生動(dòng)態(tài)代理了!不過幸好我們有 CGLib。CGLIB(Code Generation Library)是一個(gè)強(qiáng)大的、高性能、高質(zhì)量的Code生成類庫,它可以在運(yùn)行期擴(kuò)展Java類與實(shí)現(xiàn)Java接口。
CGLIB 通過動(dòng)態(tài)生成一個(gè)需要被代理類的子類(即被代理類作為父類),該子類重寫被代理類的所有不是 final 修飾的方法,并在子類中采用方法攔截的技術(shù)攔截父類所有的方法調(diào)用,進(jìn)而織入橫切邏輯。此外,因?yàn)?CGLIB 采用整型變量建立了方法索引,這比使用 JDK 動(dòng)態(tài)代理更快(使用 Java 反射技術(shù)創(chuàng)建代理類的實(shí)例)。
CGLib 創(chuàng)建某個(gè)類 A 的動(dòng)態(tài)代理類的模式是:
- 查找 A 上的所有非 final 的 public 類型的方法定義;
- 將這些方法的定義轉(zhuǎn)換成字節(jié)碼;
- 將組成的字節(jié)碼轉(zhuǎn)換成相應(yīng)的代理的 class 對(duì)象;
- 實(shí)現(xiàn) MethodInterceptor 接口,用來處理 對(duì)代理類上所有方法的請(qǐng)求(這個(gè)接口和JDK動(dòng)態(tài)代理InvocationHandler的功能和角色是一樣的)
一、首先定義一個(gè)委托類,注意就是一個(gè)普通的類
public class Dog {
public void eat(){
System.out.println("狗吃屎");
}
}
二、實(shí)現(xiàn)MethodInterceptor方法
public class CGLibProxy implements MethodInterceptor {
private Object target;
public CGLibProxy(Object target){
this.target = target;
}
public Object createProxy(){
//cglib中的增強(qiáng)器,用來創(chuàng)建動(dòng)態(tài)代理
Enhancer enhancer = new Enhancer();
//設(shè)置要?jiǎng)?chuàng)建動(dòng)態(tài)代理的類
enhancer.setSuperclass(target.getClass());
//設(shè)置回調(diào),這里相當(dāng)于是對(duì)于代理類上所有方法的調(diào)用,都會(huì)調(diào)用callback,而callback則需要實(shí)現(xiàn)intercept()方法進(jìn)行攔截。
enhancer.setCallback(this);
//創(chuàng)建代理類
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("cglib proxy start...");
methodProxy.invokeSuper(o,args);
System.out.println("cglib proxy end...");
return null;
}
}
三、測(cè)試代理類
public class Test {
public static void main(String[] args) {
// Dog dog = (Dog)ProxyFactory.cglib_getProxyInstance(new Dog());
Dog dog = (Dog)new CGLibProxy(new Dog()).createProxy();
dog.eat();
}
}
- 注意由于 CGLib 動(dòng)態(tài)代理采用的是繼承委托類的方式,因此不能代理 final 修飾的類。如果將上例中的類Dog添加 final 修飾符,再次運(yùn)行則會(huì)看到如下錯(cuò)誤信息:
Exception in thread "main" java.lang.IllegalArgumentException: Cannot subclass final class chapter14.Train at net.sf.cglib.proxy.Enhancer.generateClass(Enhancer.java:565) at net.sf.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:25) at net.sf.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:329)
兩種動(dòng)態(tài)代理區(qū)別總結(jié)
Java 動(dòng)態(tài)代理是利用反射機(jī)制生成一個(gè)實(shí)現(xiàn)代理接口的匿名類,在調(diào)用具體方法前調(diào)用 InvokeHandler 來處理。而 CGLIB 動(dòng)態(tài)代理是利用 ASM 開源包,對(duì)代理對(duì)象類的 class 文件加載進(jìn)來,通過修改其字節(jié)碼生成子類來處理。
使用場(chǎng)景
實(shí)現(xiàn) AOP 功能
Spring 的 AOP 功能就是利用動(dòng)態(tài)代理的原理實(shí)現(xiàn)的。其會(huì)根據(jù)被代理對(duì)象是否實(shí)現(xiàn)了接口選擇不同的生成代理對(duì)象的方式,如果被代理對(duì)象實(shí)現(xiàn)了需要被代理的接口,則使用 JDK 的動(dòng)態(tài)代理,否則便使用 CGLIB 代理。
- 如果目標(biāo)對(duì)象實(shí)現(xiàn)了接口,默認(rèn)情況下會(huì)采用 JDK 的動(dòng)態(tài)代理實(shí)現(xiàn) AOP,對(duì)應(yīng)的包裝類為 JdkDynamicAopProxy。
- 如果目標(biāo)對(duì)象實(shí)現(xiàn)了接口,可以強(qiáng)制使用 CGLIB 實(shí)現(xiàn) AOP
- 如果目標(biāo)對(duì)象沒有實(shí)現(xiàn)了接口,必須采用 CGLIB 庫,spring 會(huì)自動(dòng)在 JDK 動(dòng)態(tài)代理和 CGLIB 之間轉(zhuǎn)換
Spring Aop 和 cglib的關(guān)系

- 最底層是字節(jié)碼。
- ASM是操作字節(jié)碼的工具。
- cglib基于ASM字節(jié)碼工具操作字節(jié)碼(即動(dòng)態(tài)生成代理,對(duì)方法進(jìn)行增強(qiáng))。
- Spring aop基于cglib進(jìn)行封裝,實(shí)現(xiàn)cglib方式的動(dòng)態(tài)代理。
cglib依賴包
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>