Skip to content

Latest commit

 

History

History
256 lines (211 loc) · 7.33 KB

dynamic_proxy.md

File metadata and controls

256 lines (211 loc) · 7.33 KB

Java 动态代理

Advanced-DynamicProxy-1

图:Java 代理执行逻辑

代理模式简介

代理模式是常用的设计模式之一,其核心思想是:对实现类的替代,对方法的增强

Advanced-DynamicProxy-2

图:Java 代理模式 UML 示意图

需要注意的有下面几点:

  • 用户只关心接口功能,而不在乎谁提供了功能。

  • 接口真正实现者不与用户直接接触,而是通过代理。

  • 代理也实现了相应接口,能够直接与用户接触。

  • 用户调用代理类时,代理类内部调用了具体实现类的方法,所以代理类是中介者,可以增强方法操作。

  • 代理可以分为「静态代理」和「动态代理」两种。

无代理

先来看一下不使用代理的情况:抽象接口 -> 实现接口 -> 主类调用。

public interface Subject {
    public void doSomething();
}

代码清单:抽象接口

public class SubjectImpl implements Subject {
    @Override
    public void doSomething() {
        System.out.println("call doSomething()");
    }
}

代码清单:接口实现类

public class SubjectTest {
    public static void main(String[] args) {
        Subject sub = new SubjectImpl();
        sub.doSomething();
    }
}

代码清单:主类

如需额外添加功能,则要修改接口实现类。如此一来,通用功能代码与具体实现代码混合,逻辑不清。

public class SubjectImpl implements Subject {
    @Override
    public void doSomething() {
        before();
        System.out.println("call doSomething()");
        after();
    }
    public void before() {
        System.out.println("call before()");
    }
    public void after() {
        System.out.println("call after()");
    }
}

代码清单:接口实现类(添加通用功能)

静态代理

使用静态代理模式优化上述逻辑:创建静态代理类,将通用功能代码与具体实现代码分离。

public class SubjectProxy implements Subject {
    /* 具体实现类 */
    private Subject subject;
    public SubjectProxy() {
        subject = new SubjectImpl();
    }
    @Override
    public void doSomething() {
        before();
        subject.doSomething();
        after();
    }
    private void before() {
        System.out.println("call before()");
    }
    private void after() {
        System.out.println("call after()");
    }
}

代码清单:静态代理类

public class SubjectTest {
    public static void main(String[] args) {
        Subject sub = new SubjectProxy();
        sub.doSomething();
    }
}

代码清单:主类

静态代理带来了一定的灵活性,在不改变被代理接口实现类源代码的情况下,增强类方法,只需调用端少量修改调用代码即可。

静态代理的缺陷在于:通用功能代码还是不方便复用,需要写大量的代理包装类,代码冗余。

JDK 动态代理

JDK 动态代理需要一个实现InvocationHandler接口的通用代理类,并通过Proxy类动态生成出实际代理类,使用invoke()反射调用方法。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.lang.reflect.Method;
public class ProxyHandler implements InvocationHandler {
    /* 通用实现类 */
    private Object target;
    /* 绑定通用实现类 */
    public Object bind(Object target) {
        this.target = target;
        return Proxy.newProxyInstance(
            /* 代理类类加载器 */
            this.target.getClass().getClassLoader(),
            /* 代理类实现接口 */
            this.target.getClass().getInterfaces(),
            /* 调用转发类 */
            this
        );
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
    throws Throwable {
        before();
        /* 反射调用 */
        Object result = method.invoke(this.target, args);
        after();
        return result;
    }
    private void before() {
        System.out.println("call before()");
    }
    private void after() {
        System.out.println("call after()");
    }
}

代码清单:JDK 动态代理

public class SubjectTest {
    public static void main(String[] args) {
        Subject sub = new ProxyHandler().bind(new SubjectImpl());
        sub.doSomething();
    }
}

代码清单:主类

动态代理十分灵活,对于绑定的任意接口实现类,都可以动态生成一个具体的代理类,无需用户手动创建。

可以看到,JDK 动态代理也需要被代理方法实现某一接口,JDK 动态代理根据接口信息拦截特定的方法,如果方法没有实现任何接口,JDK 动态代理也就无能为力了。

CGLib 动态代理

cglib(Byte Code Generation Library)是一个 Java 动态字节码生成库,提供高阶 API 方便动态生成 Java 字节码,底层使用了小而快的字节码处理框架ASM。利用cglib,可以实现不依赖接口的动态代理。

cglib开源地址:https://github.com/cglib/cglib

/* 无实现接口 */
public class SubjectImpl {
    public void doSomething() {
        System.out.println("call doSomething()");
    }
}

代码清单:代理类

import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class CGLibProxy implements MenthodInterceptor {
    private CGLibProxy() {}
    /* 单例模式 */
    private static CGLibProxy instance = new CGLibProxy();
    public static CGLibProxy getInstance() {
        return instance;
    }
    /* 取得动态代理(泛型) */
    @SuppressWarnings("unchecked")
    public <T> T getProxy(Class<T> clazz) {
        return (T)Enhancer.create(clazz, this);
    }
    /* 方法拦截 */
    @Override
    public Object intercept(
        Object obj,
        Method method,
        Object[] args,
        MethodProxy proxy
    ) throws Throwable {
        before();
        Object result = proxy.invokeSuper(obj, args);
        after();
        return result;
    }
    private void before() {
        System.out.println("call before()");
    }
    private void after() {
        System.out.println("call after()");
    }
}

代码清单:cglib动态代理

public class SubjectTest {
    public static void main(String[] args) {
        SubjectImpl sub = CGLibProxy.getInstance()
                                    .getProxy(SubjectImpl.class);
        sub.doSomething();
    }
}

代码清单:主类

可以看到,cglib实现动态代理不需要接口信息。但是正因为没有接口信息,cglib动态代理没有办法拦截指定方法,被代理类中定义的所有方法都被cglib拦截包装。

总结

静态代理、JDK 动态代理、cglib动态代理这三种代理方式各有特点。

  • 静态代理与 JDK 动态代理都需要被代理类实现接口,通过接口信息对特定方法进行拦截包装。

  • CGLib 动态代理虽然不需要接口信息,但是其会对被代理类的所有方法都进行拦截包装。另外,因其实现方式是继承,所以不能代理final方法。