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


     正文前先来一波福利推荐:

     福利一:

    百万年薪架构师视频,该视频可以学到很多东西,是本人花钱买的VIP课程,学习消化了一年,为了支持一下女朋友公众号也方便大家学习,共享给大家。

    福利二:

    毕业答辩以及工作上各种答辩,平时积累了不少精品PPT,现在共享给大家,大大小小加起来有几千套,总有适合你的一款,很多是网上是下载不到。

    获取方式:

    微信关注 精品3分钟 ,id为 jingpin3mins,关注后回复   百万年薪架构师 ,精品收藏PPT  获取云盘链接,谢谢大家支持!

    -----------------------正文开始---------------------------

     

    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
       {

          doSomeThing();
          /* 注:此处实际上使用Invocation.proceed()方法完成interceptorChain链的遍历调用(即执行所有注册的Interceptor的intercept方法),到最终被代理对象的原始方法调用 */
          return invocation.proceed();
        }

    /* 使用当前的这个拦截器实现对目标对象的代理(内部实现时Java的动态代理)
         * @see org.apache.ibatis.plugin.Interceptor#plugin(java.lang.Object)
         */

        @Override
        public Object plugin(Object target)

        {

        /* 当目标类是StatementHandler类型时,才包装目标类,不做无意义的代理 */
        return (target instanceof StatementHandler)?Plugin.wrap(target, this):target;
        }

    /* 初始化Configuration时通过配置文件配置property传递参数给此方法并调用。
         * @see org.apache.ibatis.plugin.Interceptor#setProperties(java.util.Properties)
         */
        @Override
        public void setProperties(Properties properties) {  
            Iterator iterator = properties.keySet().iterator();
            while (iterator.hasNext()){
                String keyValue = String.valueOf(iterator.next());
                System.out.println(properties.getProperty(keyValue));
            }
        }
    
    }

    我们看点在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  
      }  
      
    }  
  • 相关阅读:
    进入新的一线开发周期
    某框架的反人类特性
    8月最后一天随想
    jython 2.7 b3发布
    [转] 基于 Apache Mahout 构建社会化推荐引擎
    FreeBSD10上编译尝试DeepIn UI
    PDT已有很大改进
    两张图
    FreeBSD暂时用9.X系列为宜
    backbone教程
  • 原文地址:https://www.cnblogs.com/gxyandwmm/p/9367507.html
Copyright © 2020-2023  润新知