您现在的位置是:首页 > 文章详情

深入理解 Java 动态代理机制

日期:2018-11-28点击:405

Java 有两种代理方式,一种是静态代理,另一种是动态代理。对于静态代理,其实就是通过依赖注入,对对象进行封装,不让外部知道实现的细节。很多 API 就是通过这种形式来封装的。

代理模式结构图(图片来自《大话设计模式》)

images/yj4PSjSeajp5perpcMt7MajRXfWNdAtK.png


下面看下两者在概念上的解释:


静态代理


静态代理类:由程序员创建或者由第三方工具生成,再进行编译;在程序运行之前,代理类的.class文件已经存在了。


静态代理类通常只代理一个类。


静态代理事先知道要代理的是什么。


动态代理


动态代理类:在程序运行时,通过反射机制动态生成。


动态代理类通常代理接口下的所有类。


动态代理事先不知道要代理的是什么,只有在运行的时候才能确定。


动态代理的调用处理程序必须事先InvocationHandler接口,及使用Proxy类中的newProxyInstance方法动态的创建代理类。


Java动态代理只能代理接口,要代理类需要使用第三方的CLIGB等类库。


动态代理的好处


Java动态代理的优势是实现无侵入式的代码扩展,也就是方法的增强;让你可以在不用修改源码的情况下,增强一些方法;在方法的前后你可以做你任何想做的事情(甚至不去执行这个方法就可以)。此外,也可以减少代码量,如果采用静态代理,类的方法比较多的时候,得手写大量代码。


动态代理实例


静态代理的实例这里就不说了,比较简单。在 java 的 java.lang.reflect 包下提供了一个 Proxy 类和一个 InvocationHandler 接口,通过这个类和这个接口可以生成 JDK 动态代理类和动态代理对象。下面讲讲动态代理的实现。


先定义一个接口:

public interface Person {     void setName(String name); }

再定义一个学生 Student 类来实现 Person 接口,每一个学生都有一个自己的名字:

public class Student implements Person {          private String mName;     public Student(String name) {         mName = name;     }     public void setName(String name) {         mName = name;     } }

Student 类中,定义了一个私有变量 mName,用来表示 Student 的名字。接下去定义一个代理 handler,就是用来帮我们处理代理的 :

public class PersonHandler<T> implements InvocationHandler {     // 代理的目标对象     private T mTarget;     public PersonHandler(T target) {         mTarget = target;     }     @Override     public Object invoke(Object o, Method method, Object[] objects) throws Throwable {         // 调用开始前的操作         ProxyUtil.start();         // 调用方法,通过反射的形式来调用 mTarget 的 method          Object result = method.invoke(mTarget, objects);         // 打印改名前的名字         ProxyUtil.log(objects[0].toString());         // 调用结束后的操作         ProxyUtil.finish();         return result;     } }

可以发现,我们在调用代码前后都做了一些操作,甚至可以直接拦截该方法,不让其运行。其中定义了一个 ProxyUtil 类,方便我们做一些操作:

public class ProxyUtil {     private static final String TAG = "ProxyUtil";     public static void start() {         Log.d(TAG, "start: " + System.currentTimeMillis());     }     public static void finish() {         Log.d(TAG, "finish: " + System.currentTimeMillis());     }     public static void log(String name) {         Log.d(TAG, "log: " + name);     } }

接下去开始编写代理的实现:

public class MainActivity extends AppCompatActivity {     @Override     protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         setContentView(R.layout.activity_main);         //创建一个实例对象,这个对象是被代理的对象         Person zhangsan = new Student("张三");         //创建一个与代理对象相关联的 InvocationHandler         PersonHandler stuHandler = new PersonHandler<>(zhangsan);         //创建一个代理对象 stuProxy 来代理 zhangsan,代理对象的每个执行方法都会替换执行 Invocation 中的 invoke 方法         Person stuProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, stuHandler);         //代理执行 setName 的方法         stuProxy.setName("王五"); }


看下打印输出:

images/X7FcwCJraREkrESp6ebnye2C8A6RKnp3.png


可以发现代理成功了。并且我们在调用方式的之前之后,都做了一些操作。Spring 的 AOP 其就是通过动态代理的机制实现的。


其中,我们将 stuProxy 的类名打印出来:

Log.d(TAG, "onCreate: " + stuProxy.getClass().getCanonicalName());

images/QSehmiw7iKzDEYKxBKMFjnZibtScQTEK.png

发现其名字竟然是 $Proxy0。具体原因下面会解释。


源码分析


上面我们利用 Proxy 类的 newProxyInstance 方法创建了一个动态代理对象,查看该方法的源码: 

/**      * Returns an instance of a proxy class for the specified interfaces      * that dispatches method invocations to the specified invocation      * handler.      *      * <p>{@code Proxy.newProxyInstance} throws      * {@code IllegalArgumentException} for the same reasons that      * {@code Proxy.getProxyClass} does.      *      * @param   loader the class loader to define the proxy class      * @param   interfaces the list of interfaces for the proxy class      *          to implement      * @param   h the invocation handler to dispatch method invocations to      * @return  a proxy instance with the specified invocation handler of a      *          proxy class that is defined by the specified class loader      *          and that implements the specified interfaces      * @throws  IllegalArgumentException if any of the restrictions on the      *          parameters that may be passed to {@code getProxyClass}      *          are violated      * @throws  SecurityException if a security manager, <em>s</em>, is present      *          and any of the following conditions is met:      *          <ul>      *          <li> the given {@code loader} is {@code null} and      *               the caller's class loader is not {@code null} and the      *               invocation of {@link SecurityManager#checkPermission      *               s.checkPermission} with      *               {@code RuntimePermission("getClassLoader")} permission      *               denies access;</li>      *          <li> for each proxy interface, {@code intf},      *               the caller's class loader is not the same as or an      *               ancestor of the class loader for {@code intf} and      *               invocation of {@link SecurityManager#checkPackageAccess      *               s.checkPackageAccess()} denies access to {@code intf};</li>      *          <li> any of the given proxy interfaces is non-public and the      *               caller class is not in the same {@linkplain Package runtime package}      *               as the non-public interface and the invocation of      *               {@link SecurityManager#checkPermission s.checkPermission} with      *               {@code ReflectPermission("newProxyInPackage.{package name}")}      *               permission denies access.</li>      *          </ul>      * @throws  NullPointerException if the {@code interfaces} array      *          argument or any of its elements are {@code null}, or      *          if the invocation handler, {@code h}, is      *          {@code null}      */     @CallerSensitive     public static Object newProxyInstance(ClassLoader loader,                                           Class<?>[] interfaces,                                           InvocationHandler h)         throws IllegalArgumentException     { // 判空,判断 h 对象是否为空,为空就抛出 NullPointerException         Objects.requireNonNull(h);         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.          */         Class<?> cl = getProxyClass0(loader, intfs);         /*          * Invoke its constructor with the designated invocation handler.          */         try {             if (sm != null) {                 checkNewProxyPermission(Reflection.getCallerClass(), cl);             }             final Constructor<?> cons = cl.getConstructor(constructorParams);             final InvocationHandler ih = h;             if (!Modifier.isPublic(cl.getModifiers())) {                 AccessController.doPrivileged(new PrivilegedAction<Void>() {                     public Void run() {                         cons.setAccessible(true);                         return null;                     }                 });             }             return cons.newInstance(new Object[]{h});         } catch (IllegalAccessException|InstantiationException e) {             throw new InternalError(e.toString(), e);         } catch (InvocationTargetException e) {             Throwable t = e.getCause();             if (t instanceof RuntimeException) {                 throw (RuntimeException) t;             } else {                 throw new InternalError(t.toString(), t);             }         } catch (NoSuchMethodException e) {             throw new InternalError(e.toString(), e);         }     }

在生成代理类的过程中,会进行一些列检查,比如访问权限之类的。接下去我们来看 getProxyClass0 方法的源码:

/**      * Generate a proxy class.  Must call the checkProxyAccess method      * to perform permission checks before calling this.      */     private static Class<?> getProxyClass0(ClassLoader loader,                                            Class<?>... interfaces) {  // 数量超过 65535 就抛出异常,665535 这个就不用说了吧         if (interfaces.length > 65535) {             throw new IllegalArgumentException("interface limit exceeded");         }         // If the proxy class defined by the given loader implementing         // the given interfaces exists, this will simply return the cached copy;         // otherwise, it will create the proxy class via the ProxyClassFactory         return proxyClassCache.get(loader, interfaces);     }

最后发现会对生成的代理类进行缓存,有了,就不直接返回,没有的,还得生成代理类,我们继续往下走:

proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

关键点在于  ProxyClassFactory 这个类,从名字也可以猜出来这个类的作用。看看代码:

/**      * A factory function that generates, defines and returns the proxy class given      * the ClassLoader and array of interfaces.      */     private static final class ProxyClassFactory         implements BiFunction<ClassLoader, Class<?>[], Class<?>>     {         // prefix for all proxy class names 定义前缀         private static final String proxyClassNamePrefix = "$Proxy";         // next number to use for generation of unique proxy class names  原子操作,适用于多线程         private static final AtomicLong nextUniqueNumber = new AtomicLong();         @Override         public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {             Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);             for (Class<?> intf : interfaces) {                 /*                  * Verify that the class loader resolves the name of this                  * interface to the same Class object.                  */                 Class<?> interfaceClass = null;                 try { // 通过反射获取到接口类                     interfaceClass = Class.forName(intf.getName(), false, loader);                 } catch (ClassNotFoundException e) {                 } // 所得到的接口类与传进来的不相等,说明不是同一个类                 if (interfaceClass != intf) {                     throw new IllegalArgumentException(                         intf + " is not visible from class loader");                 }                 /*                  * Verify that the Class object actually represents an                  * interface.                  */                 if (!interfaceClass.isInterface()) {                     throw new IllegalArgumentException(                         interfaceClass.getName() + " is not an interface");                 }                 /*                  * Verify that this interface is not a duplicate.                   */                 if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {                     throw new IllegalArgumentException(                         "repeated interface: " + interfaceClass.getName());                 }             }             String proxyPkg = null;     // package to define proxy class in             int accessFlags = Modifier.PUBLIC | Modifier.FINAL;             /*              * Record the package of a non-public proxy interface so that the              * proxy class will be defined in the same package.  Verify that              * all non-public proxy interfaces are in the same package.              */             for (Class<?> intf : interfaces) {                 int flags = intf.getModifiers();                 if (!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 package                 proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";             }             /*              * Choose a name for the proxy class to generate.              */             long num = nextUniqueNumber.getAndIncrement(); // 生产代理类的名字             String proxyName = proxyPkg + proxyClassNamePrefix + num;             /*              * Generate the specified proxy class.              */             byte[] proxyClassFile = ProxyGenerator.generateProxyClass(                 proxyName, interfaces, accessFlags);             try {                 return defineClass0(loader, proxyName,                                     proxyClassFile, 0, proxyClassFile.length);             } catch (ClassFormatError e) {                 /*                  * A ClassFormatError here means that (barring bugs in the                  * proxy class generation code) there was some other                  * invalid aspect of the arguments supplied to the proxy                  * class creation (such as virtual machine limitations                  * exceeded).                  */                 throw new IllegalArgumentException(e.toString());             }         }     }

这里会通过反射获取接口的各种修饰符,包名等,然后根据规则命名代理类,最后调用 ProxyGenerator.generateProxyClass 生成了代理类。


 ProxyGenerator.generateProxyClass 具体实现在 eclipse 上打开后,说是找不到源码:

images/6RsSyYmCFaDb43kGmRjCY3XyYiMDErD5.png


 不过,从其他地方找到了部分代码:

public static byte[] generateProxyClass(final String name,                                              Class[] interfaces)      {          ProxyGenerator gen = new ProxyGenerator(name, interfaces);       // 这里动态生成代理类的字节码,由于比较复杂就不进去看了          final byte[] classFile = gen.generateClassFile();          // 如果saveGeneratedFiles的值为true,则会把所生成的代理类的字节码保存到硬盘上          if (saveGeneratedFiles) {              java.security.AccessController.doPrivileged(              new java.security.PrivilegedAction<Void>() {                  public Void run() {                      try {                          FileOutputStream file =                              new FileOutputStream(dotToSlash(name) + ".class");                          file.write(classFile);                          file.close();                          return null;                      } catch (IOException e) {                          throw new InternalError(                              "I/O exception saving generated file: " + e);                      }                  }              });          }          // 返回代理类的字节码          return classFile;      }

我们可以自己试试 ProxyGenerator.generateProxyClass 的功能。

public class ProxyGeneratorUtils {     /**      * 把代理类的字节码写到硬盘上       * @param path 保存路径       */     public static void writeProxyClassToHardDisk(String path) { // 获取代理类的字节码           byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy11", Student.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();             }         }     } }

main 方法里面进行调用 :

public class Main {     public static void main(String[] args) {         ProxyGeneratorUtils.writeProxyClassToHardDisk("$Proxy0.class");     } }

可以发现,在根目录下生成了一个  $Proxy0.class 文件,文件内容反编译后如下:

import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; import proxy.Person; public final class $Proxy0 extends Proxy implements Person {   private static Method m1;   private static Method m2;   private static Method m3;   private static Method m0;      /**   *注意这里是生成代理类的构造方法,方法参数为InvocationHandler类型,看到这,是不是就有点明白   *为何代理对象调用方法都是执行InvocationHandler中的invoke方法,而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);   }      //这个静态块本来是在最后的,我把它拿到前面来,方便描述    static   {     try     {       //看看这儿静态块儿里面有什么,是不是找到了giveMoney方法。请记住giveMoney通过反射得到的名字m3,其他的先不管       m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });       m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);       m3 = Class.forName("proxy.Person").getMethod("giveMoney", 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());     }   }     /**   *    *这里调用代理对象的giveMoney方法,直接就调用了InvocationHandler中的invoke方法,并把m3传了进去。   *this.h.invoke(this, m3, null);这里简单,明了。   *来,再想想,代理对象持有一个InvocationHandler对象,InvocationHandler对象持有一个被代理的对象,   *再联系到InvacationHandler中的invoke方法。嗯,就是这样。   */   public final void giveMoney()     throws    {     try     {       this.h.invoke(this, m3, null);       return;     }     catch (Error|RuntimeException localError)     {       throw localError;     }     catch (Throwable localThrowable)     {       throw new UndeclaredThrowableException(localThrowable);     }   }   //注意,这里为了节省篇幅,省去了toString,hashCode、equals方法的内容。原理和giveMoney方法一毛一样。 }

jdk 为我们的生成了一个叫 $Proxy0(这个名字后面的0是编号,有多个代理类会一次递增)的代理类,这个类文件时放在内存中的,我们在创建代理对象时,就是通过反射获得这个类的构造方法,然后创建的代理实例。通过对这个生成的代理类源码的查看,我们很容易能看出,动态代理实现的具体过程。


我们可以对 InvocationHandler 看做一个中介类,中介类持有一个被代理对象,在 invoke 方法中调用了被代理对象的相应方法,而生成的代理类中持有中介类,因此,当我们在调用代理类的时候,就是再调用中介类的 invoke 方法,通过反射转为对被代理对象的调用。


代理类调用自己方法时,通过自身持有的中介类对象来调用中介类对象的 invoke 方法,从而达到代理执行被代理对象的方法。也就是说,动态代理通过中介类实现了具体的代理功能。


生成的代理类:$Proxy0 extends Proxy implements Person,我们看到代理类继承了 Proxy 类,所以也就决定了 java 动态代理只能对接口进行代理,Java 的继承机制注定了这些动态代理类们无法实现对 class 的动态代理。



原文链接:https://blog.roncoo.com/article/133967
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章