JAVA代理,靜態(tài),動態(tài)詳解
代理
為其他對象提供一種代理以控制這個對象的訪問,在某些情況下一個對象不能直接訪問那個對象時,代理就起到了客戶端和被代理對象 (委托類) 中介作用。
按照代理的創(chuàng)建時期,代理類可以分為兩種:
靜態(tài):由程序員創(chuàng)建代理類或特定工具自動生成源代碼再對其編譯。在程序運行前代理類的.class文件就已經(jīng)存在了。
動態(tài):在程序運行時運用反射機制動態(tài)創(chuàng)建而成。
靜態(tài)代理

Subject: 代理類和被代理類實現(xiàn)同樣的接口
Proxy:代理類,里面有被代理類,具體邏輯委托被代理類進行處理
RealSubject:被代理類,可以在其內(nèi)做一些訪問權(quán)限控制,額外的業(yè)務(wù)處理
Client:看到的是代理類,并不知道具體處理業(yè)務(wù)邏輯的類,降低耦合性
代碼實現(xiàn)
UserDAO 代理和被代理的公共的接口(Subject)
public interface ProxyDao {
boolean insert(String name);
}
UserDAOImpl 被代理類(RealSubject)
public class ProxyDaoImpl implements ProxyDao {
@Override
public boolean insert(String name) {
System.out.println("insert name=" + name);
return true;
}
}
ProxyByInterface 代理類,通過實現(xiàn)接口方式實現(xiàn)代理方式(Proxy)
public class ProxyByInterface implements ProxyDao {
private ProxyDao proxyDao;
public ProxyByInterface(ProxyDao proxyDao) {
this.proxyDao = proxyDao;
}
@Override
public boolean insert(String name) {
System.out.println("before insert by interface");
return proxyDao.insert(name);
}
}
ProxyByExtend 代理類,通過繼承被代理類實現(xiàn)的代理方式(Proxy)
public class ProxyByExtend extends ProxyDaoImpl{
private ProxyDaoImpl proxyDao;
public ProxyByExtend(ProxyDaoImpl proxyDao) {
this.proxyDao = proxyDao;
}
@Override
public boolean insert(String name) {
System.out.println("before insert by extend");
return proxyDao.insert(name);
}
}
獲取代理對象客戶端(Client)
public class Client {
public static void main(String[] args) {
ProxyDaoImpl proxyDao = new ProxyDaoImpl();
//和被代理類實現(xiàn)同個接口方式進行代理
ProxyByInterface proxyByInterface = new ProxyByInterface(proxyDao);
proxyByInterface.insert("zc-Interface");
//通過繼承被代理類方式進行代理
ProxyByExtend proxyByExtend = new ProxyByExtend(proxyDao);
proxyByExtend.insert("zc-Extend");
}
}

好處:
可以不用動原來類的邏輯,再次增加一些功能,符合開閉原則。
真正的業(yè)務(wù)還是交給被代理對象處理的,無須修改原來的類就可以使用代理進行實現(xiàn)。
缺點:
出現(xiàn)了大量的代碼重復(fù)。如果接口增加一個方法,除了所有實現(xiàn)類需要實現(xiàn)這個方法外,所有代理類也需要實現(xiàn)此方法。增加了代碼維護的復(fù)雜度。
代理對象只服務(wù)于一種類型的對象,如果要服務(wù)多類型的對象。勢必要為每一種對象都進行代理,靜態(tài)代理在程序規(guī)模稍大時就無法勝任了。
動態(tài)代理
JDK動態(tài)代理
Jdk動態(tài)代理,針對的是實現(xiàn)接口的類;
要求目標(biāo)對象必須實現(xiàn)接口,因為它創(chuàng)建代理對象的時候是根據(jù)接口創(chuàng)建的。被代理對象可以可以實現(xiàn)多個接口,創(chuàng)建代理時指定創(chuàng)建某個接口的代理對象就可以調(diào)用該接口定義的方法了。
需要java.lang.reflect.InvocationHandler接口和 java.lang.reflect.Proxy 類的支持
//Object proxy:被代理的對象
//Method method:要調(diào)用的方法
//Object[] args:方法調(diào)用時所需要參數(shù)
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
//CLassLoader loader:類的加載器 //Class<?> interfaces:得到全部的接口 //InvocationHandler h:得到InvocationHandler接口的子類的實例 public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
代碼實現(xiàn)
NameDao 姓名-接口(Subject)
public interface NameDao {
public boolean addName(String name);
}
AgeDao 年齡-接口(Subject)
public interface AgeDao {
public boolean addAge(Integer age);
}
NameAndAgeDaoImpl 姓名、年齡實現(xiàn)類(RealSubject)
public class NameAndAgeDaoImpl implements NameDao,AgeDao {
@Override
public boolean addName(String name) {
System.out.println("NameDaoImpl----->" + name);
return true;
}
@Override
public boolean addAge(Integer age) {
System.out.println("AgeDaoImpl----->" + age);
return true;
}
}
MyInvocationHandler,對接口提供的方法進行增強(Proxy)
public class MyInvocationHandler implements InvocationHandler {
private Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("----------before----------");
System.out.println("Proxy=" + proxy.getClass());
System.out.println("method=" + method);
System.out.println("args=" + Arrays.toString(args));
//執(zhí)行目標(biāo)方法對象
Object result = method.invoke(target, args);
System.out.println("----------after----------");
return result;
}
}
ProxyFactory 代理工廠
public class ProxyFactory {
public static Object getProxy(Object proxyObj) {
/**
* loader 指定加載jvm運行時動態(tài)生成的代理對象的加載器
* interface 真實對象實現(xiàn)的所有接口
* h 實現(xiàn)InvocationHandler接口對象
*/
// return Proxy.newProxyInstance(proxyObj.getClass().getClassLoader(),
// proxyObj.getClass().getInterfaces(), new MyInvocationHandler(proxyObj));
return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
proxyObj.getClass().getInterfaces(), new MyInvocationHandler(proxyObj));
}
public static void main(String[] args) {
NameDao nameDao = (NameDao) getProxy(new NameAndAgeDaoImpl());
AgeDao ageDao = (AgeDao) getProxy(new NameAndAgeDaoImpl());
nameDao.addName("zc");
ageDao.addAge(20);
}
}

思考 為什么需要 實現(xiàn)接口的類,而不是 類
main函數(shù)中,運行該語句:
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
public static void main(String[] args) {
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
NameDao nameDao = (NameDao) getProxy(new NameAndAgeDaoImpl());
AgeDao ageDao = (AgeDao) getProxy(new NameAndAgeDaoImpl());
nameDao.addName("zc");
ageDao.addAge(20);
}
可以查看 $Proxy0 類:

會發(fā)現(xiàn)他已經(jīng)繼承了 Proxy , 之后才是創(chuàng)建的一個(多個)接口;而由于java是 單繼承、多接口 的特性,所以JDK動態(tài)代理,需要實現(xiàn)接口的類。
CGLib動態(tài)代理
CGLIB實現(xiàn)動態(tài)代理,并不要求被代理類必須實現(xiàn)接口,底層采用asm字節(jié)碼生成框架生成代理類字節(jié)碼(該代理類繼承了被代理類)。
所以被代理類一定不能定義為final class并且對于final 方法不能被代理。
實現(xiàn)需要
//MethodInterceptor接口的intercept方法 /** *obj 代理對象 *method 委托類方法,被代理對象的方法字節(jié)碼對象 *arg 方法參數(shù) *MethodProxy 代理方法MethodProxy對象,每個方法都會對應(yīng)有這樣一個對象 */ public Object intercept(Object obj, Method method, Object[] arg, MethodProxy proxy)
Ehancer enhancer = new Enhancer() //Enhancer為字節(jié)碼增強器,很方便對類進行擴展 enhancer.setSuperClass(被代理類.class); enhancer.setCallback(實現(xiàn)MethodInterceptor接口的對象) enhancer.create()//返回代理對象,是被代理類的子類
代碼實現(xiàn)
UserDaoImpl 用戶實現(xiàn)類(RealSubject)
public class UserDaoImpl {
public boolean insert(String name) {
System.out.println("insert name=" + name);
return true;
}
public final boolean insert1(String name) {
System.out.println("final insert name=" + name);
return true;
}
}
CglibProxy CGLIB代理類(Proxy)
public class CglibProxy implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("----------before----------");
System.out.println("Proxy=" + o.getClass());
System.out.println("method=" + method);
System.out.println("args=" + Arrays.toString(objects));
System.out.println("methodProxy=" + methodProxy);
//執(zhí)行目標(biāo)方法對象
Object result = methodProxy.invokeSuper(o, objects);
System.out.println("----------after----------");
return result;
}
}
ProxyFactory 代理工廠
public class ProxyFactory {
private static Enhancer enhancer = new Enhancer();
private static CglibProxy cglibProxy = new CglibProxy();
public static Object getProxy(Class cls) {
enhancer.setSuperclass(cls);
enhancer.setCallback(cglibProxy);
return enhancer.create();
}
public static void main(String[] args) {
UserDaoImpl userDao = (UserDaoImpl) getProxy(UserDaoImpl.class);
userDao.insert("zc");
}
}

思考
為什么這里面使用 invokeSuper() ,不使用 invoke()
1.Method method 是被代理對象的方法字節(jié)碼對象。
2.MethodProxy methodProxy 是代理對象的方法字節(jié)碼對象。
使用 MethodProxy 的好處:
不需要給代理對象傳入被代理對象,效率更高。不會出現(xiàn)死循環(huán)的問題。
第一點查看代碼就可以看出,對第二點進行講解:
如何出現(xiàn)死循環(huán)的現(xiàn)象:
Proxy.newProxyInstance(xxx, xxx,
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
...
//加入這一句
proxy.toString();
...
}
});
原因:代理對象方法的時候,都會經(jīng)過攔截器方法。因此,如果在攔截器中再調(diào)用代理對象的方法,就會再次進入攔截器,這樣就形成了死循環(huán)。
**invokeSuper()**方法,可以使用代理對象父類的方法(就是被代理對象)而不必經(jīng)過攔截器-----詳情可以學(xué)習(xí):《類加載機制》、《雙親委派模型》
總結(jié)
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
Spring Validation方法實現(xiàn)原理分析
這篇文章主要介紹了Spring Validation實現(xiàn)原理分析,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-07-07
Java設(shè)計通用的返回數(shù)據(jù)格式過程講解
現(xiàn)在很多的項目server端返回client端的數(shù)據(jù)多數(shù)以JSON格式返回,同時結(jié)合其它需要,通常加一下狀態(tài)碼和信息之類,給前端處理帶來很大的方便,這篇文章就用Java設(shè)計了通用的返回數(shù)據(jù)格式,感興趣的同學(xué)可以參考下文2023-05-05
JavaEE的進程,線程和創(chuàng)建線程的5種方式詳解
這篇文章主要為大家詳細(xì)介紹了JavaEE的進程,線程和創(chuàng)建線程的5種方式,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-03-03
MyBatis-Plus自定義SQL和復(fù)雜查詢的實現(xiàn)
MyBatis-Plus增強了MyBatis的功能,提供注解和XML兩種自定義SQL方式,支持復(fù)雜查詢?nèi)缍啾黻P(guān)聯(lián)、動態(tài)分頁等,通過注解如@Select、@Insert、@Update、@Delete實現(xiàn)CRUD操作,本文就來介紹一下,感興趣的可以了解一下2024-10-10
SpringBoot+easypoi實現(xiàn)數(shù)據(jù)的Excel導(dǎo)出
這篇文章主要為大家詳細(xì)介紹了SpringBoot+easypoi實現(xiàn)數(shù)據(jù)的Excel導(dǎo)出,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-05-05

