一篇不太一样的代理模式详解,仔细阅读,你一定会获取不一样的代理见解,而不是人云亦云。
查看了社区里关于代理模式描述,发现很多博客千篇一律甚至存在共性错误,写此文提出自己对代理的见解。
- 静态代理
- 动态代理
- JDK
- CGLib
- 静态代理 VS 动态代理
- 直观的看到动态代理的模样
- 那种只通过接口就能实现功能的技术是如何实现的
女朋友问我什么是代理,静态代理与动态代理的区别是什么,各有什么优势呢?什么场景下适合静态代理,什么场景该使用动态代理呢?真的如网上所说的,静态代理一无是处吗? 作为一个合格的男朋友,必须给她安排上,这就说道说道。
一、静态代理
1.1 静态代理架构图
角色:
- 接口
- 被代理实现类
- 代理实现类
核心在于代理对象与被代理对象都需要实现同一个Interface
接口,这一点也非常好理解,代理嘛 就是要代理被代理对象的所有方法。
1.2 代码案例
代码比较简单,使用静态代理为一个只有加法功能的计算器在计算前后打印日志:
/**
* 静态代理
* @author zcy
* @date 2023/2/11
* @description 求关注~
*/
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);
}
}
**// 测试结果
before plus
after plus
3**
1.3 静态代理的缺点 以及对现有博客的抨击
目前网上关于静态代理的优缺点分析都存在着一个共性的错误。我们只有在深刻的理解静态代理与其应用场景才能发现这些错误描述。
1.3.1 千篇一律的认知错误
在国内代码社区中,搜索关于静态代理的缺点
文章,几乎千篇一律的指责到:程序员要手动为每一个被代理类编写对应的代理类,如果当前系统已经有成百上千个类,工作量太大了
。
真的一定是这样吗?
在上述demo中,ProxyFactory
类构造函数会接受一个Factory
接口的实现类进行代理。因此就算此处需要新增一个被代理对象,理论上也不需要再去做一个代理对象了,因为这些被代理类都是Factory
类型。这算是Java最基础的知识了!
那么什么场景下,当新创建一个被代理类时,一定需要写一个与之对应的代理类呢?
我们还用上面的demo来看,上面的demo中代理类只干了一件事:计算的前后分别打印一行日志。假设我们现在又需要写一个被代理类:HttpPlusFactory
,我们希望在完成加法之后将结果通过HTTP
发送给其他系统,这时我们原有的代理类ProxyFactory
就显得不够用了,我们需要新建一个专门的HttpProxyFactory
代理才行。
简而言之,代理存在的目的是想在不修改原有的代码为前提实现一个共性需求。即将共性的需求放入代理中实现,假设我们有不同的共性需求,我们才需要抽象出不同的代理对象。这一段话有点绕,但是我希望你能明白含义。
1.3.2 静态代理真正的缺点
抨击完国内千篇一律的错误之后,我们来谈谈静态代理有什么不太方便的地方。
-
代理类编写麻烦:
这个代理对象与被代理对象一样要实现同一个接口,如果接口中有100个方法那么代理对象就得实现100个方法。
-
一些只有接口没有被代理类的场景无法使用静态代理:
静态代理中,一定是要存在一个被代理对象,这对于一些只通过接口就能完成业务功能的需求很不友好,譬如:
MyBatis、Feign、Dubbo
。
二、动态代理
动态代理,对于很多框架的实现非常友好,其诞生的目的就是让我们不去写代理对象(JDK或者CGlib帮助我们自动生成)。
2.1 实现方式
-
JDK
基于
Interface
生成实现类完成代理 -
Cglib
基于
Class
生成子类完成代理
2.1.1 JDK Demo
/**
* 动态代理 - 手动编写被代理类
* @author zcy
* @date 2023/2/11
* @description 求关注~
*/
public class DynamicProxy {
/**
* JDK动态代理基于接口
*/
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\. 生成动态代理
ProxyFactory proxyFactory = new ProxyFactory(beAgentFactory);
Factory proxyInstance = (Factory) Proxy.newProxyInstance(proxyFactory.getClass().getClassLoader(), new Class[]{Factory.class}, proxyFactory);
// 3.调用方法
int plus = proxyInstance.plus(1, 2);
System.out.println(plus);
}
}
**// 测试结果
before plus
after plus
3**
看完这个demo之后,你可能会想,这和静态代理有什么区别呢?还是要有接口、被代理类、代理类三个角色。但你要知道,如果Factory
接口中如有100个抽象方法,那么代理类中只需要有一个invoke
方法即可!这是和静态代理中的代理类有着本质的差别,这主要得益于动态代理中的反射机制。
当然了,如果只是这样的话,动态代理的优势还不足以让你折服,我们再来看下面这个例子:
/**
* 动态代理 - 不需要手写被代理类
* @author zcy
* @date 2023/2/11
* @description 求关注~
*/
public class DynamicProxy {
/**
* JDK动态代理基于接口
*/
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\. 生成动态代理
ProxyFactory proxyFactory = new ProxyFactory();
Factory proxyInstance = (Factory) Proxy.newProxyInstance(proxyFactory.getClass().getClassLoader(), new Class[]{Factory.class}, proxyFactory);
// 3.调用方法
int plus = proxyInstance.plus(1, 2);
System.out.println(plus);
}
}
**// 测试结果
before plus
after plus
3**
在上面这个例子中,我们直接去掉了被代理对象,而是将业务抽象到了代理类中。想一想这还是得益于反射机制吧,这时候你再对比对比静态代理的代码,是不是发现静态代理就没法这么玩了。
2.1.2 Cglib Demo
/**
* Cglib实现动态代理
*
* @author zcy
* @date 2023/2/12
* @description 求关注~
*/
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
实现方式中,重点在于CallBack
中,也就是此处的ProxyCallBack
,其内部的intercept
方法参数中也有Method
、参数等信息,因此使用上和JDK Proxy
感觉非常相似。
2.2 JDK Proxy VS Cglib Proxy
- JDK Proxy是基于接口生成实现类完成代理,Cglib Proxy是基于Class生成子类完成代理。所有Cglib中被代理类中的方法不能有
private、final
修饰。而JDK Proxy就没有此限制,因为Java语言中接口中的方法天然不能使用private、final
进行修饰。 - 速度,这是网上传的比较多的一种比较,因为我没有做过相关实验,因此不在此定论。
三、静态代理 VS 动态代理
3.1 区别
- 静态代理一定要编写至少一个被代理类与一个代理类,而动态代理可以不编写任何被代理类与代理类
- 静态代理编写麻烦,因为代理类与被代理类需要实现同一个接口,如果接口有100个方法,那么就需要实现100个方法,而动态代理不需要,动态代理底层使用反射极大减少了开发量,将100个方法压缩成一个
invoke
方法即可。
3.2 使用场景
3.2.1 静态代理
适合不存在共性需求的场景,比如被代理类中有100个方法,代理对象中自然也有100个方法,但是这100个方法没有共性需求,可能第一个方法是打印日志,第二个方法需要发送HTTP… 那么这时候就适合用静态代理了(假设一定需要使用代理的话)。
3.2.2 动态代理
动态代理非常适合框架底层,并且共性需求很大,参考MyBatis、Feign、Dubbo。
四、直观的看到动态代理的模样
为了加深动态代理的理解,这里以JDK动态代理为例,将上述2.1.1 JDK Demo
中JDK生成的代理类打印出来。
在main方法中添加添加如下代码,最后会在项目根路径下保存生成的动态代理类:
//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属性,就是我们实现的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生成的代理类是实现了接口,在实现的方法中调用了父类Proxy中的InvocationHandler
的invoke
方法,并且将method
信息、参数信息都传过去。
五、那种只通过接口就能实现功能的技术是如何实现的
目前市面上只通编写接口就能实现功能的框架有很多,比如:MyBatis
、Feign
等。
那么你是否也折服于他们只通过Interface
就能完成功能的能力?但只要你熟练掌握动态代理的使用与原理,理解这些框架并不难。
这些框架大致的实现思路为:
- 肯定使用JDK动态代理,因为只有接口,没有实现类;
- 依赖Spring的生命周期钩子,对需要生成动态代理的接口进行代理,并将生成好的代理类放入
Spring IOC
中以提供后续业务的使用。
这里我们开发一个Demo,需求是只需编写Interface接口
配合上一些注解完成HTTP发送。
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注解修饰的类,并创建其代理对象并存入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动态创建代理对象
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实现类:
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("发送HTTP请求: " + annotation.url());
return "";
}
public static <T> T getInstance(Class<T> interfaces) {
return (T) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), new Class[]{interfaces}, new JDKProxy());
}
}
- 业务应用
@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();
}
}
// 结果:
// 发送HTTP请求: https://xxxx.com