Java代理源码分析——JDK代理(Proxy、InvocationHandler与示例)

本文深入剖析了Java动态代理的实现,重点讲解了Proxy类和InvocationHandler接口的作用及原理。Proxy类通过缓存机制高效生成代理类,而InvocationHandler接口作为调用处理器,实现了方法调用的增强。示例展示了如何创建并使用代理对象,动态代理机制在不修改原始对象代码的基础上,实现了功能扩展。
摘要由CSDN通过智能技术生成

前言

  • Java的代理机制使得我们可以在不修改原始的Java对象的代码的情况下,对原始的Java对象进行功能的增强。
  • Java的代理机制包括了静态代理和动态代理,静态代理需要我们为每个需要被代理的对象编写对应的代理类,当被代理对象较多时,使用静态代理就不太合适了,而动态代理使得我们可以把需要被代理的对象作为参数,传递给用于生成代理对象的方法,并在运行时动态地创建代理对象。
  • 动态代理又包括了使用JDK api实现的JDK代理和使用cglib包实现的cglib代理,JDK代理需要被代理对象实现了接口,通过invocationHandler调用原对象方法实现代理,而cglib不需要被代理对象实现接口,通过继承原对象并重写字节码的方式实现代理。
  • 本文介绍了JDK代理的机制以及简单的使用示例。
  • JDK代理的核心包括Proxy类和InvocationHandler接口,如图为Proxy类的UML图。
  • 源码较多,建议自行搭配源码学习!
    java.lang.reflect.Proxy UML图

1 核心——Proxy类

1.1 变量/域

1.1.1 proxyClassCache

  • 类型:WeakCache<ClassLoader, Class<?>[], Class<?>>
  • 目的:缓存生成的代理类的class对象的工厂对象,并通过缓存的工厂对象产生代理类的class对象。
  • 原理:在Proxy调用newProxyInstance方法时,会首先在缓存中查找是否存在对应代理类的class对象,若找到了则可以直接利用该class对象,生成并返回新的代理对象;否则会利用ProxyClassFactory创建新的代理类的class对象(并放入缓存),生成并返回新的代理对象。proxyClassCache通过一个静态的WeakCache缓存实现,WeakCache通过嵌套的concurrentMap实现二级缓存:一级缓存的key(key)是将传入的ClassLoader包装而生成的CacheKey(继承于WeakReference,见下);二级缓存(valuesMap)的key(subkey)是将传入的接口class对象数组通过KeyFactory(见1.3.1)生成的,而二级缓存的value是一个实现了supplier接口(见下)的对象。该对象是一个用于产生目标代理类class对象的工厂,对该对象调用get方法来获取真正的代理类的class对象。此外,在WeakCache中使用到了弱引用类WeakReference和一个弱引用队列ReferenceQueue解决concurrentMap的垃圾回收问题。
  • supplier接口:泛型函数式接口,通过重写get方法,可以返回一个对象。
  • 为什么要使用WeakReference和ReferenceQueue:要解决这个问题,首先需要知道什么是强引用和弱引用。以本例来说,当我们直接向concurrentMap中存放一个键值对时,我们向map中添加了三个对象的引用,键值对的entry(node),key和value,引用默认是强引用,意味着当我们没有显式地从map中remove(key)时,这三个对象是不会被垃圾回收的。因此如果程序长时间运行而没有显式remove,map中加入了越来越多的对象,就可能造成性能问题。与强引用不同的是,如果一个对象只存在弱引用,那么它在下一次垃圾回收时,就会被回收。因此,在这里使用了WeakReference弱引用包装类和ReferenceQueue弱引用队列来解决对象回收的问题。具体的做法是,将map的key包装到WeakReference中,实现key到其他对象的弱引用,但是如果只有key使用了弱引用是不够的,因为就算key被垃圾回收了,entry和value仍然没有被回收。利用ReferenceQueue可以解决这个问题,WeakReference在创建的时候需要两个参数,一个是被包装的key,另一个则是一个指定的ReferenceQueue,key被注册到该ReferenceQueue中。当key被回收时,会向该ReferenceQueue中加入这个key。这时会产生一个看似矛盾的问题,为什么想要key被回收,但是又要在它被回收的时候加入队列中?这是因为在key被回收时,我们还需要通过它去清除map中的整个entry对象。在本例中,concurrentMap每次调用get方法时,都会执行一次清除工作,去清除存在于自身中且与ReferenceQueue中的key对应的entry,key和value,这样就可以及时清理掉一级缓存concurrentMap中过期的对象。

1.2 方法

1.2.1 newProxyInstance(核心)

  • 目的:获取新的代理对象。
  • 原理:根据传入的类加载器、接口class对象的数组和invocationHandler实现类,从proxyClassCache中获取一个实现了传入的接口方法的代理类的class对象,随后通过反射的方式获取class对象中的constructor,调用newInstance方法生成代理对象实例并返回。

1.2.2 getProxyClass

  • 目的:从proxyClassCache中根据类加载器和接口class对象的数组返回代理类的class对象(比newProxyInstance少一个通过反射生成代理对象的步骤)。

1.2.3 defineClass0(核心)

  • 目的:生成新的代理类的class对象。
  • 原理:defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length),通过方法生成的新的代理类的class对象中,成员变量/实例域中包含了指定的接口中的所有方法的method对象和后期通过构造方法传入的invocationHandler实现类,成员方法中实现了指定的接口中的所有方法。在外部调用代理对象的方法时,这些方法内部实际上是通过代理对象中的invocationHandler,转而调用invoke(Object proxy, Method method, Object[] args),其中proxy代表代理对象自身this,method是代理对象方法对应的成员变量method对象,args是method所需参数。这里可能有些抽象,可以看 3.4 中的示例就明白了。

1.3 内部类

1.3.1 KeyFactory

  • 目的:生成proxyClassCache中二级缓存(valuesMap)的键,该键标识了传入的一组class对象
  • 原理:KeyFactory是BiFunction接口的实现类(泛型函数式接口,通过重写apply方法,可以传入两个对象,运算然后返回一个结果对象),泛型参数为BiFunction<ClassLoader, Class<?>[], Object>,表示可以传入一个类加载器和一个接口class对象的数组,通过apply方法生成一个Key?对象(见1.3.3),并返回。KeyFactory实际上并没有调用classLoader,仅仅通过class<?>[]的长度判断返回哪一种Key。

1.3.2 ProxyClassFactory

  • 目的:生成新的代理类的class对象。
  • 原理:ProxyClassFactory是BiFunction接口的实现类,泛型参数为BiFunction<ClassLoader, Class<?>[], Class<?>>,表示可以传入一个类加载器和一个接口class对象的数组,通过apply方法生成一个代理类的class对象,并返回。之所以它可以生成代理类的class对象,也是因为在apply方法中调用了native的defineClass0方法。apply方法中包括了生成代理类class对象的一些预处理,如检查传入的class对象数组中的class对象是否被类加载器可见、是否是接口、生成新的代理类的类名proxyName等,最后调用了defineClass0方法来生成新的代理类的class对象,该class对象随后用于生成代理类实例。ProxyClassFactory的apply方法在proxyClassCache的二级缓存的value——工厂对象调用get方法时调用,表示通过该工厂对象返回代理类的class对象。

1.3.3 Key1,Key2,KeyX

  • 目的:是proxyClassCache二级缓存的键,具体类型由KeyFactory选择性生成。
  • 原理:KeyFactory在生成Key?对象时,这个对象的实际类型由传入的接口class对象的数组大小决定,数组中大于2个class对象时生成KeyX类的对象/实例,而只有1个(最常见)或2个class对象时分别生成Key1类和Key2类的对象,这样做是因为针对后两种的情况进行了代码的优化。

2 核心——InvocationHandler接口

2.1 目的

通过InvocationHandler的实现类,实现了使得代理对象在调用接口方法时,转而执行增强的、代理版的方法——invoke(Object proxy, Method method, Object[] args)。InvocationHandler的实现类可以看做代理对象方法调用的调用处理器,或调用转发器。

2.1 原理

该接口的实现类相当于一个方法调用处理器,在Proxy.newProxyInstance调用时被传入到Proxy对象中。随后,在Proxy产生了代理类的class对象,并通过反射生成代理对象时,被传入到代理对象中。因此,代理对象调用任何方法时,都会使用invocationHandler执行定义的invoke方法。可以看到真正的增强功能的逻辑(切面)是在invoke方法中定义的。

3 示例

3.1 InvocationHandler实现类

class InvocationHandlerImpl implements InvocationHandler {
    Object targetObject;

    public InvocationHandlerImpl(Object targetObject) {
        this.targetObject = targetObject;
    }
	
	// 由3.4 可知,proxy代表代理对象本身(此处没有使用),method代表被代理接口中当前被调用的method对象,args为method对象参数
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // your code before method invoked
        System.out.println("start to do something..");
        Object result = method.invoke(targetObject, args);
        // your code after method invoked
        System.out.println("end to do something..");
        return result;
    }
}

3.2 被代理的接口

interface Person {
    void eat();

    void drink();
}

3.3 main方法中创建接口的代理对象

public static void main(String[] args) {
		/*
		由于JDK代理只能代理实现了接口的对象,这里直接使用匿名内部类创建了被代理的对象
		将对象传入invocationHandler实现类中,后者调用invoke方法时,可以通过method反射调用到被代理对象的方法
		*/
        Person person = new Person() {
            @Override
            public void eat() {
                System.out.println("eating...");
            }

            @Override
            public void drink() {
                System.out.println("drinking...");
            }
        };
        Person proxyPerson = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class[]{Person.class}, new InvocationHandlerImpl(person));
        proxyPerson.eat();
        // 打印 start to do something..
		//      eating...
  		//      end to do something..
    }

3.4 Proxy中自动创建的代理类class对象(简要)

public class $Proxy0 {
    InvocationHandler invocationHandler;
    Method originalEat = xxx; // 此处为被代理接口中原方法的method对象,调用处理器调用invoke方法时会用到
    Method originalDrink = xxx;
	
	// 通过构造注入调用处理器
	public $Proxy0(InvocationHandler invocationHandler){
		this.invocationHandler = invocationHandler;
	}
	
	// 实现了接口方法,并在每个方法中通过invocationHandler调用增强的方法
    public void eat() throws Throwable {
        invocationHandler.invoke(this, originalEat, null);
    }

    public void drink() throws Throwable {
        invocationHandler.invoke(this, originalDrink, null);
    }
}

参考

  • https://blog.csdn.net/jiankunking/article/details/52143504
  • https://www.cnblogs.com/liuyun1995/p/8144676.html
  • https://zhuanlan.zhihu.com/p/88759564
  • https://segmentfault.com/a/1190000011291179
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值