Java动态代理—JDK DynamicProxy

Posted Moon&&Dragon

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java动态代理—JDK DynamicProxy相关的知识,希望对你有一定的参考价值。

动态代理—JDK DynamicProxy

1 概念

代理的概念,在静态代理中已经有写到,那么理解了静态代理后,静态代理有什么弊端呢?其实也是显而易见的,当业务逻辑代码需要添加前置或者后置功能时,我们就需要去书写一个静态代理类,如果业务代码很多,那么代理类又是翻上一番,就会造成代码的臃肿,也会增加我们的重复劳动。那么,这时候,动态代理就来了。

动态代理的实现有俩种方式,可以通过JDK实现动态代理,也可以使用cglib来实现动态代理。动态代理解决了静态代理需要写很多类的弊端。

2 实现动态代理

使用JDK实现动态代理,需要了解俩个核心类:

  • Proxy: 该类就是JDK提供的用来创建一个动态代理的类,它有很多方法,其中最常用的就是newProxyInstance()
  • InvocationHandler: 这个是一个接口,我们需要来实现这个接口,重写里面的invoke()方法,这个方法就是去处理我们需要代理类的方法。

2.1 前期准备entity类

结婚接口:

public interface Marry {
    /**
     * 结婚方法
     */
    void marry();
}

人类接口:

public interface Person extends Marry{
    /**
     * 说话方法
     */
    void talk();
}

男人类:

public class Man implements Person{
    /**
     * 姓名
     */
    private String name;
    
    public void setName(String name) {
        this.name = name;
    }

    /**
     * 重写结婚方法
     */
    @Override
    public void marry() {
        System.out.println(name+"在结婚");
    }

    /**
     * 重写说话方法
     */
    @Override
    public void talk() {
        System.out.println(name+"在说话");
    }
}

2.2 实现InvocationHandler接口

我们创建一个类,来实现InvocationHandler接口,该类的核心方法就是重写的invoke方法,在代理对象中会调用该方法。

/**
 * @author 晓龙
 * @version 1.8.271
 * @ProtectName ProxyDemo
 * @ClassName ProxyHandler
 * @Description 动态代理处理器
 * @createTime 2021年05月23日 12:52:00
 */
public class ProxyHandler implements InvocationHandler {
    // 代理的对象
    private Person person;
    // 代理增强方法名
    private String methodName = null;

    public void setPerson(Person person) {
        this.person = person;
    }

    public void setMethodName(String methodName) {
        this.methodName = methodName;
    }

    /**
     * @param proxy  代理的对象
     * @param method 代理对象的方法,默认的话会把所有方法都代理
     * @param args   对象参数
     * @return 返回值
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object invoke;
        if (methodName != null) {
            // 判断代理方法名
            if (methodName.equals(method.getName()) || "*".equals(methodName)) {
                // 前置方法
                beforeMarry();
                // 执行,拿到返回值
                invoke = method.invoke(person, args);
                // 后置方法
                afterMarry();
            } else {
                invoke = method.invoke(person, args);
            }
        } else {
            System.out.println("请设置方法");
            invoke = method.invoke(person, args);
        }
        return invoke;
    }

    /**
     * 前置方法
     */
    private void beforeMarry() {
        System.out.println("结婚前准备");
    }

    /**
     * 后置方法
     */
    private void afterMarry() {
        System.out.println("结婚后准备");
    }
}

2.3 书写动态代理工具类

该类其实也可以不要,直接在上面实现InvocationHandler接口的类中书写代理方法,这里为了方便理解,就分开来写。

/**
 * @author 晓龙
 * @version 1.8.271
 * @ProtectName ProxyDemo
 * @ClassName DynamicProxy
 * @Description 动态代理工具类
 * @createTime 2021年05月23日 13:01:00
 */
public class DynamicProxy {
    private DynamicProxy() {
    }

    /**
     * 获取动态代理对象
     *
     * @param clazz      类
     * @param person     代理对象
     * @param methodName 代理对象的方法
     * @return 代理对象
     */
    public static Person getProxyObject(Class<?> clazz, Person person, String methodName) {
        // 实例代理调度器
        ProxyHandler proxyHandler = new ProxyHandler();
        // 设置代理对象
        proxyHandler.setPerson(person);
        // 设置代理方法名
        proxyHandler.setMethodName(methodName);
        // 返回jdk动态代理对象
        // 本质:会去修改obj代理对象的class文件,存在内存中,在该class文件中会去调用调度器的invoke方法
        return (Person) Proxy.newProxyInstance(clazz.getClassLoader(), person.getClass().getInterfaces(), proxyHandler);
    }
}

2.4 测试类

这里测试类对代理前对象和代理后对象进行了比较,因为动态代理默认的话会把类中所有的方法进行代理处理,所以在invoke方法中也简单的设置了方法名判断,这里也进行比较。

/**
 * @author 晓龙
 * @version 1.8.271
 * @ProtectName ProxyDemo
 * @ClassName Test
 * @Description 测试JDK动态代理
 * @createTime 2021年05月23日 12:59:00
 */
public class Test {
    public static void main(String[] args) {
        // 正常的对象
        Man man = new Man();
        man.setName("jack");
        // 正常结婚
        System.out.println("=========正常结婚============");
        man.marry();
        // 代理对象和增强方法名
        System.out.println("=========代理增强结婚=========");
        Person proxyMan =DynamicProxy.getProxyObject(Test.class, man,"marry");
        // 代理对象结婚---该方法被代理
        proxyMan.marry();
        // 代理对象其他方法----没有被代理
        System.out.println("=========代理未增强============");
        proxyMan.talk();
    }
}

2.5 结果

在这里插入图片描述

3 测试动态代理底层是如何实现的

3.1 动态代理是怎么实现的

其实动态代理底层的实现很好理解,在我们debug的时候就可以发现,我们正常的对象已经被变成了Proxy#的对象。在Proxy类中,其实是把我们的类的class文件进行了修改,然后再去通过新的class实例对象,返回给我们。

  • 代理名字的由来:

在这里插入图片描述

Proxy类

// 下面这些代码是Proxy类中的源码,我们可以看出代理类的名字来由
private static final String proxyClassNamePrefix = "$Proxy";
...
// 如果包名是空的话,也是有默认包路径,也是在反射工具类中定义好了
if (proxyPkg == null) {
  proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}
long num = nextUniqueNumber.getAndIncrement();
// 这里就是代理类的名字,是有代理类的包+$Proxy+数字组成
String proxyName = proxyPkg + proxyClassNamePrefix + num;

ReflectUtil类

public static final String PROXY_PACKAGE = "com.sun.proxy";
  • 修改class类

    知道了名字的由来,继续跟着往下看,会发现,创造出了一个byte数组

    Proxy类

    byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);
    

    通过ProxyGenerator.generateProxyClass()方法,把代理名和接口等信息传入后,生成了一个byte数组,那么这个数组是什么呢?其实这个数组就是新的class文件,也就是代理后的class文件。

    有了新的class文件,那么返回的对象也就很简单了。

3.2 为什么JDK动态代理对象必须有接口?

在使用JDK动态代理的时候,我们会发现,我们想要代理一个类的时候,我们必须要确认这个类有接口,我们只能去代理接口,这是为什么呢。通过下面一个小测试就可以很清晰的了解到了。

3.2.1 获得代理对象的class文件

动态代理的底层是去生产一个新的类文件,但是这个类文件是存在在内存中,我们无法直观的看到这个类文件做了什么事情。那么我们通过IO把生成的类文件写到磁盘来进行查看。

IO写出class文件

/**
 * @author 晓龙
 * @version 1.8.271
 * @ProtectName ProxyDemo
 * @ClassName WriteClass
 * @Description 写出代理生成class文件
 * @createTime 2021年05月23日 16:08:00
 */
public class WriteClass {
    public static void main(String[] args) throws Exception {
        // 正常的对象
        Man man = new Man();
        man.setName("jack");
        // 通过和Proxy类中一样的方法,模拟一个class文件的生成
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass("Jack",man.getClass().getInterfaces(),1);
        // 通过IO写出到磁盘
        FileOutputStream out = new FileOutputStream("/Users/xiaolong/Desktop/Jack.class");
        out.write(proxyClassFile);
        out.flush();
        out.close();
    }
}

运行上述代码后,我们在桌面发现了生成的class文件,但是该class文件我们是无法直接打开,那么我们可以通过IDEA的反编译功能进行查看,我们把它拷贝到项目的target目录下。

在这里插入图片描述

3.2.2 分析class文件

我们打开该class文件

import com.moon.dynamicProxy.pojo.Person;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

/*这里的代理对象是默认继承了Proxy类,然后去实现我们的接口,因为java的单继承多实现,继承已经被Proxy占用,那么我们必须要使用接口*/
public class Jack extends Proxy implements Person {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;
    private static Method m4;

    public Jack(InvocationHandler var1) throws  {
        super(var1);
    }

		......
      
    // 这里主要看我们的代理的方法
    public final void marry() throws  {
        try {
            /* 
            我们发现我们的代理方法默认会去调用父类的h对象的invoke方法,并且把原来的方法m4传进去了
          	那么这个super.h.invoke()是什么?在Proxy类中有这样的一段属性
          	protected InvocationHandler h;
          	在我们调用Proxy类的newProxyInstance()方法时,我们传入的参数是
          	public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h){...}
            这里我们在最后传入了我们实现的InvocationHandler接口类,所以说,这里调用的父类的h的invoke方							法,其实就是调用了我们写的invoke方法。
          	*/
            super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            // 获得原来的方法
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.moon.dynamicProxy.pojo.Person").getMethod("talk");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            m4 = Class.forName("com.moon.dynamicProxy.pojo.Person").getMethod("marry");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

以上是关于Java动态代理—JDK DynamicProxy的主要内容,如果未能解决你的问题,请参考以下文章

Java动态代理—CGLIB DynamicProxy

静态代理和动态代理原理及实现

设计模式 - 动态代理模式

Java的动态代理(dynamic proxy)

Java的动态代理(dynamic proxy)

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