Java 动态代理学模式习与分析

预备知识:反射、静态代理模式

由于静态代理的缺陷,我们要手动为每一个目标类编写对应的代理类。如果当前系统已经有成百上千个类,这会使得工作量成倍增加。

一、动态代理使用

1. 基本了解
  • 动态代理和静态代理角色一样。
  • 动态代理的代理类是动态生成的,不是我们直接写好的。
  • 动态代理分为两大类:基于接口的动态代理,基于类的动态代理。以下使用的是基于接口的JDK动态代理。

动态代理基本框架图
使用前我们需要大概了解两个类,
Proxy 类和 InvocationHandler 类。

Proxy 类中有一个 newProxyInstance() 方法,来生成一个代理对象。
参数1 loader : 用哪个类加载器去加载代理对象
参数2 interfaces : 动态代理类需要实现的接口
参数3 h : InvocationHandler 类,动态代理方法在执行时,会调用h里面的invoke方法去执行

Proxy.newProxyInstance(this.getClass().getClassLoader(),
	target.getClass().getInterfaces(),
	this);

InvocationHandler 类是处理调用程序用的,我们需要实现 invoke() 方法。

public class ProxyInvocationHandler implements InvocationHandler {
	@Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return null;
    }
}
2. 使用流程

与静态代理一样,需要定义一个接口。

public interface UserService {
    void add();
    void delete();
}

真实对象实现接口方法。

public class UserServiceImpl implements UserService{

    @Override
    public void add() {
        System.out.println("add");
    }

    @Override
    public void delete() {
        System.out.println("delete");
    }
}

创建 ProxyInvocationHandler 类并且实现接口 InvocationHandler。

public class ProxyInvocationHandler implements InvocationHandler {

    //被代理的对象
    private Object target;

	//设置被代理的对象
    public void setTarget(Object target) {
        this.target = target;
    }

    //生成得到代理类
    public Object getProxy() {
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                this);
    }


    //处理代理实例,并返回结果
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //如何调用这个方法的?
        Object result = method.invoke(target, args);

        log(method.getName());
        
        return result;
    }

    //代理对象增加的方法
    private void log(String msg) {
        System.out.println("执行了" + msg + "方法");
    }
}

通过代理调用方法

public class Client {
    public static void main(String[] args) {
        //真实角色
        UserServiceImpl userService = new UserServiceImpl();
        //动态代理角色
        ProxyInvocationHandler pih = new ProxyInvocationHandler();
        //设置代理对象
        pih.setTarget(userService);
        //动态生成代理类
        UserService proxy = (UserService) pih.getProxy();

        proxy.delete();
        proxy.add();
    }
}

运行结果:
在这里插入图片描述
动态代理和静态代理调用的过程:
在这里插入图片描述

二、源码思路

以上有个问题,就是生成的动态代理类,它是怎么调用 ProxyInvocationHandler 类中的 invoke() 方法的呢?

下面简单分析一下,最简单的方法就是使用断点看程序是怎么调用了什么方法。
在这里插入图片描述
直接来到源码重点的地方,发现这里生成了$Proxy0的字节码文件。
在这里插入图片描述
在这里插入图片描述
使用 createProxyClassFile() 方法调用 ProxyGenerator.generateProxyClass() 来生成的字节码文件。

private static void createProxyClassFile() {
    String name = "ProxyTest";

    byte[] data = ProxyGenerator.generateProxyClass(name, new Class[]{UserService.class});
    FileOutputStream out = null;
    try {
        out = new FileOutputStream(name + ".class");
        out.write(data);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (null != out) try {
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

打开生成的 ProxyTest.class 文件,我们只需要关注这里的 add 方法。

public final class ProxyTest extends Proxy implements UserService {

	...
	
    public final void add() throws  {
        try {
            super.h.invoke(this, m3, (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"));
            m6 = Class.forName("com.test.proxy.demo1.UserService").getMethod("query");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.test.proxy.demo1.UserService").getMethod("add");
            m5 = Class.forName("com.test.proxy.demo1.UserService").getMethod("delete");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            m4 = Class.forName("com.test.proxy.demo1.UserService").getMethod("update");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

注意看重点的方法

super.h.invoke(this, m3, (Object[])null);

这里的属性 h 是父类 Proxy 类中的 protected InvocationHandler h; 并且这个属性是我们在调用 Proxy.newProxyInstance() 方法时传入的参数,也就是ProxyInvocationHandler 类的对象。
在这里插入图片描述
所以这里调用的 invoke 方法就正是我们 ProxyInvocationHandler 类内部的方法。
在这里插入图片描述
也正是因为这样,所以程序才可以在调用实体的方法时,调用ProxyInvocationHandler 类内部的 invoke() 方法。

动态代理创建对象的过程:
在这里插入图片描述

三、总结

动态代理的好处:

  • 可以使真实角色的操作更加纯粹,不用去关注一些公共的业务
  • 公共也就就交给代理角色,实现了业务的分工
  • 公共业务发生扩展的时候,方便集中管理
  • 一个动态代理类代理的是一个接口,一般就是对应的一类业务
  • 一个动态代理类可以代理多个类,只要是实现了同一个接口即可

参考文章:
Java 动态代理作用是什么?——bravo1988
通俗易懂的23种设计模式教学 ——遇见狂神说
Java JDK 动态代理(AOP)使用及实现原理分析 ——衣舞晨风

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值