本篇博文介绍的是JDK的动态代理,Java中动态代理不仅仅是JDK的动态代理还有CGLIB代理。

(JDK)动态代理是指客户通过代理类来调用其它对象的方法,并且是在程序运行时根据需要动态创建目标类的代理对象。

即,不直接调用目标对象而是通过代理对象调用。代理对象不直接生成,而是程序运行时根据需要动态生成!

代理对象,不需要实现接口,但是目标对象要实现接口,否则不能用动态代理。代理对象的生成,是利用JDK 的API,动态的在内存中构建代理对象。 动态代理也叫做:JDK 代理、接口代理。
JDK 实现代理只需要使用newProxyInstance 方法,但是该方法需要接收三个参数,完整的写法是:static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )

代理设计模式的原理:

使用一个代理将对象包装起来, 然后用该代理对象取代原始对象. 任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。

动态代理之所以被称为动态,是因为运行时才将它的类创建出来。代码开始执行时,还没有proxy类,它是根据需要从传入的接口集创建的。


【1】Proxy

专门完成代理的操作类,是所有动态代理类的父类。通过此类为一个或多个接口动态地生成实现类。提供了许多用于创建动态代理类和动态代理对象的静态方法。

static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)

创建一个动态代理类所对应的Class对象。

static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

直接创建一个动态代理对象。

其他方法如下图:


【2】InvocationHandler

InvocationHandler 是一个接口,官方文档解释说,每个代理的实例都有一个与之关联的 InvocationHandler 实现类,如果代理的方法被调用,那么代理便会通知和转发给内部的 InvocationHandler 实现类的invoke方法,由它决定处理。

InvocationHandler接口源码如下:

 * //InvocationHandler是一个被代理实例的调用处理程序实现的接口。* // 每一个代理实例都有一个相关联的invocation handler* //当代理实例方法被调用时,代理会转发到相关联的invocation handler的invoke方法。
public interface InvocationHandler {public Object invoke(Object proxy, Method method, Object[] args)throws Throwable;
}

InvocationHandler 内部只有一个 invoke() 方法,当一个代理实例的方法被调用时,与代理实例相关联的invocationHhandlerinvoke方法将会被调用。正是这个方法决定了怎么样处理代理传递过来的方法调用。

  • proxy 代理对象
  • method 代理对象调用的方法
  • args 调用的方法中的参数

因为,Proxy 动态产生的代理对象会调用 InvocationHandler 实现类,所以 InvocationHandler 是实际执行者。

故而,总的运行流程为:获取代理实例–>代理实例.方法 --> InvocationHandler.invoke() --> 接口真正实现类的方法

InvocationHandler根本就不是proxy,它只是一个帮助proxy的类,proxy会被调用转发给它处理。Proxy本身是利用静态的Proxy.newProxyInstance()方法在运行时动态地创建的。


【3】动态代理步骤

① 父接口

父接口Human定义了两个方法:

interface Human {void info();void fly();
}

② 实现类SuperMan(被代理类)

// 被代理类
class SuperMan implements Human {public void info() {System.out.println("我是超人!我怕谁!");}public void fly() {System.out.println("I believe I can fly!");}
}

③ InvocationHandler实现类

也就是代理类的调用处理程序。

class MyInvocationHandler implements InvocationHandler {// 被代理类对象的声明Object obj;// 动态的创建一个代理类的对象public Object getProxyInstance()(Object obj){this.obj = obj;return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), this);}@Overridepublic Object invoke(Object proxy, Method method, Object[] args)throws Throwable {Object returnVal = method.invoke(obj, args);return returnVal;}
}

④ 测试方法

public static void main(String[] args) {//创建一个被代理类的对象SuperMan man = new SuperMan();//创建调用处理程序MyInvocationHandler handler = new MyInvocationHandler();//返回一个代理类的对象Object obj = handler.getProxyInstance()(man);System.out.println(obj.getClass());//代理实例 强转Human hu = (Human)obj;//通过代理类的对象调用重写的抽象方法hu.info();System.out.println();hu.fly();}

测试结果如下:

// 这里表明拿到的是代理对象
class com.web.test.$Proxy0
我是超人!我怕谁!I believe I can fly!

【4】JDK动态代理原理

① 关键入口代码

Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj
.getClass().getInterfaces(), this);

根据类加载器,目标类的上行接口和InvocationHandler获取代理对象。这里有两个问题,第一如何获取?第二获取的代理对象是个什么样子?

先分析如何获取。

② 追踪源码到Proxy.newProxyInstance

     * //loader   定义代理类的类加载器* // interfaces  代理类需要实现的接口* // h -- 用来转发/反射目标方法的handler* //返回一个代理类的实例对象附带指定的代理类的invocation handler。* //该代理类被指定的类加载器定义且实现了指定的接口。*/@CallerSensitivepublic static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException {Objects.requireNonNull(h)//...//寻找或者产生代理类的classClass<?> cl = getProxyClass0(loader, intfs);try {if (sm != null) {checkNewProxyPermission(Reflection.getCallerClass(), cl);}// 拿到代理类的构造器--这一步很关键!final Constructor<?> cons = cl.getConstructor(constructorParams);final InvocationHandler ih = h;//如果非public,setAccessible(true);--反射中常见操作if (!Modifier.isPublic(cl.getModifiers())) {AccessController.doPrivileged(new PrivilegedAction<Void>() {public Void run() {cons.setAccessible(true);return null;}});}// 通过构造器反射获取代理类实例对象(InvocationHandler作为参数)return cons.newInstance(new Object[]{h});}//...}

过程很清晰(参考代码注释),继续细究下去。


Class<?> cl = getProxyClass0(loader, intfs)做了什么?

源码如下:

//产生一个代理类 class。在调用该方法前必须调用checkProxyAccess 方法进行权限检查private static Class<?> getProxyClass0(ClassLoader loader,Class<?>... interfaces) {if (interfaces.length > 65535) {throw new IllegalArgumentException("interface limit exceeded");}//如果代理类已经存在,则直接返回缓存中的copy;//否则,通过ProxyClassFactory创建代理类return proxyClassCache.get(loader, interfaces);}

如果缓存中有,就直接返回proxy class;否则就要通过ProxyClassFactory获取。

这里的cache 是WeakCache,暂且不去理会,继续看ProxyClassFactory。

Proxy的静态内部类–Proxy$ProxyClassFactory源码如下:

// 通过给定的类加载器和接口,产生、定义并返回代理类。
private static final class ProxyClassFactoryimplements BiFunction<ClassLoader, Class<?>[], Class<?>>
{// prefix for all proxy class names--代理类前缀private static final String proxyClassNamePrefix = "$Proxy";//代理类编号private static final AtomicLong nextUniqueNumber = new AtomicLong();@Overridepublic Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);           //...省略代码//定义代理类所在的包String proxyPkg = null;    int accessFlags = Modifier.PUBLIC | Modifier.FINAL;记录非公共代理接口的包,以便在同一包中定义代理类。
验证所有非公共代理接口都在同一个包中。for (Class<?> intf : interfaces) {int flags = intf.getModifiers();// 如果修饰符非public,则变为finalif (!Modifier.isPublic(flags)) {accessFlags = Modifier.FINAL;String name = intf.getName();int n = name.lastIndexOf('.');String pkg = ((n == -1) ? "" : name.substring(0, n + 1));if (proxyPkg == null) {proxyPkg = pkg;} else if (!pkg.equals(proxyPkg)) {throw new IllegalArgumentException("non-public interfaces from different packages");}}}if (proxyPkg == null) {// if no non-public proxy interfaces, use com.sun.proxy packageproxyPkg = ReflectUtil.PROXY_PACKAGE + ".";}long num = nextUniqueNumber.getAndIncrement();// 代理类的完整名字=包名+前缀+序号String proxyName = proxyPkg + proxyClassNamePrefix + num;/* Generate the specified proxy class.*/// 这里,产生指定的proxy class 字节数组!!!byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);try {// 通过一个native方法获取proxy classreturn defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);} // ... 省略代码}
}

从ProxyClassFactory中下面的方法可以看到具体生成字节流的方法是ProxyGenerator.generateProxyClass(..).。最后通过native方法生成Class对象。同时对class对象的包名称有一些规定比如命名为com.web.test$proxy0

要想得到字节码实例我们需要先下载这部分字节流,然后通过反编译得到java代码。

ProxyGenerator.generateProxyClass(..)方法生成字节流,然后写进硬盘.假设我把proxyName定义为Human$ProxyCode

获取代理类代码如下:

public class TestProxyJava {public static void generateClassFile(Class clazz,String proxyName) throws Exception{//根据类信息和提供的代理类名称,生成字节码byte[] classFile = ProxyGenerator.generateProxyClass(proxyName,new Class[]{clazz});String paths = clazz.getResource(".").getPath();  System.out.println(paths);  FileOutputStream out = null;     //保留到硬盘中  out = new FileOutputStream(paths+proxyName+".class");    out.write(classFile);    out.flush(); }public static void main(String[] args) {try {generateClassFile(Human.class,"Human$ProxyCode");} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}
}
}

获取到的代理类源码如下:

public final class Human$ProxyCode extends Proxy implements Human{private static Method m1;private static Method m3;private static Method m4;private static Method m2;private static Method m0;public Human$ProxyCode(InvocationHandler paramInvocationHandler)throws {super(paramInvocationHandler);}public final boolean equals(Object paramObject)throws {try{return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();}catch (Error|RuntimeException localError){throw localError;}catch (Throwable localThrowable){throw new UndeclaredThrowableException(localThrowable);}}
//final flypublic final void fly()throws {try{this.h.invoke(this, m3, null);return;}catch (Error|RuntimeException localError){throw localError;}catch (Throwable localThrowable){throw new UndeclaredThrowableException(localThrowable);}}public final void info()throws {try{/调用InvokeHandler的invoke方法,并将具体方法传了进去this.h.invoke(this, m4, null);return;}catch (Error|RuntimeException localError){throw localError;}catch (Throwable localThrowable){throw new UndeclaredThrowableException(localThrowable);}}public final String toString()throws {try{return (String)this.h.invoke(this, m2, null);}catch (Error|RuntimeException localError){throw localError;}catch (Throwable localThrowable){throw new UndeclaredThrowableException(localThrowable);}}public final int hashCode()throws {try{return ((Integer)this.h.invoke(this, m0, null)).intValue();}catch (Error|RuntimeException localError){throw localError;}catch (Throwable localThrowable){throw new UndeclaredThrowableException(localThrowable);}}static{try{m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });m3 = Class.forName("com.web.test.Human").getMethod("fly", new Class[0]);m4 = Class.forName("com.web.test.Human").getMethod("info", new Class[0]);m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);return;}catch (NoSuchMethodException localNoSuchMethodException){throw new NoSuchMethodError(localNoSuchMethodException.getMessage());}catch (ClassNotFoundException localClassNotFoundException){throw new NoClassDefFoundError(localClassNotFoundException.getMessage());}}
}

可以发现这个类extends Proxy实现了我们需要代理的接口Human,且他的构造函数确实是需要传递一个InvocationHandler对象。

那么现在的情况就是我们的生成了一个代理类,这个代理类是我们需要代理的接口的实现类。我们的接口中定义的infofly方法,在这个代理类中帮我们实现了,并且全部变成了final的。同时覆盖了一些Object类中的方法。

以info这个方法举例,方法中会调用InvocationHandler类中的invoke方法(也就是我们实现的逻辑的地方),同时把自己的Method对象,参数列表等传入进去。

再回头看上面的测试main方法:

public static void main(String[] args) {SuperMan man = new SuperMan();//创建一个被代理类的对象MyInvocationHandler handler = new MyInvocationHandler();Object obj = handler.bind(man);//返回一个代理类的对象System.out.println(obj.getClass());// 现在知道为什么可以强转了吧Human hu = (Human)obj;//通过代理类的对象调用重写的抽象方法hu.info();System.out.println();hu.fly();
}

【5】动态代理与AOP

在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。

AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

主要功能:日志记录,性能统计,安全控制,事务处理,异常处理等等。

简而言之,在不改变原有代码基础上进行功能增强!

添加HumanUtil模拟增强方法:

class HumanUtil {public void method1() {System.out.println("=======方法一=======");}public void method2() {System.out.println("=======方法二=======");}
}

修改MyInvocationHandler如下:

class MyInvocationHandler implements InvocationHandler {// 被代理类对象的声明Object obj;// 动态的创建一个代理类的对象public Object bind(Object obj){this.obj = obj;return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), this);}@Overridepublic Object invoke(Object proxy, Method method, Object[] args)throws Throwable {HumanUtil h = new HumanUtil();h.method1();Object returnVal = method.invoke(obj, args);h.method2();return returnVal;}
}

再次测试如下:

class com.web.test.$Proxy0
=======方法一=======
我是超人!我怕谁!
=======方法二==============方法一=======
I believe I can fly!
=======方法二=======

使用Proxy生成一个动态代理时,往往并不会凭空产生一个动态代理,这样没有太大的意义。通常都是为指定的目标对象生成动态代理。

这种动态代理在AOP中被称为AOP代理,AOP代理可代替目标对象,AOP代理包含了目标对象的全部方法。但AOP代理中的方法与目标对象的方法存在差异:AOP代理里的方法可以在执行目标方法之前、之后插入一些通用处理。

如下图所示:


【6】JDK动态代理总结


① 概率总结

① JDK动态代理只能代理有接口的类,并且只能代理接口方法,不能代理一般的类中自定义的方法

因为代理类是一个实现了被代理对象的上行接口的类,所以类必须有接口。而且不能代理被代理对象中自定义的方法!

② 提供了一个使用InvocationHandler作为参数的构造方法

在代理类中做一层包装,业务逻辑在invoke方法中实现。

③ 重写了Object类的equals、hashCode、toString

它们都只是简单的调用了InvocationHandler的invoke方法,即可以对其进行特殊的操作,也就是说JDK的动态代理还可以代理上述三个方法。

④ 在invoke方法中我们甚至可以不用Method.invoke方法调用实现类就返回

这种方式常常用在RPC框架中,在invoke方法中发起通信调用远端的接口等

② 动态代理在框架中的应用

① 在mybatis中的应用

如在MyBatis原理分析之获取SqlSession一文中获取executor 实例就应用了代理模式。在MyBatis原理分析之获取Mapper接口的代理对象也应用了java的动态代理思想。

③ 代理模式的几种变异体

① 远程代理

远程代理可以作为另一个JVM上对象的本地代表。调用代理的方法,会被代理利用网络转发到远程执行,并且结果会通过网络返回给代理,再由代理将结果转给客户。

②虚拟代理

虚拟代理作为创建开销大的对象的代表。虚拟代理经常直到我们真正需要一个对象的时候才创建它。当对象在创建前和创建中时,由虚拟代理来扮演对象的替身。对象创建后,代理就会将请求直接委托给对象。

③ 缓存代理

缓存代理会维护之前创建的对象,当收到请求时,在可能的情况下返回缓存的对象。它也允许许多个客户共享结果,以减少计算或网络延迟。

④ 保护代理

保护代理可以根据客户的角色来决定是否允许客户访问特定的方法。所以保护代理可能只提供给客户部分接口。保护代理通常是由Java的动态代理技术实现。

⑤ 同步代理

在多线程的情况下为主题提供安全的访问。

⑥ 复杂隐藏代理

用来隐藏一个类的复杂集合的复杂度,并进行访问控制。有时候也称为外观代理(Facade Proxy),这不难理解。复杂隐藏代理和外观模式是不一样的,因为代理控制访问,而外观模式只提供另一组接口。


【7】获取代理类class文件的简单方法


在【4】中通过ProxyGenerator.generateProxyClass(proxyName,new Class[]{clazz});获取class文件,比较繁琐,这里介绍一种简单方式。

关键代码如下:

//生成$Proxy0的class文件
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

修改main方法如下:

public static void main(String[] args) {//生成$Proxy0的class文件System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");SuperMan man = new SuperMan();//创建一个被代理类的对象MyInvocationHandler handler = new MyInvocationHandler();Object obj = handler.bind(man);//返回一个代理类的对象System.out.println(obj.getClass());Human hu = (Human)obj;hu.info();//通过代理类的对象调用重写的抽象方法System.out.println();hu.fly();}

生成的$Proxy0路径在项目根目录下com/web/test–该包名为测试代码所在的包。

总结:

java动态代理是利用反射机制生成一个实现代理接口的代理类,在调用具体方法前调用内部InvokeHandler来处理,InvokeHandler将会调用invoke方法并处理目标实际方法。

而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。

  • 如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
  • 如果目标对象实现了接口,可以强制使用CGLIB实现AOP
  • 如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换

其他博客参考:
Cglib动态代理与Spring中动态代理思想

Java中的代理模式与动(静)态代理

Java反射机制(Reflection)简解与示例

一文读懂反射机制

Java中动态代理使用与原理详解相关推荐

  1. java throw与throws_基于Java中throw和throws的区别(详解)

    系统自动抛出的异常 所有系统定义的编译和运行异常都可以由系统自动抛出,称为标准异常,并且 Java 强烈地要求应用程序进行完整的异常处理,给用户友好的提示,或者修正后使程序继续执行. 语句抛出的异常 ...

  2. java里的进制转换函数_基于Java中进制的转换函数详解

    十进制转成十六进制: Integer.toHexString(int i) 十进制转成八进制 Integer.toOctalString(int i) 十进制转成二进制 Integer.toBinar ...

  3. throws java_基于Java中throw和throws的区别(详解)

    系统自动抛出的异常 所有系统定义的编译和运行异常都可以由系统自动抛出,称为标准异常,并且 Java 强烈地要求应用程序进行完整的异常处理,给用户友好的提示,或者修正后使程序继续执行. 语句抛出的异常 ...

  4. java线程和内核线程的,Java中内核线程理论及实例详解

    1.概念 内核线程是直接由操作系统内核控制的,内核通过调度器来完成内核线程的调度并负责将其映射到处理器上执行.内核态下的线程执行速度理论上是最高的,但是用户不会直接操作内核线程,而是通过内核线程的接口 ...

  5. Sklearn中predict_proba函数用法及原理详解

    Sklearn中predict_proba函数用法及原理详解(以logistic回归为例) 网上对predict_proba的数学原理解释的太少了,也不明确,特意总结一下,并给出有些不能用该方法的原因 ...

  6. 【夯实Spring Cloud】Spring Cloud中使用Hystrix实现断路器原理详解(上)

    本文属于[夯实Spring Cloud]系列文章,该系列旨在用通俗易懂的语言,带大家了解和学习Spring Cloud技术,希望能给读者带来一些干货.系列目录如下: [夯实Spring Cloud]D ...

  7. java中List的用法和实例详解

    Java中List的用法和实例详解 List的用法 List包括List接口以及List接口的所有实现类.因为List接口实现了Collection接口,所以List接口拥有Collection接口提 ...

  8. java中switchcase用法,java中的switch case语句使用详解

    java中的switch case语句 switch-case语句格式如下: ? swtich()变量类型只能是int.short.char.byte和enum类型(JDK 1.7 之后,类型也可以是 ...

  9. java中带符号十六进制转换成十进制详解

    java中带符号十六进制转换成十进制详解 代码如下 代码如下 必须拿ffff进行测试,否则测不出异同 public void test1(){String strHex="ffff" ...

  10. java里throws详细讲解,基于Java中throw和throws的区别(详解)

    系统自动抛出的异常 所有系统定义的编译和运行异常都可以由系统自动抛出,称为标准异常,并且 Java 强烈地要求应用程序进行完整的异常处理,给用户友好的提示,或者修正后使程序继续执行. 语句抛出的异常 ...

最新文章

  1. 【进大厂大数据爬虫技术核心难点】纯前端开发的爬虫程序,很多BAT技术大咖都为之惊叹
  2. LeetCode——树:层次遍历、前中后序遍历
  3. Android之adb jdwp获取debug版本app的进程Id
  4. android 代码布局设置wrap_content,android ScrollView布局(wrap_content,最大大小)
  5. java 数组 null值_数组的元素String在java中包含null
  6. acs880变频器选型手册_变频器是否需要加进线、出线电抗器?
  7. 【C++】常用查找算法
  8. Win10操作系统下,如何打开DOS窗口(图文教程)
  9. 2022最新性能测试面试题(带答案)
  10. 无线投屏(智慧教室)
  11. mac m1使用picGo + gitee搭建免费图床
  12. 如何分析PARSEC源码
  13. linux 键盘过滤,键盘过滤驱动程序不responsing
  14. 智能家庭监控开发框架
  15. 创建三个窗口进行卖票 总票100张 使用实现Runnable接口的方法实现
  16. Vue简单示例——weex
  17. 终极单词index 排序 A-B
  18. MySQL基础+高级
  19. BP神经网络——案例一
  20. 通俗理解“极大似然估计”

热门文章

  1. fences(桌面整理软件)与eDiary3.3.3下载链接
  2. 北京市中小学信息学竞赛汇总 徐于铃
  3. 智能机器人建房子后房价走势_机器人建楼、5G住宅……房地产下半场要这样玩...
  4. 人工智能 - 人脸合成 (腾讯AI开放平台)
  5. 双眼融合训练一个月_双眼视觉是什么?为什么要进行视功能训练?
  6. 八数码问题引发的思考
  7. 安信可开发经验分享 | 安信可ESP-C3-12F模组使用内置USB烧录下载更新固件,无需TTL-USB转接器即可更新固件,下载固件速度更快更省时间。
  8. 腾讯视频TS文件转MP4
  9. kali安装python3.8_kali2019.4试用记录
  10. nio java是什么_JAVA NIO是什么(zz)