网上关于 Java 的动态代理, Proxy 和 InvocationHandler 这些概念有讲解得非常高深的文章. 其实这些概念没有那么复杂. 现在咱们通过一个最简单的例子认识什么是 InvocationHandler. 值得一提的是, InvocationHandler 在 Spring 框架实现中被广泛使用, 这意味着我们吃透了 InvocationHandler, 就为将来的 Spring 源码学习打下一个坚实的基础.
开发一个接口, 包含两个方法, 可以向指定的人问候 "你好" 或者 "再见".
- public interface IHello {
- void sayHello(String name);
- void sayGoogBye(String name);
- }
创建一个简单的类, 实现这个 IHello 接口.
- public class Helloimplements implements IHello {
- @Override
- public void sayHello(String name) {
- System.out.println("Hello" + name);
- }
- @Override
- public void sayGoogBye(String name) {
- System.out.println(name+"GoodBye!");
- }
- }
消费这个实现类, 迄今为止没什么特别的.
现在假设我们接到了这个需求: 老板要求在该实现类每次问候某人时, 必须把问候的细节记录到日志文件里. 为了简单起见, 我们在问候前打印下面的一行语句来模拟日志记录的动作.
System.out.println("问候之前的日志记录...");
您也许会说, 这还不简单? 直接修改 Helloimplements 的对应方法, 把这行日志插入到对应方法即可.
然而, 老板的要求是: 不允许你修改原来的 Helloimplements 类. 在现实场景中, Helloimplements 可能是第三方的 jar 包提供的, 我们没有办法修改代码.
您也许会说, 我们可以用设计模式里的代理模式, 即创建一个新的 Java 类作为代理类, 同样实现 IHello 接口, 然后将 Helloimplements 类的实例传入代理类. 我们虽然被要求不允许修改 Helloimplements 的代码, 但是可以把日志记录代码写在代理类里. 完整代码如下:
- public class StaticProxy implements IHello {
- private IHello iHello;
- public void setImpl(IHello impl){
- this.iHello = impl;
- }
- @Override
- public void sayHello(String name) {
- System.out.println("问候之前的日志记录...");
- iHello.sayHello(name);
- }
- @Override
- public void sayGoogBye(String name) {
- System.out.println("问候之前的日志记录...");
- iHello.sayGoogBye(name);
- }
- static public void main(String[] arg) {
- Helloimplements hello = new Helloimplements();
- StaticProxy proxy = new StaticProxy();
- proxy.setImpl(hello);
- proxy.sayHello("Jerry");
- }
- }
这种做法能够实现需求:
下面我们再看如何用 InvocationHandler 实现同样的效果.
InvocationHandler 是一个 JDK 提供的标准接口. 看下面的代码:
- import java.lang.reflect.InvocationHandler;
- import java.lang.reflect.Method;
- import java.lang.reflect.Proxy;
- public class DynaProxyHello implements InvocationHandler {
- private Object delegate;
- public Object bind(Object delegate) {
- this.delegate = delegate;
- return Proxy.newProxyInstance(
- this.delegate.getClass().getClassLoader(), this.delegate
- .getClass().getInterfaces(), this);
- }
- public Object invoke(Object proxy, Method method, Object[] args)
- throws Throwable {
- Object result = null;
- try {
- System.out.println("问候之前的日志记录...");
- // JVM 通过这条语句执行原来的方法 (反射机制)
- result = method.invoke(this.delegate, args);
- } catch (Exception e) {
- e.printStackTrace();
- }
- return result;
- }
上面代码里的 bind 方法很想我之前代理类 StaticProxy 的 setImpl 方法, 只不过这个 bind 方法的输入参数类型更加通用. 日志记录的代码写在方法 invoke 里.
看看如何使用:
- static public void main(String[] arg) {
- DynaProxyHello helloproxy = new DynaProxyHello();
- Helloimplements hello = new Helloimplements();
- IHello ihello = (IHello) helloproxy.bind(hello);
- ihello.sayHello("Jerry");
- }
- }
执行效果和 StaticProxy 那种解决方案完全一致.
咱们先来调试一下. 当 bind 方法执行时, 方法 Proxy.newProxyInstance 被调用, Helloimplements 类的实例被传入.
我们在调试器里观察 IHello ihello = (IHello) helloproxy.bind(hello) 这行语句返回的 ihello 变量. 虽然它的静态类型是 IHello, 但请注意, 在调试器里观察它的实际类型, 并不是 Helloimplements 的实例, 而是 JVM 给我们加过工的, 包含了我们在 invoke 方法里手写的那行日志记录代码. 这个 ihello 类型为 $Proxy0.
当这个被 JVM 加过工的变量的 sayHello 方法被调用时, JVM 自动将调用转交到 DynaProxyHello.invoke 去:
于是, 在 invoke 方法里, 我们手写的日志记录代码被执行, 然后通过 Java 反射执行原始的 sayHello 代码.
有的朋友可能会问, 你这个 InvocationHandler 看起来比静态代理 StaticProxy 还复杂啊? 有什么好处?
假设老板的需求又变了, 在调用问候和说再见的方法里, 要使用不同的日志记录策略.
看看用 InvocationHandler 如何优雅实现吧:
希望这个例子能让大家对 Java 的动态代理之 InvocationHandler 有了最基本的了解.
来源: http://www.bubuko.com/infodetail-3063151.html