JDK动态代理基本流程

784 阅读2分钟

参考自 java-dynamic-proxies

1. Introduction

动态代理允许一个仅有一个方法的类去服务于有着任意方法数量的其他类,进行多次方法调用。

一个方法代理可以被认为是一种切面,也可以被认为是一个接口的实现。

在这种覆盖下,所有的方法调用都会被导向一个单独的handler方法 —— invoke()方法

2. Invocation Handler

首先, 创建java.lang.reflect.InvocationHandler接口的实现类:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class DynamicInvocationHandler implements InvocationHandler {

    private static Logger logger = LoggerFactory.getLogger(DynamicInvocationHandler.class);


    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        logger.info("Invoked method: {}", method.getName());
        
        return 42;
    }
}

3. 创建 Proxy 实例

代理的实例对象通过InvocationHandler提供服务,我们创建实例通过一个工厂方法调用java.lang.reflect.Proxy

Map proxyInstance = (Map) Proxy.newProxyInstance(
        DynamicInvocationHandler.class.getClassLoader(),
        new Class[]{Map.class},
        new DynamicInvocationHandler()
);

我们有了一个代理对象的实例后我们也可以调用它的接口方法。

proxyInstance.put("hello", "world");

预期:在日志中输出put()方法被调用

结果:

DynamicInvocationHandler - Invoked method: put

4. InvocaitonHandler 通过 Lambda 表达式创建

由于InvocaitonHandler是函数式接口(一个接口(除default方法以外)只有一个方法,那么这个接口就可以被当作函数式接口)。所以handler就可以通过lambda被创建。

Map proxyInstance2 = (Map) Proxy.newProxyInstance(
        DynamicProxyTest.class.getClassLoader(),
        new Class[]{Map.class},
        (proxy, method, methodArgs) -> {
            if (method.getName().equals("get")) {
                return 42;
            } else {
                throw new UnsupportedOperationException("Unsupport method: " + method.getName());
            }
        });

// 42
System.out.println(proxyInstance2.get("hello"));

// exception
proxyInstance2.put("hello", "world");

  1. 动态代理计算耗时
public class TimingDynamicInvocationHandler implements InvocationHandler {
    private static Logger logger = LoggerFactory.getLogger(TimingDynamicInvocationHandler.class);

    private final Map<String, Method> methods = new HashMap<>();

    private Object target;

    public TimingDynamicInvocationHandler(Object target) {
        this.target = target;

        for (Method method: target.getClass().getDeclaredMethods()) {
            this.methods.put(method.getName(), method);
        }
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long start = System.nanoTime();
        Object result = methods.get(method.getName()).invoke(target, args);
        long elapsed = System.nanoTime() - start;

        logger.info("Executing {} finished in {} ns", method.getName(), elapsed);
        return result;
    }
}

可用于不同的类:

Map mapProxyInstance = (Map) Proxy.newProxyInstance(
        DynamicProxyTest.class.getClassLoader(), new Class[] { Map.class },
        new TimingDynamicInvocationHandler(new HashMap<>()));

mapProxyInstance.put("hello", "world");

mapProxyInstance.get("hello");

CharSequence csProxyInstance = (CharSequence) Proxy.newProxyInstance(
        DynamicProxyTest.class.getClassLoader(),
        new Class[] { CharSequence.class },
        new TimingDynamicInvocationHandler("Hello World"));

csProxyInstance.length();

Output:

TimingDynamicInvocationHandler - Excuting put finished in 23467 ns
TimingDynamicInvocationHandler - Excuting get finished in 9387 ns
TimingDynamicInvocationHandler - Excuting length finished in 7680 ns

6. 结论

注意:JDK动态代理仅能通过接口实现,所有目标类必须实现接口才能够被代理。所以会有局限性,可以考虑CGLIB通过继承实现一个子类来代理对象。