Java反射-动态代理
使用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之前,之后或者之后,调用其他对象上的其他方法。