学习AOP时遇到关于InvocationHandler接口的问题

动态代理是不少框架和技术的基础, spring 的AOP实现就是基于动态代理实现的。了解动态代理的机制对于理解AOP的底层实现是颇有帮助的。 

       查看doc文档就能够知道,在java.lang.reflect包中有一个叫Proxy的类。下面是doc文档对Proxy类的说明: 

       "A dynamic proxy class (simply referred to as a proxy class below) is a class that implements a list of interfaces specified at runtime when the class is created, with behavior as described below. A proxy interface is such an interface that is implemented by a proxy class. A proxy instance is an instance of a proxy class. Each proxy instance has an associated invocation handler object, which implements the interface InvocationHandler." 

        Proxy类的设计用到代理模式的设计思想,Proxy类对象实现了代理目标的全部接口,并代替目标对象进行实际的操做。但这种替代不是一种简单的替代,这样没有任何意义,代理的目的是在目标对象方法的基础上做加强,这种加强的本质一般就是对目标对象的方法进行拦截。因此,Proxy应该包括一个方法拦截器,来指示当拦截到方法调用时做何种处理。InvocationHandler就是拦截器的接口。 

      InvocationHandler接口也是在java.lang.reflec 

     Object invoke(Object proxy, Method method, Object[] args) 

     这个接口有三个参数,其中第二和第三个参数都比较好理解,一个是被拦截的方法,一个是该方法的参数列表。关键是第一个参数。按照doc文档的解析, 

      proxy - the proxy instance that the method was invoked on 

      也就是说,proxy应该是一个代理实例,但为何要传入这个参数呢? 

      带着这个问题,本身编了个小程序做了一点试验。 

/////////////////////////////////////// 

      public interface IAnimal { 
           void info(); 
      } 

//////////////////////////////////// 

    public class Dog implements IAnimal 

    { 

          public void info() { 
             System.out.println("I am a dog!"); 
          } 
    } 

/////////////////////////////////////// 
import java.lang.reflect.*; 

public class ProxyTest { 
public static void main(String[] args) throws InterruptedException { 
  final IAnimal animal = new Dog(); 
  Object proxyObj =Proxy.newProxyInstance( 
    animal.getClass().getClassLoader(), 
    animal.getClass().getInterfaces(), 
    new InvocationHandler() 
    { 
     public Object invoke(Object proxy, Method method, Object[] args) 
     { 
      try { 
       System.out.println("被拦截的方法:" + method.getName()); 
       return method.invoke(animal, args); 
      } 
      catch (IllegalArgumentException e) { 
       // TODO Auto-generated catch block 
       e.printStackTrace(); 
       return null; 
      } catch (IllegalAccessException e) { 
       // TODO Auto-generated catch block 
       e.printStackTrace(); 
       return null; 
      } catch (InvocationTargetException e) { 
       // TODO Auto-generated catch block 
       e.printStackTrace(); 
       return null; 
      } 
     } 
    }); 
  if(proxyObj instanceof IAnimal) 
  { 
   System.out.println("the proxyObj is an animal!"); 
  } 
  else 
  { 
   System.out.println("the proxyObj isn't an animal!"); 
  } 
  
  if(proxyObj instanceof Dog) 
  { 
   System.out.println("the proxyObj is a dog!"); 
  } 
  else 
  { 
   System.out.println("the proxyObj isn't a dog!"); 
  } 
  
  IAnimal animalProxy = (IAnimal)proxyObj; 
  animalProxy.info(); 
  animalProxy.hashCode(); 
  System.out.println(animalProxy.getClass().getName().toString()); 



程序执行的结果以下: 

the proxyObj is an animal! 
the proxyObj isn't a dog! 
被拦截的方法:info 
I am a dog! 
被拦截的方法:hashCode 
$Proxy0 

从结果能够看出如下几点: 

1. proxyObj 是一个实现了目标对象接口的对象,而不一样于目标对象。也就是说,这种代理机制是面向接口,而不是面向类的。 

2. info方法(在接口中)被成功拦截了,hashCode方法也成功被拦截了,但意外的是,getClass方法(继承自Object 类的方法)并无被拦截!! 

3. 应用调试还能够看出Invocation接口中invoke方法的传入的proxy参数确实就是代理对象实例proxyObj 

为什么getClass()没有被拦截?proxy参数又有何用呢? 

先无论,作一个试验看看。既然这个proxy参数就是代理实例对象,它理所固然和proxyObj是同样的,能够调用info等方法。因而咱们能够在invoke方法中加上以下一条语句: 

((IAnimal)proxy).info(); 

结果是: 

the proxyObj is an animal! 
the proxyObj isn't a dog! 
被拦截的方法:info 
被拦截的方法:info 

....... 

被拦截的方法:info 
被拦截的方法:info 

而后就是栈溢出 

结果是很明显的,在invoke方法中调用proxy中的方法会再一次引起invoke方法,这就陷入了死循环,最终结果固然是栈溢出的。 

能够在invoke方法中调用proxy.getClass(), 程序能够正常运行。但若是调用hashCode()方法一样会致使栈溢出。 

       经过上面的试验,能够得出一些初步结论,invoke 接口中的proxy参数不能用于调用所实现接口的方法。奇怪的是hashCode()和getClass()方法都是从Object中继承下来的方法,为何一个能够另外一个不能够呢?带首疑问到doc文档看一下Object中这两个方法,发现getClass()是定义为final的,而hashCode()不是。难道是这个缘由,因而找到一个非final方法,如equals试了一下,真的又会致使栈溢出;找另外一个final方法如wait(),试了一下,invoke又不拦截了。final 难道就是关键之处? 

      还有一个问题就是proxy有什么用?既然proxy能够调用getClass()方法,咱们就能够获得proxy的Class类象,从而能够得到关于proxy代理实例的全部类信息,如方法列表,Annotation等,这就为咱们提供的一个分析proxy的有力工具,如经过分析Annotation分析方法的声明式事务需求。我想传入proxy参数应该是这样一个用意吧。

摘自:
  http://shizukyo.iteye.com/blog/245108
相关文章
相关标签/搜索