通过源代码分析Mybatis的功能

栏目: IT技术 · 发布时间: 4年前

内容简介:Mybatis在初始化的时候,会读取xml中的SQL,解析后会生成SqlSource对象,SqlSource对象分为两种。因为解析的时候会先解析

SQL解析

Mybatis在初始化的时候,会读取xml中的SQL,解析后会生成SqlSource对象,SqlSource对象分为两种。

  • DynamicSqlSource ,动态SQL,获取SQL( getBoundSQL 方法中)的时候生成参数化SQL。

  • RawSqlSource ,原始SQL,创建对象时直接生成参数化SQL。

因为 RawSqlSource 不会重复去生成参数化SQL,调用的时候直接传入参数并执行,而 DynamicSqlSource 则是每次执行的时候参数化SQL,所以 RawSqlSourceDynamicSqlSource 的性能要好的。

解析的时候会先解析 include 标签和 selectkey 标签,然后判断是否是动态SQL,判断取决于以下两个条件:

  • SQL中有动态拼接字符串,简单来说就是是否使用了 ${} 表达式。注意这种方式存在 SQL 注入,谨慎使用。
  • SQL中有 trimwheresetforeachifchoosewhenotherwisebind 标签

相关代码如下:

protected MixedSqlNode parseDynamicTags(XNode node) {
    // 创建 SqlNode 数组
    List<SqlNode> contents = new ArrayList<>();
    // 遍历 SQL 节点的所有子节点
    NodeList children = node.getNode().getChildNodes();
    for (int i = 0; i < children.getLength(); i++) {
        // 当前子节点
        XNode child = node.newXNode(children.item(i));
        // 如果类型是 Node.CDATA_SECTION_NODE 或者 Node.TEXT_NODE 时
        if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
            // 获得内容
            String data = child.getStringBody("");
            // 创建 TextSqlNode 对象
            TextSqlNode textSqlNode = new TextSqlNode(data);
            // 如果是动态的 TextSqlNode 对象(是否使用了${}表达式)
            if (textSqlNode.isDynamic()) {
                // 添加到 contents 中
                contents.add(textSqlNode);
                // 标记为动态 SQL
                isDynamic = true;
                // 如果是非动态的 TextSqlNode 对象
            } else {
                // 创建 StaticTextSqlNode 添加到 contents 中
                contents.add(new StaticTextSqlNode(data));
            }
            // 如果类型是 Node.ELEMENT_NODE,其实就是XMl中<where>等那些动态标签
        } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
            // 根据子节点的标签,获得对应的 NodeHandler 对象
            String nodeName = child.getNode().getNodeName();
            NodeHandler handler = nodeHandlerMap.get(nodeName);
            if (handler == null) { // 获得不到,说明是未知的标签,抛出 BuilderException 异常
                throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
            }
            // 执行 NodeHandler 处理
            handler.handleNode(child, contents);
            // 标记为动态 SQL
            isDynamic = true;
        }
    }
    // 创建 MixedSqlNode 对象
    return new MixedSqlNode(contents);
}

参数解析

Mybais中用于解析Mapper方法的参数的类是 ParamNameResolver ,它主要做了这些事情:

  • 每个Mapper方法第一次运行时会去创建 ParamNameResolver ,之后会缓存

  • 创建时会根据方法签名,解析出参数名,解析的规则顺序是

    1. 如果参数类型是 RowBounds 或者 ResultHandler 类型或者他们的子类,则不处理。

    2. 如果参数中有 Param 注解,则使用 Param 中的值作为参数名

    3. 如果配置项 useActualParamName =true, argn (n>=0) 标作为参数名,如果你是 Java 8以上并且开启了 -parameters`,则是实际的参数名

      如果配置项 useActualParamName =false,则使用 n (n>=0)作为参数名

相关源代码:

public ParamNameResolver(Configuration config, Method method) {
    final Class<?>[] paramTypes = method.getParameterTypes();
    final Annotation[][] paramAnnotations = method.getParameterAnnotations();
    final SortedMap<Integer, String> map = new TreeMap<Integer, String>();
    int paramCount = paramAnnotations.length;
    // 获取方法中每个参数在SQL中的参数名
    for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
        // 跳过RowBounds、ResultHandler类型
        if (isSpecialParameter(paramTypes[paramIndex])) {
            continue;
        }
        String name = null;
        // 遍历参数上面的所有注解,如果有Param注解,使用它的值作为参数名
        for (Annotation annotation : paramAnnotations[paramIndex]) {
            if (annotation instanceof Param) {
                hasParamAnnotation = true;
                name = ((Param) annotation).value();
                break;
            }
        }
        // 如果没有指定注解
        if (name == null) {
            // 如果开启了useActualParamName配置,则参数名为argn(n>=0),如果是Java8以上并且开启-parameters,则为实际的参数名
            if (config.isUseActualParamName()) {
                name = getActualParamName(method, paramIndex);
            }
            // 否则为下标
            if (name == null) {
                name = String.valueOf(map.size());
            }
        }
        map.put(paramIndex, name);
    }
    names = Collections.unmodifiableSortedMap(map);
}

而在使用这个 names 构建xml中参数对象和值的映射时,还进行了进一步的处理。

public Object getNamedParams(Object[] args) {
    final int paramCount = names.size();
    // 无参数,直接返回null
    if (args == null || paramCount == 0) {
        return null;
    } else if (!hasParamAnnotation && paramCount == 1) {
        // 一个参数,并且没有注解,直接返回这个对象
        return args[names.firstKey()];
    } else {
        // 其他情况则返回一个Map对象
        final Map<String, Object> param = new ParamMap<Object>();
        int i = 0;
        for (Map.Entry<Integer, String> entry : names.entrySet()) {
            // 先直接放入name的键和对应位置的参数值,其实就是构造函数中存入的值
            param.put(entry.getValue(), args[entry.getKey()]);
            // add generic param names (param1, param2, ...)
            final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
            // 防止覆盖 @Param 的参数值
            if (!names.containsValue(genericParamName)) {
                // 然后放入GENERIC_NAME_PREFIX + index + 1,其实就是param1,params2,paramn
                param.put(genericParamName, args[entry.getKey()]);
            }
            i++;
        }
        return param;
    }
}

另外值得一提的是,对于集合类型,最后还有一个特殊处理

private Object wrapCollection(final Object object) {
    // 如果对象是集合属性
    if (object instanceof Collection) {
        StrictMap<Object> map = new StrictMap<Object>();
        // 加入一个collection参数
        map.put("collection", object);
        // 如果是一个List集合
        if (object instanceof List) {
            // 额外加入一个list属性使用
            map.put("list", object);
        }
        return map;
    } else if (object != null && object.getClass().isArray()) {
        // 数组使用array
        StrictMap<Object> map = new StrictMap<Object>();
        map.put("array", object);
        return map;
    }
    return object;
}

由此我们可以得出使用参数的结论:

  • 如果参数加了 @Param 注解,则使用注解的值作为参数
  • 如果只有一个参数,并且不是集合类型和数组,且没有加注解,则使用对象的属性名作为参数
  • 如果只有一个参数,并且是集合类型,则使用 collection 参数,如果是 List 对象,可以额外使用 list 参数。
  • 如果只有一个参数,并且是数组,则可以使用 array 参数
  • 如果有多个参数,没有加 @Param 注解的可以使用 argn 或者 n (n>=0,取决于 useActualParamName 配置项)作为参数,加了注解的使用注解的值。
  • 如果有多个参数,任意参数只要不是和 @Param 中的值覆盖,都可以使用 paramn (n>=1)

延迟加载

Mybatis是支持延迟加载的,具体的实现方式根据 resultMap 创建返回对象时,发现fetchType=“lazy”,则使用代理对象,默认使用 Javassist (MyBatis 3.3 以上,可以修改为使用 CgLib )。代码处理逻辑在处理返回结果集时,具体代码调用关系如下:

PreparedStatementHandler.query => handleResultSets => handleResultSet => handleRowValues => handleRowValuesForNestedResultMap => getRowValue

getRowValue 中,有一个方法 createResultObject 创建返回对象,其中的关键代码创建了代理对象:

if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
    resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
}

另一方面, getRowValue 会调用 applyPropertyMappings 方法,其内部会调用 getPropertyMappingValue ,继续追踪到 getNestedQueryMappingValue 方法,在这里,有几行关键代码:

// 如果要求延迟加载,则延迟加载
if (propertyMapping.isLazy()) {
    // 如果该属性配置了延迟加载,则将其添加到 `ResultLoader.loaderMap` 中,等待真正使用时再执行嵌套查询并得到结果对象。
    lazyLoader.addLoader(property, metaResultObject, resultLoader);
    // 返回已定义
    value = DEFERED;
    // 如果不要求延迟加载,则直接执行加载对应的值
} else {
    value = resultLoader.loadResult();
}

这几行的目的是跳过属性值的加载,等真正需要值的时候,再获取值。

Executor

Executor是一个接口,其直接实现的类是 BaseExecutorCachingExecutorBaseExecutor 又派生了 BatchExecutorReuseExecutorSimpleExecutorClosedExecutor 。其继承结构如图:

其中 ClosedExecutor 是一个私有类,用户不直接使用它。

  • BaseExecutor :模板类,里面有各个Executor的公用的方法。
  • SimpleExecutor :最常用的 Executor ,默认是使用它去连接数据库,执行SQL语句,没有特殊行为。
  • ReuseExecutor :SQL语句执行后会进行缓存,不会关闭 Statement ,下次执行时会复用,缓存的 key 值是 BoundSql 解析后SQL,清空缓存使用 doFlushStatements 。其他与 SimpleExecutor 相同。
  • BatchExecutor :当有 连续InsertUpdateDelete 的操作语句,并且语句的 BoundSql 相同,则这些语句会批量执行。使用 doFlushStatements 方法获取批量操作的返回值。
  • CachingExecutor :当你开启二级缓存的时候,会使用 CachingExecutor 装饰 SimpleExecutorReuseExecutorBatchExecutor ,Mybatis通过 CachingExecutor 来实现二级缓存。

缓存

一级缓存

Mybatis一级缓存的实现主要是在 BaseExecutor 中,在它的查询方法里,会优先查询缓存中的值,如果不存在,再查询数据库,查询部分的代码如下,关键代码在17-24行:

@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    // 已经关闭,则抛出 ExecutorException 异常
    if (closed) {
        throw new ExecutorException("Executor was closed.");
    }
    // 清空本地缓存,如果 queryStack 为零,并且要求清空本地缓存。
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
        clearLocalCache();
    }
    List<E> list;
    try {
        // queryStack + 1
        queryStack++;
        // 从一级缓存中,获取查询结果
        list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
        // 获取到,则进行处理
        if (list != null) {
            handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
            // 获得不到,则从数据库中查询
        } else {
            list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
        }
    } finally {
        // queryStack - 1
        queryStack--;
    }
    if (queryStack == 0) {
        // 执行延迟加载
        for (DeferredLoad deferredLoad : deferredLoads) {
            deferredLoad.load();
        }
        // issue #601
        // 清空 deferredLoads
        deferredLoads.clear();
        // 如果缓存级别是 LocalCacheScope.STATEMENT ,则进行清理
        if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
            // issue #482
            clearLocalCache();
        }
    }
    return list;
}

而在 queryFromDatabase 中,则会将查询出来的结果放到缓存中。

// 从数据库中读取操作
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    // 在缓存中,添加占位对象。此处的占位符,和延迟加载有关,可见 `DeferredLoad#canLoad()` 方法
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
        // 执行读操作
        list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
        // 从缓存中,移除占位对象
        localCache.removeObject(key);
    }
    // 添加到缓存中
    localCache.putObject(key, list);
    // 暂时忽略,存储过程相关
    if (ms.getStatementType() == StatementType.CALLABLE) {
        localOutputParameterCache.putObject(key, parameter);
    }
    return list;
}

而一级缓存的Key,从方法的参数可以看出,与调用方法、参数、rowBounds分页参数、最终生成的sql有关。

@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    if (closed) {
        throw new ExecutorException("Executor was closed.");
    }
    // 创建 CacheKey 对象
    CacheKey cacheKey = new CacheKey();
    // 设置 id、offset、limit、sql 到 CacheKey 对象中
    cacheKey.update(ms.getId());
    cacheKey.update(rowBounds.getOffset());
    cacheKey.update(rowBounds.getLimit());
    cacheKey.update(boundSql.getSql());
    // 设置 ParameterMapping 数组的元素对应的每个 value 到 CacheKey 对象中
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
    // mimic DefaultParameterHandler logic 这块逻辑,和 DefaultParameterHandler 获取 value 是一致的。
    for (ParameterMapping parameterMapping : parameterMappings) {
        if (parameterMapping.getMode() != ParameterMode.OUT) {
            Object value;
            String propertyName = parameterMapping.getProperty();
            if (boundSql.hasAdditionalParameter(propertyName)) {
                value = boundSql.getAdditionalParameter(propertyName);
            } else if (parameterObject == null) {
                value = null;
            } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                value = parameterObject;
            } else {
                MetaObject metaObject = configuration.newMetaObject(parameterObject);
                value = metaObject.getValue(propertyName);
            }
            cacheKey.update(value);
        }
    }
    // 设置 Environment.id 到 CacheKey 对象中
    if (configuration.getEnvironment() != null) {
        // issue #176
        cacheKey.update(configuration.getEnvironment().getId());
    }
    return cacheKey;
}

通过查看一级缓存类的实现,可以看出一级缓存是通过HashMap结构存储的:

/**
 * 一级缓存的实现类,部分源代码
 */
public class PerpetualCache implements Cache {
    /**
     * 缓存容器
     */
    private Map<Object, Object> cache = new HashMap<>();

    @Override
    public void putObject(Object key, Object value) {
        cache.put(key, value);
    }

    @Override
    public Object getObject(Object key) {
        return cache.get(key);
    }

    @Override
    public Object removeObject(Object key) {
        return cache.remove(key);
    }
}

通过配置项,我们可以控制一级缓存的使用范围,默认是Session级别的,也就是SqlSession的范围内有效。也可以配制成Statement级别,当本次查询结束后立即清除缓存。

当进行插入、更新、删除操作时,也会在执行SQL之前清空以及缓存。

二级缓存

Mybatis二级缓存的实现是依靠 CachingExecutor 装饰其他的 Executor 实现。原理是在查询的时候先根据CacheKey查询缓存中是否存在值,如果存在则返回缓存的值,没有则查询数据库。

CachingExecutorquery 方法中,就有缓存的使用:

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
        throws SQLException {
    Cache cache = ms.getCache();
    if (cache != null) {
        // 如果需要清空缓存,则进行清空
        flushCacheIfRequired(ms);
        if (ms.isUseCache() && resultHandler == null) {
            // 暂时忽略,存储过程相关
            ensureNoOutParams(ms, boundSql);
            @SuppressWarnings("unchecked")
            // 从二级缓存中,获取结果
            List<E> list = (List<E>) tcm.getObject(cache, key);
            if (list == null) {
                // 如果不存在,则从数据库中查询
                list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                // 缓存结果到二级缓存中
                tcm.putObject(cache, key, list); // issue #578 and #116
            }
            // 如果存在,则直接返回结果
            return list;
        }
    }
    // 不使用缓存,则从数据库中查询
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

那么这个 Cache 是在哪里创建的呢?通过调用的追溯,可以找到它的创建:

public Cache useNewCache(Class<? extends Cache> typeClass,
                         Class<? extends Cache> evictionClass,
                         Long flushInterval,
                         Integer size,
                         boolean readWrite,
                         boolean blocking,
                         Properties props) {
    // 创建 Cache 对象
    Cache cache = new CacheBuilder(currentNamespace)
        .implementation(valueOrDefault(typeClass, PerpetualCache.class))
        .addDecorator(valueOrDefault(evictionClass, LruCache.class))
        .clearInterval(flushInterval)
        .size(size)
        .readWrite(readWrite)
        .blocking(blocking)
        .properties(props)
        .build();
    // 添加到 configuration 的 caches 中
    configuration.addCache(cache);
    // 赋值给 currentCache
    currentCache = cache;
    return cache;
}

从方法的第一行可以看出,Cache对象的范围是namespace,同一个namespace下的所有mapper方法共享Cache对象,也就是说,共享这个缓存。

另一个创建方法是通过CacheRef里面的:

public Cache useCacheRef(String namespace) {
    if (namespace == null) {
        throw new BuilderException("cache-ref element requires a namespace attribute.");
    }
    try {
        unresolvedCacheRef = true; // 标记未解决
        // 获得 Cache 对象
        Cache cache = configuration.getCache(namespace);
        // 获得不到,抛出 IncompleteElementException 异常
        if (cache == null) {
            throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.");
        }
        // 记录当前 Cache 对象
        currentCache = cache;
        unresolvedCacheRef = false; // 标记已解决
        return cache;
    } catch (IllegalArgumentException e) {
        throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.", e);
    }
}

这里的话会通过 CacheRef 中的参数 namespace ,找到那个 Cache 对象,且这里使用了 unresolvedCacheRef ,因为Mapper文件的加载是有顺序的,可能当前加载时引用的那个 namespace 的Mapper文件还没有加载,所以用这个标记一下,延后加载。

二级缓存通过 TransactionalCache 来管理,内部使用的是一个HashMap。Key是Cache对象,默认的实现是 PerpetualCache ,一个namespace下共享这个对象。Value是另一个Cache的对象,默认实现是 TransactionalCache ,是前面那个Key值的装饰器,扩展了事务方面的功能。

通过查看 TransactionalCache 的源码我们可以知道,默认查询后添加的缓存保存在待提交对象里。

public void putObject(Object key, Object object) {
    // 暂存 KV 到 entriesToAddOnCommit 中
    entriesToAddOnCommit.put(key, object);
}

只有等到 commit 的时候才会去刷入缓存。

public void commit() {
    // 如果 clearOnCommit 为 true ,则清空 delegate 缓存
    if (clearOnCommit) {
        delegate.clear();
    }
    // 将 entriesToAddOnCommit、entriesMissedInCache 刷入 delegate 中
    flushPendingEntries();
    // 重置
    reset();
}

查看 clear 代码,只是做了标记,并没有真正释放对象。在查询时根据标记直接返回空,在 commit 才真正释放对象:

public void clear() {
    // 标记 clearOnCommit 为 true
    clearOnCommit = true;
    // 清空 entriesToAddOnCommit
    entriesToAddOnCommit.clear();
}

public Object getObject(Object key) {
    // issue #116
    // 从 delegate 中获取 key 对应的 value
    Object object = delegate.getObject(key);
    // 如果不存在,则添加到 entriesMissedInCache 中
    if (object == null) {
        entriesMissedInCache.add(key);
    }
    // issue #146
    // 如果 clearOnCommit 为 true ,表示处于持续清空状态,则返回 null
    if (clearOnCommit) {
        return null;
        // 返回 value
    } else {
        return object;
    }
}

rollback 会清空这些临时缓存:

public void rollback() {
    // 从 delegate 移除出 entriesMissedInCache
    unlockMissedEntries();
    // 重置
    reset();
}

private void reset() {
    // 重置 clearOnCommit 为 false
    clearOnCommit = false;
    // 清空 entriesToAddOnCommit、entriesMissedInCache
    entriesToAddOnCommit.clear();
    entriesMissedInCache.clear();
}

根据二级缓存代码可以看出,二级缓存是基于 namespace 的,可以跨SqlSession。也正是因为基于 namespace ,如果在不同的 namespace 中修改了同一个表的数据,会导致脏读的问题。

插件

Mybatis的插件是通过代理对象实现的,可以代理的对象有:

  • Executor :执行器,执行器是执行过程中第一个代理对象,它内部调用 StatementHandler 返回SQL结果。
  • StatementHandler :语句处理器,执行SQL前调用 ParameterHandler 处理参数,执行SQL后调用 ResultSetHandler 处理返回结果
  • ParameterHandler :参数处理器
  • ResultSetHandler :返回对象处理器

这四个对象的接口的所有方法都可以用插件拦截。

插件的实现代码如下:

// 创建 ParameterHandler 对象
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    // 创建 ParameterHandler 对象
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    // 应用插件
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
}

// 创建 ResultSetHandler 对象
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
                                            ResultHandler resultHandler, BoundSql boundSql) {
    // 创建 DefaultResultSetHandler 对象
    ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
    // 应用插件
    resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
    return resultSetHandler;
}

// 创建 StatementHandler 对象
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    // 创建 RoutingStatementHandler 对象
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    // 应用插件
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
}

/**
     * 创建 Executor 对象
     *
     * @param transaction 事务对象
     * @param executorType 执行器类型
     * @return Executor 对象
     */
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    // 获得执行器类型
    executorType = executorType == null ? defaultExecutorType : executorType; // 使用默认
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType; // 使用 ExecutorType.SIMPLE
    // 创建对应实现的 Executor 对象
    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);
    }
    // 如果开启缓存,创建 CachingExecutor 对象,进行包装
    if (cacheEnabled) {
        executor = new CachingExecutor(executor);
    }
    // 应用插件
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
}

可以很明显的看到,四个方法内都有 interceptorChain.pluginAll() 方法的调用,继续查看这个方法:

/**
 * 应用所有插件
 *
 * @param target 目标对象
 * @return 应用结果
 */
public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
        target = interceptor.plugin(target);
    }
    return target;
}

这个方法比较简单,就是遍历 interceptors 列表,然后调用器 plugin 方法。 interceptors 是在解析XML配置文件是通过反射创建的,而创建后会立即调用 setProperties 方法

我们通常配置插件时,会在 interceptor.plugin 调用 Plugin.wrap ,这里面通过Java的动态代理,拦截方法的实现:

/**
 * 创建目标类的代理对象
 *
 * @param target 目标类
 * @param interceptor 拦截器对象
 * @return 代理对象
 */
public static Object wrap(Object target, Interceptor interceptor) {
    // 获得拦截的方法映射
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    // 获得目标类的类型
    Class<?> type = target.getClass();
    // 获得目标类的接口集合
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    // 若有接口,则创建目标对象的 JDK Proxy 对象
    if (interfaces.length > 0) {
        return Proxy.newProxyInstance(
            type.getClassLoader(),
            interfaces,
            new Plugin(target, interceptor, signatureMap)); // 因为 Plugin 实现了 InvocationHandler 接口,所以可以作为 JDK 动态代理的调用处理器
    }
    // 如果没有,则返回原始的目标对象
    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);
    }
}

而拦截的参数传了 Plugin 对象,Plugin本身是实现了 InvocationHandler 接口,其 invoke 方法里面调用了 interceptor.intercept ,这个方法就是我们实现拦截处理的地方。

注意到里面有个 getSignatureMap 方法,这个方法实现的是查找我们自定义拦截器的注解,通过注解确定哪些方法需要被拦截:

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<>();
    for (Signature sig : sigs) {
        Set<Method> methods = signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>());
        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;
}

通过源代码我们可以知道,创建一个插件需要做以下事情:

  1. 创建一个类,实现 Interceptor 接口。
  2. 这个类必须使用 @Intercepts@Signature 来表明要拦截哪个对象的哪些方法。
  3. 这个类的 plugin 方法中调用 Plugin.wrap(target, this)
  4. (可选)这个类的 setProperties 方法设置一些参数。
  5. XML中 <plugins> 节点配置 <plugin interceptor="你的自定义类的全名称"></plugin>

可以在第三点中根据具体的业务情况不进行本次SQL操作的代理,毕竟动态代理还是有性能损耗的。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

亮剑.NET

亮剑.NET

2009-3 / 55.00元

《亮剑.NET:SharePoint Server 2007开发实战》共分为8章,详细讲解了SharePoint上常见的开发任务,讲述了各种开发场景下需要了解的知识,并提供了丰富的实例。《亮剑.NET:SharePoint Server 2007开发实战》第1章为基础知识,讲述SharePoint的基本概念,基本的对象模型,代码编写注意事项,并讲解了一个集开发和部署打包为一体的项目结构的创建;第2......一起来看看 《亮剑.NET》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

在线进制转换器
在线进制转换器

各进制数互转换器

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码