`

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接口, 基本都能写出来,只是写的好与坏,是不是低耦合、高内聚的代码。类的命名是不是让别人一看就懂,这些都决定着一个人水平的高低;

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

 

先分享到这里.....

  • 大小: 48.3 KB
分享到:
评论
5 楼 donaldfischer 2016-12-10  
mybatis 源码例子
4 楼 小码虫 2015-12-30  
3 楼 zhch152 2015-04-19  
2 楼 该用户名已经存在 2014-01-17  
不错不错,谢谢分享!
1 楼 yepeilong09 2012-12-29  
  解释的很好

相关推荐

    mybatis 分页拦截器及拦截器配置

    MyBatis中的拦截器(Interceptor)是基于Java的动态代理机制实现的,它可以拦截执行SQL的生命周期中的某些环节,如:预处理、结果映射等。在分页拦截器中,它会在执行查询之前对SQL进行修改,自动添加LIMIT和OFFSET...

    mybatis拦截器实现通用权限字段添加的方法

    MyBatis拦截器实现通用权限字段添加的方法 MyBatis拦截器是一种非常实用的技术,可以用来实现各种复杂的数据库操作。本文将详细介绍如何使用MyBatis拦截器来实现通用权限字段添加,达到灵活、可靠、可维护的数据库...

    Mybatis分页拦截器

    总的来说,Mybatis分页拦截器是提升项目效率的一个重要工具,它通过插件化的方式,使开发者能方便地为Mybatis添加自定义的分页功能,同时保持代码的整洁和可维护性。在不同版本的Mybatis中,正确理解和使用拦截器,...

    mybatis分页拦截器(自动封装版)剖析.pdf

    MyBatis 分页拦截器是一种优化数据库操作的技术,它的主要目的是在不修改大量现有业务代码的情况下,实现对数据库查询结果的自动分页。在自动封装版的MyBatis分页拦截器中,开发者通常会创建一个拦截器类,该类会...

    通过Mybatis拦截器自动定位慢SQL并记录日志

    Mybatis拦截器(Interceptor)是一种插件机制,它允许我们在Mybatis执行SQL语句之前或之后进行自定义操作,比如统计SQL执行时间、添加日志等。拦截器基于Java的动态代理实现,可以拦截Mapper接口方法的调用。 接...

    Mybatis自定义拦截器,对模糊查询传值的特殊字符(\,_,%)统一进行转义处理的代码

    代码包含: EscapeUtil.java:特殊字符(\,_,%)转义工具类 MyQueryInterceptor.java: Mybatis自定义拦截器 注意:该拦截器只支持QueryWrapper的like方法,serviceImpl层传全角模糊查询(%%) mapper或xml层的全角模糊查询(%...

    MyBatis拦截器:给参数对象属性赋值的实例

    MyBatis拦截器是MyBatis框架中的一种插件机制,允许用户自定义代码来扩展MyBatis的功能。在这个特定的实例中,我们讨论的是一个用于给参数对象属性赋值的拦截器。这个拦截器的主要目标是在执行增删改操作时,自动为...

    mybatis使用拦截器实现分页操作

    在MyBatis框架中,拦截器(Interceptor)是一种强大的工具,可以用来扩展MyBatis的行为。在本案例中,我们将探讨如何利用MyBatis的拦截器功能实现分页操作,以此来提高代码的复用性和可维护性。下面将详细阐述这一...

    Mybatis自定义拦截器,对模糊查询传值的特殊字符统一进行转义处理的代码

    特殊字符(\,_,%)转义工具类 MyQueryInterceptor.java: Mybatis自定义拦截器 注意:该拦截器只支持QueryWrapper的like方法,serviceImpl层传全角模糊查询(%%) mapper或xml层的全角模糊查询(%*%)和半角模糊查询(%*或*%)

    Mybatis拦截器记录数据更新历史记录到MongoDB

    在“Mybatis拦截器记录数据更新历史记录到MongoDB”这个项目中,我们需要创建一个自定义的拦截器类,该类需要实现`org.apache.ibatis.plugin.Interceptor`接口并覆写`intercept`方法。在这个方法里,我们可以捕获到...

    MyBatis拦截器分页与动态修改SQL及其参数值

    1. **MyBatis拦截器**:MyBatis提供了一种插件机制,即拦截器(Interceptor),它基于Java的动态代理,可以在SQL执行前或执行后进行干预。例如,我们可以创建一个自定义的拦截器,来实现特定的功能,如日志记录、...

    mybatis拦截器修改执行sql语句

    通过mybatis拦截器将查询语句、更新语句、删除语句、插入语句中指定表明替换为另一个表名

    MyBatis拦截器 添加查询条件动态修改sql

    通过mybatis的拦截器,实现为所有sql(或指定sql) 统一添加查询条件,譬如通过线程变量传递某参数(日期),来实现对指定参数的数据筛选,而不需要在每个查询前,手动将该条件注入到查询中。因该资料网络较少,故特此...

    mybatis分页拦截器

    1. **拦截器接口**:在MyBatis中,所有的拦截器都需要实现`Interceptor`接口,该接口定义了一个`intercept`方法,这个方法会在指定的执行点被调用。在分页拦截器中,我们通常在这个方法中处理分页逻辑。 2. **配置...

    mybatis拦截器的完整实现

    MyBatis使用了Java的动态代理和AOP(面向切面编程)思想,拦截器实际上是一个实现了`org.apache.ibatis.plugin.Interceptor`接口的类。当MyBatis执行Mapper的方法时,会通过拦截器链对方法进行处理。每个拦截器都有`...

    MyBatis拦截器分页

    MyBatis拦截器是基于Java的动态代理机制,通过实现`Interceptor`接口,我们可以定义拦截规则,然后在`intercept`方法中执行自定义逻辑。拦截器链是由多个拦截器组成,当执行一个SQL时,每个拦截器的`intercept`方法...

    springboot+mybatis拦截器实现自动分页

    2. **配置拦截器**: 在Spring Boot的配置文件`application.yml`或`application.properties`中,我们需要配置MyBatis的拦截器,并将我们创建的`PaginationInterceptor`加入到拦截器链中。 ```yaml mybatis: ...

Global site tag (gtag.js) - Google Analytics