动态代理技术

139 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

动态代理技术

作为Spring核心特点AOP中涉及到的前置知识,理解其使用过程与原理非常重要。动态代理技术在程序运行期间,可以不修改目标源代码的前提下,为目标方法进行增强,常用于权限验证,添加日志,缓存功能等。

JDK动态代理 -- 基于接口的动态代理技术

作为Java内置的代理技术,该技术只能作用于有实现接口的类,存在一定的局限性。核心类涉及:Proxy,MethodInovationHandler接口。

涉及类/接口作用
Proxy生成增强方法后的代理对象
Method起到调用目标对象的目标方法的作用
InovationHandler调用处理器,负责增强目标方法。

JDK动态代理的实现

为了实现JDK的动态代理,我们创建ITarget接口, TargetImpl类实现ITarget接口与Advice通知类(用于增强方法的类)。下面的代码阅读需要一些反射的知识。

// ITarget.java
public interface ITarget {
    void hello();
}
// Target.java
public class TargetImpl implements ITarget {
    @Override
    public void hello() {
        System.out.println("Hello World!");
    }
}
// Advice.java
public class Advice {
    public void before() {
        System.out.println("前置方法...");
    }

    public void afterRunning() {
        System.out.println("后置方法...");
    }
}
// 测试类
public class TestDynamicProxy {

    @Test
    public void testJDKProxy() {
        // 1. 创建目标对象
        ITarget target = new TargetImpl();

        // 2. 创建通知
        Advice advice = new Advice();

        // 3. 创建代理对象
        ITarget proxy = (ITarget) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                // 4. 在这里增强所调用的方法
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        advice.before();
                        // 这句话可以理解为 --> object invoke = target.method(args);
                        // 简单来说就是调用了target对象的指定方法
                        Object invoke = method.invoke(target, args);
                        advice.afterRunning();
                        // 这里一般返回方法执行后的结果
                        return invoke;
                    }
                }
        );

        // 5. 调用方法
        proxy.hello();
    }
}

image.png

主要说说Proxy.newProxyInstance()方法,该方法需要传入三个参数ClassLoader loader, Class<?>[] interfaces, InvocationHandler h。第一个参数为类加载器,第二参数为代理对象的实现接口列表,第三个参数为InvocationHandler接口的实现类即可。这里关于接口列表的话,没有尝试过多接口,有兴趣的话,可以自己试一试。

cglib动态代理 -- 基于父类的动态代理技术

cglib在实现动态代理的时候,通过Enhancer增强器类实现,其实需要传入回调接口,使用其子接口MethodInterceptor实现。因为目标类不需要再需要接口继承,所以只有三个文件TargetImpl类实现ITarget接口与Advice通知类。其他文件不变,只改动测试代码。

// 测试类
public class TestDynamicProxy {

    @Test
    public void testCglibProxy() {
        // 1. 创建目标对象
        TargetImpl target = new TargetImpl();

        // 2. 创建通知
        Advice advice = new Advice();

        // 3. 创建增强器
        Enhancer enhancer = new Enhancer();

        // 4. 创建代理
//        TargetImpl proxy = (TargetImpl) enhancer.create(
//                TargetImpl.class,
//                new MethodInterceptor() {
//                    @Override
//                    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
//                        // 与JDK中InvocationHandler同理,增强指定方法
//                        advice.before();
//                        Object invoke = proxy.invokeSuper(obj, args);
//                        advice.afterRunning();
//                        return invoke;
//                    }
//                });

        // 函数式接口实现
        TargetImpl proxy2 = (TargetImpl) enhancer.create(
                TargetImpl.class,
                (MethodInterceptor) (obj, method, args, proxy) -> {
                        // 与JDK中InvocationHandler同理,增强指定方法
                        advice.before();
                        Object invoke = proxy.invokeSuper(obj, args);
                        advice.afterRunning();
                        return invoke;
                    }
                );

        // 5. 调用方法
        proxy2.hello();
    }
}

image.png 使用cglib代理调用原来目标对象的目标方法的时候,使用了proxy.invokeSuper(obj, args),与JDK动态代理类似,在intercept()方法中,obj对象为代理对象,不过多了一个MethodProxy proxy对象。该对象的主要作用同样是进行方法的调用,但是在方法调用的时候,因为我们没有使用目标对象,而是使用了代理对象,所以这里我们需要利用proxy.invokeSuper()方法进行调用目标对象的原始方法。

查阅invokerSuper()方法的官方注解Invoke the original (super) method on the specified object.,我们知道cglib代理是基于父类实现的,所以这里指的super method就是原目标对象的method。但如果我们在这里调用的proxy.invoke(obj, args),那么就相当于我们一直调用代理对象的目标方法,会导致无限循环。

image.png

image.png 我们会无限的调用hello()方法,而代理对象的hello()方法又要经过增强,导致栈内存溢出。

最近在学习Spring框架,希望能从中多理解框架背后的设计理念。