首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 其他教程 > 开源软件 >

MyBatis之拦截器interceptor代码评析

2012-09-24 
MyBatis之拦截器interceptor代码赏析???? 拦截器已经是各个开源软件必不可少的功能。 在讨论各种问题的时候

MyBatis之拦截器interceptor代码赏析

???? 拦截器已经是各个开源软件必不可少的功能。 在讨论各种问题的时候也经常听说这个对象被拦截了等等。那么在JAVA的世界里, 是怎么实现拦截器的功能的呢 ?? 要了解这些, 必须先从代理类(Proxy)说起,但是我们在这里不打算从这里介绍,我们直接上Mybatis的测试代码。

????

public class PluginTest {  @Test  public void mapPluginShouldInterceptGet() {    Map map = new HashMap();    map = (Map) new AlwaysMapPlugin().plugin(map);    assertEquals("Always", map.get("Anything"));  }  @Test  public void shouldNotInterceptToString() {    Map map = new HashMap();    map = (Map) new AlwaysMapPlugin().plugin(map);    assertFalse("Always".equals(map.toString()));  }  @Intercepts({      @Signature(type = Map.class, method = "get", args = {Object.class})})  public static class AlwaysMapPlugin implements Interceptor {    public Object intercept(Invocation invocation) throws Throwable {      return "Always";    }    public Object plugin(Object target) {      return Plugin.wrap(target, this);    }    public void setProperties(Properties properties) {    }  }}

?

MyBatis 直接抽象了一个plugin的概念,中文意思就是“插头”吧,很形象。我要对一个对象拦截,我就要把这个插头插入到目标对象中:

map = (Map) new AlwaysMapPlugin().plugin(map);

?

这里生成了一个AlwaysMapPlugin,并调用plugin方法,把自己插入到Map的一个实例对象中;我们先不管plugin方法里面到底做了什么,我们只要知道它还是给我返回了一个Map的实例。

????

assertEquals("Always", map.get("Anything"));

?

接着我们就调用了Map实例的get方法,我们纳闷了,我们并没有给Map实例中添加任何数据,但是却能得到"Always”。

很神奇吧...

要解开这个神奇,还是要回到AlwaysMapPlugin().plugin(Object object)的方法上来。

public Object plugin(Object target) {      return Plugin.wrap(target, this);}

直接委托给了Plugin类的一个静态方法:

??

public static Object wrap(Object target, Interceptor interceptor) {    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);    Class<?> type = target.getClass();    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);    if (interfaces.length > 0) {      return Proxy.newProxyInstance(          type.getClassLoader(),          interfaces,          new Plugin(target, interceptor, signatureMap));    }    return target;  }

我们先抛开乱七八糟的逻辑,我们看到在满足一定的条件的情况下,会返回一个Proxy.newProxyInstance类的一个实例。也就是说AlwaysMapPlugin().plugin(Object object)的方法返回的实例,是有可能和原来的实例不是同一个,而是原来实例的一个代理类。

public static Object newProxyInstance(ClassLoader loader,  Class<?>[] interfaces,  InvocationHandler h)

?这个是Proxy.newProxyInstance方法的签名,不明白的同志自己去了解下。

我们已经得到了目标对象的代理类,可是我们只想对目标对象的某几个方法进行拦截, 是怎么做到的呢?

这次我们把目光转向我们的“插头”吧。

???

@Intercepts({      @Signature(type = Map.class, method = "get", args = {Object.class})})  public static class AlwaysMapPlugin implements Interceptor {    public Object intercept(Invocation invocation) throws Throwable {      return "Always";    }    public Object plugin(Object target) {      return Plugin.wrap(target, this);    }    public void setProperties(Properties properties) {    }  }

?

这里通过注解告诉我们,会对Map.get(Object obj)的方法进行拦截。而且在拦截之后,直接返回了Always, 这就是最终的原因了。

?

罗里吧嗦一大堆,我们直接看MyBatis里面抽象的几个相关类吧:

?

1.? Plugin??类:也就是我们说的“插板”类,?继承了InvocationHandler 接口,主要的职责是将目标对象、拦截器组装成一个新的代理类。 简单的代码如下:

public class Plugin implements InvocationHandler {  private Object target; // 目标对象  private Interceptor interceptor; //拦截器对象  private Map<Class<?>, Set<Method>> signatureMap; //目标对象的方法签名  private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {    this.target = target;    this.interceptor = interceptor;    this.signatureMap = signatureMap;  }  public static Object wrap(Object target, Interceptor interceptor) {    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);    Class<?> type = target.getClass();    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);    if (interfaces.length > 0) {      return Proxy.newProxyInstance(  // 返回一个目标对象的代理类          type.getClassLoader(),          interfaces,          new Plugin(target, interceptor, signatureMap));    }    return target;  }  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {    try {      Set<Method> methods = signatureMap.get(method.getDeclaringClass());      if (methods != null && methods.contains(method)) {        return interceptor.intercept(new Invocation(target, method, args));//对目标对象中的某个方法进行拦截,将相关的实现给到了拦截器的实现类      }      return method.invoke(target, args);    } catch (Exception e) {      throw ExceptionUtil.unwrapThrowable(e);    }  }

?2. Interceptor 拦截器的接口:

?

public interface Interceptor {  Object intercept(Invocation invocation) throws Throwable; // 对方法进行拦截的抽象方法  Object plugin(Object target); //把拦截器插入到目标对象的方法  void setProperties(Properties properties);}

?3. Invocation 真正对目标类方法的拦截的实现, 这里没有什么说的。 值得一提的是, 如果我们想实现类似spring的拦截器,比如说前置通知、后置通知、环绕通知等,应该是可以在这里做文章的。

public class Invocation {  private Object target;  private Method method;  private Object[] args;  public Invocation(Object target, Method method, Object[] args) {    this.target = target;    this.method = method;    this.args = args;  }  public Object getTarget() {    return target;  }  public Method getMethod() {    return method;  }  public Object[] getArgs() {    return args;  }  public Object proceed() throws InvocationTargetException, IllegalAccessException {    return method.invoke(target, args);  }}

?4. 搞定。

?

总结:其实拦截器还是很简单的, 只需要熟识了Proxy类、InvocationHandler接口, 基本都能写出来,只是写的好与坏,是不是低耦合、高内聚的代码。类的命名是不是让别人一看就懂,这些都决定着一个人水平的高低;

拦截器使用的场景: 比如说 权限、日志记录、缓存等等;

?

先分享到这里.....

热点排行