一篇不太一樣的代理模式詳解催烘,仔細閱讀,你一定會獲取不一樣的代理見解缎罢,而不是人云亦云伊群。
查看了社區(qū)里關(guān)于代理模式描述,發(fā)現(xiàn)很多博客千篇一律甚至存在共性錯誤策精,寫此文提出自己對代理的見解舰始。
- 靜態(tài)代理
- 動態(tài)代理
- JDK
- CGLib
- 靜態(tài)代理 VS 動態(tài)代理
- 直觀的看到動態(tài)代理的模樣
- 那種只通過接口就能實現(xiàn)功能的技術(shù)是如何實現(xiàn)的
女朋友問我什么是代理,靜態(tài)代理與動態(tài)代理的區(qū)別是什么蛮寂,各有什么優(yōu)勢呢蔽午?什么場景下適合靜態(tài)代理易茬,什么場景該使用動態(tài)代理呢酬蹋?真的如網(wǎng)上所說的,靜態(tài)代理一無是處嗎抽莱? 作為一個合格的男朋友范抓,必須給她安排上,這就說道說道食铐。
一匕垫、靜態(tài)代理
1.1 靜態(tài)代理架構(gòu)圖
角色:
- 接口
- 被代理實現(xiàn)類
- 代理實現(xiàn)類
核心在于代理對象與被代理對象都需要實現(xiàn)同一個Interface
接口,這一點也非常好理解虐呻,代理嘛 就是要代理被代理對象的所有方法象泵。
1.2 代碼案例
代碼比較簡單,使用靜態(tài)代理為一個只有加法功能的計算器在計算前后打印日志:
/**
* 靜態(tài)代理
* @author zcy
* @date 2023/2/11
* @description 求關(guān)注~
*/
public class StaticProxy {
/**
* 接口
*/
interface Factory {
int plus(int one, int two);
}
/**
* 被代理對象
*/
static class PlusFactory implements Factory {
@Override
public int plus(int one, int two) {
return one + two;
}
}
/**
* 代理對象
*/
static class ProxyFactory implements Factory {
private Factory beAgentFactory;
public ProxyFactory(Factory beAgentFactory) {
this.beAgentFactory = beAgentFactory;
}
@Override
public int plus(int one, int two) {
try {
System.out.println("before plus");
return beAgentFactory.plus(one, two);
} finally {
System.out.println("after plus");
}
}
}
/**
* 測試
*
* @param args
*/
public static void main(String[] args) {
// 被代理對象
PlusFactory beAgentFactory = new PlusFactory();
ProxyFactory proxyFactory = new ProxyFactory(beAgentFactory);
int result = proxyFactory.plus(1, 2);
System.out.println(result);
}
}
**// 測試結(jié)果
before plus
after plus
3**
1.3 靜態(tài)代理的缺點 以及對現(xiàn)有博客的抨擊
目前網(wǎng)上關(guān)于靜態(tài)代理的優(yōu)缺點分析都存在著一個共性的錯誤斟叼。我們只有在深刻的理解靜態(tài)代理與其應(yīng)用場景才能發(fā)現(xiàn)這些錯誤描述偶惠。
1.3.1 千篇一律的認知錯誤
在國內(nèi)代碼社區(qū)中,搜索關(guān)于靜態(tài)代理的缺點
文章朗涩,幾乎千篇一律的指責(zé)到:程序員要手動為每一個被代理類編寫對應(yīng)的代理類忽孽,如果當(dāng)前系統(tǒng)已經(jīng)有成百上千個類,工作量太大了
谢床。
真的一定是這樣嗎兄一?
在上述demo中,ProxyFactory
類構(gòu)造函數(shù)會接受一個Factory
接口的實現(xiàn)類進行代理识腿。因此就算此處需要新增一個被代理對象出革,理論上也不需要再去做一個代理對象了,因為這些被代理類都是Factory
類型渡讼。這算是Java最基礎(chǔ)的知識了骂束!
那么什么場景下费薄,當(dāng)新創(chuàng)建一個被代理類時,一定需要寫一個與之對應(yīng)的代理類呢栖雾?
我們還用上面的demo來看楞抡,上面的demo中代理類只干了一件事:計算的前后分別打印一行日志。假設(shè)我們現(xiàn)在又需要寫一個被代理類:HttpPlusFactory
析藕,我們希望在完成加法之后將結(jié)果通過HTTP
發(fā)送給其他系統(tǒng)召廷,這時我們原有的代理類ProxyFactory
就顯得不夠用了,我們需要新建一個專門的HttpProxyFactory
代理才行账胧。
簡而言之竞慢,代理存在的目的是想在不修改原有的代碼為前提實現(xiàn)一個共性需求控汉。即將共性的需求放入代理中實現(xiàn)挽荠,假設(shè)我們有不同的共性需求颅痊,我們才需要抽象出不同的代理對象纽匙。這一段話有點繞森逮,但是我希望你能明白含義嚎京。
1.3.2 靜態(tài)代理真正的缺點
抨擊完國內(nèi)千篇一律的錯誤之后啃擦,我們來談?wù)勳o態(tài)代理有什么不太方便的地方厨埋。
-
代理類編寫麻煩:
這個代理對象與被代理對象一樣要實現(xiàn)同一個接口准脂,如果接口中有100個方法那么代理對象就得實現(xiàn)100個方法劫扒。
-
一些只有接口沒有被代理類的場景無法使用靜態(tài)代理:
靜態(tài)代理中,一定是要存在一個被代理對象狸膏,這對于一些只通過接口就能完成業(yè)務(wù)功能的需求很不友好沟饥,譬如:
MyBatis、Feign湾戳、Dubbo
贤旷。
二、動態(tài)代理
動態(tài)代理砾脑,對于很多框架的實現(xiàn)非常友好幼驶,其誕生的目的就是讓我們不去寫代理對象(JDK或者CGlib幫助我們自動生成)。
2.1 實現(xiàn)方式
-
JDK
基于
Interface
生成實現(xiàn)類完成代理 -
Cglib
基于
Class
生成子類完成代理
2.1.1 JDK Demo
/**
* 動態(tài)代理 - 手動編寫被代理類
* @author zcy
* @date 2023/2/11
* @description 求關(guān)注~
*/
public class DynamicProxy {
/**
* JDK動態(tài)代理基于接口
*/
interface Factory {
int plus(int one, int two);
}
/**
* 被代理對象
*/
static class PlusFactory implements Factory {
@Override
public int plus(int one, int two) {
return one + two;
}
}
/**
* 代理對象
*/
static class ProxyFactory implements InvocationHandler {
private Factory beAgentFactory;
public ProxyFactory(Factory beAgentFactory) {
this.beAgentFactory = beAgentFactory;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
System.out.println("before plus");
return method.invoke(beAgentFactory, args);
} finally {
System.out.println("after plus");
}
}
}
public static void main(String[] args) {
// 1\. 被代理對象
Factory beAgentFactory = new PlusFactory();
// 2\. 生成動態(tài)代理
ProxyFactory proxyFactory = new ProxyFactory(beAgentFactory);
Factory proxyInstance = (Factory) Proxy.newProxyInstance(proxyFactory.getClass().getClassLoader(), new Class[]{Factory.class}, proxyFactory);
// 3.調(diào)用方法
int plus = proxyInstance.plus(1, 2);
System.out.println(plus);
}
}
**// 測試結(jié)果
before plus
after plus
3**
看完這個demo之后拦止,你可能會想县遣,這和靜態(tài)代理有什么區(qū)別呢?還是要有接口汹族、被代理類萧求、代理類三個角色。但你要知道顶瞒,如果Factory
接口中如有100個抽象方法夸政,那么代理類中只需要有一個invoke
方法即可!這是和靜態(tài)代理中的代理類有著本質(zhì)的差別榴徐,這主要得益于動態(tài)代理中的反射機制守问。
當(dāng)然了匀归,如果只是這樣的話,動態(tài)代理的優(yōu)勢還不足以讓你折服耗帕,我們再來看下面這個例子:
/**
* 動態(tài)代理 - 不需要手寫被代理類
* @author zcy
* @date 2023/2/11
* @description 求關(guān)注~
*/
public class DynamicProxy {
/**
* JDK動態(tài)代理基于接口
*/
interface Factory {
int plus(int one, int two);
}
static class ProxyFactory implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
System.out.println("before plus");
if (method.getName().equals("plus")) {
return (int) args[0] + (int) args[1];
}
return null;
} finally {
System.out.println("after plus");
}
}
}
public static void main(String[] args) {
// 2\. 生成動態(tài)代理
ProxyFactory proxyFactory = new ProxyFactory();
Factory proxyInstance = (Factory) Proxy.newProxyInstance(proxyFactory.getClass().getClassLoader(), new Class[]{Factory.class}, proxyFactory);
// 3.調(diào)用方法
int plus = proxyInstance.plus(1, 2);
System.out.println(plus);
}
}
**// 測試結(jié)果
before plus
after plus
3**
在上面這個例子中穆端,我們直接去掉了被代理對象,而是將業(yè)務(wù)抽象到了代理類中仿便。想一想這還是得益于反射機制吧体啰,這時候你再對比對比靜態(tài)代理的代碼,是不是發(fā)現(xiàn)靜態(tài)代理就沒法這么玩了嗽仪。
2.1.2 Cglib Demo
/**
* Cglib實現(xiàn)動態(tài)代理
*
* @author zcy
* @date 2023/2/12
* @description 求關(guān)注~
*/
public class CglibProxy {
/**
* 被代理對象
*/
static class PlusFactory {
public int plus(int one, int two) {
return one + two;
}
}
/**
* Cglib Callback
*/
static class ProxyCallBack implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
try {
System.out.println("before calculate");
return methodProxy.invokeSuper(o, objects);
} finally {
System.out.println("after calculate");
}
}
}
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(PlusFactory.class);
enhancer.setCallback(new ProxyCallBack());
PlusFactory proxy = (PlusFactory) enhancer.create();
int result = proxy.plus(1, 2);
System.out.println(result);
}
}
可以看到Cglib
實現(xiàn)方式中荒勇,重點在于CallBack
中,也就是此處的ProxyCallBack
闻坚,其內(nèi)部的intercept
方法參數(shù)中也有Method
沽翔、參數(shù)等信息,因此使用上和JDK Proxy
感覺非常相似窿凤。
2.2 JDK Proxy VS Cglib Proxy
- JDK Proxy是基于接口生成實現(xiàn)類完成代理仅偎,Cglib Proxy是基于Class生成子類完成代理。所有Cglib中被代理類中的方法不能有
private卷玉、final
修飾哨颂。而JDK Proxy就沒有此限制,因為Java語言中接口中的方法天然不能使用private相种、final
進行修飾。 - 速度品姓,這是網(wǎng)上傳的比較多的一種比較寝并,因為我沒有做過相關(guān)實驗,因此不在此定論腹备。
三衬潦、靜態(tài)代理 VS 動態(tài)代理
3.1 區(qū)別
- 靜態(tài)代理一定要編寫至少一個被代理類與一個代理類,而動態(tài)代理可以不編寫任何被代理類與代理類
- 靜態(tài)代理編寫麻煩植酥,因為代理類與被代理類需要實現(xiàn)同一個接口镀岛,如果接口有100個方法,那么就需要實現(xiàn)100個方法友驮,而動態(tài)代理不需要漂羊,動態(tài)代理底層使用反射極大減少了開發(fā)量,將100個方法壓縮成一個
invoke
方法即可卸留。
3.2 使用場景
3.2.1 靜態(tài)代理
適合不存在共性需求的場景走越,比如被代理類中有100個方法,代理對象中自然也有100個方法耻瑟,但是這100個方法沒有共性需求旨指,可能第一個方法是打印日志赏酥,第二個方法需要發(fā)送HTTP… 那么這時候就適合用靜態(tài)代理了(假設(shè)一定需要使用代理的話)。
3.2.2 動態(tài)代理
動態(tài)代理非常適合框架底層谆构,并且共性需求很大裸扶,參考MyBatis、Feign搬素、Dubbo姓言。
四、直觀的看到動態(tài)代理的模樣
為了加深動態(tài)代理的理解蔗蹋,這里以JDK動態(tài)代理為例何荚,將上述2.1.1 JDK Demo
中JDK生成的代理類打印出來。
在main方法中添加添加如下代碼猪杭,最后會在項目根路徑下保存生成的動態(tài)代理類:
//JDK1.8及以前的版本
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
//JDK1.8以后的版本
System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
生成的代理類如下:
final class $Proxy0 extends Proxy implements Factory {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
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 plus(int var1, int var2) throws {
try {
// 這里的super是Proxy餐塘,里面的h屬性,就是我們實現(xiàn)的InnvocationHandler類
return (Integer)super.h.invoke(this, m3, new Object[]{var1, var2});
} catch (RuntimeException | Error var4) {
throw var4;
} catch (Throwable var5) {
throw new UndeclaredThrowableException(var5);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("com.example.demo.dynamic.DynamicProxy$Factory").getMethod("plus", Integer.TYPE, Integer.TYPE);
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
可以看到JDK生成的代理類是實現(xiàn)了接口皂吮,在實現(xiàn)的方法中調(diào)用了父類Proxy中的InvocationHandler
的invoke
方法戒傻,并且將method
信息、參數(shù)信息都傳過去蜂筹。
五需纳、那種只通過接口就能實現(xiàn)功能的技術(shù)是如何實現(xiàn)的
目前市面上只通編寫接口就能實現(xiàn)功能的框架有很多,比如:MyBatis
艺挪、Feign
等不翩。
那么你是否也折服于他們只通過Interface
就能完成功能的能力?但只要你熟練掌握動態(tài)代理的使用與原理麻裳,理解這些框架并不難口蝠。
這些框架大致的實現(xiàn)思路為:
- 肯定使用JDK動態(tài)代理,因為只有接口津坑,沒有實現(xiàn)類妙蔗;
- 依賴Spring的生命周期鉤子,對需要生成動態(tài)代理的接口進行代理疆瑰,并將生成好的代理類放入
Spring IOC
中以提供后續(xù)業(yè)務(wù)的使用眉反。
這里我們開發(fā)一個Demo,需求是只需編寫Interface接口
配合上一些注解完成HTTP發(fā)送穆役。
Demo如下:
-com.example.demo
-app
-baseinterface
-ann
HttpExecute.java
HttpService.java
-proxy
JDKProxy.java
-scanner
ClassScanner.java
-service
BaseInterfaceBusiness.java
- 定義注解:
@Documented
@Target(value = ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface HttpService {
}
@Documented
@Target(value = ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface HttpExecute {
String url();
}
- Spring生命周期鉤子寸五,掃描所有使用@HttpService注解修飾的類,并創(chuàng)建其代理對象并存入IOC容器中
@Component
public class ClassScanner implements BeanDefinitionRegistryPostProcessor {
private final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
private MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(this.resourcePatternResolver);
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
try {
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + "com" + "/" + DEFAULT_RESOURCE_PATTERN;
Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
for (Resource resource : resources) {
if (resource.isReadable()) {
MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
if (annotationMetadata.getAnnotationTypes().contains(HttpService.class.getName())) {
Class<?> aClass;
aClass = Class.forName(annotationMetadata.getClassName());
if (aClass != null) {
configurableListableBeanFactory.
registerSingleton(
toLowerCaseFirstOne(aClass.getSimpleName()),
// 使用JDK動態(tài)創(chuàng)建代理對象
JDKProxy.getInstance(aClass));
System.out.println("掃描到的 HttpService 接口" + aClass.getSimpleName());
}
} else {
continue;
}
}
}
} catch (IOException e) {
} catch (Exception e) {
}
}
public static String toLowerCaseFirstOne(String s) {
if (Character.isLowerCase(s.charAt(0))) {
return s;
} else {
return (new StringBuilder()).append(Character.toLowerCase(s.charAt(0))).append(s.substring(1)).toString();
}
}
}
- InvocationHandler實現(xiàn)類:
public class JDKProxy implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 對象的所有Object類,直接通過
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
HttpExecute annotation = method.getAnnotation(HttpExecute.class);
System.out.println("發(fā)送HTTP請求: " + annotation.url());
return "";
}
public static <T> T getInstance(Class<T> interfaces) {
return (T) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), new Class[]{interfaces}, new JDKProxy());
}
}
- 業(yè)務(wù)應(yīng)用
@HttpService
public interface BaseInterfaceBusiness {
@HttpExecute(url = "<https://xxxx.com>")
void reduceInventory();
}
- 測試
@SpringBootTest
public class DynamicAppTest {
@Resource
private BaseInterfaceBusiness baseInterfaceBusiness;
@Test
public void baseInterfaceTest() {
baseInterfaceBusiness.reduceInventory();
}
}
// 結(jié)果:
// 發(fā)送HTTP請求: https://xxxx.com