Java代理的幾種實現(xiàn)方式總結(jié)
1. 簡介
本文將通過例子說明java代理的幾種實現(xiàn)方式,并比較它們之間的差異。
1.1 例子應(yīng)用場景
代理對象給被代理對象(目標對象),增加一個目標對象方法被調(diào)用的日志,用于后續(xù)通過日志采集統(tǒng)計方法被調(diào)用次數(shù)。
1.2 被代理對象代碼
后面所有例子均使用這些相同代碼。
● 被代理對象接口
public interface Animal { void say(); }
● 被代理對象Dog類
import lombok.extern.slf4j.Slf4j; @Slf4j public class Dog implements Animal { @Override public void say() { log.info("汪汪"); } }
2. 靜態(tài)代理
簡單點說,靜態(tài)代理就是在編碼階段已經(jīng)確定了代理對象。
這里簡單回顧下代理模式的基本類結(jié)構(gòu):
類 | 描述 |
---|---|
Subject | 被代理目標類的接口。 |
RealSubject | 被代理目標類。 |
ProxySubject | 代理類,需要實現(xiàn)被代理目標類接口,同時持有被代理目標類對象實例,實際調(diào)用時,通過委托的方式調(diào)用被代理目標類。 |
SubjectFactory | 被代理目標類的工廠類,通過工廠類屏蔽創(chuàng)建Subject的細節(jié),客戶端看到的是Subject,至于具體是哪個實現(xiàn)類不需要關(guān)心。 |
2.1 代碼示例
2.1.1 DogStaticProxy
Dog代理類:
import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; @Slf4j @AllArgsConstructor // 代理類,實現(xiàn)Subject接口 public class DogStaticProxy implements Animal { // 持有RealSubject對象實例 private Dog dog; @Override public void say() { // 增強的能力,打印方法調(diào)用日志 log.info("Dog.say() called."); // 通過委托的方式調(diào)用RealSubject對象實例方法 dog.say(); } }
2.1.2 DuckStaticProxy
Duck代理類:
import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; @Slf4j @AllArgsConstructor public class DuckStaticProxy implements Animal { private Duck duck; @Override public void say() { log.info("Dog.say() called."); duck.say(); } }
2.1.3 StaticProxyFactory
代理工廠類:
public class StaticProxyFactory { // 屏蔽創(chuàng)建Subject實例的細節(jié),使用者看到的只有Subject接口,即Animal public static Animal createDog() { return new DogStaticProxy(new Dog()); } public static Animal createDuck() { return new DuckStaticProxy(new Duck()); } }
2.1.4 StaticProxyFactoryTest
測試代碼:
import org.junit.jupiter.api.Test; class StaticProxyFactoryTest { @Test public void exec() { Animal dog = StaticProxyFactory.createDog(); Animal duck = StaticProxyFactory.createDuck(); dog.say(); duck.say(); } }
2.1.5 輸出日志
2023-12-28 12:00:35 INFO DogStaticProxy:16 - Dog.say() called.
2023-12-28 12:00:35 INFO Dog:10 - 汪汪
2023-12-28 12:00:35 INFO DuckStaticProxy:13 - Dog.say() called.
2023-12-28 12:00:35 INFO Duck:9 - 嘎嘎
3. JDK動態(tài)代理
動態(tài)代理就是在編碼階段還沒有確定代理對象,在運行期才動態(tài)確定。
如下是JDK動態(tài)代理的類結(jié)構(gòu):
類 | 描述 |
---|---|
Subject | 被代理目標類的接口。 |
RealSubject | 被代理目標類。 |
InvocationHandler | 可以理解為一個攔截器,被代理對象方法調(diào)用時都會先調(diào)用此接口的invoke方法,在invoke方法內(nèi)部去調(diào)用被代理對象方法。 |
InvocationHandler實現(xiàn)類 | InvocationHandler的實現(xiàn)類 |
Proxy | JDK代理工具類,用于生成代理對象,生成代理對象時會用到RealSubject和InvocationHandler實現(xiàn)類。 |
3.1 代碼示例
3.1.1 AnimalInvocationHandler
InvocationHandler的實現(xiàn)類:
import lombok.extern.slf4j.Slf4j; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; @Slf4j public class AnimalInvocationHandler implements InvocationHandler { private Animal animal; // 通過構(gòu)造器傳入目標類實例對象 public AnimalInvocationHandler(Animal animal) { this.animal = animal; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 增強能力,打印方法調(diào)用日志,采集后統(tǒng)計調(diào)用次數(shù) log.info(getCalledMethodInfo(method)); // 調(diào)用目標類方法,參數(shù)是目標類實例和對應(yīng)的方法參數(shù) return method.invoke(animal, args); } // 輔助方法,返回被調(diào)用方法信息 private String getCalledMethodInfo(Method method) { StringBuilder builder = new StringBuilder(); builder.append(animal.getClass().getSimpleName()) .append(".") .append(method.getName()) .append("() called."); return builder.toString(); } }
3.1.2 JDKProxyFactory
代理工廠類:
import java.lang.reflect.Proxy; public class JDKProxyFactory { public static Animal createDog() { return getProxy(new Dog()); } public static Animal createDuck() { return getProxy(new Duck()); } private static Animal getProxy(Animal implInstance) { AnimalInvocationHandler handler = new AnimalInvocationHandler(implInstance); // 創(chuàng)建代理對象 Object proxy = Proxy.newProxyInstance(implInstance.getClass().getClassLoader(), implInstance.getClass().getInterfaces(), handler); return (Animal) proxy; } }
3.1.3 JDKProxyFactoryTest
測試類:
import org.junit.jupiter.api.Test; class JDKProxyFactoryTest { @Test public void exec() { Animal dog = JDKProxyFactory.createDog(); Animal duck = JDKProxyFactory.createDuck(); dog.say(); duck.say(); } }
3.1.4 輸出日志
2023-12-28 12:27:37 INFO AnimalInvocationHandler:19 - Dog.say() called.
2023-12-28 12:27:37 INFO Dog:10 - 汪汪
2023-12-28 12:27:37 INFO AnimalInvocationHandler:19 - Duck.say() called.
2023-12-28 12:27:37 INFO Duck:9 - 嘎嘎
3.2 通用能力重構(gòu)
打印方法調(diào)用日志不僅僅適用于Animal,也適用于其他所有有此需求的類,可以優(yōu)化一下。
注:此優(yōu)化非JDK動態(tài)代理的能力,存粹就是順便處理一下,重構(gòu)無處不在,只要你想做。
3.2.1 代碼
3.2.1.1 MethodPrinterInvocationHandler
AnimalInvocationHandler只能適用于Animal,重構(gòu)通過泛型實現(xiàn):
import lombok.extern.slf4j.Slf4j; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; @Slf4j // 使用泛型,只要是有接口的實現(xiàn)類都可以使用 public class MethodPrinterInvocationHandler<T> implements InvocationHandler { private T t; public MethodPrinterInvocationHandler(T t) { this.t = t; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { log.info(getCalledMethod(method)); return method.invoke(t, args); } private String getCalledMethod(Method method) { StringBuilder builder = new StringBuilder(); builder.append(t.getClass().getSimpleName()) .append(".") .append(method.getName()) .append("() called."); return builder.toString(); } }
3.2.1.2 MethodPrinterJDKProxyFactory
代理工廠類同樣通過泛型實現(xiàn):
import java.lang.reflect.Proxy; public class MethodPrinterJDKProxyFactory { // 使用泛型,除了Animal接口和其子類,其他接口和子類也可以使用 public static <T, R> R getProxy(T implInstance ) { MethodPrinterInvocationHandler<T> handler = new MethodPrinterInvocationHandler(implInstance); return (R) Proxy.newProxyInstance(implInstance.getClass().getClassLoader(), implInstance.getClass().getInterfaces(), handler); } }
3.2.1.3 AnimalJDKProxyFactory
public class AnimalJDKProxyFactory { public static Animal createDuck() { return MethodPrinterJDKProxyFactory.getProxy(new Duck()); } public static Animal createDog() { return MethodPrinterJDKProxyFactory.getProxy(new Dog()); } }
3.2.1.4 MethodPrinterJDKProxyFactoryTest
import org.junit.jupiter.api.Test; class MethodPrinterJDKProxyFactoryTest { @Test public void exec() { Animal dog = AnimalJDKProxyFactory.createDog(); Animal duck = AnimalJDKProxyFactory.createDuck(); dog.say(); duck.say(); } }
3.2.1.5 打印日志
2023-12-28 12:54:00 INFO MethodPrinterInvocationHandler:18 - Dog.say() called.
2023-12-28 12:54:00 INFO Dog:10 - 汪汪
2023-12-28 12:54:00 INFO MethodPrinterInvocationHandler:18 - Duck.say() called.
2023-12-28 12:54:00 INFO Duck:9 - 嘎嘎
3.3 JDK動態(tài)代理限制
JDK動態(tài)代理只能代理接口,無法直接代理類,下面看個例子。
3.3.1 代碼
3.3.1.1 Cat
新增一個類,沒有實現(xiàn)Animal接口,用于證明沒有接口無法使用JDK動態(tài)代理:
import lombok.extern.slf4j.Slf4j; @Slf4j public class Cat { public void say() { log.info("喵喵"); } }
3.3.1.2 CatInvocationHandler
攔截器也要調(diào)整,將Cat作為構(gòu)造器參數(shù),不能再使用Animal:
import lombok.extern.slf4j.Slf4j; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; @Slf4j public class CatInvocationHandler implements InvocationHandler { private Cat cat; public CatInvocationHandler(Cat cat) { this.cat = cat; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { log.info(getCalledMethod(method)); return method.invoke(cat, args); } private String getCalledMethod(Method method) { StringBuilder builder = new StringBuilder(); builder.append(cat.getClass().getSimpleName()) .append(".") .append(method.getName()) .append("() called."); return builder.toString(); } }
3.3.1.3 ErrorJDKProxyFactory
代理工廠類:
import java.lang.reflect.Proxy; public class ErrorJDKProxyFactory { public static Cat createCat() { return getProxy(new Cat()); } private static Cat getProxy(Cat cat) { CatInvocationHandler handler = new CatInvocationHandler(cat); // 這里的寫法仍然不變,和之前一樣,只是類型變化 return (Cat) Proxy.newProxyInstance(cat.getClass().getClassLoader(), cat.getClass().getInterfaces(), handler); } }
3.3.1.4 ErrorJDKProxyFactoryTest
測試類:
import org.junit.jupiter.api.Test; class ErrorJDKProxyFactoryTest { @Test void createCat() { Cat cat = ErrorJDKProxyFactory.createCat(); cat.say(); } }
3.3.1.5 輸出日志
java.lang.ClassCastException: com.sun.proxy.$Proxy8 cannot be cast to com.kengcoder.javaawsome.proxy.jdkproxy.Cat at com.kengcoder.javaawsome.proxy.jdkproxy.ErrorJDKProxyFactory.getProxy(ErrorJDKProxyFactory.java:13) at com.kengcoder.javaawsome.proxy.jdkproxy.ErrorJDKProxyFactory.createCat(ErrorJDKProxyFactory.java:8)
3.3.1.6 原因分析
● 通過類代理
兩個對象類型不一致,沒法做強轉(zhuǎn)。
● 通過接口代理
代理對象類型為Animal,可以強轉(zhuǎn)為Animal接口。
4. CGLIB動態(tài)代理
CGLIB動態(tài)代理是三方庫實現(xiàn),和JDK動態(tài)代理的區(qū)別是:既可以代理接口,也可以代理類。
CGLIB動態(tài)代理實現(xiàn)的類結(jié)構(gòu)如下:
類 | 描述 |
---|---|
Subject | 被代理目標類的接口。 |
RealSubject | 被代理目標類。 |
MethodInterceptor | 可以理解為一個攔截器,被代理對象方法調(diào)用時都會先調(diào)用此接口的intercept方法,在intercept方法內(nèi)部去調(diào)用被代理對象方法。 |
MethodInterceptor實現(xiàn)類 | MethodInterceptor的實現(xiàn)類 |
Enhancer | 代理工具類,用于生成代理對象,生成代理對象時會用到RealSubject和MethodInterceptor實現(xiàn)類。 |
4.1 代碼
4.1.1 CglibInterceptor
MethodInterceptor的實現(xiàn)類:
import lombok.extern.slf4j.Slf4j; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; @Slf4j public class CglibInterceptor implements MethodInterceptor { @Override public Object intercept(Object target, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { log.info(getCalledMethod(method)); return methodProxy.invokeSuper(target, args); } private String getCalledMethod(Method method) { StringBuilder builder = new StringBuilder(); builder.append(method.getDeclaringClass().getSimpleName()) .append(".") .append(method.getName()) .append("() called."); return builder.toString(); } }
4.1.2 CglibProxyFactory
CGLIB代理工廠類,抽象出通用方法:
import net.sf.cglib.proxy.Enhancer; public class CglibProxyFactory { public static <T> T getProxy(Class<T> clazz) { Enhancer enhancer = new Enhancer(); // 設(shè)置超類,字面看就是代理對象是目標類的子類,通過繼承方式實現(xiàn),而不是組合方式實現(xiàn)。 enhancer.setSuperclass(clazz); enhancer.setCallback(new CglibInterceptor()); return (T)enhancer.create(); } }
4.1.3 AnimalCglibProxyFactory
Animal工廠類:
public class AnimalCglibProxyFactory { public static Animal createDog() { return CglibProxyFactory.getProxy(Dog.class); } public static Animal createDuck() { return CglibProxyFactory.getProxy(Duck.class); } }
4.1.4 CglibProxyFactoryTest
import org.junit.jupiter.api.Test; class CglibProxyFactoryTest { @Test public void exec() { Animal dog = AnimalCglibProxyFactory.createDog(); Animal duck = AnimalCglibProxyFactory.createDuck(); dog.say(); duck.say(); } }
4.1.5 輸出日志
2023-12-28 13:23:56 INFO CglibInterceptor:13 - Dog.say() called.
2023-12-28 13:23:56 INFO Dog:10 - 汪汪
2023-12-28 13:23:56 INFO CglibInterceptor:13 - Duck.say() called.
2023-12-28 13:23:56 INFO Duck:9 - 嘎嘎
5. 總結(jié)
本文介紹了幾種java代理的實現(xiàn)方式,在過程中通過重構(gòu)展示了如何通過java泛型實現(xiàn)通用能力,以及通過工廠模式屏蔽類實例創(chuàng)建細節(jié),提升擴展性。
幾種java代理的差別如下:
比較項 | 靜態(tài)代理 | JDK動態(tài)代理 | CGLIB動態(tài)代理 |
---|---|---|---|
代理對象 | 接口和類 | 接口 | 接口和類 |
實現(xiàn)接口方法 | 需手動一一實現(xiàn) | 自動實現(xiàn) | 自動實現(xiàn) |
攔截方法 | 每個方法獨立實現(xiàn),完全隔離開。 | 都在一處實現(xiàn),當(dāng)指定方法攔截時要增加判斷邏輯。 | 同靜態(tài)代理。 |
6. 最后
以上就是Java代理的幾種實現(xiàn)方式總結(jié)的詳細內(nèi)容,更多關(guān)于Java代理實現(xiàn)方式的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Jmeter內(nèi)置變量vars和props的使用詳解
JMeter是一個功能強大的負載測試工具,它提供了許多有用的內(nèi)置變量來支持測試過程,其中最常用的變量是 vars 和 props,本文通過代碼示例詳細給大家介紹了Jmeter內(nèi)置變量vars和props的使用,需要的朋友可以參考下2024-08-08java線程并發(fā)控制同步工具CountDownLatch
這篇文章主要為大家介紹了java線程并發(fā)控制同步工具CountDownLatch使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-08-08SpringBoot?Validation快速實現(xiàn)數(shù)據(jù)校驗的示例代碼
在實際開發(fā)中,肯定會經(jīng)常遇到對參數(shù)字段進行校驗的場景,通常我們只能寫大量的if else來完成校驗工作,而如果使用SpringBoot Validation則可以輕松的通過注解來完成,接下來小編給大家介紹下利用SpringBoot?Validation快速實現(xiàn)數(shù)據(jù)校驗的示例代碼,需要的朋友參考下吧2022-06-06如何通過ServletInputStream讀取http請求傳入的數(shù)據(jù)
這篇文章主要介紹了如何通過ServletInputStream讀取http請求傳入的數(shù)據(jù),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-10-10