zoukankan      html  css  js  c++  java
  • mybatis插件原理

      Mybatis 在运行过程中,可以自己编写插件做一些全局处理。我们以一个插件为例子查看其原理。

    1. 注入过程

    1. 在构造会话工厂的时候注入拦截器

        @Bean
        public MybatisSqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) throws Exception {
            MybatisSqlSessionFactoryBean sessionFactory = new MybatisSqlSessionFactoryBean();
            /**
             * 重点,使分页插件生效
             */
            // 配置数据源,此处配置为关键配置,如果没有将 dynamicDataSource作为数据源则不能实现切换
            sessionFactory.setDataSource(dataSource);
            // 扫描Model
            sessionFactory.setTypeAliasesPackage("com.xm.ggn.bean");
            PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
            // 扫描映射文件
            sessionFactory.setMapperLocations(resolver.getResources("classpath*:mapper/**/*mapper.xml"));
    
            GlobalConfiguration defaults = GlobalConfigUtils.defaults();
            // 设置下划线为false
            defaults.setDbColumnUnderline(false);
            // 设置自定义SQL注入器
            defaults.setSqlInjector(new MyAutoSqlInjector());
            sessionFactory.setGlobalConfig(defaults);
    
            // 添加插件
            Interceptor[] interceptors = getPlugins();
            if (ArrayUtils.isNotEmpty(interceptors)) {
                sessionFactory.setPlugins(interceptors);
            }
    
            return sessionFactory;
        }
    
        private Interceptor[] getPlugins() {
            Interceptor[] plugins = new Interceptor[0];
    
            // PageHelper分页插件
            PageInterceptor pageInterceptor = new PageInterceptor();
            Properties properties = new Properties();
            properties.setProperty("helperDialect", "mysql");
            properties.setProperty("reasonable", "true");
            pageInterceptor.setProperties(properties);
    
            SqlExplainInterceptor sqlExplainInterceptor = new SqlExplainInterceptor();
    
            plugins = ArrayUtils.add(plugins, pageInterceptor);
            plugins = ArrayUtils.add(plugins, sqlExplainInterceptor);
            return plugins;
        }

    2. com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean#buildSqlSessionFactory  构造过程中通过如下代码添加到Configuration 对象

            if (!isEmpty(this.plugins)) {
                for (Interceptor plugin : this.plugins) {
                    configuration.addInterceptor(plugin);
                    if (LOGGER.isDebugEnabled()) {
                        LOGGER.debug("Registered plugin: '" + plugin + "'");
                    }
                }
            }

    3. org.apache.ibatis.session.Configuration#addInterceptor 维护到自己内部属性中

        protected final InterceptorChain interceptorChain = new InterceptorChain();
    
        public void addInterceptor(Interceptor interceptor) {
            this.interceptorChain.addInterceptor(interceptor);
        }

    这里采用了一个责任链条模式的设计:org.apache.ibatis.plugin.InterceptorChain 源码如下:

    public class InterceptorChain {
    
      private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
    
      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);
      }
    
    }

      代码跟到这里发现维护到了内部链条的集合属性中。

    2 调用过程

      以com.baomidou.mybatisplus.plugins.SqlExplainInterceptor 为例子分析其执行过程。

    1. 源码如下:

    package com.baomidou.mybatisplus.plugins;
    
    import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.util.Properties;
    
    import org.apache.ibatis.builder.StaticSqlSource;
    import org.apache.ibatis.executor.Executor;
    import org.apache.ibatis.logging.Log;
    import org.apache.ibatis.logging.LogFactory;
    import org.apache.ibatis.mapping.BoundSql;
    import org.apache.ibatis.mapping.MappedStatement;
    import org.apache.ibatis.mapping.SqlCommandType;
    import org.apache.ibatis.plugin.Interceptor;
    import org.apache.ibatis.plugin.Intercepts;
    import org.apache.ibatis.plugin.Invocation;
    import org.apache.ibatis.plugin.Plugin;
    import org.apache.ibatis.plugin.Signature;
    import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
    import org.apache.ibatis.session.Configuration;
    
    import com.baomidou.mybatisplus.enums.DBType;
    import com.baomidou.mybatisplus.exceptions.MybatisPlusException;
    import com.baomidou.mybatisplus.toolkit.GlobalConfigUtils;
    import com.baomidou.mybatisplus.toolkit.StringUtils;
    import com.baomidou.mybatisplus.toolkit.VersionUtils;
    
    /**
     * <p>
     * SQL 执行分析拦截器【 目前只支持 MYSQL-5.6.3 以上版本 】
     * </p>
     *
     * @author hubin
     * @Date 2016-08-16
     */
    @Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})
    public class SqlExplainInterceptor implements Interceptor {
    
        private static final Log logger = LogFactory.getLog(SqlExplainInterceptor.class);
        /**
         * Mysql支持分析SQL的最小版本
         */
        private final String minMySQLVersion = "5.6.3";
        /**
         * 发现执行全表 delete update 语句是否停止执行
         */
        private boolean stopProceed = false;
    
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            /**
             * 处理 DELETE UPDATE 语句
             */
            MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
            if (ms.getSqlCommandType() == SqlCommandType.DELETE || ms.getSqlCommandType() == SqlCommandType.UPDATE) {
                Executor executor = (Executor) invocation.getTarget();
                Configuration configuration = ms.getConfiguration();
                Object parameter = invocation.getArgs()[1];
                BoundSql boundSql = ms.getBoundSql(parameter);
                Connection connection = executor.getTransaction().getConnection();
                String databaseVersion = connection.getMetaData().getDatabaseProductVersion();
                if (GlobalConfigUtils.getDbType(configuration).equals(DBType.MYSQL)
                    && VersionUtils.compare(minMySQLVersion, databaseVersion)) {
                    logger.warn("Warn: Your mysql version needs to be greater than '5.6.3' to execute of Sql Explain!");
                    return invocation.proceed();
                }
                /**
                 * 执行 SQL 分析
                 */
                sqlExplain(configuration, ms, boundSql, connection, parameter);
            }
            return invocation.proceed();
        }
    
        /**
         * <p>
         * 判断是否执行 SQL
         * </p>
         *
         * @param configuration
         * @param mappedStatement
         * @param boundSql
         * @param connection
         * @param parameter
         * @return
         * @throws Exception
         */
        protected void sqlExplain(Configuration configuration, MappedStatement mappedStatement, BoundSql boundSql,
                                  Connection connection, Object parameter) {
            StringBuilder explain = new StringBuilder("EXPLAIN ");
            explain.append(boundSql.getSql());
            String sqlExplain = explain.toString();
            StaticSqlSource sqlsource = new StaticSqlSource(configuration, sqlExplain, boundSql.getParameterMappings());
            MappedStatement.Builder builder = new MappedStatement.Builder(configuration, "explain_sql", sqlsource,
                SqlCommandType.SELECT);
            builder.resultMaps(mappedStatement.getResultMaps()).resultSetType(mappedStatement.getResultSetType())
                .statementType(mappedStatement.getStatementType());
            MappedStatement queryStatement = builder.build();
            DefaultParameterHandler handler = new DefaultParameterHandler(queryStatement, parameter, boundSql);
            try (PreparedStatement stmt = connection.prepareStatement(sqlExplain)) {
                handler.setParameters(stmt);
                try (ResultSet rs = stmt.executeQuery()) {
                    while (rs.next()) {
                        if (!"Using where".equals(rs.getString("Extra"))) {
                            if (this.isStopProceed()) {
                                throw new MybatisPlusException("Error: Full table operation is prohibited. SQL: " + boundSql.getSql());
                            }
                            break;
                        }
                    }
                }
    
    
            } catch (Exception e) {
                throw new MybatisPlusException(e);
            }
        }
    
        @Override
        public Object plugin(Object target) {
            if (target instanceof Executor) {
                return Plugin.wrap(target, this);
            }
            return target;
        }
    
        @Override
        public void setProperties(Properties prop) {
            String stopProceed = prop.getProperty("stopProceed");
            if (StringUtils.isNotEmpty(stopProceed)) {
                this.stopProceed = Boolean.valueOf(stopProceed);
            }
        }
    
        public boolean isStopProceed() {
            return stopProceed;
        }
    
        public void setStopProceed(boolean stopProceed) {
            this.stopProceed = stopProceed;
        }
    
    }

      可以看到可以使用Intercepts 需要拦截的方法签名(包括所属类、方法名称、参数class)

     2. 查看执行过程

      查看Mybatis的Configuration 对象有下面四个方法:

      public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
        ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
        parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
        return parameterHandler;
      }
    
      public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
          ResultHandler resultHandler, BoundSql boundSql) {
        ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
        resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
        return resultSetHandler;
      }
    
      public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
        statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
        return statementHandler;
      }
    
      public Executor newExecutor(Transaction transaction) {
        return newExecutor(transaction, defaultExecutorType);
      }
    
      public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
        executorType = executorType == null ? defaultExecutorType : executorType;
        executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
        Executor executor;
        if (ExecutorType.BATCH == executorType) {
          executor = new BatchExecutor(this, transaction);
        } else if (ExecutorType.REUSE == executorType) {
          executor = new ReuseExecutor(this, transaction);
        } else {
          executor = new SimpleExecutor(this, transaction);
        }
        if (cacheEnabled) {
          executor = new CachingExecutor(executor);
        }
        executor = (Executor) interceptorChain.pluginAll(executor);
        return executor;
      }

     可以看到:

    (1) 在构造ParameterHandler(java参数转为JDBC需要的参数处理器)、ResultSetHandler(负责将JDBC返回的ResultSet 结果集转换成List 类型的集合)、StatementHandler (封装JDBC Statement操作)、Executor( 执行器)过程中会调用interceptorChain.pluginAll

    (2) interceptorChain.pluginAll 调用具体执行器的plugin方法,上面例子就是:com.baomidou.mybatisplus.plugins.SqlExplainInterceptor#plugin

    方法内部先判断参数是否是Executor, 也就是这个拦截器只针对Executor 进行拦截。类注解上的方法签名也可以看出来

    (3) 然后调用org.apache.ibatis.plugin.Plugin#wrap 进行包装, org.apache.ibatis.plugin.Plugin 源码如下:

    package org.apache.ibatis.plugin;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.util.HashMap;
    import java.util.HashSet;
    import java.util.Map;
    import java.util.Set;
    
    import org.apache.ibatis.reflection.ExceptionUtil;
    
    /**
     * @author Clinton Begin
     */
    public class Plugin implements InvocationHandler {
    
      private final Object target;
      private final Interceptor interceptor;
      private final 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;
      }
    
      @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)) {
            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) {
        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());      
        }
        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()]);
      }
    
    }
    View Code

      org.apache.ibatis.plugin.Plugin#wrap 方法的核心逻辑是: 读取插件类上面的Intercepts 注解,然后获取Signature 上的注解,也就是读取拦截的相关类以及方法名称、参数类。最后通过反射读取到方法之后添加到signatureMap 属性中;然后用JDK创建代理对象之后返回。

    例如:上面SqlExplainInterceptor 拦截器获取到的signatureMap 如下:

    3. 这样通过Configuration 拿到的对象,如果需要走拦截器返回的就是代理对象;如果不需要拦截器使用的就是默认的相关对象。

    比如上面使用了插件之后,返回的Executor实际是代理之后的对象;代理对象走的逻辑就是org.apache.ibatis.plugin.Plugin#invoke 方法里面逻辑。 

     4. Mybatis 执行过程中会先通过Configuration 上面四个newXXX 方法拿对象。拿到代理对象之后那么插件就会生效。这里也需要理解Mybais的执行过程。以一次查询为例分析其过程:

    1》通过调用configuration.newExecutor(tx, execType); 创建Executor 执行器

    2》执行查询或者修改最后会调用到XXXExecutor 的doUpdate或者doQuery 方法。比如:org.apache.ibatis.executor.SimpleExecutor#doQuery

      @Override
      public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        Statement stmt = null;
        try {
          Configuration configuration = ms.getConfiguration();
          StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
          stmt = prepareStatement(handler, ms.getStatementLog());
          return handler.<E>query(stmt, resultHandler);
        } finally {
          closeStatement(stmt);
        }
      }

    可以看到首先调用configuration.newStatementHandler 创建StatementHandler, 这里会使用插件然后走代理机制。继续追代码会调用到:org.apache.ibatis.executor.statement.RoutingStatementHandler#RoutingStatementHandler

      public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    
        switch (ms.getStatementType()) {
          case STATEMENT:
            delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
            break;
          case PREPARED:
            delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
            break;
          case CALLABLE:
            delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
            break;
          default:
            throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
        }
    
      }

    然后会调用:org.apache.ibatis.executor.statement.BaseStatementHandler#BaseStatementHandler

    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);
      }
    ...

      也就是说创建StatementHandler的过程中会创建ParameterHandler、ResultSetHandler, 都是通过Configuration 创建的。

    3》然后调用到org.apache.ibatis.executor.statement.SimpleStatementHandler#query

      @Override
      public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
        String sql = boundSql.getSql();
        statement.execute(sql);
        return resultSetHandler.<E>handleResultSets(statement);
      }

    这里的resultSetHandler就是上面的DefaultResultSetHandler(使用插件会先到代理)

    【当你用心写完每一篇博客之后,你会发现它比你用代码实现功能更有成就感!】
  • 相关阅读:
    爬虫_监控某个元素所有事件
    Selenium_Selenium WebDriver 中鼠标和键盘事件分析及扩展
    Selenium_webdriver获取iframe子页面元素
    Java_Servlet 中文乱码问题及解决方案剖析
    thinkphp自动填充分析
    linux安装配置SVN并设置钩子
    HTTP协议提要
    一致性Hash算法(分布式算法)
    设计模式之:原型模式
    面向对象设计的五大原则
  • 原文地址:https://www.cnblogs.com/qlqwjy/p/15422887.html
Copyright © 2011-2022 走看看