代理模式

代理设计模式(Proxy Design Pattern)

1. 介绍

1.1 定义

代理模式,是指客户端(Client)并不直接调用实际的对象(RealSubject),而是通过调用代理(Proxy),来间接的调用实际的对象。

应用实例: Windows 里面的快捷方式

1.2 主要作用

通过增加代理对象,间接访问目标对象,在访问目标对象时做一些控制

1.3 解决的问题

防止直接访问目标对象给系统带来的不必要复杂性。

2.模式原理

UML类图

3.实例讲解

接下来我用一个实例来对代理模式进行更深一步的介绍。
实例概况 程序员写代码之前要写文档

3.1 实现方式一:静态代理

步骤1: 创建抽象对象接口(Subject):声明要做的事

public interface IDeveloper {
    public void writeCode();
}

步骤2: 创建真实对象类(RealSubject)

public class Developer implements IDeveloper {
    private String name;

    public Developer(String name) {
        this.name = name;
    }

    @Override
    public void writeCode() {
        System.out.println("Developer " + name + " writes code");
    }
}

步骤3:创建代理对象类(Proxy),并通过代理类创建真实对象实例并访问其方法

public class DeveloperProxy implements IDeveloper {
    private IDeveloper developer;

    public DeveloperProxy(IDeveloper developer) {
        this.developer = developer;
    }

    @Override
    public void writeCode() {
        System.out.println("Write documentation...");
        this.developer.writeCode();
    }
}

步骤4:客户端调用

public class DeveloperTest {
    public static void main(String[] args) {
        IDeveloper andi = new Developer("Andi");
//        andi.writeCode();

        DeveloperProxy andiProxy = new DeveloperProxy(andi);
        andiProxy.writeCode();
    }
}

结果输出

Write documentation...
Developer Andi writes code
优点
  1. 易于理解和实现
  2. 代理类和真实类的关系是编译期静态决定的,和下文的动态代理比较起来,执行时没有任何额外开销。
缺点

每一个真实类都需要一个创建新的代理类。还是以上述文档更新为例,假设老板对测试工程师也提出了新的要求。那么采用静态代理的方式,测试工程师的实现类ITester也得创建一个对应的ITesterProxy类。(正是因这个缺点,才诞生了Java的动态代理实现方式)

public interface ITester {
    public void doTesting();
}

public class Tester implements ITester {
    private String name;
    public Tester(String name){
        this.name = name;
    }
    @Override
    public void doTesting() {
        System.out.println("Tester " + name + " is testing code");
    }
}

public class TesterProxy implements ITester{
    private ITester tester;
    public TesterProxy(ITester tester){
        this.tester = tester;
    }
    @Override
    public void doTesting() {
        System.out.println("Tester is preparing test documentation...");
        tester.doTesting();
    }
}

3.2 实现方式二:动态代理之InvocationHandler

步骤1: 通过InvocationHandler, 我可以用一个EngineerProxyDynamic代理类来同时代理Developer和Tester的行为

public class EngineerProxyDynamic implements InvocationHandler {
    Object obj;

    public Object bind(Object obj) {
        this.obj = obj;
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), this);
    }

  //真实类的writeCode和doTesting方法在动态代理类里通过反射的方式进行执行。
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Engineer writes document...");
        Object res = method.invoke(obj, args);
        return res;
    }
}

步骤2:客户端调用

public class DeveloperTest {
    public static void main(String[] args) {
        IDeveloper andi = new Developer("Andi");
        Tester bob = new Tester("Bob");

        IDeveloper andiProxy = (IDeveloper) new EngineerProxyDynamic().bind(andi);
        ITester bobProxy = (ITester) new EngineerProxyDynamic().bind(bob);
        andiProxy.writeCode();
        bobProxy.doTesting();
    }
}

结果输出

Engineer writes document...
Developer Andi writes code
Engineer writes document...
Tester Bob is testing code
优点

每一个真实类不需要创建一个新的代理类

缺点
  1. 执行时有额外开销
  2. 无法代理没有实现任何接口的真实类
public class ProductOwner {
    private String name;
    public ProductOwner(String name){
        this.name = name;
    }
    public void defineBackLog(){
        System.out.println("PO: " + name + " defines Backlog.");
    }
}

我们仍然采取EngineerProxyDynamic代理类去代理它,编译时不会出错,运行时出错

Exception in thread "main" java.lang.ClassCastException: com.sun.proxy.$Proxy2 cannot be cast to com.beibei.design.structural.ProductOwner
    at com.beibei.design.structural.DeveloperTest.main(DeveloperTest.java:23)

3.3 实现方式三:动态代理之CGLIB

CGLIB是一个Java字节码生成库,提供了易用的API对Java字节码进行创建和修改。关于这个开源库的更多细节,请移步至CGLIB在github上的仓库:https://github.com/cglib/cglib

我们现在尝试用CGLIB来代理之前采用InvocationHandler没有成功代理的ProductOwner类(该类未实现任何接口)。

现在我改为使用CGLIB API来创建代理类:

public class EngineerCGLibProxy {
    Object obj;

    public Object bind(final Object target) {
        this.obj = target;
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(obj.getClass());
        enhancer.setCallback(
                new MethodInterceptor() {
                    @Override
                    public Object intercept(Object obj, Method method, Object[] args,
                                            MethodProxy proxy) throws Throwable {
                        System.out.println("Engineer 2 writes document");
                        Object res = method.invoke(target, args);
                        return res;
                    }
                }
        );
        return enhancer.create();
    }
}

客户端调用

public class DeveloperTest {
    public static void main(String[] args) {
        ProductOwner ross = new ProductOwner("Ross");
        ProductOwner rossProxy = (ProductOwner) new EngineerCGLibProxy().bind(ross);
        rossProxy.defineBackLog();
    }
}

遇到问题

Exception in thread "main" java.lang.IllegalArgumentException: Superclass has no null constructors but no arguments were given

解决,目标对象,定义一个无参数构造函数参考解决方法

public ProductOwner() {}

结果输出

Enginner 2 writes document
PO: Ross defines Backlog.
优点

可以代理没有实现任何接口的真实类

缺点

通过CGLIB成功创建的动态代理,实际是被代理类的一个子类。那么如果被代理类被标记成final,也就无法通过CGLIB去创建动态代理。

如果我们了解了CGLIB创建代理类的原理,那么其局限性也就一目了然。我们现在做个实验,将ProductOwner类加上final修饰符,使其不可被继承:

再次执行测试代码,这次就报错了: Cannot subclass final class XXXX。

3.4 实现方式四:动态代理之 通过编译期提供的API动态创建代理类

假设我们确实需要给一个既是final,又未实现任何接口的ProductOwner类创建动态代码。除了InvocationHandler和CGLIB外,我们还有最后一招:

我直接把一个代理类的源代码用字符串拼出来,然后基于这个字符串调用JDK的Compiler(编译期)API,动态的创建一个新的.java文件,然后动态编译这个.java文件,这样也能得到一个新的代理类。


public class ProductOwnerSourceCodeProxy {

    public static void main(String[] arg) throws Exception {
        Class c = getProxyClass();
        Constructor[] constructor = c.getConstructors();
        Object POProxy = constructor[0].newInstance("Ross");
        Method defineBackLog = c.getDeclaredMethod("defineBackLog");
        defineBackLog.invoke(POProxy);
    }

    private static String getSourceCode() {
        String src = "package com.beibei.design.structural.proxy;\n\n"
                + "public final class ProductOwnerSCProxy {\n"
                + "\tprivate String name;\n\n"
                + "\tpublic ProductOwnerSCProxy(String name){\n"
                + "\t\tthis.name = name;\n" + "\t}\n\n"
                + "\t\tpublic void defineBackLog(){\n"
                + "\t\tSystem.out.println(\"PO writes some document before defining BackLog\");"
                + "\t\tSystem.out.println(\"PO: \" + name + \" defines Backlog.\");}}\n";
        return src;
    }

    private static String createJavaFile(String sourceCode) {
        String fileName = "/Users/anbeibei/AndroidStudioProjects/2019/DesignPattern/src/com/beibei/design/structural/proxy/ProductOwnerSCProxy.java";
        File javaFile = new File(fileName);
        Writer writer;
        try {
            writer = new FileWriter(javaFile);
            writer.write(sourceCode);
            writer.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return fileName;
    }

    private static void compile(String fileName) {
        try {
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            StandardJavaFileManager sjfm = compiler.getStandardFileManager(null, null, null);
            Iterable iter = sjfm.getJavaFileObjects(fileName);
            JavaCompiler.CompilationTask ct = compiler.getTask(null, sjfm, null, null, null, iter);
            ct.call();
            sjfm.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static Class loadClass() {
        URL[] urls;
        String path = "file:///Users/anbeibei/AndroidStudioProjects/2019/DesignPattern/src/com/beibei/design/structural/";
        Class c = null;
        try {
            urls = new URL[]{(new URL(path))};
            URLClassLoader ul = new URLClassLoader(urls);
            c = ul.loadClass("com.beibei.design.structural.proxy.ProductOwnerSCProxy");//需要指定包名
            ul.close();
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
        return c;
    }

    private static Class getProxyClass() {
        String sourceCode = getSourceCode();
        String javaFile = createJavaFile(sourceCode);
        compile(javaFile);
        return loadClass();
    }
}

测试结果

PO writes some document before defining BackLog
PO: Ross defines Backlog.

遇到问题
无法加载生成的.class文件,URLClassLoader加载需要指定包名

Java代理设计模式的四种具体实现:静态代理和动态代理

你可能感兴趣的:(代理模式)