Mybatis使用动态代理实现拦截器功能

1、背景介绍

  拦截器顾名思义为拦截某个功能的一个武器,在众多框架中均有“拦截器”。这个Plugin有什么用呢?或者说拦截器有什么用呢?可以想想拦截器是怎么实现的。Plugin用到了Java中很重要的一个特性——动态代理。所以这个Plugin可以理解为,在调用一个方法时,我“拦截”其方法做一些我想让它做的事(包括方法的前与后)。在Mybatis中可以拦截以下方法:

这正是mybatis中大名鼎鼎的四大对象;

2、源码过程跟踪,了解拦截器拦截过程以及原理

 1 //ParameterHandler 处理sql的参数对象
 2 public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
 3     ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
 4     //包装参数插件
 5     parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
 6     return parameterHandler;
 7 }
 8 
 9 //ResultSetHandler 处理sql的返回结果集
10 public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
11                                             ResultHandler resultHandler, BoundSql boundSql) {
12     ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
13     //包装返回结果插件
14     resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
15     return resultSetHandler;
16 }
17 
18 //StatementHandler 数据库的处理对象
19 public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
20     StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
21     //包装数据库执行sql插件
22     statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
23     return statementHandler;
24 }
25 
26 public Executor newExecutor(Transaction transaction) {
27     //创建Mybatis的执行器:Executor
28     return newExecutor(transaction, defaultExecutorType);
29 }
30 
31 public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
32     executorType = executorType == null ? defaultExecutorType : executorType;
33     executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
34     Executor executor;
35     //mybatis支持的三种执行器:batch、reuse、simple,其中默认支持的是simple
36     if (ExecutorType.BATCH == executorType) {
37         executor = new BatchExecutor(this, transaction);
38     } else if (ExecutorType.REUSE == executorType) {
39         executor = new ReuseExecutor(this, transaction);
40     } else {
       //默认为SimpleExecutor
41         executor = new SimpleExecutor(this, transaction);
42     }
      //如果开启二级缓存  则对executor进行缓存包装
43     if (cacheEnabled) {
44         executor = new CachingExecutor(executor);
45     }
46     //包装执行器插件
47     executor = (Executor) interceptorChain.pluginAll(executor);
48     return executor;
49 }

  我们可以看到这四大对象 在创建的过程中 都调用了 (Executor) interceptorChain.pluginAll(Object target)  代码,该代码是怎么样的呢  正是我们所说的拦截器链,将四大对象传入到拦截器链进行处理 然后返回包装后的 四大对象 如果我们在拦截器链中进行拦截处理 则实现了拦截技术;

下面我们看连接器链中的内容:

public class InterceptorChain {

  private final List<Interceptor> interceptors = new ArrayList<Interceptor>();

  //在pluginAll方法中 遍历拦截器集 将传入的target 也就是四大对象进行传入 在interceptor中的Plugin方法中处理
  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }

  public void addInterceptor(Interceptor interceptor) {
    interceptors.add(interceptor);
  }

  public List<Interceptor> getInterceptors() {
    return Collections.unmodifiableList(interceptors);
  }

}

下面我们看一下interceptor对象的源代码:

public interface Interceptor {

  Object intercept(Invocation invocation) throws Throwable;

  Object plugin(Object target);

  void setProperties(Properties properties);

}

从源代码看出 interceptor是一个接口 接口中 有三个方法 分别是 intercept plugin 和 setProperties;下面分别介绍着几个方法;

我们自己写插件或者拦截四大对象后 进行相应功能的添加就在要实现该接口,然后实现接口的三个方法;

 举例实现该接口:

注意: 记住必须使用 注解的方式实现声明拦截器拦截哪个类对象 原因在后面源码中进行分析

@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class }) })
public class ExamplePlugin implements Interceptor {

    /* 此方法用于实现拦截逻辑
     * @see org.apache.ibatis.plugin.Interceptor#intercept(org.apache.ibatis.plugin.Invocation)
     */
    @Override
   public Object intercept(Invocation invocation) throws Throwable
   {

我们看点在plugin方法中 通过Plugin.wrap创建了代理对象 我们来看源代码:看到了Plugin 类实现了 InvocationHandler 是不是感觉很熟悉 这就是我们上篇文章中讲过的 动态代理中的 invocationHandel类

前面说了为什么自己实现拦截器类时 为什么必须使用注解的方式  因为在获得数字签名Map的方法中 存在使用反射获得注解信息的方法

//获得Interceptor注解,@Signature中的type(要拦截的类),method(拦截类的方法)和args(拦截器用于这些类中)

Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);

所以如果没有添加注解方式 则会抛出 throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName())

然后我们看一下wrap方法 该方法就是通过数据签名 然后看数字签名中的是否包含要拦截对象和方法  如果包含 则创建代理对象 返回代理对象

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; //此处的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) {
      //生成代理对象,Plugin对象为该代理对象的InvocationHandler
      return Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }
 
  @Override
  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)) {
        //调用代理类所属拦截器的intercept方法
        return interceptor.intercept(new Invocation(target, method, args));
      }
      return method.invoke(target, args);
    } catch (Exception e) {
      throw ExceptionUtil.unwrapThrowable(e);
    }
  }
 
  private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
    //获得Interceptor注解,@Signature中的type(要拦截的类),method(拦截类的方法)和args(拦截器用于这些类中)
    Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
    // issue #251
    if (interceptsAnnotation == null) {
      throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());      
    }
    //获得注解type,method 和args生成一个signature数组
    Signature[] sigs = interceptsAnnotation.value();
    Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>();
    for (Signature sig : sigs) {
      Set<Method> methods = signatureMap.get(sig.type());
      if (methods == null) {
        methods = new HashSet<Method>();
        signatureMap.put(sig.type(), methods);
      }
      try {
        //获得类的方法
        Method method = sig.type().getMethod(sig.method(), sig.args());
        methods.add(method);
      } catch (NoSuchMethodException e) {
        throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
      }
    }
    return signatureMap;
  }
  //获得所有接口
  private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
    Set<Class<?>> interfaces = new HashSet<Class<?>>();
    while (type != null) {
      //获得接口
      for (Class<?> c : type.getInterfaces()) {
        if (signatureMap.containsKey(c)) {
          interfaces.add(c);
        }
      }
      //获得父类
      type = type.getSuperclass();
    }
    //返回一个接口的数组
    return interfaces.toArray(new Class<?>[interfaces.size()]);
  }
 
}

 到这里 代理对象以及中间对象invocationHandler对象已经创建完成  现在我们看一下 被代理对象 被代理对象就是 statementHandler 该接口的子类  这类的是 

SimpleStatementHandler 

由此可以看到该类实现了 statementHandler接口的方法。

通过看这里的源码也可以看出 其实mysql的底层其实也是使用了 底层的jdbc来实现的!

public abstract class BaseStatementHandler implements StatementHandler {
 
  protected final Configuration configuration;
  protected final ObjectFactory objectFactory;
  protected final TypeHandlerRegistry typeHandlerRegistry;
  protected final ResultSetHandler resultSetHandler;
  protected final ParameterHandler parameterHandler;
 
  protected final Executor executor;
  protected final MappedStatement mappedStatement;
  protected final RowBounds rowBounds;
 
  protected BoundSql boundSql;
 
  protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    this.configuration = mappedStatement.getConfiguration();
    this.executor = executor;
    this.mappedStatement = mappedStatement;
    this.rowBounds = rowBounds;
 
    this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
    this.objectFactory = configuration.getObjectFactory();
 
    if (boundSql == null) { // issue #435, get the key before calculating the statement
      generateKeys(parameterObject);
      boundSql = mappedStatement.getBoundSql(parameterObject);
    }
 
    this.boundSql = boundSql;
 
    this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
    this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
  }
  //获取BoundSql
  public BoundSql getBoundSql() {
    return boundSql;
  }
  //获取ParameterHandler
  public ParameterHandler getParameterHandler() {
    return parameterHandler;
  }
 
  //准备语句
  public Statement prepare(Connection connection) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
      //实例化Statement
      statement = instantiateStatement(connection);
      //设置超时
      setStatementTimeout(statement);
      //设置读取条数
      setFetchSize(statement);
      return statement;
    } catch (SQLException e) {
      closeStatement(statement);
      throw e;
    } catch (Exception e) {
      closeStatement(statement);
      throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
    }
  }
  //如何实例化Statement,交给子类去做
  protected abstract Statement instantiateStatement(Connection connection) throws SQLException;
 
  //设置超时,实际就是调用Statement.setQueryTimeout
  protected void setStatementTimeout(Statement stmt) throws SQLException {
    Integer timeout = mappedStatement.getTimeout();
    Integer defaultTimeout = configuration.getDefaultStatementTimeout();
    if (timeout != null) {
      stmt.setQueryTimeout(timeout);
    } else if (defaultTimeout != null) {
      stmt.setQueryTimeout(defaultTimeout);
    }
  }
  //设置读取条数,其实就是调用Statement.setFetchSize
  protected void setFetchSize(Statement stmt) throws SQLException {
    Integer fetchSize = mappedStatement.getFetchSize();
    if (fetchSize != null) {
      stmt.setFetchSize(fetchSize);
    }
  }
  //关闭Statement
  protected void closeStatement(Statement statement) {
    try {
      if (statement != null) {
        statement.close();
      }
    } catch (SQLException e) {
      //ignore
    }
  }
    
  protected void generateKeys(Object parameter) {
    KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
    ErrorContext.instance().store();
    keyGenerator.processBefore(executor, mappedStatement, null, parameter);
    ErrorContext.instance().recall();
  }
 
}

public class SimpleStatementHandler extends BaseStatementHandler {  
  
  public SimpleStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {  
    super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);  
  }  
  
  @Override  
  public int update(Statement statement) throws SQLException {  
    String sql = boundSql.getSql();  
    Object parameterObject = boundSql.getParameterObject();  
    KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();  
    int rows;  
    if (keyGenerator instanceof Jdbc3KeyGenerator) {  
      statement.execute(sql, Statement.RETURN_GENERATED_KEYS);  
      rows = statement.getUpdateCount();  
      keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);  
    } else if (keyGenerator instanceof SelectKeyGenerator) {  
      statement.execute(sql);  
      rows = statement.getUpdateCount();  
      keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);  
    } else {  
      statement.execute(sql);  
      rows = statement.getUpdateCount();  
    }  
    return rows;  
  }  
  
  @Override  
  public void batch(Statement statement) throws SQLException {  
    String sql = boundSql.getSql();  
    statement.addBatch(sql);  
  }  
  
  @Override  
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {  
    String sql = boundSql.getSql();  
    statement.execute(sql);  
    return resultSetHandler.<E>handleResultSets(statement);  
  }  
  
  @Override  
  protected Statement instantiateStatement(Connection connection) throws SQLException {  
    if (mappedStatement.getResultSetType() != null) {  
      return connection.createStatement(mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);  
    } else {  
      return connection.createStatement();  
    }  
  }  
  
  @Override  
  public void parameterize(Statement statement) throws SQLException {  
    // N/A  
  }  
  
}  

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Spark生态圈

[spark] DAGScheduler 提交stage源码解析

DAGScheduler在划分完Stage后([spark] DAGScheduler划分stage源码解析 ),将会通过submitStage(finalSt...

963
来自专栏IT可乐

mybatis源码解读(二)——构建Configuration对象

1942
来自专栏Spark生态圈

[spark] Task执行流程

在文章TaskScheduler 任务提交与调度源码解析 中介绍了Task在executor上的逻辑分配,调用TaskSchedulerImpl的resourc...

1631
来自专栏菩提树下的杨过

struts2: 玩转 rest-plugin

近期使用struts2的rest-plugin,参考官方示例struts2-rest-showcase,做了一个restful service小项目,但官网提供...

3095
来自专栏YG小书屋

Hadoop Streaming 读ORC文件

hadoop Streaming的处理流程是先通过inputFormat读出输入文件内容,将其传递mapper,再将mapper返回的key,value传给re...

1522
来自专栏码匠的流水账

聊聊curator recipes的LeaderLatch

curator-recipes-4.0.1-sources.jar!/org/apache/curator/framework/recipes/leader/L...

1873
来自专栏清晨我上码

异步任务执行的设计模式

2283
来自专栏岑玉海

MD5鉴定文件是否相同

由于诸多安全因素,需要对网上下载的一些文件进行完整性校验。比如,由于工作需要我下载了一个EMOS_1.5_i386.iso镜像文件(extmail邮件系统),需...

4355
来自专栏菩提树下的杨过

Jboss EAP:native management API学习

上一节已经学习了CLI命令行来控制JBOSS,如果想在程序中以编码方式来控制JBOSS,可以参考下面的代码,实际上在前面的文章,用代码控制Jboss上的Data...

2069
来自专栏Java帮帮-微信公众号-技术文章全总结

Java类加载器(用户自定义类加载器实现)

java类加载器主要分为如下几种: jvm提供的类加载器 根类加载器:底层实现,主要加载java核心类库(如:java.lang.*) 扩展类加载器:使用jav...

3576

扫码关注云+社区

领取腾讯云代金券