动态代理是指什么?它有哪几种实现方法? 答:- 动态代理是指在程序运行时生成代理类。
-
有两种实现方式:
- JDK 动态代理,被代理对象必须实现接口,利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用 InvokeHandler 来处理;
- 字节码实现(比如说 cglib/asm 等),得用 ASM 开源包,将代理对象类的 class 文件加载进来,通过修改其字节码生成子类来处理。
动态代理是和反射机制一脉相承的,其核心也就是两个:动态 + 代理。动态是指在在运行时生成的代理类,与此对应的就是静态代理,代理类在程序编译时就实现。现在很多框架都是利用类似机制来提供灵活性的扩展性,比如用来包装 RPC 调用,面向切面编程(AOP)等。
从 JDK 1.3 开始,Java 提供了原生动态代理技术,允许开发者在运行时创建接口的代理实例,主要包括 Proxy 类和 InvocationHandler 接口。
代码示例:JDK 动态代理
1.假设有一个写文件的需求,首先需要定义接口:
public interface Writer {
public void write(String fileName, String str);
public void write(String fileName, byte[] bs);
}
复制代码
2.再定义写文件的类并实现Writer接口
public class FileWriter implements Writer {
@Override public void write(String fileName, String str) {
System.out.println("call write str in FileWriter");
}
@Override
public void write(String fileName, byte[] bs) {
System.out.println("call write bytes in FileWriter");
}
}
复制代码
如果我们要写文件的话,通过 Writer writer = new FileWriter() 就可以实现了。假设某天突然来了个需求,说我们写磁盘的时候要判断
服务器存储空间是否达到某个临界值,如果达到了就不能再写了。对于这个需求来说我们可以直接修改 FileWriter 类来实现,但这样做有
两个问题:
-
改现有代码风险高,可能改动过程中影响原有逻辑,不符合开闭原则——对扩展开放,对修改关闭
-
这个需求跟写文件的业务无关,直接放在业务代码里面会导致耦合度比较大,不利于维护
通过代理模式就可以避免上述两个问题,接下来我们看看如何利用 Java 的动态代理来实现这个需求。
首先,我们需要创建一个实现 java.lang.reflect.InvocationHandler 接口的类:
public class FileWriterInvocationHandler implements InvocationHandler {
Writer writer = null;
public FileWriterInvocationHandler(Writer writer) {
this.writer = writer;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Exception {
boolean localNoSpace = false;
System.out.println("check local filesystem space."); //检测磁盘空间代码,返回值 //可以更新 localNoSpace 变量
if (localNoSpace) {
throw new Exception("no space."); //如果空间不足,抛出空间不足的异常
}
return method.invoke(writer, args); //调用真实对象(FileWriter)的方法
}
}
复制代码
FileWriterInvocationHandler 的构造方法中会保存实际用于写文件的对象,即 FileWriter 对象。 invoke 方法中先检查磁盘,如果没问题再调用文件的写方法 method.invoke(writer, args) ,这个写法是Java反射机制提供的。看起来 invoke 方法就是我们想要的功能。 但我们要怎么调用 invoke 呢?这里就用到 Java 的动态代理技术了,在运行时将 Writer 接口动态地跟代理对象(FileWriterInvocationHandler对象)绑定在一起。
下面,我们看看如何创建代理对象并进行绑定:
public class DynamicProxyDriver {
public static void main(String[] args) {
/**
* Proxy.newProxyInstance 包括三个参数
* 第一个参数:定义代理类的 classloader,一般用被代理接口的 classloader
* 第二个参数:需要被代理的接口列表
* 第三个参数:实现了 InvocationHandler 接口的对象
* 返回值:代理对象
*/
Writer writer = (Writer) Proxy.newProxyInstance(
Writer.class.getClassLoader(),
new Class[]{Writer.class},
new FileWriterInvocationHandler(new FileWriter())); //这就是动态的原因,运行时才创建代理类
try {
writer.write("file1.txt", "text"); //调用代理对象的write方法
} catch (Exception e) {
e.printStackTrace();
}
writer.write("file2.txt", new byte[]{}); //调用代理对象的write方法
}
}
复制代码
通过Java语言提供的 Proxy.newProxyInstance() 即可创建 Writer 接口的动态代理对象,代码注释中有该方法的参数说明。对照本例,简
单梳理一下Java动态代理机制
-
当通过 Proxy.newProxyInstance() 创建代理对象后,在 Writer 接口中调用 write 方法便会跳转到 FileWriterInvocationHandler 对象的 invoke 方法中执行
-
比如,执行 writer.write("file1.txt", "text"); 时,程序跳转到 invoke 方法,它的第二个参数 method 对象是 write 方法,第三个
参数 args 是调用 write 方法的实参 file1.txt 和 text 。3. invoke 方法中的最后一行 method.invoke(writer, args); 代表 method 方法(即 write 方法)由 writer 对象调用,参数是 args ,
跟 writer.write("file1.txt", "text") 是一样的。
这样我们就通过代理模式既实现新需求,有没有修改现有的代码。经过上述的讲解,希望你对代理模式的概念和优势有一定的了解。
代理模式除了上述提到的用处外,还有一个用处是转发调用请求,以 Manis 为例,假如我们为 ClientProtocol 创建一个代理对象 manisD
b ,在 manisDb 上调用 getTableCount 方法时,便会跳转到代理对象的 invoke 方法中执行,在 invoke 方法中我们就可以将调用的方法和
参数反序列化,并通过网络发送服务端,这就实现了调用请求的转发。