谈谈代理模式之动态代理(JDK)

一、JDK动态代理实现

动态代理最重要的当然是动态两个字,学习动态代理的过程,最重要的就是理解何为动态,话不多说,马上开整。
我们来明确一点:动态代理解决的问题是面对新的需求时,不需要修改代理对象的代码,只需要新增接口和真实对象,在客户端调用即可完成新的代理。

这样做的目的:

满足软件工程的开闭原则,提高类的可维护性和可扩展性。

二、JDK proxy

JDK ProxyJDK 提供的一个动态代理机制,它涉及到两个核心类,分别是ProxyInvocationHandler,我们先来了解如何使用它们。
在这里插入图片描述

可以看到和静态代理区别不大,唯一的变动是代理对象,我做了标注:由代理工厂生产。

这句话的意思是:代理对象是在程序运行过程中,由代理工厂动态生成,代理对象本身不存在 Java 源文件。

那么,我们的关注点有2个:
1、如何实现一个代理工厂
2、如何通过代理工厂动态生成代理对象
首先,代理工厂需要实现InvocationHanlder接口并实现其invoke()方法。

这里先定义接口和真实对象

UserDao

package cn.kexing.SpringAOP.JDKProxy;

public interface UserDao {
    String getName(String id);
}

真实对象

package cn.kexing.SpringAOP.JDKProxy;

public class UserDaoImpl implements UserDao {
    @Override
    public String getName(String id) {
        System.out.println("张三"+id);
        return null;
    }
}

代理工厂(需要实现InvocationHandler接口)

//UserDao增强方法
//代理工厂
class UserDaoProxy implements InvocationHandler {
    //代理真实的对象
    private Object objectReal;
    UserDaoProxy(Object objectReal){
        this.objectReal = objectReal;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //执行方法之前(增强方法)
        System.out.println("正在执行"+method+"-----"+"传入参数"+ Arrays.toString(args));
        //被增强方法执行
        Object res = method.invoke(objectReal,args);
        //执行方法之后(增强方法)
        System.out.println("增强完毕");
        return res;
    }
}

invoke() 方法有3个参数:

  • Object proxy:代理对象
  • Method method:真正执行的方法
  • Object[] agrs:调用第二个参数method 时传入的参数列表值

invoke() 方法是一个代理方法,也就是说最后客户端请求代理时,执行的就是该方法。代理工厂类到这里为止已经结束了,我们接下来看第二点:如何通过代理工厂动态生成代理对象。

生成代理对象需要用到Proxy类,它可以帮助我们生成任意一个代理对象,里面提供一个静态方法newProxyInstance

 Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h);

实例化代理对象时,需要传入3个参数:

  • ClassLoader loader:加载动态代理类的类加载器
  • Class<?>[] interfaces:代理类实现的接口,可以传入多个接口
  • InvocationHandler h:指定代理类的调用处理程序,即调用接口中的方法时,会找到该代理工厂h,执行invoke()方法

我们在客户端请求代理时,就需要用到上面这个方法。

public class JDKProxy {
    public static void main(String[] args) {
        //实例化一个真实对象
         UserDaoImpl user = new UserDaoImpl();
         //实例化代理工厂,传入真实对象引用控制,控制对其访问
         UserDaoProxy proxy = new UserDaoProxy(user);
         //实例化代理对象,该对象可以代理执行真实对象方法
         UserDao userDao = (UserDao) Proxy.newProxyInstance(user.getClass().getClassLoader(),user.getClass().getInterfaces(),proxy);
         //代理对象执行
         userDao.getName("11111");
    }
}

执行结果和静态代理的结果相同,但二者的思想是不一样的,一个是静态,一个是动态。
在这里插入图片描述

如果现在要新增一个代理对象时,比如我想新增一个帮我跑腿买奶茶的对象,这是如果是静态代理就需要去修改代理对象里面的逻辑,那如果是动态代理只需要两步:

  1. 创建买奶茶的借口
  2. 创建卖奶茶的奶茶店(真实对象)

再回想开闭原则面向扩展开放,面向修改关闭。动态代理正是满足了这一重要原则,在面对功能需求扩展时,只需要关注扩展的部分,不需要修改系统中原有的代码。

三、主要实现源码

下面是jdk动态代理实现的重要源码,老张现在还是一知半解,但还是放在下面供大家参考:

private static final Class<?>[] constructorParams ={ InvocationHandler.class };
public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h) {
    // 获取代理类的 Class 对象
    Class<?> cl = getProxyClass0(loader, intfs);
    // 获取代理对象的显示构造器,参数类型是 InvocationHandler
    final Constructor<?> cons = cl.getConstructor(constructorParams);
    // 反射,通过构造器实例化动态代理对象
    return cons.newInstance(new Object[]{h});
}

我们看到第 6 行获取了一个动态代理对象,那么是如何生成的呢?接着往下看。

private static Class<?> getProxyClass0(ClassLoader loader,
                                       Class<?>... interfaces) {
    // 去代理类对象缓存中获取代理类的 Class 对象
    return proxyClassCache.get(loader, interfaces);
}

发现里面用到一个缓存 proxyClassCache,从结构来看类似于是一个 map 结构,根据类加载器loader和真实对象实现的接口interfaces查找是否有对应的 Class 对象,我们接着往下看 get() 方法。

public V get(K key, P parameter) {
     // 先从缓存中查询是否能根据 key 和 parameter 查询到 Class 对象
     // ...
     // 生成一个代理类
     Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
 }

在 get() 方法中,如果没有从缓存中获取到 Class 对象,则需要利用 subKeyFactory 去实例化一个动态代理对象,而在 Proxy 类中包含一个 ProxyClassFactory 内部类,由它来创建一个动态代理类,所以我们接着去看 ProxyClassFactory 中的 apply() 方法。

private static final class ProxyClassFactory
    implements BiFunction<ClassLoader, Class<?>[], Class<?>> {
    // 非常重要,这就是我们看到的动态代理的对象名前缀!
	private static final String proxyClassNamePrefix = "$Proxy";

    @Override
    public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
        Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
        // 一些状态校验
		
        // 计数器,该计数器记录了当前已经实例化多少个代理对象
        long num = nextUniqueNumber.getAndIncrement();
        // 动态代理对象名拼接!包名 + "$Proxy" + 数字
        String proxyName = proxyPkg + proxyClassNamePrefix + num;

        // 生成字节码文件,返回一个字节数组
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
            proxyName, interfaces, accessFlags);
        try {
            // 利用字节码文件创建该字节码的 Class 类对象
            return defineClass0(loader, proxyName,
                                proxyClassFile, 0, proxyClassFile.length);
        } catch (ClassFormatError e) {
            throw new IllegalArgumentException(e.toString());
        }
    }
}

apply() 方法中注意有两个非常重要的方法:

  • ProxyGenerator.generateProxyClass():它是生成字节码文件的方法,它返回了一个字节数组,字节码文件本质上就是一个字节数组,所以proxyClassFile数组就是一个字节码文件
  • defineClass0():生成字节码文件的 Class 对象,它是一个 native 本地方法,调用操作系统底层的方法创建类对象

动态代理还有一个常用的实现那就是 Cglib动态代理,在后续的学习中继续更新

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值