Java 静态代理和动态代理

Java 静态代理

静态代理通常用于对原有业务逻辑的扩充。比如持有二方包的某个类,并调用了其中的某些方法。然后出于某种原因,比如记录日志、打印方法执行时间,但是又不好将这些逻辑写入二方包的方法里。所以可以创建一个代理类实现和二方方法相同的方法,通过让代理类持有真实对象,然后在原代码中调用代理类方法,来达到添加我们需要业务逻辑的目的。

这其实也就是代理模式的一种实现,通过对真实对象的封装,来实现扩展性。

一个典型的代理模式通常有三个角色,这里称之为**代理三要素**

共同接口

  1. public interface Action {
  2. public void doSomething();
  3. }

真实对象

  1. public class RealObject implements Action{
  2.  
  3. public void doSomething() {
  4. System.out.println("do something");
  5. }
  6. }

代理对象

  1. public class Proxy implements Action {
  2. private Action realObject;
  3.  
  4. public Proxy(Action realObject) {
  5. this.realObject = realObject;
  6. }
  7. public void doSomething() {
  8. System.out.println("proxy do");
  9. realObject.doSomething();
  10. }
  11. }

运行代码

  1. Proxy proxy = new Proxy(new RealObject());
  2. proxy.doSomething();

这种代理模式也最为简单,就是通过proxy持有realObject的引用,并进行一层封装。

静态代理的优点和缺点

先看看代理模式的优点: 扩展原功能,不侵入原代码。

再看看这种代理模式的缺点:

假如有这样一个需求,有十个不同的RealObject,同时我们要去代理的方法是不同的,比要代理方法:doSomething、doAnotherThing、doTwoAnotherThing,添加代理前,原代码可能是这样的:

  1. realObject.doSomething();
  2. realObject1.doAnotherThing();
  3. realObject2.doTwoAnother();

为了解决这个问题,我们有方案一:

为这些方法创建不同的代理类,代理后的代码是这样的:

  1. proxy.doSomething();
  2. proxy1.doAnotherThing();
  3. proxy2.doTwoAnother();

当然,也有方案二:

通过创建一个proxy,持有不同的realObject,实现Action1、Action2、Action3接口,来让代码变成这样:

  1. proxy.doSomething();
  2. proxy.doAnotherThing();
  3. proxy.doTwoAnother();

于是你的代理模型会变成这样:

毫无疑问,仅仅为了扩展同样的功能,在方案一种,我们会重复创建多个逻辑相同,仅仅RealObject引用不同的Proxy。

而在方案二中,会导致proxy的膨胀,而且这种膨胀往往是无意义的。此外,假如方法签名是相同的,更需要在调用的时候引入额外的判断逻辑。

java 动态代理

搞清楚静态代理的缺点十分重要,因为动态代理的目的就是为了解决静态代理的缺点。通过使用动态代理,我们可以通过在运行时,动态生成一个持有RealObject、并实现代理接口的Proxy,同时注入我们相同的扩展逻辑。哪怕你要代理的RealObject是不同的对象,甚至代理不同的方法,都可以动过动态代理,来扩展功能。

简单理解,动态代理就是我们上面提到的方案一,只不过这些proxy的创建都是自动的并且是在运行期生成的。

动态代理基本用法

使用动态代理,需要将要扩展的功能写在一个InvocationHandler 实现类里:

  1. public class DynamicProxyHandler implements InvocationHandler {
  2. private Object realObject;
  3.  
  4. public DynamicProxyHandler(Object realObject) {
  5. this.realObject = realObject;
  6. }
  7.  
  8. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  9. //代理扩展逻辑
  10. System.out.println("proxy do");
  11.  
  12. return method.invoke(realObject, args);
  13. }
  14. }

这个Handler中的invoke方法中实现了代理类要扩展的公共功能。

到这里,需要先看一下这个handler的用法:

  1. public static void main(String[] args) {
  2. RealObject realObject = new RealObject();
  3. Action proxy = (Action) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Action.class}, new DynamicProxyHandler(realObject));
  4. proxy.doSomething();
  5. }

Proxy.newProxyInstance 传入的是一个ClassLoader, 一个代理接口,和我们定义的handler,返回的是一个Proxy的实例。

仔细体会这个过程,其实有点类似我们在静态代理中提到的方案一,生成了一个包含我们扩展功能,持有RealObject引用,实现Action接口的代理实例Proxy。只不过这个Proxy不是我们自己写的,而是java帮我们生成的,有没有一点动态的味道。

让我们再回顾一下代理三要素:真实对象:RealObject,代理接口:Action,代理实例:Proxy

上面的代码实含义也就是,输入 RealObject、Action,返回一个Proxy。妥妥的代理模式。

综上,动态生成+代理模式,也就是动态代理。
网上搜了不少文章,到了这里,接下来就是和cglib等动态代理实现方法做一下横向比较。本文不做横向比较,为了不偏离主题,接下来做纵向挖掘。

看一下源码

道理清楚了,但是这篇文章题目是搞懂,所以来看一下这个Proxy是如何自动被生成的。入口就在newProxyInstance方法,核心代码如下:

  1. private static final Class<?>[] constructorParams =
  2. { InvocationHandler.class };
  3.  
  4. public static Object newProxyInstance(ClassLoader loader,
  5. Class<?>[] interfaces,
  6. InvocationHandler h)
  7. throws IllegalArgumentException
  8. {
  9. Class<?> cl = getProxyClass0(loader, intfs);
  10. ...
  11. final Constructor<?> cons = cl.getConstructor(constructorParams);
  12.  
  13. if (!Modifier.isPublic(cl.getModifiers())) {
  14. AccessController.doPrivileged(new PrivilegedAction<Void>() {
  15. public Void run() {
  16. cons.setAccessible(true);
  17. return null;
  18. }
  19. });
  20. }
  21. return cons.newInstance(new Object[]{h});
  22. }

整体流程就是:

1、生成代理类Proxy的Class对象。

2、如果Class作用域为私有,通过 setAccessible 支持访问

3、获取Proxy Class构造函数,创建Proxy代理实例。

生成Proxy的Class文件

生成Class对象的方法中,先是通过传进来的ClassLoader参数和Class[] 数组作为组成键,维护了一个对于Proxy的Class对象的缓存。这样需要相同Proxy的Class对象时,只需要创建一次。

第一次创建该Class文件时,为了线程安全,方法进行了大量的处理,最后会来到ProxyClassFactory的apply方法中,经过以下流程:

1、校验传入的接口是否由传入的ClassLoader加载的。

2、校验传入是否是接口的Class对象。

3、校验是否传入重复的接口。

4、拼装代理类包名和类名,生成.class 文件的字节码。

5、调用native方法,传入字节码,生成Class对象。

  1. proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
  2. long num = nextUniqueNumber.getAndIncrement();
  3. String proxyName = proxyPkg + proxyClassNamePrefix + num;
  4. byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
  5. proxyName, interfaces, accessFlags);
  6. return defineClass0(loader, proxyName,
  7. proxyClassFile, 0, proxyClassFile.length);

看一下第四步生成.class文件字节码的过程,主要分为两个阶段:

  1. addProxyMethod(hashCodeMethod, Object.class);
  2. addProxyMethod(equalsMethod, Object.class);
  3. addProxyMethod(toStringMethod, Object.class);
  4.  
  5. for (int i = 0; i < interfaces.length; i++) {
  6. Method[] methods = interfaces[i].getMethods();
  7. for (int j = 0; j < methods.length; j++) {
  8. addProxyMethod(methods[j], interfaces[i]);
  9. }
  10. }
  11. methods.add(this.generateConstructor());
  12.  
  13. for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
  14. for (ProxyMethod pm : sigmethods) {
  15. fields.add(new FieldInfo(pm.methodFieldName,
  16. "Ljava/lang/reflect/Method;", ACC_PRIVATE | ACC_STATIC));
  17. methods.add(pm.generateMethod());
  18. }
  19. }
  20. methods.add(generateStaticInitializer());

第一个阶段的代码比较清晰,主要就是添加各种Method,比如toString()、equals,以及传入的代理接口中的方法。再添加一下构造方法以及静态初始化方法。这要构成了一个对象,存储生成Proxy的Class的一些信息。

到了这里,已经把要构造的Proxy的方法基本定义完成了,接下来就要生成这个.class文件了。

  1. ByteArrayOutputStream bout = new ByteArrayOutputStream();
  2. DataOutputStream dout = new DataOutputStream(bout);
  3. dout.writeInt(0xCAFEBABE);
  4. ...
  5. dout.writeShort(ACC_PUBLIC | ACC_FINAL | ACC_SUPER);
  6. ...
  7. return bout.toByteArray();

看到这个CAFEBABE,就清楚第二阶段的内容了。CAFEBABE是Class文件的魔数,关于Class文件这个咖啡宝贝的魔数,相信做Java的人都知道。没错,第二阶段就是生成字节码。按JVM规范,写入Class文件中包括权限控制、方法表、字段表等内容,生成符合规范的Class文件。最后返回对应的字节码。

字节码生成以后,通过调用native方法defineClass解析字节码,就生成了Proxy的Class对象。

Proxy构造方法

看一下Proxy的构造方法字节码生成部分:

  1. MethodInfo minfo = new MethodInfo("<init>", "(Ljava/lang/reflect/InvocationHandler;)V",ACC_PUBLIC);
  2. DataOutputStream out = new DataOutputStream(minfo.code);
  3. code_aload(0, out);
  4. code_aload(1, out);
  5. out.writeByte(opc_invokespecial);
  6. out.writeShort(cp.getMethodRef(superclassName,"<init>", "(Ljava/lang/reflect/InvocationHandler;)V"));
  7. ...

关键在于,生成了一个参数为InvocationHandler的构造方法,code加载的是jvm方法区中的代码,然后通过invokespecial指令调用了父类构造方法。

查看生成的Class文件

上面利用字节码生成技术产生Class文件的过程,看起来可能比较晦涩,其实我们可以查看这个产生的Proxy到底是个什么样子。

注意ProxyGenerator中有这样一个逻辑:

  1. if(saveGeneratedFiles) {
  2. ...
  3. FileOutputStream file = new FileOutputStream(dotToSlash(name) + ".class");
  4. file.write(classFile);
  5. ...
  6. }

再看一下saveGeneratedFiles这个变量:

  1. private final static boolean saveGeneratedFiles =
  2. java.security.AccessController.doPrivileged(
  3. new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles"))
  4. .booleanValue();

这是一个final类型的变量,通过GetBooleanAction方法读取系统变量,获取系统设置。默认这个值是false,稍微看一下System这个类的源码,发现有可以设置系统变量的Api,然后在程序的main 函数设置一下这个变量:

System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

这个时候,再跑一遍程序,就可以看到生成的Proxy的Class文件了,直接双击利用 ide 反编译。

  1.  
  2. package com.sun.proxy;
  3.  
  4. import java.lang.reflect.InvocationHandler;
  5. import java.lang.reflect.Method;
  6. import java.lang.reflect.Proxy;
  7. import java.lang.reflect.UndeclaredThrowableException;
  8.  
  9. public final class $Proxy0 extends Proxy implements Action {
  10. private static Method m1;
  11. private static Method m3;
  12. private static Method m2;
  13. private static Method m0;
  14.  
  15. public $Proxy0(InvocationHandler var1) throws {
  16. super(var1);
  17. }
  18.  
  19.  
  20. public final void doSomething() throws {
  21. try {
  22. super.h.invoke(this, m3, (Object[])null);
  23. } catch (RuntimeException | Error var2) {
  24. throw var2;
  25. } catch (Throwable var3) {
  26. throw new UndeclaredThrowableException(var3);
  27. }
  28. }
  29.  
  30. ...
  31.  
  32. static {
  33. try {
  34. ...
  35. m3 = Class.forName("Action").getMethod("doSomething", new Class[0]);
  36. } catch (NoSuchMethodException var2) {
  37. throw new NoSuchMethodError(var2.getMessage());
  38. } catch (ClassNotFoundException var3) {
  39. throw new NoClassDefFoundError(var3.getMessage());
  40. }
  41. }
  42. }
  43.  

省略一些无关代码,可以看到两个重要的方法。

一个就是我们的代理方法doSomething、另一个就是构造方法。

这个$Proxy0 继承 Proxy并调用了父类的构造方法,回忆一下上文提到的invokeSpecial,怎么样,对上了吧。

看一下Proxy中这个构造方法:

  1. protected Proxy(InvocationHandler h) {
  2. Objects.requireNonNull(h);
  3. this.h = h;
  4. }

在看一下$Proxy0 的代理方法:

super.h.invoke(this, m3, (Object[])null);

再来回顾一下生成Proxy实例的过程:

  1. private static final Class<?>[] constructorParams =
  2. { InvocationHandler.class };
  3. ...
  4. final Constructor<?> cons = cl.getConstructor(constructorParams);
  5. ...
  6. return cons.newInstance(new Object[]{h});

其实newInstance生成Proxy实例时,通过$Proxy0的Class对象,选择了这个InvocationHandler为参数的构造方法,传入我们定义的InvocationHandler并生成了一个 Proxy0的实例!InvocationHandler 里有realObject的逻辑以及我们的扩展逻辑,当我们调用Proxy0的doSomething方法时,就会调用到我们InvocationHandler 里 实现的invoke方法。

对上面这个过程,做一张图总结一下:

版权声明:本文为博主原创文章,转载请注明出处。 https://blog.csdn.net/WangQYoho/article/details/77584832

个人分类: Java

相关热词: 代理模式静态代理 jdk的静态代理 aop静态代理

原文地址:https://www.cnblogs.com/biaogejiushibiao/p/9470420.html

时间: 2024-10-24 13:49:24

Java 静态代理和动态代理的相关文章

深入浅出java静态代理和动态代理

首先介绍一下,什么是代理: 代理模式,是常用的设计模式.特征是,代理类与委托类有相同的接口,代理类主要负责为委托类预处理消息.过滤消息.把消息转发给委托类.以及事后处理消息. 代理类和委托类,存在着关联关系.代理类的对象本身并不真正实现服务,知识通过调用委托类的对象的相关方法. 代理类可以分为两种:静态代理和动态代理. 静态代理: 代理类是由程序员创建,或由工具生成的代码 编译成的.在程序运行前,代理类的 *.class文件已经存在了.直接就可以运行 . 动态代理: 动态代理的代理类.没有直接由

【Java】代处理?代理模式 - 静态代理,动态代理

>不用代理 有时候,我希望在一些方法前后都打印一些日志,于是有了如下代码. 这是一个处理float类型加法的方法,我想在调用它前打印一下参数,调用后打印下计算结果.(至于为什么不直接用+号运算,见[Java]Float计算不准确) package com.nicchagil.study.java.demo.No09代理.No01不用代理; import java.math.BigDecimal; public class FloatCalculator { public float add(fl

java静态代理与动态代理简单分析

原创作品,可以转载,但是请标注出处地址http://www.cnblogs.com/V1haoge/p/5860749.html 1.动态代理(Dynamic Proxy) 代理分为静态代理和动态代理,静态代理是在编译时就将接口.实现类.代理类一股脑儿全部手动完成,但如果我们需要很多的代理,每一个都这么手动的去创建实属浪费时间,而且会有大量的重复代码,此时我们就可以采用动态代理,动态代理可以在程序运行期间根据需要动态的创建代理类及其实例,来完成具体的功能. 其实方法直接调用就可以完成功能,为什么

Java基础:静态代理和动态代理

转载请注明出处:jiq?钦's technical Blog 一.静态代理: 假设原来有一个实现了指定接口/抽象类的子类: class RealSubject implements Subject{ public void request(){ System.out.print("real request handling\n"); } } 现在有两种情况会发生: 1新的代码需要调用Subject接口,但是需要给每个接口加入新代码(比如日志记录.权限控制等): 2旧的代码已经使用了Su

【java项目实战】代理模式(Proxy Pattern),静态代理 VS 动态代理

这篇博文,我们主要以类图和代码的形式来对照学习一下静态代理和动态代理.重点解析各自的优缺点. 定义 代理模式(Proxy Pattern)是对象的结构型模式,代理模式给某一个对象提供了一个代理对象,并由代理对象控制对原对象的引用. 代理模式不会改变原来的接口和行为,仅仅是转由代理干某件事,代理能够控制原来的目标,比如:代理商,代理商仅仅会买东西,但并不会改变行为.不会制造东西. 让我们通过以下的代码好好理解一下这句话. 分类 静态代理和动态代理 静态代理 静态代理类图 代码演示样例 接口 pac

Java设计模式学习06——静态代理与动态代理(转)

原地址:http://blog.csdn.net/xu__cg/article/details/52970885 一.代理模式 为某个对象提供一个代理,从而控制这个代理的访问.代理类和委托类具有共同的父类或父接口,这样在任何使用委托类对象的地方都可以使用代理类对象替代.代理类负责请求的预处理.过滤.将请求分配给委托类处理.以及委托类处理完请求的后续处理. 二.代理模式结构 UML类图: 由上图代理模式的结构为: 抽象角色: 真实对象和代理对象的共同接口. 代理角色: 代理对象角色内部含有对真实对

Java:静态代理and动态代理

代理模式是常用的设计模式,其特征是代理类与委托类具有相同的接口,在具体实现上,有静态代理和动态代理之分.代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务,也就是说代理类主要负责为委托类预处理消息.过滤消息.把消息转发给委托类,以及事后处理消息等. 静态代理和动态代理的一个显著区别: 静态代理:由程序员创建或特定工具自动生成源代码,再对其编译.在程序运行前,代理类的.class文件就

JAVA学习篇--静态代理VS动态代理

本篇博客的由来,之前我们学习大话设计,就了解了代理模式,但为什么还要说呢? 原因: 1,通过DRP这个项目,了解到了动态代理,认识到我们之前一直使用的都是静态代理,那么动态代理又有什么好处呢?它们二者的区别是什么呢? 2,通过学习动态代理了解到动态代理是一种符合AOP设计思想的技术,那么什么又是AOP? 下面是我对它们的理解! 代理Proxy: Proxy代理模式是一种结构型设计模式,主要解决的问题是:在直接访问对象时带来的问题 代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对

java 代理模式(静态代理、动态代理、Cglib代理) 转载

Java的三种代理模式 1.代理模式 代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能.这里使用到编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方法 代理模式最大的特点就是代理类和实际业务类实现同一个接口(或继承同一父类),代理对象持有一个实际对象的引用,外部调用时操作的是代理对象,而在代理对象的内部实现中又会去调

java静态代理和动态代理(一)

代理Proxy: Proxy代理模式是一种结构型设计模式,主要解决的问题是:在直接访问对象时带来的问题. 代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问.代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理. 为了保持行为的一致性,代理类和委托类通常会实现相同的接口,所以在访问者看来两者没有丝毫的区别.通过代理类这中间一层,能有效控制对委托类对象的直接访问,也可以很好地隐藏和保护委托类对象,同时也为实施不同控制策略预留了空间,从