什么是代理模式:代理模式是一種結(jié)構(gòu)型設(shè)計(jì)模式,主要用于給某一個(gè)對(duì)象提供代理對(duì)象,并由代理對(duì)象控制對(duì)真實(shí)對(duì)象的訪問。
主要應(yīng)用:Spring AOP、日志、用戶鑒權(quán)、Hibernate數(shù)據(jù)查詢、測試框架的后端mock、RPC遠(yuǎn)程調(diào)用、Java注解對(duì)象獲取、、全局性異常處理、性能監(jiān)控,甚至事務(wù)處理等。
代理模式角色:主要分為調(diào)用方、代理對(duì)象,業(yè)務(wù)對(duì)象、抽象接口
- 抽象接口:定義對(duì)外提供的方法(功能)。
- 業(yè)務(wù)對(duì)象:實(shí)現(xiàn)抽象接口所定義的具體功能。
- 代理對(duì)象:實(shí)現(xiàn)抽象接口,封裝業(yè)務(wù)對(duì)象,控制對(duì)象的訪問,并提供給調(diào)用方使用。
代理模式的優(yōu)點(diǎn):
- 可以使真是角色的操作更純粹,不用去關(guān)注一些公共業(yè)務(wù)
- 公共業(yè)務(wù)就交給代理角色,實(shí)現(xiàn)了業(yè)務(wù)的分工
- 公共業(yè)務(wù)發(fā)生擴(kuò)展的時(shí)候,方便集中管理
缺點(diǎn):
- 一個(gè)真實(shí)角色就會(huì)產(chǎn)生一個(gè)代理角色,代碼量會(huì)翻倍
代理模式按照代理類的創(chuàng)建時(shí)機(jī)可分為靜態(tài)代理與動(dòng)態(tài)代理:
- 靜態(tài)代理:程序運(yùn)行前就已經(jīng)有代理類的字節(jié)碼
- 動(dòng)態(tài)代理:在程序運(yùn)行期間動(dòng)態(tài)生成代理類,在Java中JDK動(dòng)態(tài)代理與Cglib動(dòng)態(tài)代理。
下面用一個(gè)案例來分析靜態(tài)代理、JDK動(dòng)態(tài)代理與Cglib動(dòng)態(tài)代理的實(shí)現(xiàn)。
編寫一個(gè)日志打印功能的代理:如下UserDao是一個(gè)抽象接口,定義了獲取、設(shè)置用戶信息的方法,UserDaoImpl是具體的實(shí)現(xiàn)類。
public interface UserDao {
public void setUserInfo(String userInfo);
public String getUserInfo();
}
public class UserDaoImpl implements UserDao{
private String userInfo;
@Override
public void setUserInfo(String userInfo) {
this.userInfo = userInfo;
System.out.println("插入信息:" + userInfo);
}
@Override
public String getUserInfo() {
System.out.println("獲取信息:" + userInfo);
return userInfo;
}
}
首先從靜態(tài)代理開始。
靜態(tài)代理
靜態(tài)代理需要手工編碼創(chuàng)建代理類,這里定義為LogStaticProxy,代理類實(shí)現(xiàn)了UserDao接口,并持有代理類對(duì)象的引用,在具體業(yè)務(wù)方法前后執(zhí)行了日志打印的代碼,如下。
public class LogStaticProxy implements UserDao{
UserDao userDao;
public LogStaticProxy(UserDao userDao){
this.userDao = userDao;
}
@Override
public void setUserInfo(String userInfo) {
before();
this.userDao.setUserInfo(userInfo);
after();
}
@Override
public String getUserInfo() {
before();
String rst = this.userDao.getUserInfo();
after();
return rst;
}
private void before(){
System.out.println("log before" + (new Date().toString()));
}
private void after(){
System.out.println("log after" + (new Date().toString()));
}
}
測試類:
public class TestClient1 {
public static void main(String[] args) {
UserDao userDao = new UserDaoImpl();
UserDao proxy = new LogStaticProxy(userDao);
proxy.setUserInfo("我是static proxy");
System.out.println(proxy.getUserInfo());
// 輸出
// log beforeTue Jun 14 20:10:06 CST 2022
// 插入信息:我是static proxy
// log afterTue Jun 14 20:10:06 CST 2022
// log beforeTue Jun 14 20:10:06 CST 2022
// 獲取信息:我是static proxy
// log afterTue Jun 14 20:10:06 CST 2022
// 我是static proxy
}
}
靜態(tài)代理很好的實(shí)現(xiàn)了與業(yè)務(wù)無關(guān)的代碼的抽離,但是其還有不足之處:
- 當(dāng)代理的類很多時(shí),有兩種實(shí)現(xiàn)方式
- 要求代理類實(shí)現(xiàn)每一個(gè)類的接口,這樣會(huì)導(dǎo)致代理類過于臃腫
- 對(duì)于每一個(gè)類都生成一個(gè)代理類,這樣產(chǎn)生過多的代理類
- 當(dāng)被代理類增加、刪除、修改接口時(shí),代理類也需要做對(duì)應(yīng)的改造
一種改進(jìn)的方法就是使用動(dòng)態(tài)代理。使用動(dòng)態(tài)代理,無需在開發(fā)階段編寫代理類,只需要編寫代理要執(zhí)行的操作即可,代理類實(shí)在運(yùn)行時(shí)動(dòng)態(tài)生成的。
JDK動(dòng)態(tài)代理
使用方法如下,首先需要編寫一個(gè)用于執(zhí)行代理操作的類LogJdkHandler,這個(gè)類需要實(shí)現(xiàn)java.lang.reflect.InvocationHandler接口,并且持有代理對(duì)象的引用。用戶使用代理類調(diào)用接口方法時(shí),會(huì)觸發(fā)LogJdkHandler所實(shí)現(xiàn)的invoke方法。真正原有對(duì)象的方法是通過反射的方式調(diào)用的,即method.invoke(target, args)。
public class LogJdkHandler implements InvocationHandler{
private Object target;
public LogJdkHandler(Object userDao) {
this.target = userDao;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// proxy是動(dòng)態(tài)生成的代理類的對(duì)象
// method是被觸發(fā)的方法
// args是方法入?yún)⒘斜? before();
Object result = method.invoke(target, args);
after();
return result;
}
private void before(){
System.out.println("log before" + (new Date().toString()));
}
private void after(){
System.out.println("log after" + (new Date().toString()));
}
}
然后就可以通過java.lang.reflect.Proxy的靜態(tài)方法newProxyInstance創(chuàng)建目標(biāo)對(duì)象的代理對(duì)象,具體方法如下:
public class TestClient2 {
public static void main(String[] args) {
// 1. 創(chuàng)建代理對(duì)象
UserDao userDao = new UserDaoImpl();
// 2. 創(chuàng)建代理的處理類
LogJdkHandler handler = new LogJdkHandler(userDao);
// 3. 使用Proxy.newProxyInstance生成代理對(duì)象
UserDao proxy = (UserDao)Proxy.newProxyInstance(
userDao.getClass().getClassLoader(),
userDao.getClass().getInterfaces(),
handler);
proxy.setUserInfo("我是jdk proxy");
System.out.println(proxy.getUserInfo());
// 輸出:
// 插入信息:我是jdk proxy
// log afterTue Jun 14 20:45:20 CST 2022
// log beforeTue Jun 14 20:45:20 CST 2022
// 獲取信息:我是jdk proxy
// log afterTue Jun 14 20:45:20 CST 2022
// 我是jdk proxy
}
}
JDK動(dòng)態(tài)代理的實(shí)現(xiàn)原理
在測試類中,使用的是Proxy.newProxyInstance創(chuàng)建的代理對(duì)象,點(diǎn)擊進(jìn)入該方法,可以看到代理類的創(chuàng)建過程。newProxyInstance方法的入?yún)⒁来问谴韺?duì)象的類加載器ClassLoader loader,代理對(duì)象實(shí)現(xiàn)的接口列表Class<?>[] interfaces、以及我們自己實(shí)現(xiàn)的InvocationHandler h。
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* Look up or generate the designated proxy class.
*/
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
最主要的是下面兩行代碼:
Class<?> cl = getProxyClass0(loader, intfs);
首先去獲取生成的代理類類實(shí)例Class<?> cl,getProxyClass0(loader, intfs)方法會(huì)首先判斷該類實(shí)例是否在內(nèi)存中,是則直接獲取,如果不在,則進(jìn)行生成。然后獲取類實(shí)例的構(gòu)造函數(shù)。
final Constructor<?> cons = cl.getConstructor(constructorParams);
可以看到,cl.getConstructor代碼獲取的是參數(shù)類型為的constructorParams構(gòu)造函數(shù),該類型在Proxy類中有定義:
private static final Class<?>[] constructorParams =
{ InvocationHandler.class };
該類型即為我們實(shí)現(xiàn)的接口InvocationHandler。最后通過反射調(diào)用構(gòu)造函數(shù)來創(chuàng)建類實(shí)例,其入?yún)⒓礊镻roxy.newInstance傳入的第三個(gè)參數(shù)h,即我們創(chuàng)建的LogJdkHandler對(duì)象:
return cons.newInstance(new Object[]{h});
代理得的動(dòng)態(tài)創(chuàng)建過程以及將生成的代理類放入到內(nèi)存中的方法這里先不展開(感興趣可以點(diǎn)擊閱讀JDK動(dòng)態(tài)代理代理類創(chuàng)建源碼淺析)。為了方便理解,可以先看一下生成的代理類結(jié)構(gòu):
在測試類生成代理前運(yùn)行如下代碼,就可以將動(dòng)態(tài)生成的代理類的class文件保存到工程com\sun\proxy目錄下的,類似于$Proxy0.class的文件。
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
反編譯$Proxy0.class,得到如下代碼
package com.sun.proxy;
import com.maomao.patterndesign.p1_proxy.UserDao;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements UserDao {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m4;
private static Method m0;
public $Proxy0(InvocationHandler paramInvocationHandler) {
super(paramInvocationHandler);
}
public final boolean equals(Object paramObject) {
try {
return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
} catch (Error|RuntimeException error) {
throw null;
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final String toString() {
try {
return (String)this.h.invoke(this, m2, null);
} catch (Error|RuntimeException error) {
throw null;
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final String getUserInfo() {
try {
return (String)this.h.invoke(this, m3, null);
} catch (Error|RuntimeException error) {
throw null;
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final void setUserInfo(String paramString) {
try {
this.h.invoke(this, m4, new Object[] { paramString });
return;
} catch (Error|RuntimeException error) {
throw null;
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final int hashCode() {
try {
return ((Integer)this.h.invoke(this, m0, null)).intValue();
} catch (Error|RuntimeException error) {
throw null;
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m3 = Class.forName("com.maomao.patterndesign.p1_proxy.UserDao").getMethod("getUserInfo", new Class[0]);
m4 = Class.forName("com.maomao.patterndesign.p1_proxy.UserDao").getMethod("setUserInfo", new Class[] { Class.forName("java.lang.String") });
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
return;
} catch (NoSuchMethodException noSuchMethodException) {
throw new NoSuchMethodError(noSuchMethodException.getMessage());
} catch (ClassNotFoundException classNotFoundException) {
throw new NoClassDefFoundError(classNotFoundException.getMessage());
}
}
}
反編譯后可以看到生成的代理類的結(jié)構(gòu)如下,生成的代理類名稱為$Proxy0。$Proxy0繼承了Proxy類并且實(shí)現(xiàn)了UserDao接口定義的方法。$Proxy0擁有一個(gè)有參構(gòu)造函數(shù),入?yún)⑹?code>InvocationHandler(即我們自定義的攔截方法),并在構(gòu)造函數(shù)調(diào)用父類的構(gòu)造函數(shù)super(paramInvocationHandler);。展開Proxy類,可以看到類中定義了成員字段,該字段用于保留攔截器對(duì)象的引用。
/**
* the invocation handler for this proxy instance.
* @serial
*/
protected InvocationHandler h;
代理類除了實(shí)現(xiàn)UserDao的方法外(setUserInfo,getUserInfo),還重寫了hashCode()、toString以及equals方法。在每個(gè)重寫的方法中都有類似的調(diào)用邏輯,以setUserInfo為例:
public final void setUserInfo(String paramString) {
try {
this.h.invoke(this, m4, new Object[] { paramString });
return;
} catch (Error|RuntimeException error) {
throw null;
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
可以看到,當(dāng)用戶調(diào)用代理類對(duì)象的setUserInfo方法后,代理類對(duì)象會(huì)執(zhí)行this.h.invoke(this, m4, new Object[] { paramString });語句,該語句就是調(diào)用了我么實(shí)現(xiàn)的InvocationHandler的invoke方法,即我們?cè)跀r截器中重寫的方法。該方法的入?yún)⒁来问谴眍悓?duì)象自身的引用,觸發(fā)的方法對(duì)象、參數(shù)列表。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// proxy是動(dòng)態(tài)生成的代理類的對(duì)象
// method是被觸發(fā)的方法
// args是方法入?yún)⒘斜? before();
Object result = method.invoke(target, args);
after();
return result;
}
這里有一個(gè)值得注意的地方是,在invoke方法中執(zhí)行真正的業(yè)務(wù)代碼,是通過反射調(diào)用的,即method.invoke(target, args);。傳入的第一個(gè)參數(shù)是我們代理的對(duì)象引用,而不是入?yún)?code>proxy,如果傳入proxy的話,會(huì)繼續(xù)觸發(fā)代理類的方法,而代理類的方法會(huì)執(zhí)行this.h.invoke,因此會(huì)陷入無盡循環(huán)。
Cglib動(dòng)態(tài)代理
首先需要?jiǎng)?chuàng)建一個(gè)方法攔截器類LogMethodInterceptor,用于編寫代理的功能。該方法攔截器類需要實(shí)現(xiàn)Cglib的MethodInterceptor接口,該接口定義了一個(gè)intercept方法,入?yún)⑷缦拢?/p>
- Object:由CGlib創(chuàng)建的被代理的對(duì)象
- method:觸發(fā)的方法
- args:方法入?yún)?/li>
- methodProxy:增強(qiáng)的方法
public class LogMethodInterceptor implements MethodInterceptor{
@Override
public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
before();
Object rst = methodProxy.invokeSuper(object, args);
after();
return rst;
}
private void before(){
System.out.println("log before" + (new Date().toString()));
}
private void after(){
System.out.println("log after" + (new Date().toString()));
}
}
之后就可以使用Cglib來創(chuàng)建代理對(duì)象了:
public class TestClient3 {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserDaoImpl.class);
LogMethodInterceptor interceptor = new LogMethodInterceptor();
enhancer.setCallback(interceptor);
UserDao userDao = (UserDao) enhancer.create();
userDao.setUserInfo("我是cglib");
System.out.println(userDao.getUserInfo());
// log beforeTue Jun 14 21:13:15 CST 2022
// 插入信息:我是cglib
// log afterTue Jun 14 21:13:15 CST 2022
// log beforeTue Jun 14 21:13:15 CST 2022
// 獲取信息:我是cglib
// log afterTue Jun 14 21:13:15 CST 2022
// 我是cglib
}
}
首先創(chuàng)建Enhancer對(duì)象enhancer,設(shè)置要增強(qiáng)的類以及攔截方法類,然后就能通過調(diào)用enhancer.create()方法創(chuàng)建被代理的類的對(duì)象了。