深入理解JDK动态代理?

本文深入探讨了Java动态代理的概念,通过对比静态代理阐述其优势。动态代理主要解决了在不修改已有类的情况下,对类的功能进行扩展的问题。文中详细讲解了如何实现InvocationHandler接口并利用Proxy.newProxyInstance()生成代理类,以及代理类的内部工作原理。最后,通过代码示例展示了如何优化工具类,提高代码复用性。
摘要由CSDN通过智能技术生成

简单理解动态代理

动态代理其实可以看成两部分组成,动态代理,动态体现在自动生成,不用我们自己操心,代理则是找个人帮你做完事,也不用自己操心。

在日常开发程序中,其实存在大部分类是不能够轻易修改,也不敢改,但该类确实已无法满足现在的功能,那么怎么样进行功能增强呢?这里我们使用到动态代理模式,降低代码耦合度。

 

什么是静态代理?

有动态代理自然而然少不了静态代理,这里我们先演示下什么静态代理,对于动态代理就非常的好理解了。

场景演示:

先编写对库存进行减少操作的实现类

public interface IStockService {
    void reduce();
}

public class StockServiceImpl implements IStockService {
    @Override
    public void reduce() {
        System.out.println("reduce stock...");
    }
}

上述代码我们可以当做是日常中已经编写很久的类(不好修改的类),现在要在reduce功能前后加上日志输出,这里不推荐继承,直接使用 组合方式 ,接着定义好一个新类,既然要进行功能增强,肯定就要去实现这个接口IStockService 提供的原有所有的功能,然后在在原有功能上扩展我们自己的新功能;定义好该类就可以看成代理类,这也就是我们所述的静态代理模式

提示:若结合工厂模式会有奇效

public class StockLogServiceProxy implements IStockService {

    //将存在很久的类IStockService通过组合方式传入
    private IStockService stockService;

    public StockLogServiceProxy (IStockService stockService) {
        this.stockService = stockService;
    }

    public void reduce() {
        System.out.println("reduce before info...");
        stockService.reduce();
        System.out.println("reduce after info...");
    }
}

public class StockTest {
    public static void main(String[] args) {
        IStockService stockService = new StockServiceImpl();

		//组合方式传入我们原来的库存对象
        StockLogServiceLProxy proxy = new StockLogServiceLProxy(stockService);

		//库存原始方法
        //stockService.reduce();
        
        //调用代理类方法
        proxy.reduce();
    }
}

从输出结果可以看出此功能已经okay

reduce before info...
reduce stock...
reduce after info...

但是随着时间推移,现在又要实现一个数据校验功能, 因为在扣减库存之前肯定是要判断数据是否正确。检验完之后=>日志输出=>扣减库存。于是我们又是与同样的方式再新定义个有检验功能的代理类

public class StockVerfiyServiceProxy implements IStockService {

	//依然是通过组合注入原始类
    private IStockService stockService;

    public StockVerfiyServiceProxy(IStockService stockService) {
        this.stockService = stockService;
    }

    @Override
    public void reduce() {
        System.out.println("reduce verify before...");
        stockService.reduce();
        System.out.println("reduce verify after...");
    }
}

测试结果发现也已经okay,但是如果每次来一个新功能都这样去定义一个新的代理类,这样做很不友好,从而衍生出了动态代理,即自动帮我们生成XxxProxy代理类,然后我们只需要写新功能即可。

reduce verify before...
reduce before info...
reduce stock...
reduce after info...
reduce verify after...

 

动态代理模式

上述的StockLogServiceLProxyStockVerfiyServiceProxy都是自定义代理类,但因为每有一个新功能来就要写一个代理类很不友好,java中提供了一种 动态代理模式,自动帮我们生成代理类,而我们只需要专注于写新功能实现

实现InvocationHandler接口

因为代理类不需自己写,但需要实现的新功能需写上,那么写在哪呢?jdk动态代理提供了一个接口InvocationHandler,实现该接口,然后将需要被代理的接口通过组合方式注入,接着调用invoke方法即可

  • 实现接口InvocationHandler
  • 注入被代理接口(原始类)
  • 调用invoke方法

下面对reduce方法添加日志输出功能、校验功能,总之你之后新功能都可以往这里写

//第一步:实现InvocationHandler 接口
public class StockServiceHandler implements InvocationHandler {

    //第二步:通过组合方式注入进来
    //IStockService是被代理的接口,reduce方式需要被增强
    private IStockService target;

    public StockServiceHandler(IStockService target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

		//...新功能
		System.out.println("reduce verify before info...");
        System.out.println("reduce before info...");
        
        //第三步:调用invoke方法,前后输出语句表示reduce方法的日志输出功能
        Object invoke = method.invoke(target);
        
        //....新功能
        System.out.println("reduce after info...");
        System.out.println("reduce verify after info...");
        return invoke;
    }
}

 

构建代理类

上述我们的日志、检验代理类不用手写,可以通过java提供方法自动生成,方法如下:

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

可以看到需要传入三个参数,类加载器、Clazz接口、上述的InvocationHandler实现类,下面先演示怎么生成,然后在具体分析这三个参数

public class StockTest {
    public static void main(String[] args) {
        
        IStockService stockService = new StockServiceImpl();
        
        //这里生成的并不是接口IStockService,而是新的代理类$Proxy0,该代理类加强了IStockService接口中的reduce方法
        IStockService iStockService = (IStockService)Proxy.newProxyInstance(
    	    IStockService.class.getClassLoader(), //类加载器
        	new Class[]{IStockService.class},//Clazz接口
	        new StockServiceHandler(stockService));//InvocationHandler 功能实现类
        
        //调用代理类生成的reduce方法,也可以说是继承接口IStockService的reduce方法,角度不一样理解不一样
        //调用reduce方法,reduce里面会调用invoke方法
        iStockService.reduce();
    }
}

运行结果如下所示和上述静态代理模式一样

reduce verify before info...
reduce before info...
reduce stock...
reduce after info...
reduce verify after info...

 

反编译代理类

虽然我们不用再手写代理类了,但是我们还是想到知道代理类中发生了啥,通过JDK-GUI反编译得到$Proxy0代理类的结构,这里只需要关注两个方法,$Proxy0构造方法、IStockService接口定义的方法,通过代码将生成的代理类存储到本地磁盘

import mapper.impl.StockLogServiceLProxy;
import sun.misc.ProxyGenerator;

import java.io.FileOutputStream;
import java.io.IOException;

/**
 * 代理类的生成工具
 *
 * @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("$Proxy0", StockLogServiceLProxy.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("D:/$Proxy0.class");
    }
}

通过代码可以发现代理类自动实现了接口IStockService(被代理接口),然后通过super调用父类Proxy构造方法注入我们自定义的InvocationHandler实现类,然后覆写reduce方法,reduce方法里面调用我们在InvocationHandler类中实现的invoke方法,看完这段描述,接着看代码里面的注释

import mapper.*;
import java.lang.reflect.*;

public final class $Proxy0 extends Proxy implements IStockService
{
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;
    
    //Proxy0的构造方法是通过调用父类Proxy构造方法完成InvocationHandler的注入
    //那么怎么注入InvocationHandler的实现类呢?待会后面会说到
    public $Proxy0(final InvocationHandler invocationHandler) {
        super(invocationHandler);
    }

    //覆写了IStockService接口中的reduce方法,然后里面通过调用我们实现的InvocationHandler子类实现功能的增强
    //这就是为什么在spring、mybatis中为什么在找一个方法被谁调用的时,发现这个方法死活找不到调用的地方,其实这个方法
    //的调用实在自动生成的代理类中被调用,而我们debuge的时候是看不到这个调用的
    //但是我们知道了动态代理之后,我们就应该明白代理类调用的方法是被加强过的方法,不是你之前那个原始方法
    public final void reduce() {
        try {
            super.h.invoke(this, $Proxy0.m3, null);
        }
        catch (Error | RuntimeException error) {
            throw;
        }
        catch (Throwable t) {
            throw new UndeclaredThrowableException(t);
        }
    }
    
    //下面所有方法不用关心    
    public final boolean equals(final Object o) {
      return (boolean)super.h.invoke(this, $Proxy0.m1, new Object[]);
    }
    
    public final String toString() {
      return (String)super.h.invoke(this, $Proxy0.m2, null);
    }
    
    public final int hashCode() {
      return (int)super.h.invoke(this, $Proxy0.m0, null);
    }
    
    static {
       $Proxy0.m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
       $Proxy0.m2 = Class.forName("java.lang.Object").getMethod("toString", (Class<?>[])new Class[0]);
       $Proxy0.m3 = Class.forName("mapper.IStockService").getMethod("reduce", (Class<?>[])new Class[0]);
       $Proxy0.m0 = Class.forName("java.lang.Object").getMethod("hashCode", (Class<?>[])new Class[0]);
    }
}

看完这个反编译之后的代理类,可以总结一点就是:自己会去实现接口,然后覆写接口中的方法,然后覆写的方法中再去调用我们自定义的invoke方法即可,流程图如下所示:
在这里插入图片描述
 

Proxy类

$Proxy0名字的由来?

点进Proxy.newProxyInstance方法查看源码,会看到一行代码

Class<?> cl = getProxyClass0(loader, intfs);

然后继续跟踪进入内部实现会发现apply方法,然后进入ProxyClassFactory类找到apply方法,清晰可以看见一行代码

  • proxyPkg 表示全包名
  • proxyClassNamePrefix 写死的字符串常量$Proxy
  • num 是自增的长整型数

所以可以知道$Proxy0这个代理类的名字是怎么来的

long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg + proxyClassNamePrefix + num;

紧接着下面一行通过ProxyGenerator来加载代理类文件,然后在通过defineClass0定义$Proxy0代理类,自始至终,我们的代理类Clazz就已经自动生成完成(有了字节码文件就可以做任何事情了),至此getProxyClass0生成字节码的方法就已经结束,接下来就是生成代理来对象的过程,那么问题来了?

我们自己定义的InvocationHandler实现类什么时候被注入进去的呢?下面开始会说到

byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
            try {
                return defineClass0(loader, proxyName,
                                    proxyClassFile, 0, proxyClassFile.length);

 

注入InvocationHandler实现类

前面已经通过方法把代理类的字节码文件都给生成好了,下面就是开始怎么样生成代理类对象的过程?我们回到Proxy.newProxyInstance方法中,紧接着后面一行代码

return cons.newInstance(new Object[]{h});

cons是代理类的Clazz对象,newInstance方法把我们自定义的InvocationHandler实现类给注入进来,在继续进入内部实现, 发现是通过反射有参构造方法进行new代理类实例

此处ca是代理类,前面通过反编译知道代理类中$Proxy0有参构造方法就是需要传入InvocationHandler实现类,就是在这一行代码实现的,所以InvocationHandler实现类的注入是在代理类通过反射有参构造方法中注入的

T inst = (T) ca.newInstance(initargs);
return inst;

 

解析newProxyInstance()三大参数

前面Proxy.newProxyInstance方法参数如下:

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

参数一:ClassLoader loader 类加载器

       所有的类被加载都是需要通过类加载进行引导加载进入内存,然而我们动态代理中代理类是在运行时生成的,所以这里也就是为什么需要传入一个类加载器的原因

       大部分情况下类加载器只需要注入接口的类加载即可,如果不填接口的也行,我这里测试发现只要在你的根目录(classes文件夹)下面能够找到的类就都可以,最好不要写那种敏感包下的类(javajavaxsun等核心包)
 

参数二:Class[] interfaces Clazz接口数组

只要传入接口对象即可:被代理接口的Clazz文件,可以通过两种方式:

方式一:new Class[]{} 方式

数组中传入接口的Clazz文件,一定要是接口,否则会报错说不是一个interface异常

public class StockTest {
    public static void main(String[] args) {

        IStockService stockService = new StockServiceImpl();

        IStockService iStockService = (IStockService)Proxy.newProxyInstance(
                                        A.class.getClassLoader(),
                                        new Class[]{IStockService.class},
                                        new StockServiceHandler(stockService));

        iStockService.reduce();
    }
}
方式二:getClass() 方式

getClass()方法是一个实例方法,那么必然是由对象才可以调用,所以得先要有对象才可以调用此方法,这个对象我们可以使用被代理类的对象即可,如果是通过getClass()方法,配合getInterfaces()方法一起使用

    public final native Class<?> getClass();
public class StockTest {
    public static void main(String[] args) {

        IStockService stockService = new StockServiceImpl();

        IStockService iStockService = (IStockService)Proxy.newProxyInstance(
                                        A.class.getClassLoader(),
                                        stockService.getClass().getInterfaces(),
                                        new StockServiceHandler(stockService));

        iStockService.reduce();
    }
}

在这里需要注意方法getInterfaces()的使用,查看JDK1.8API,返回值是包含表示该类实现的所有接口的对象的数组,这句话值得品味,举个反例,代码如下:

public class StockTest {
    public static void main(String[] args) {

        IStockService stockService = new StockServiceImpl();

        IStockService iStockService = (IStockService)Proxy.newProxyInstance(
                                        A.class.getClassLoader(),
                                        IStockService.class.getInterfaces(),
                                        new StockServiceHandler(stockService));

        iStockService.reduce();
    }
}

执行抛出异常:

Exception in thread "main" java.lang.ClassCastException: com.sun.proxy.$Proxy0 cannot be cast to mapper.IStockService
	at mapper.StockTest.main(StockTest.java:14)

因为通过IStockService本身就一个接口,上面已经没有接口,所以getInterfaces()获取到一个数组长度为0的数组,长度为0,其实压根就是没有获取到值,自然而然底层反射也不会成功。反正你最终给到newProxyInstance()方法就是一个接口数组里面包含着所有接口的Clazz

▶推荐这个参数使用自己 new数组方式 (new Class[]{IStockService.class}),绝对不会报错。
 

参数三:InvocationHandler 功能实现类

InvocationHandler接口就是给我们自定义功能的一个规范,抽象功能的接口,实现invoke方法即可

 

代码优化

Proxy.newProxyInstance()这个方法调用其实是一个公用部分,我们可以将其抽取出一个共有工具类,专门用于生成代理类

public class StockTest {
    public static void main(String[] args) {

        IStockService stockService = new StockServiceImpl();
        
        IStockService proxyInstance = getProxyInstance(IStockService.class, new StockServiceHandler(stockService));

        proxyInstance.reduce();
    }

    public static <T> T getProxyInstance(Class<T> clazz, InvocationHandler handler) {
        return (T)Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, handler);
    }
}

InvocationHandler 也可以定义成泛型,用于多个接口使用,做成通用的工具类,大部分情况下是一个接口有很多的实现类,不写泛型也可以

package mapper.impl;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class StockServiceHandler<T> implements InvocationHandler {

    private T target;

    public StockServiceHandler(T target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        System.out.println("reduce verify before info...");
        System.out.println("reduce before info...");
        Object invoke = method.invoke(target);
        System.out.println("reduce after info...");
        System.out.println("reduce verify after info...");
        return invoke;
    }
}

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

魔道不误砍柴功

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值