Java反射-动态代理

时间:2020-01-09 10:36:19  来源:igfitidea点击:

使用Java Reflection,我们可以在运行时创建接口的动态实现。我们可以使用类java.lang.reflect.Proxy来实现。此类的名称是为什么我将这些动态接口实现称为动态代理的原因。动态代理可以用于许多不同的目的,例如数据库连接和事务管理,用于单元测试的动态模拟对象以及其他类似AOP的方法拦截目的。

创建代理

我们可以使用Proxy.newProxyInstance()方法创建动态代理。 newProxyInstance()方法采用3个参数:

  • " 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的

如前所述,我们必须将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方法对象表示在动态代理实现的接口上调用的方法。从"方法"对象中,我们可以获取方法名称,参数类型,返回类型等。有关更多信息,请参见"方法"上的文本。

" Object [] args"数组包含在调用实现的接口中的方法时传递给代理的参数值。注意:已实现的接口中的基元(int,long等)被包装在它们的对象副本(integer,Long等)中。

已知用例

已知动态代理至少可用于以下目的:

  • 数据库连接和事务管理
  • 用于单元测试的动态模拟对象
  • DI容器适应定制工厂接口
  • 类似于AOP的方法拦截

数据库连接和事务管理

Spring框架具有一个事务代理,可以为我们启动和提交/回滚事务。在高级连接以及事务划分和传播一文中将更详细地描述其工作方式,因此,我将仅对其进行简要描述。调用顺序与此类似:

web controller --> proxy.execute(...);
  proxy --> connection.setAutoCommit(false);
  proxy --> realAction.execute();
    realAction does database work
  proxy --> connection.commit();

用于单元测试的动态模拟对象

Butterfly Testing工具利用动态代理来实现用于单元测试的动态存根,模拟和代理。测试使用另一个类B(实际上是接口)的类A时,可以将B的模拟实现传递给A而不是真实的B。现在记录了B上的所有方法调用,并且可以设置模拟B的返回值是要返回。

此外,Butterfly Testing Tools允许我们将实数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之前,之后或者之后,调用其他对象上的其他方法。