代理设计模式(Proxy Design Pattern)
1. 介绍
1.1 定义
代理模式,是指客户端(Client)并不直接调用实际的对象(RealSubject),而是通过调用代理(Proxy),来间接的调用实际的对象。
应用实例: Windows 里面的快捷方式
1.2 主要作用
通过增加代理对象,间接访问目标对象,在访问目标对象时做一些控制
1.3 解决的问题
防止直接访问目标对象给系统带来的不必要复杂性。
2.模式原理
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
优点
- 易于理解和实现
- 代理类和真实类的关系是编译期静态决定的,和下文的动态代理比较起来,执行时没有任何额外开销。
缺点
每一个真实类都需要一个创建新的代理类。还是以上述文档更新为例,假设老板对测试工程师也提出了新的要求。那么采用静态代理的方式,测试工程师的实现类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
优点
每一个真实类不需要创建一个新的代理类
缺点
- 执行时有额外开销
- 无法代理没有实现任何接口的真实类
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<? extends JavaFileObject> 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.