使用反射可以在运行时动态实现接口。这可以使用类java.lang.reflect.Proxy
。这个类的名称是我将这些动态接口实现称之为动态代理的原因。动态代理有多种不同的用途,例如,数据库连接和事务管理、用于单元测试的动态模拟对象、其他类似AOP的方法拦截。
创建代理
可以使用Proxy.newProxyInstance()
方法创建动态代理。newProxyInstance()
方法有3个参数:
- "load"动态代理类的
ClassLoader
- 需要实现的接口数组
- 接收所有方法转发的
InvocationHandler
示例如下:
InvocationHandler handler = new MyInvocationHandler();
MyInterface proxy = (MyInterface) Proxy.newProxyInstance(
MyInterface.class.getClassLoader(),
new Class[] { MyInterface.class },
handler);
运行代码后,变量proxy
包含一个MyInterface
接口的动态实现。所有对代理对象的调用将被转发到InvocationHandler
接口的实现handler
。InvocationHandler
会在下一节介绍。
调用处理程序
前文已经提到,必须传一个InvocationHandler
实现给Proxy.newProxyInstance()
方法。所有动态代理调用都会传给InvocationHandler
实现。InvocationHandler
接口代码如下:
public interface InvocationHandler{
Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
示例实现如下:
public class MyInvocationHandler implements InvocationHandler{
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
//do something "dynamic"
}
}
传给invoke()
方法的proxy
参数是实现了对应接口的动态代理对象。通常情况下,你不需要这个对象。
传给invoke()
方法的Method
对象代表调用动态代理实现的接口的方法。从Method
对象可以获得方法名称、参数类型、返回值类型等。Methods 节有关于方法更详细的描述。Object[] args
数组包含动态代理对象被调用的方法需要使用的参数。注意:基本类型(int、lang等)在动态代理中需要使用它们的包装类型(Integer、Long等)。
已知用例
已知动态代理至少用于以下目的:
- 数据库连接和事务管理
- 用于单元测试的动态对象模拟
- 适配DI容器以自定义工厂接口
- 类AOP的方法拦截
数据库连接与事务管理
Spring框架有一个事务代理,可以开启、提交/回滚事务。它是如何工作的,在文章Advanced Connection and Transaction Demarcation and Propagation中有详细讲解,所以这里只做简要介绍。调用序列和下面的流程类似:
web controller --> proxy.execute(...);
proxy --> connection.setAutoCommit(false);
proxy --> realAction.execute();
realAction does database work
proxy --> connection.commit();
单元测试动态对象模拟
Butterfly Testing Tools 利用动态代理实现单元测试的动态存根、代理和代理。当使用类B(真实接口)测试类A,可以传一个B的模拟实现给A以代替真实的B。所有的对B的方法调用都会被记录,也可以去设置B模拟对象的返回值。
此外,Butterfly Testing Tools允许您包装真实B为模拟B,所以对模拟B方法的调用都会被记录,并且可以转发到真实B。所以,这可以用来检测调用真实B的方法。例如,如果测试一个Dao,你可以包装数据库连接为一个模拟对象。Dao是不知道区别的,且Dao可以按常用方法读写数据,这是因为模拟对象转发给了数据库连接。但是,现在你也通过模拟对象检查Dao连接的属性,例如如果调用了connection.close()
(或者没有调用),如果你期望的话。这通常不可能从DAO的返回值来确定。
DI容器与自定义工厂的适配
依赖注入容器Butterfly Container有一个非常强大的特性,它允许你把整个容器注入到它创建的bean中。但是,你可能不希望在容器接口上有依赖,容器能自动适配你自定义的工厂接口。你只需要接口,不需要实现。因此,工厂接口和你的类可能像这样:
public interface IMyFactory {
Bean bean1();
Person person();
...
}
public class MyAction{
protected IMyFactory myFactory= null;
public MyAction(IMyFactory factory){
this.myFactory = factory;
}
public void execute(){
Bean bean = this.myFactory.bean();
Person person = this.myFactory.person();
}
}
当MyAction
类调用容器通过构造函数注入的IMyFactory
实例的方法时,方法调用被转化为调用IContainer.instance()
的方法,这是从容器的实例获取的方法。因此,一个对象可以把Butterfly Container作为一个运行时工厂使用,而不仅仅是在创建时注入。并且,这对Butterfly Container 接口没有特殊依赖。
类AOP的方法拦截
Spring框架可以拦截所有对bean的方法调用,假如bean实现了某些接口。Spring框架把bean包装成动态代理。所有bean调用都被动态代理截获。代理在把调用请求委派给bean之前或之后可以决定去调用其他对象的其他方法。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。