关于Java中的动态代理,我们首先需要了解的是一种常用的设计模式--代理模式,而对于代理,根据创建代理类的时间点,又可以分为静态代理和动态代理。
静态代理
1、静态代理
静态代理:由程序员创建或特定工具自动生成源代码,也就是在编译时就已经将接口,被代理类,代理类等确定下来。在程序运行之前,代理类的.class文件就已经生成。
2、静态代理简单实现
根据上面代理模式的类图,来写一个简单的静态代理的例子。我这儿举一个比较粗糙的例子,假如一个班的同学要向老师交班费,但是都是通过班长把自己的钱转交给老师。这里,班长就是代理学生上交班费,
班长就是学生的代理。
首先,我们创建一个Person接口。这个接口就是学生(被代理类),和班长(代理类)的公共接口,他们都有上交班费的行为。这样,学生上交班费就可以让班长来代理执行。
/** * 创建Person接口 * @author ChenHao */ public interface Person { //上交班费 void giveMoney(); }
Student类实现Person接口。Student可以具体实施上交班费的动作。
public class Student implements Person { private String name; public Student(String name) { this.name = name; } @Override public void giveMoney() { System.out.println(name + "上交班费50元"); } }
StudentsProxy类,这个类也实现了Person接口,但是还另外持有一个学生类对象,由于实现了Peson接口,同时持有一个学生对象,那么他可以代理学生类对象执行上交班费(执行giveMoney()方法)行为。
/** * 学生代理类,也实现了Person接口,保存一个学生实体,这样既可以代理学生产生行为 * @author ChenHao * */ public class StudentsProxy implements Person{ //被代理的学生 Student stu; public StudentsProxy(Student stu) { this.stu = stu; } //代理上交班费,调用被代理学生的上交班费行为 public void giveMoney() { stu.giveMoney(); } }
下面测试一下,看如何使用代理模式:
public class StaticProxyTest { public static void main(String[] args) { //被代理的学生张三,他的班费上交有代理对象monitor(班长)完成 Student zhangsan = new Student("张三"); //生成代理对象,并将张三传给代理对象 Person monitor = new StudentsProxy(zhangsan); //班长代理上交班费 monitor.giveMoney(); } }
运行结果:
这里并没有直接通过张三(被代理对象)来执行上交班费的行为,而是通过班长(代理对象)来代理执行了。这就是代理模式。
代理模式最主要的就是有一个公共接口(Person),一个具体的类(Student),一个代理类(StudentsProxy),代理类持有具体类的实例,代为执行具体类实例方法。上面说到,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。这里的间接性就是指不直接调用实际对象的方法,那么我们在代理过程中就可以加上一些其他用途。就这个例子来说,加入班长在帮张三上交班费之前想要先反映一下张三最近学习有很大进步,通过代理模式很轻松就能办到:
这里并没有直接通过张三(被代理对象)来执行上交班费的行为,而是通过班长(代理对象)来代理执行了。这就是代理模式。
代理模式最主要的就是有一个公共接口(Person),一个具体的类(Student),一个代理类(StudentsProxy),代理类持有具体类的实例,代为执行具体类实例方法。上面说到,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。这里的间接性就是指不直接调用实际对象的方法,那么我们在代理过程中就可以加上一些其他用途。就这个例子来说,加入班长在帮张三上交班费之前想要先反映一下张三最近学习有很大进步,通过代理模式很轻松就能办到:
/** * 学生代理类,也实现了Person接口,保存一个学生实体,这样既可以代理学生产生行为 * @author ChenHao * */ public class StudentsProxy implements Person{ //被代理的学生 Student stu; public StudentsProxy(Student stu) { this.stu = stu; } //代理上交班费,调用被代理学生的上交班费行为 public void giveMoney() { System.out.println("张三最近学习有进步!"); stu.giveMoney(); } }
模式优缺点
优点
- 1、 代理模式能够协调调用者和被调用者,在一定程度上降低了系统的耦合度。
- 2、 代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了的作用和保护了目标对象的
缺点
- 1、由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。
- 2、 实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
动态代理
1.动态代理
前面介绍了静态代理,虽然静态代理模式很好用,但是静态代理还是存在一些局限性的,比如使用静态代理模式需要程序员手写很多代码,这个过程是比较浪费时间和精力的。一旦需要代理的类中方法比较多,或者需要同时代理多个对象的时候,这无疑会增加很大的复杂度。
代理类在程序运行时创建的代理方式被成为动态代理。 我们上面静态代理的例子中,代理类(studentProxy)是自己定义好的,在程序运行之前就已经编译完成。然而动态代理,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。
2、动态代理简单实现
在java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过这个类和这个接口可以生成JDK动态代理类和动态代理对象。
public class DynamicProxyTest { interface IHello { void sayHello(); } static class Hello implements IHello { @Override public void sayHello() { System.out.println("hello world"); } } static class DynamicProxy implements InvocationHandler { Object originalObj; Object bind(Object originalObj) { this.originalObj = originalObj; return Proxy.newProxyInstance(originalObj.getClass().getClassLoader(), originalObj.getClass().getInterfaces(), this); } /** *Object proxy是代理的对象, Method method是真实对象中调用方法的Method类, Object[] args是真实对象中调用方法的参数 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("welcome"); return method.invoke(originalObj, args); } } public static void main(String[] args) { IHello hello = (IHello) new DynamicProxy().bind(new Hello()); hello.sayHello(); } }
运行结果如下:
welcome hello world
动态代理原理分析
推荐博客
上面说到,动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。是因为所有被代理执行的方法,都是通过在InvocationHandler中的invoke方法调用的,所以我们只要在invoke方法中统一处理,就可以对所有被代理的方法进行相同的操作了。上述代码里,唯一的“黑厘子”就是Proxy.newProxyInstance()方法,除此之外再没有任何特殊之处。
在JDK动态代理中涉及如下角色:
业务接口Interface、业务实现类target、业务处理类Handler、JVM在内存中生成的动态代理类$Proxy0
动态代理原理图:
说白了,动态代理的过程是这样的:
-
Proxy通过传递给它的参数(interfaces/invocationHandler)生成代理类$Proxy0;
-
Proxy通过传递给它的参数(ClassLoader)来加载生成的代理类$Proxy0的字节码文件;
动态代理的关键代码就是Proxy.newProxyInstance(classLoader, interfaces, handler),我们跟进源代码看看
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { // handler不能为空 if (h == null) { throw new NullPointerException(); } final Class<?>[] intfs = interfaces.clone(); final SecurityManager sm = System.getSecurityManager(); if (sm != null) { checkProxyAccess(Reflection.getCallerClass(), loader, intfs); } /* * Look up or generate the designated proxy class. */ // 通过loader和接口,得到代理的Class对象 Class<?> cl = getProxyClass0(loader, intfs); /* * Invoke its constructor with the designated invocation handler. */ try { final Constructor<?> cons = cl.getConstructor(constructorParams); final InvocationHandler ih = h; if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) { // create proxy instance with doPrivilege as the proxy class may // implement non-public interfaces that requires a special permission return AccessController.doPrivileged(new PrivilegedAction<Object>() { public Object run() { return newInstance(cons, ih); } }); } else { // 创建代理对象的实例 return newInstance(cons, ih); } } catch (NoSuchMethodException e) { throw new InternalError(e.toString()); } }
我们看一下newInstance方法的源代码:
private static Object newInstance(Constructor<?> cons, InvocationHandler h) { try { return cons.newInstance(new Object[] {h} ); } catch (IllegalAccessException | InstantiationException e) { throw new InternalError(e.toString()); } catch (InvocationTargetException e) { Throwable t = e.getCause(); if (t instanceof RuntimeException) { throw (RuntimeException) t; } else { throw new InternalError(t.toString()); } } }
讲解完了代理类的生成源码,我们一定想要看看代理类的代码是什么样的,下面提供一个生成代理类的方法供大家使用:
/** * 代理类的生成工具 * @author ChenHao * @since 2019-4-2 */ public class ProxyGeneratorUtils { /** * 把代理类的字节码写到硬盘上 * @param path 保存路径 */ public static void writeProxyClassToHardDisk(String path) { // 第一种方法 // System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", true); // 第二种方法 // 获取代理类的字节码 byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy11", UserServiceImpl.class.getInterfaces()); FileOutputStream out = null; try { out = new FileOutputStream(path); out.write(classFile); out.flush(); } catch (Exception e) { e.printStackTrace(); } finally { try { out.close(); } catch (IOException e) { e.printStackTrace(); } } } public static void main(String[] args) { ProxyGeneratorUtils.writeProxyClassToHardDisk("C:/x/$Proxy11.class"); } }
此时就会在指定的C盘x文件夹下生成代理类的.class文件,我们看下反编译后的结果:
package org.fenixsoft.bytecode; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; public final class $Proxy0 extends Proxy implements DynamicProxyTest.IHello { private static Method m3; private static Method m1; private static Method m0; private static Method m2; /** *注意这里是生成代理类的构造方法,方法参数为InvocationHandler类型,看到这,是不是就有点明白 *super(paramInvocationHandler),是调用父类Proxy的构造方法。 *父类持有:protected InvocationHandler h; *Proxy构造方法: * protected Proxy(InvocationHandler h) { * Objects.requireNonNull(h); * this.h = h; * } * */ public $Proxy0(InvocationHandler paramInvocationHandler) throws { super(paramInvocationHandler); } /** * *这里调用代理对象的sayHello方法,直接就调用了InvocationHandler中的invoke方法,并把m3传了进去。 *this.h.invoke(this, m3, null); this.h就是父类Proxy中保存的InvocationHandler实例变量 *来,再想想,代理对象持有一个InvocationHandler对象,InvocationHandler对象持有一个被代理的对象, *再联系到InvacationHandler中的invoke方法。嗯,就是这样。 */ public final void sayHello() throws { try { this.h.invoke(this, m3, null); return; } catch (RuntimeException localRuntimeException) { throw localRuntimeException; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } // 此处由于版面原因,省略equals()、hashCode()、toString()三个方法的代码 // 这3个方法的内容与sayHello()非常相似。 static { try { m3 = Class.forName("org.fenixsoft.bytecode.DynamicProxyTest$IHello").getMethod("sayHello", new Class[0]); m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") }); m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); return; } catch (NoSuchMethodException localNoSuchMethodException) { throw new NoSuchMethodError(localNoSuchMethodException.getMessage()); } catch (ClassNotFoundException localClassNotFoundException) { throw new NoClassDefFoundError(localClassNotFoundException.getMessage()); } } }
这个代理类的实现代码也很简单,它为传入接口中的每一个方法,以及从 java.lang.Object中继承来的equals()、hashCode()、toString()方法都生成了对应的实现 ,并且统一调用了InvocationHandler对象的invoke()方法(代码中的“this.h”就是父类Proxy中保存的InvocationHandler实例变量)来实现这些方法的内容,各个方法的区别不过是传入的参数和Method对象有所不同而已,所以无论调用动态代理的哪一个方法,实际上都是在执行InvocationHandler.invoke()中的代理逻辑。