结构型模式-代理模式原理

2020-03-04 1516点热度 0人点赞 0条评论
设计模式系列文章:https://zhum.in/blog/category/notes/design-pattern
Demo code:https://github.com/njZhuMin/BlogSampleCode

代理模式

代理模式 (Proxy Pattern) 是指为其他对象提供一种代理,以控制对这个对象的访问,属于结构型模式。
在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

代理模式一般包含三种角色:

  • 抽象主题角色 (Subject):声明真实主题与代理的共同接口方法,该类可以是接口也可以是抽象类。
  • 真实主题角色 (RealSubject):也称为被代理类,该类定义了代理所表示的真实对象,是负责执行系统真正的逻辑业务对象。
  • 代理主题角色 (Proxy):也称为代理类,其内部持有 RealSubject 的引用,因此具备完全的对 RealSubject 的代理权。
代理者内部持有真实角色的引用

客户端调用代理对象的方法,此时代理对象会调用被代理对象(真正的执行者)的方法,但可能会在原代码逻辑前后增加一些代码逻辑,而使调用者无感知。

代理模式在实现方式上分为静态代理动态代理

静态代理模式

显式声明被代理的对象

依赖关系

举一个常见的例子,购房者有购房的需求,通常会通过房产中介来寻找合适的房源。购房者是真正有找房需求的人,但是大量房源掌握在房产中介手上,这时房产中介就作为购房者的代理,替买房者执行了寻找房源的动作。

public interface IHouseBuyer {
    void findHouse();
}

public class ShanghaiBuyer implements IHouseBuyer {
    @Override
    public void findHouse() {
        System.out.println("[Shanghai Buyer] Want to find a 100m^2 house in Shanghai.");
    }
}

public class ShanghaiAgent {

    private IHouseBuyer houseBuyer;

    public ShanghaiAgent(IHouseBuyer houseBuyer) {
        this.houseBuyer = houseBuyer;
    }

    public void findHouseInShanghai() {
        System.out.println("[Shanghai Agent] Search house for buyer in Shanghai...");
        houseBuyer.findHouse();
        System.out.println("[Shanghai Agent] House found! Contact house buyer for deal...");
    }
}

public class Test {
    public static void main(String[] args) {
        ShanghaiAgent shanghaiAgent = new ShanghaiAgent(new ShanghaiBuyer());
        shanghaiAgent.findHouseInShanghai();
    }
}

依赖关系如下:

proxy_house_buyer

在这个案例中,一家上海房产中介作为代理者,可以帮助上海的购房者寻找房源。而购房者不需要关心寻找房源的过程细节,只需要委托代理者即可完成这个动作。

静态代理的业务场景

那么,现在又有一个北京的购房者需要找北京的房源:

public class BeijingBuyer implements IHouseBuyer {
    @Override
    public void findHouse() {
        System.out.println("[Beijing Buyer] Want to find a 80m^2 house in Beijing.");
    }
}

这时上海的房产中介就搞不定了,因为他不了解北京的房源。这就暴露了静态代理模式的弊端:其可以代理的对象不具有通用性。

那么,有没有一个代理者,不管你想找哪里的房,只要你是想找房(满足某个特定接口的动作),我就可以帮你搞定(完成通用的代理)呢?这时就需要动态代理模式了。

常用的动态代理模式的实现方式有:JDK 动态代理和 CGLib 动态代理。

JDK 动态代理

案例

用 JDK 动态代理改造我们的房产中介:

public class JdkProxyAgent implements InvocationHandler {

    private IHouseBuyer target;

    public IHouseBuyer getInstance(IHouseBuyer target){
        this.target = target;
        Class<?> clazz = target.getClass();
        return (IHouseBuyer) Proxy.newProxyInstance(
                clazz.getClassLoader(), clazz.getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        beforeInvoke();
        Object result = method.invoke(this.target, args);
        afterInvoke();
        return result;
    }

    private void beforeInvoke() {
        System.out.println("[Proxy Agent] Search house for buyer...");
    }

    private void afterInvoke() {
        System.out.println("[Proxy Agent] House found! Contact house buyer for deal...");
    }
}

public class JDKProxyAgentTest {
    public static void main(String[] args) {
        JdkProxyAgent proxyAgent = new JdkProxyAgent();
        IHouseBuyer shanghaiBuyer = proxyAgent.getInstance(new ShanghaiBuyer());
        shanghaiBuyer.findHouse();
        IHouseBuyer beijingBuyer = proxyAgent.getInstance(new BeijingBuyer());
        beijingBuyer.findHouse();
    }
}

原理实现

JDK 动态代理采用字节重组,重新生成对象来替代原始对象,以达到动态代理的目的。 JDK 动态代理生成对象的步骤如下:

  1. 获取被代理对象的引用,然后反射获取它的所有接口。
  2. 重新生成一个新的类,实现被代理类实现的所有接口。
  3. 动态生成 Java 代码,新加的业务逻辑方法由一定的逻辑代码调用。
  4. 编译动态生成的 Java 源代码并生成 class 文件。
  5. 将新的 class 文件重新加载到 JVM 中。

以上过程就叫字节码重组。 JDK 规范中指出,在 ClassPath 下以 $开头的 class 文件,通常都是自动生成的。

理解了 JDKProxy 字节码重组思路,我们来照猫画虎试试能不能碰瓷出一个低配版的 JDKProxy 出来。

研究重组的源文件

在这里打个断点,我们可以发现通过 JdkProxyAgent 生成的对象并不是 ShanghaiBuyer 的原始对象了,而是一个 com.sun.proxy.$Proxy0 对象。

JdkProxyAgent proxyAgent = new JdkProxyAgent();
IHouseBuyer shanghaiBuyer = proxyAgent.getInstance(new ShanghaiBuyer());

那么我们就把这个对象想办法搞出来,看看 JDKProxy 重组后的对象长什么样:

public class JDKProxyAgentTest {
    public static void main(String[] args) {
        JdkProxyAgent proxyAgent = new JdkProxyAgent();
        IHouseBuyer shanghaiBuyer = proxyAgent.getInstance(new ShanghaiBuyer());
        shanghaiBuyer.findHouse();
        IHouseBuyer beijingBuyer = proxyAgent.getInstance(new BeijingBuyer());
        beijingBuyer.findHouse();

        // Serialize JDKProxy generated class to disk
        try {
            // JDK 9+ moved generateProxyClass to java.lang.reflect.ProxyGenerator
            // With JDK 8, call sun.misc.ProxyGenerator directly
            Class<?> clazz = Class.forName("java.lang.reflect.ProxyGenerator");
            Method method = clazz.getDeclaredMethod("generateProxyClass", String.class, Class[].class);
            method.setAccessible(true);
            byte[] bytes = (byte[]) method.invoke(null,"Proxy0", new Class[]{IHouseBuyer.class});
            FileOutputStream fos = new FileOutputStream("Proxy0.class");
            fos.write(bytes);
            fos.flush();
            fos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

生成的 Class 文件内容如下:

import example.Proxy.IHouseBuyer;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

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

    publicProxy0(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 void findHouse() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    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 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"));
            m3 = Class.forName("example.Proxy.IHouseBuyer").getMethod("findHouse");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

我们发现,$Proxy0 继承了 Proxy 类,同时还实现了 IHouseBuyer 接口,而且重写了 findHouse()等方法。在静态块中用反射查找到了目标对象的所有方法,而且保存了所有方法的引用,然后通过反射调用重写后的目标对象的对应方法。

基于 JDKProxy 原理的实现

从使用者角度入手,我们先要提供一个 InvocationHandler 接口:

public interface MyInvocationHandler {
    Object invoke(Object var1, Method var2, Object[] var3) throws Throwable;
}

然后需要实现 Proxy 的接口和一个将编译好的 class 文件加载进来的 ClassLoader 类:

public class MyClassLoader extends ClassLoader {

    private File classPathFile;

    public MyClassLoader() {
        String classPath = MyClassLoader.class.getResource("").getPath();
        this.classPathFile = new File(classPath);
    }

    protected Class<?> findClass(String name) throws ClassNotFoundException {
        String className = MyClassLoader.class.getPackage().getName() + "." + name;
        if (classPathFile != null) {
            File classFile = new File(classPathFile, name.replaceAll("\\.", "/") + ".class");

            if (classFile.exists()) {
                try (FileInputStream fis = new FileInputStream(classFile);
                     ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
                    byte[] buff = new byte[1024];
                    int len;
                    while ((len = fis.read(buff)) != -1) {
                        bos.write(buff, 0, len);
                    }
                    return defineClass(className, bos.toByteArray(), 0, bos.size());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }
}

动态代理实现:

public class MyProxy {
    private static final String ln = "\n";

    public static Object newProxyInstance(MyClassLoader classLoader,
                                          Class<?>[] interfaces,
                                          MyInvocationHandler h) {
        // 1. Dynamically generate .java src file
        String srcCode = generateSrc(interfaces);
        if (srcCode == null) {
            throw new RuntimeException("Error generating proxy class source file.");
        }

        // 2. Save Proxy0.java to disk
        String srcPath = MyProxy.class.getResource("").getPath();
        File srcFile = new File(srcPath + "Proxy0.java");
        try (FileWriter fileWriter = new FileWriter(srcFile)) {
            fileWriter.write(srcCode);
            fileWriter.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 3. Compile Proxy0.java file toProxy0.class
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        try (StandardJavaFileManager fileManager = compiler.getStandardFileManager
                (null, null, StandardCharsets.UTF_8)) {
            Iterable<? extends JavaFileObject> iterable = fileManager.getJavaFileObjects(srcFile);
            JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, null, null, null, iterable);
            task.call();
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 4. Load Proxy0.java into JVM
        Class<?> proxyClass = null;
        try {
            proxyClass = classLoader.findClass("Proxy0");
            boolean delTempSrcFile = srcFile.delete();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        // 5. Re-assemble bytecode and return proxied object
        if (proxyClass == null) {
            throw new RuntimeException("Error loading generated class file");
        }
        try {
            Constructor<?> constructor = proxyClass.getConstructor(MyInvocationHandler.class);
            return constructor.newInstance(h);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private static String generateSrc(Class<?>[] interfaces){
        StringBuilder sb = new StringBuilder();
        sb.append(MyProxy.class.getPackage() + ";" + ln)
            .append("import " + interfaces[0].getName() + ";" + ln)
            .append("import java.lang.reflect.*;" + ln)
            .append("public final class Proxy0 implements " + interfaces[0].getName() + " {" + ln)
            .append("private MyInvocationHandler h;" + ln)
            .append("publicProxy0(MyInvocationHandler h) {" + ln)
            .append("this.h = h;" + ln)
            .append("}" + ln);

        for (Method m : interfaces[0].getMethods()) {
            Class<?>[] params = m.getParameterTypes();

            StringBuilder paramNames = new StringBuilder();
            StringBuilder paramValues = new StringBuilder();
            StringBuilder paramClasses = new StringBuilder();

            // Get single method params
            for (int i = 0; i < params.length; i++) {
                Class<?> clazz = params[i];
                String type = clazz.getName();
                String paramName = "var" + i;
                paramNames.append(type + " " +  paramName);
                paramValues.append(paramName);
                paramClasses.append(clazz.getName() + ".class");
                if (i < params.length - 1) {
                    paramNames.append(",");
                    paramClasses.append(",");
                    paramValues.append(",");
                }
            }

            sb.append("public final " + m.getReturnType().getName() + " " + m.getName())
                .append("(" + paramNames.toString() + ") {" + ln)
                .append("try {" + ln)
                .append("Method m = " + interfaces[0].getName() + ".class.getMethod(\"")
                .append(m.getName() + "\", new Class[]{ " + paramClasses.toString() + " });" + ln);

            String returnInvokedMethod = "this.h.invoke(this, m, new Object[]{" + paramValues.toString() + "})";
            if (m.getReturnType() != void.class) {
                if (isBasicDataType(m.getReturnType())) {
                    sb.append("return ((" + getPackagedDataType(m.getReturnType()) + ")" + returnInvokedMethod + "." + m.getReturnType().getSimpleName() + "Value();" + ln);
                } else {
                    sb.append("return (" + m.getReturnType().getSimpleName() + ") " + returnInvokedMethod + ";" + ln);
                }
            } else {
                sb.append(returnInvokedMethod + ";" + ln);
            }

            sb.append("} catch(Error _ex) { }" + ln)
                .append("catch(Throwable e) {" + ln)
                .append("throw new UndeclaredThrowableException(e);" + ln)
                .append("}" + ln);
            sb.append(getEmptyReturnCode(m.getReturnType()) + ln);
            sb.append("}" + ln);
        }
        sb.append("}" + ln);
        return sb.toString();
    }

    private static String getPackagedDataType(Class<?> clazz) {
        return clazz.getSimpleName().substring(0, 1).toUpperCase() + clazz.getSimpleName().substring(1);
    }

    private static boolean isBasicDataType(Class<?> clazz) {
        return clazz == byte.class || clazz == short.class || clazz == int.class ||
                clazz == long.class || clazz == float.class || clazz == double.class ||
                clazz == char.class || clazz == boolean.class;
    }

    private static String getEmptyReturnCode(Class<?> clazz) {
        if (clazz == void.class) {
            return "";
        }
        if (clazz == byte.class || clazz == short.class || clazz == int.class ||
                clazz == long.class) {
            return "return 0;";
        }
        if (clazz == float.class || clazz == double.class) {
            return "return 0.0;";
        }
        if (clazz == char.class) {
            return "return \"\";";
        }
        if (clazz == boolean.class) {
            return "return false;";
        }
        return "return null;";
    }
}

简单分别测试一下带返回值和多参数的方法:

public interface IRichHouseBuyer {
    void findHouse();
    String payTaxes(String tax1, String tax2, String tax3);
}

public class RichHouseBuyer implements IRichHouseBuyer {
    @Override
    public void findHouse() {
        System.out.println("[Rich Buyer] Wants to find a large house...");
    }

    @Override
    public String payTaxes(String tax1, String tax2, String tax3) {
        System.out.println("[Rich Buyer] Paying tax for deal...");
        return "Taxes paid: " + tax1 + ", " + tax2 + ", " + tax3;
    }
}

public class MyProxyAgent implements MyInvocationHandler {
    private IRichHouseBuyer target;

    public IRichHouseBuyer getInstance(IRichHouseBuyer target){
        this.target = target;
        Class<?> clazz = target.getClass();
        return (IRichHouseBuyer) MyProxy.newProxyInstance(
                new MyClassLoader(), clazz.getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        beforeInvoke();
        Object result = method.invoke(this.target, args);
        afterInvoke();
        return result;
    }

    private void beforeInvoke() {
        System.out.println("[Proxy Agent] Prepare for paperwork...");
    }

    private void afterInvoke() {
        System.out.println("[Proxy Agent] Closing deal successfully...");
    }
}

public class MyProxyAgentTest {
    public static void main(String[] args) {
        MyProxyAgent proxyAgent = new MyProxyAgent();
        IRichHouseBuyer richBuyer = proxyAgent.getInstance(new RichHouseBuyer());
        richBuyer.findHouse();
        System.out.println(richBuyer.payTaxes("10W", "2W", "20W"));
    }
}

CGLib 动态代理

案例

与 JDK 代理的实现方式不同,CGLib 不关心这个类实现的接口,他是通过动态继承目标对象来实现动态代理的:

public class CGLibProxyAgent implements MethodInterceptor {

    public Object getInstance(Class<?> clazz) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        beforeInvoke();
        Object result = methodProxy.invokeSuper(o, objects);
        afterInvoke();
        return result;
    }

    private void beforeInvoke() {
        System.out.println("[Proxy Agent] Search house for buyer...");
    }

    private void afterInvoke() {
        System.out.println("[Proxy Agent] House found! Contact house buyer for deal...");
    }
}

public class CGLibProxyAgentTest {
    public static void main(String[] args) {
        try {
            CGLibProxyAgent proxyAgent = new CGLibProxyAgent();
            IHouseBuyer shanghaiBuyer = (IHouseBuyer)proxyAgent.getInstance(ShanghaiBuyer.class);
            shanghaiBuyer.findHouse();
            IHouseBuyer beijingBuyer = (IHouseBuyer)proxyAgent.getInstance(BeijingBuyer.class);
            beijingBuyer.findHouse();
        } catch (Exception e) {
            e.printStackTrace(); }
    }
}

原理简单分析

在测试方法里加上一行属性,让 CGLib 将动态代理生成的中间 class 文件输出:

System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "cglib_proxy_class/");

可以看到对于一个代理类,CGLib 生成了 3 个 Class 文件:

ShanghaiBuyerEnhancerByCGLIB596e8770
ShanghaiBuyerFastClassByCGLIBb24a9524
ShanghaiBuyerEnhancerByCGLIB596e8770FastClassByCGLIB8df1674d

其中第一个是继承了 IHouseBuyer 的代理类,我们可以找到其中实现了 findHouse 方法。代理类会获得所有从父类继承来的方法,并且会有对应的 MethodProxy 方法。

public class ShanghaiBuyerEnhancerByCGLIB596e8770 extends ShanghaiBuyer implements Factory {

    private static final Method CGLIBfindHouse0Method;
    private static final MethodProxy CGLIBfindHouse0Proxy;

    final void CGLIBfindHouse0() {
        super.findHouse();
    }

    public final void findHouse() {
        MethodInterceptor var10000 = this.CGLIBCALLBACK_0;
        if (var10000 == null) {
            CGLIBBIND_CALLBACKS(this);
            var10000 = this.CGLIBCALLBACK_0;
        }

        if (var10000 != null) {
            var10000.intercept(this, CGLIBfindHouse0Method, CGLIBemptyArgs, CGLIBfindHouse0Proxy);
        } else {
            super.findHouse();
        }
    }
}

而调用者正是调用 CGLib 的代理方法,从而调用真正的实现方法:

MethodProxyinvokeSuper()方法非常关键,我们看一下它具体做了什么:

public Object invokeSuper(Object obj, Object[] args) throws Throwable {
        try {
            this.init();
            MethodProxy.FastClassInfo fci = this.fastClassInfo;
            return fci.f2.invoke(fci.i2, obj, args);
        } catch (InvocationTargetException var4) {
            throw var4.getTargetException();
        }
    }
}
public class MethodProxy {
    private Signature sig1;
    private Signature sig2;
    private MethodProxy.CreateInfo createInfo;
    private final Object initLock = new Object();
    private volatile MethodProxy.FastClassInfo fastClassInfo;

    public static MethodProxy create(Class c1, Class c2, String desc, String name1, String name2) {
        MethodProxy proxy = new MethodProxy();
        proxy.sig1 = new Signature(name1, desc);
        proxy.sig2 = new Signature(name2, desc);
        proxy.createInfo = new MethodProxy.CreateInfo(c1, c2);
        return proxy;
    }
}

private static class CreateInfo {
    Class c1;
    Class c2;
    NamingPolicy namingPolicy;
    GeneratorStrategy strategy;
    boolean attemptLoad;
    public CreateInfo(Class c1, Class c2) {
        this.c1 = c1;
        this.c2 = c2;
        AbstractClassGenerator fromEnhancer = AbstractClassGenerator.getCurrent();
        if (fromEnhancer != null) {
            this.namingPolicy = fromEnhancer.getNamingPolicy();
            this.strategy = fromEnhancer.getStrategy();
            this.attemptLoad = fromEnhancer.getAttemptLoad();
        }
    }
}

private static class FastClassInfo {
    FastClass f1;
    FastClass f2;
    int i1;
    int i2;
    private FastClassInfo() {}
}

上面的代码调用就是获取代理类对应的 FastClass,并执行代理方法。

CGLib 代理执行代理方法的效率之所以比 JDK 的高,是因为 CGlib 采用了 FastClass 机制,它的原理简单来说就是:为代理类和被代理类各生成一个类,这个类会为代理类或被代理类的方法分配一个索引。这个索引在方法调用时当作一个入参,FastClass 就可以直接定位要调用的方法并直接进行调用,省去了反射的过程,所以调用效率比 JDK 基于反射机制的动态代理高。

public class MethodProxy {
    private void init() {
        if (this.fastClassInfo == null) {
            synchronized(this.initLock) {
                if (this.fastClassInfo == null) {
                    MethodProxy.CreateInfo ci = this.createInfo;
                    MethodProxy.FastClassInfo fci = new MethodProxy.FastClassInfo();
                    fci.f1 = helper(ci, ci.c1);
                    fci.f2 = helper(ci, ci.c2);
                    fci.i1 = fci.f1.getIndex(this.sig1);
                    fci.i2 = fci.f2.getIndex(this.sig2);
                    this.fastClassInfo = fci;
                    this.createInfo = null;
                }
            }
        }
    }
}

进一步,FastClass 并不是在程序启动时就与代理类一起被创建的,而是在第一次执行 MethodProxyinvoke()invokeSuper()方法时生成的。上面的 helper 方法会判断如果 FastClass 对象已经在缓存中就直接取出,缓存中没取到就生成新的 FastClass 。

至此,CGLib 动态代理的原理我们就基本搞清楚了。

SilverLining

也可能是只程序猿

文章评论