Better

业精于勤荒于嬉

Java反射11-动态代理

Better's Avatar 2017-02-26 Java译文

  1. 1. 创建代理
  2. 2. InvoicationHanlder‘s
  3. 3. 已知用例
    1. 3.1. 数据库连接和事务管理
    2. 3.2. 单元测试中动态模拟对象
    3. 3.3. Adaptation of DI Container to Custom Factory Interfaces
    4. 3.4. AOP-like Method Interception
  • Creating Proxies
  • InvocationHandler’s
  • Known Use Cases
  • Database Connection and Transaction Management
  • Dynamic Mock Objects for Unit Testing
  • Adaptation of DI Container to Custom Factory Interfaces
  • AOP-like Method Interception

Java反射使得你可以在程序运行时能动态实现接口。你可以通过java.lang.reflect.Proxy来实现。这个类的名称是为什么我将这些动态接口实现称为动态代理。动态代理在许多不同情形下回使用。比如数据库的链接,事务管理,单元测试的动态摸你对象,还有像AOP比如方法拦截。

创建代理

你可以使用Proxy.newProxyInstance()方法创建代理。方法需要3个参数:

  1. 类加载器(ClssLoader)用于动态的加载类。
  2. 一组接口的数组将要被实现的。
  3. 一个用于所有方法去调用的代理的

InvocationHandler
例子如下:

1
2
3
4
5
InvocationHandler handler = new MyInvocationHandler();
MyInterface proxy = (MyInterface) Proxy.newProxyInstance(
MyInterface.class.getClassLoader(),
new Class[] { MyInterface.class },
handler);

在运行了这段代码过后,proxy对象就动态的实现了MyInterface接口。所有调用proxy的方法都传递到实现了InvoicationHanler接口的handler对象。接下来就讲解InvoicationHandler

InvoicationHanlder‘s

正如前文Proxy.newInstance()方法必须提供一个InvoicationHanler。对动态代理的所有方法调用都将转发到此InvocationHandler实现。InvoicationHanler是这样定义的:

1
2
3
4
public interface InvocationHandler{
Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}

一个实现的接口的列子:

1
2
3
4
5
6
7
public class MyInvocationHandler implements InvocationHandler{

public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
//do something "dynamic"
}
}

传递给invoke()方法的参数proxy是实现了接口的动态代理对象。通常你不需要这个对象。
传递给invoke()方法的参数Method对象表示的是调用代理实现方法的方法。对于这个Method对象你可以获取到方法的名称,参数类型,返回类型等。参见前面的‘Method’文章。
这个Object[]数组包含了方法调用的时候传递给proxy对象对应的参数。
注意:在实现的接口里面,基本的数据类型(int,long)被包裹成对应的对象(Intger,Long等)。

已知用例

动态代理被用于至少在以下的情形:

  • 数据库连接和事务管理
  • 单元测试模拟对象
  • Adaptation of DI Container to Custom Factory Interfaces
  • AOP方法拦截

数据库连接和事务管理

Spring框架为你提供了一个可以提交,回滚一个事物的事物代理。至于具体是怎样工作的在文章 Advanced Connection and Transaction Demarcation and Propagation有具体的描述,所以我只是简单的阐述一下。调用顺序如下:

1
2
3
4
5
web controller --> proxy.execute(...);
proxy --> connection.setAutoCommit(false);
proxy --> realAction.execute();
realAction does database work
proxy --> connection.commit();

单元测试中动态模拟对象

Butterfly Testing Tools使用动态代理实现动态存根,模拟和代理单元测试。当测试类A用到了另外的类B(接口),你可以传递一个B的模拟实现到A而不是传递一个真的B。所有调用B的方法都被记录了,你可以设置返回的B的值。

Adaptation of DI Container to Custom Factory Interfaces

依赖注入容器Butterfly Container具有一个强大的功能,允许您将整个容器注入由其生成的bean中。但是如果您不希望依赖容器接口,容器能够适应您的设计的自定义工厂接口。你仅仅需要的是这些接口。没有被实现的。因此工厂的接口和你的类大概是这个样子:

1
2
3
4
5
public interface IMyFactory {
Bean bean1();
Person person();
...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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类调用通过MyAction类构造方法传递进来的IMyFactory对象的方法时,方法调用转换为对IContainer.instance()方法的调用,IContainer.instance()方法是用于从容器获取实例的方法。这就是为什么一个对象能够在运行时把Butterfly Container当做一个工厂,而不仅仅是在创建时将依赖注入自身。并且这在Butterfly Container没有任何的的依赖。

AOP-like Method Interception

在Spring框架中是有可能在指定的Bean调用方法之前拦截该方法的,理由是bean实现了某些接口方法。Spring框架将bean包装在动态代理中。所有对bean的bean调用都被拦截了。动态代理也能在对其他对象调用的时候进行检查,而不是在将方法调用委托给bean包装之后。

原文

This article was last updated on days ago, and the information described in the article may have changed.