网站定制兴田德润i在哪里,如何建立自己的手机网站,seo金融术语,网页制作软件属于什么软件一篇不太一样的代理模式详解#xff0c;仔细阅读#xff0c;你一定会获取不一样的代理见解#xff0c;而不是人云亦云。 查看了社区里关于代理模式描述#xff0c;发现很多博客千篇一律甚至存在共性错误#xff0c;写此文提出自己对代理的见解。 静态代理动态代理 JDKCGLi… 一篇不太一样的代理模式详解仔细阅读你一定会获取不一样的代理见解而不是人云亦云。 查看了社区里关于代理模式描述发现很多博客千篇一律甚至存在共性错误写此文提出自己对代理的见解。 静态代理动态代理 JDKCGLib静态代理 VS 动态代理直观的看到动态代理的模样那种只通过接口就能实现功能的技术是如何实现的 女朋友问我什么是代理静态代理与动态代理的区别是什么各有什么优势呢什么场景下适合静态代理什么场景该使用动态代理呢真的如网上所说的静态代理一无是处吗 作为一个合格的男朋友必须给她安排上这就说道说道。 一、静态代理
1.1 静态代理架构图 静态代理类图.png
角色
接口被代理实现类代理实现类
核心在于代理对象与被代理对象都需要实现同一个Interface接口这一点也非常好理解代理嘛 就是要代理被代理对象的所有方法。
1.2 代码案例
代码比较简单使用静态代理为一个只有加法功能的计算器在计算前后打印日志
/*** 静态代理*/
public class StaticProxy {/*** 接口*/interface Factory {int plus(int one, int two);}/*** 被代理对象*/static class PlusFactory implements Factory {Overridepublic int plus(int one, int two) {return one two;}}/*** 代理对象*/static class ProxyFactory implements Factory {private Factory beAgentFactory;public ProxyFactory(Factory beAgentFactory) {this.beAgentFactory beAgentFactory;}Overridepublic 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
/*** 动态代理 - 手动编写被代理类*/
public class DynamicProxy {/*** JDK动态代理基于接口*/interface Factory {int plus(int one, int two);}/*** 被代理对象*/static class PlusFactory implements Factory {Overridepublic int plus(int one, int two) {return one two;}}/*** 代理对象*/static class ProxyFactory implements InvocationHandler {private Factory beAgentFactory;public ProxyFactory(Factory beAgentFactory) {this.beAgentFactory beAgentFactory;}Overridepublic 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方法即可这是和静态代理中的代理类有着本质的差别这主要得益于动态代理中的反射机制。
当然了如果只是这样的话动态代理的优势还不足以让你折服我们再来看下面这个例子
/*** 动态代理 - 不需要手写被代理类*/
public class DynamicProxy {/*** JDK动态代理基于接口*/interface Factory {int plus(int one, int two);}static class ProxyFactory implements InvocationHandler {Overridepublic 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实现动态代理**/
public class CglibProxy {/*** 被代理对象*/static class PlusFactory {public int plus(int one, int two) {return one two;}}/*** Cglib Callback*/static class ProxyCallBack implements MethodInterceptor {Overridepublic 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方法即可。 静态代理VS动态代理.drawio.png
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-annHttpExecute.javaHttpService.java-proxyJDKProxy.java-scannerClassScanner.java-serviceBaseInterfaceBusiness.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);Overridepublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {}Overridepublic 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 {Overridepublic 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(ClassT 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 {Resourceprivate BaseInterfaceBusiness baseInterfaceBusiness;Testpublic void baseInterfaceTest() {baseInterfaceBusiness.reduceInventory();}}// 结果
// 发送HTTP请求: https://xxxx.com