Day6-Java 动态代理

JAVA动态代理

这个主要实现的其实也就是可以不修改原来的代码来实现方法的增强,具体实现看以下代码。

0x01 API

主要方法

package java.lang.reflect;

import java.lang.reflect.InvocationHandler;

/**
 * Creator: yz
 * Date: 2020/1/15
 */
public class Proxy implements java.io.Serializable {

  // 省去成员变量和部分类方法...

    /**
     * 获取动态代理处理类对象
     *
     * @param proxy 返回调用处理程序的代理实例
     * @return 代理实例的调用处理程序
     * @throws IllegalArgumentException 如果参数不是一个代理实例
     */
    public static InvocationHandler getInvocationHandler(Object proxy)
            throws IllegalArgumentException {
        ...
    }

    /**
     * 创建动态代理类实例
     *
     * @param loader     指定动态代理类的类加载器
     * @param interfaces 指定动态代理类的类需要实现的接口数组
     * @param h          动态代理处理类
     * @return 返回动态代理生成的代理类实例
     * @throws IllegalArgumentException 不正确的参数异常
     */
    public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
            throws IllegalArgumentException {
        ...
    }

    /**
     * 创建动态代理类
     *
     * @param loader     定义代理类的类加载器
     * @param interfaces 代理类要实现的接口列表
     * @return 用指定的类加载器定义的代理类,它可以实现指定的接口
     */
    public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces) {
        ...
    }

    /**
     * 检测某个类是否是动态代理类
     *
     * @param cl 要测试的类
     * @return 如该类为代理类,则为 true,否则为 false
     */
    public static boolean isProxyClass(Class<?> cl) {
        return java.lang.reflect.Proxy.class.isAssignableFrom(cl) && proxyClassCache.containsValue(cl);
    }

    /**
     * 向指定的类加载器中定义一个类对象
     *
     * @param loader 类加载器
     * @param name   类名
     * @param b      类字节码
     * @param off    截取开始位置
     * @param len    截取长度
     * @return JVM创建的类Class对象
     */
    private static native Class defineClass0(ClassLoader loader, String name, byte[] b, int off, int len);

}

newProxyInstance:获取一个新生成的动态代理类实例

InvocationHandler:获取类

getProxyClass:创建class

还有invocationHandler的方法

 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;

0x02 使用java.lang.reflect.Proxy动态创建类对象

其实就是他也有native的方法叫defineClass0,可以直接动态创建类。(直接和jvm说hello word)

大概流程就是

  1. 准备一个ClassLoader可以用系统的那个
  2. 获取java.lang.reflect.ProxydefineClass0(ClassLoader loader, String name,byte[] b, int off, int len)方法。
  3. 返shemethodinvoke类就出出来了。

0x03 使用JDK动态代理生成FileSystem动态代理类实例

 *     Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
 *                                          new Class() { Foo.class },
 *                                          handler);

handler 需要实现InvocationHandler接口

要覆盖他的构造方法和invoke方法

// 创建UnixFileSystem类实例
FileSystem fileSystem = new UnixFileSystem();

// 创建动态代理处理类
InvocationHandler handler = new JDKInvocationHandler(fileSystem);

// 通过指定类加载器、类实现的接口数组生成一个动态代理类
Class proxyClass = Proxy.getProxyClass(
      FileSystem.class.getClassLoader(),// 指定动态代理类的类加载器
      new Class[]{FileSystem.class}// 定义动态代理生成的类实现的接口
);

// 使用反射获取Proxy类构造器并创建动态代理类实例
FileSystem proxyInstance = (FileSystem) proxyClass.getConstructor(
      new Class[]{InvocationHandler.class}).newInstance(new Object[]{handler}
);
//Proxy(InvocationHandler h(接口)) 

Class类是个很有用的类,用它可以实现反射机制。理解 new Class[]{FileSystem.class} 这是一个接口的写法。Class类的对象用于表示当前运行的 Java 应用程序中的类和接口。

package com.anbai.sec.proxy;
import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class JDKInvocationHandler implements InvocationHandler, Serializable {
    private final Object target;
    public JDKInvocationHandler(Object target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 为了不影响测试Demo的输出结果,这里忽略掉toString方法
        if ("toString".equals(method.getName())) {
            return method.invoke(target, args);
        }
        System.out.println("即将调用[" + target.getClass().getName() + "]类的[" + method.getName() + "]方法...");
        Object obj = method.invoke(target, args);
        System.out.println("已完成[" + target.getClass().getName() + "]类的[" + method.getName() + "]方法调用...");
        return obj;
    }
}

这个其实之前在javaweb中学过,就是加一个中间件,你调用proxy的这个方法,它过滤以下,加点东西进去,再选择是不是要触发。

Day6-Java 动态代理

必须时接口,这样就可以反射调用。

贴一下大哥的话

动态代理生成出来的类有如下技术细节和特性:

  1. 动态代理的必须是接口类,通过动态生成一个接口实现类来代理接口的方法调用(反射机制)。

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

    必须时接口 new Class[]{FileSystem.class}

  2. 动态代理类会由java.lang.reflect.Proxy.ProxyClassFactory创建。

    proxyClassCache.get(loader, interfaces);
    
    // 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
    
  3. ProxyClassFactory会调用sun.misc.ProxyGenerator类生成该类的字节码,并调用java.lang.reflect.Proxy.defineClass0()方法将该类注册到JVM

      */
                byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                    proxyName, interfaces, accessFlags);
                try {
                    return defineClass0(loader, proxyName,
                                        proxyClassFile, 0, proxyClassFile.length);
    
  4. 该类继承于java.lang.reflect.Proxy并实现了需要被代理的接口类,因为java.lang.reflect.Proxy类实现了java.io.Serializable接口,所以被代理的类支持序列化/反序列化

  5. 该类实现了代理接口类(示例中的接口类是com.anbai.sec.proxy.FileSystem),会通过ProxyGenerator动态生成接口类(FileSystem)的所有方法,

  6. 该类因为实现了代理的接口类,所以当前类是代理的接口类的实例(proxyInstance instanceof FileSystemtrue),但不是代理接口类的实现类的实例(proxyInstance instanceof UnixFileSystemfalse)。

  7. 该类方法中包含了被代理的接口类的所有方法,通过调用动态代理处理类(InvocationHandler)的invoke方法获取方法执行结果。

  8. 该类代理的方式重写了java.lang.Object类的toStringhashCodeequals方法。

  9. 如果过动态代理生成了多个动态代理类,新生成的类名中的0会自增,如com.sun.proxy.$Proxy0/$Proxy1/$Proxy2

0x04 动态代理类实例序列化问题

通过大哥的代码,我们知道,反序列化出来的上面反编译的proxy类,但是实例化的时响应的子类

参考:https://docs.oracle.com/javase/8/docs/technotes/guides/reflection/proxy.html#serial

也就是说动态代理生成的类在序列化的时候不会序列化它的成员变量。

上一篇:翻译day6


下一篇:2021-12-24 Servlet day6文件上传