设计模式-代理模式

代理模式定义

创建一个对象的代理,用以控制这个对象的访问

UML
从类图我们可以看出,委托类(RealSubject)与代理类(Proxy)都需要实现同一个接口,他们有相同的方法(具有相同的行为),代理类引用了一个委托类的实例,代理类并不具体实现这个方法,而是直接调用委托类的方法,从而达成对委托类的代理。

静态代理

Subject类

public interface Subject {
    void request();
}

RealSubject类

public class RealSubject implements Subject{

    @Override
    public void request() {
        System.out.println("request");
    }
}

Proxy类

public class Proxy implements Subject{

    private RealSubject realSubject = new RealSubject();

    @Override
    public void request() {
        System.out.println("增强功能");
        realSubject.request();
        System.out.println("增强功能");
    }
}

Client类

public class Client {

    public static void main(String[] args) {
        Proxy proxy = new Proxy();
        proxy.request();
    }
}

通过上面静态代理的例子可以看出:

  1. 代理类和委托类实现了相同的接口,会有大量的重复代码产生,如果接口增加一个方法,不仅委托类要实现这个方法,代理类也需要实现这个方法,这就增加了代码维护的复杂度。
  2. Proxy类只为RealSubject类提供了代理,如果需要代理其他类,那么就不能胜任了,需要再添加适用于其他类的代理类。

通过以上两点,可以看出静态代理的缺点,那么我们就需要动态代理来解决了。目前Java实现动态代理有两种方案,分别是JDK提供的实现方案和CGLIB,接下来我们分别了解这两种实现方案。

JDK动态代理

在上面的示例中,一个代理只能代理一种类型,而且是在编译期就已经确定被代理的对象。而动态代理是在运行时,通过反射机制实现动态代理,并且能够代理各种类型的对象

在Java中要想实现动态代理机制,需要java.lang.reflect.InvocationHandler接口和 java.lang.reflect.Proxy 类的支持

public interface InvocationHandler {

    /**
     * proxy - 调用该方法的代理实例
     * method -所述方法对应于调用代理实例上的接口方法的实例。方法对象的声明类将是该方法声明的接口,它可以是代理类继承该方法的代理接口的超级接口。
     * args -包含的方法调用传递代理实例的参数值的对象的阵列,或null如果接口方法没有参数。原始类型的参数包含在适当的原始包装器类的实例中,例如java.lang.Integer或java.lang.Boolean 。
     */
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}
public class Proxy implements java.io.Serializable {
	/**
     * loader - 类加载器来定义代理类
     * interfaces - 代理类实现的接口列表
     * h - 得到InvocationHandler接口的子类的实例
     */
    public static Object newProxyInstance(ClassLoader loader,
                                              Class<?>[] interfaces,
                                              InvocationHandler h)
            throws IllegalArgumentException
}

现在我们用JDK提供的动态代理方案来实现最开始的例子

DynamicProxyHandler类

public class DynamicProxyHandler implements InvocationHandler {

    private Object targetObject;

    public Object newProxyInstance(Object targetObject){
        this.targetObject = targetObject;
        /*
        该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
        第一个参数指定产生代理对象的类加载器,需要将其指定为和目标对象同一个类加载器
        第二个参数要实现和目标对象一样的接口,所以只需要拿到目标对象的实现接口
        第三个参数表明这些被拦截的方法在被拦截时需要执行哪个InvocationHandler的invoke方法
         */
        return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(), this);
    }

    /**
     *
     * @param proxy
     * @param method
     * @param args
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("增强功能");
        Object invoke = method.invoke(targetObject, args);
        System.out.println("增强功能");
        return invoke;
    }
}

Client类

public class Client {
    public static void main(String[] args) {
        DynamicProxyHandler dynamicProxyHandler = new DynamicProxyHandler();
        Subject subject = (Subject) dynamicProxyHandler.newProxyInstance(new RealSubject());
        subject.request();
    }
}

由此可以看到,通过动态代理,我们可以代理很多不同类型的对象,代理类是在程序运行过程中动态生成的,即使接口变化也不影响。

查看动态代理生成的class文件

如何看到生成的动态代理的class文件呢,如果能看到,能够方便我们更加清晰的理解。下面这段代码就可以把动态代理生成的class文件保存到本地。

// 获取动态代理类的class字节码
byte[] classFile = ProxyGenerator.generateProxyClass("Proxy0",RealSubject.class.getInterfaces());
// 在当前的工程目录下保存文件
FileOutputStream fos =new FileOutputStream("Proxy0.class");
fos.write(classFile);
fos.flush();
fos.close();

注意:ProxyGenerator类在JDK11更改为私有类,可以在JDK8中尝试

我们反编译生成的class文件

public final class Proxy0 extends Proxy implements Subject {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void request() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.zhaojun.sort.proxy.Subject").getMethod("request");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

可以发现,该类继承了Proxy,并且实现了我们自己定义的接口,和RealSubject类一样都是Subject的子类,然后我们看request方法,其中h,就是DynamicProxyHandler类。这样一看是不是就清楚多了呢。

CGLIB动态代理

我们发现JDK实现动态代理,需要委托类实现一个接口,生成的代理类也会实现相同的接口,从而具有了和委托类相同的“行为”,而对象的行为其实也可以在类中进行定义,我们可以通过继承委托类,实现委托类的公共方法,从而对方法进行增强。这就是CGLIB的大致思想。

要使用CGLIB首先要引入依赖

<dependency>
  <groupId>cglib</groupId>
  <artifactId>cglib</artifactId>
  <version>3.2.5</version>
</dependency>

RealSubjectNoImplInterface类,与RealSubject相比,没有实现任何接口

public class RealSubjectNoImplInterface {
    public void request() {
        System.out.println("request");
    }
}

DynamicProxyInterceptor类

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class DynamicProxyInterceptor implements MethodInterceptor {


    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("增强功能");
        Object invoke = methodProxy.invokeSuper(o, objects);
        System.out.println("增强功能");
        return invoke;
    }
}

Client类

import net.sf.cglib.proxy.Enhancer;

public class Client {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(RealSubjectNoImplInterface.class);
        enhancer.setCallback(new DynamicProxyInterceptor());
        RealSubjectNoImplInterface subjectNoImplInterface = (RealSubjectNoImplInterface)enhancer.create();
        subjectNoImplInterface.request();
    }
}

区别

名称 区别
静态代理 动态代理的理论基础。
JDK动态代理 需要有顶层接口才能使用(Java中类不能多继承),但是在只有顶层接口的时候也可以使用。使用反射完成。使用了动态生成字节码技术。
CGLIB动态代理 可以直接代理类,使用字节码技术,不能对 final类进行代理(Java语法决定,final类不能有子类)。使用了动态生成字节码技术。

设计模式-代理模式
https://www.zhaojun.inkhttps://www.zhaojun.ink/archives/proxy-pattern
作者
卑微幻想家
发布于
2021-03-19
许可协议