插件是应用程序中最常见的一种扩展方式,比如,在Chrome 浏览器上我们可以安装各种插件来增强浏览器自身的功能。在 Java 世界中,很多开源框架也使用了插件扩展方式,例如,Dubbo 通过 SPI 方式实现了插件化的效果,SkyWalking 依赖“微内核+插件”的架构轻松加载插件,实现扩展效果。
MyBatis 作为持久层框架中的佼佼者,也提供了类似的插件扩展机制。MyBatis 将插件单独分离出一个模块,位于 org.apache.ibatis.plugin 包中,在该模块中主要使用了两种设计模式:代理模式和责任链模式。
责任链模式
我们在写业务系统的时候,最常用的协议就是 HTTP 协议,最常用的 HTTP Server 是 Tomcat,所以这里我们就结合 Tomcat 处理 HTTP 请求的场景来说明责任链模式的核心思想。
HTTP 协议可简单分为请求头和请求体两部分,Tomcat 在收到一条完整的 HTTP 请求时,也会将其分为请求头和请求体两部分进行处理的。不过在真正的 Tomcat 实现中,会将 HTTP 请求细分为更多部分,然后逐步进行处理,整个 Tomcat 代码处理 HTTP 请求的实现也更为复杂。
试想一下,Tomcat 将处理请求的各个细节的实现代码都堆到一个类中,那这个类的代码会非常长,维护起来也非常痛苦,可以说是“牵一发而动全身”。如果 HTTP 请求升级,那就需要修改这个臃肿的类,显然是不符合“开放-封闭”原则的。
为了实现像 HTTP 这种多部分构成的协议的处理逻辑,我们可以使用责任链模式来划分协议中各个部分的处理逻辑,将那些臃肿实现类拆分成多个 Handler(或 Interceptor)处理器,在每个 Handler(或 Interceptor)处理器中只专注于 HTTP 协议中一部分数据的处理。我们可以开发多个 Handler 处理器,然后按照业务需求将多个 Handler 对象组合成一个链条,从而实现整个 HTTP 请求的处理。
这样做既可以将复杂、臃肿的逻辑拆分,便于维护,又能将不同的 Handler 处理器分配给不同的程序员开发,提高开发效率。
在责任链模式中,Handler 处理器会持有对下一个 Handler 处理器的引用,也就是说当一个 Handler 处理器完成对关注部分的处理之后,会将请求通过这个引用传递给下一个 Handler 处理器,如此往复,直到整个责任链中全部的 Handler 处理器完成处理。责任链模式的核心类图如下所示:
下面我们再从复用的角度看一下责任链模式带来的好处。
假设我们自定义了一套协议,其请求中包含 A、B、C 三个核心部分,业务系统使用 Handler A、Handler B、Handler C 三个处理器来处理这三部分的数据。如果业务变化导致我们的自定义协议也发生了变化,协议中的数据变成了 A、C、D 这三部分,那么我们只需要动态调整构成责任链的 Handler 处理器即可,最新的责任链变为 Handler A、Handler C、Handler D。如下图所示:
由此可见,责任链模式可以帮助我们复用 Handler 处理器的实现逻辑,提高系统的可维护性和灵活性,很好地符合了“开放-封闭”原则。
Interceptor的基本使用
mybatis自定义拦截器实现步骤
- 实现org.apache.ibatis.plugin.Interceptor接口。
- 添加拦截器注解@Intercepts。
- 配置文件中添加拦截器
mybatis Interceptor接口的定义
MyBatis 插件模块中最核心的接口就是 Interceptor 接口,它是所有 MyBatis 插件必须要实现的接口,其核心定义如下
public interface Interceptor {
//intercept方法就是要进行拦截的时候要执行的方法
Object intercept(Invocation invocation) throws Throwable;
//plugin方法是插件用于封装目标对象的,通过该方法我们可以返回目标对象本身,也可以返回一个它的代理,可以决定是否要进行拦截进而决定要返回一个什么样的目标对象,
// 官方提供了示例:return Plugin.wrap(target, this);
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
//setProperties方法是在Mybatis进行配置插件的时候可以配置自定义相关属性,即:接口实现对象的参数配置
default void setProperties(Properties properties) {
// NOP
}
}
拦截器注解的规则
这里我们首先定义一个DemoPlugin 类,定义如下:
@Intercepts({
@Signature(type = Executor.class, method = "query", args = {
MappedStatement.class, Object.class, RowBounds.class,
ResultHandler.class}),
@Signature(type = Executor.class, method = "close", args = {boolean.class})
})
public class DemoPlugin implements Interceptor {
private int logLevel;
// 省略其他方法的实现
}
我们看到 DemoPlugin 这个示例类除了实现 Interceptor 接口外,还被标注了 @Intercepts 和 @Signature 两个注解。
@Intercepts 注解中可以配置多个 @Signature 注解。
@Signature 注解用来指定 DemoPlugin 插件实现类要拦截的目标方法信息,其中type 属性指定了要拦截的类,method 属性指定了要拦截的目标方法名称,args 属性指定了要拦截的目标方法的参数列表。通过 @Signature 注解中的这三个配置,DemoPlugin 就可以确定要拦截的目标方法的方法签名。在上面的示例中,DemoPlugin 会拦截 Executor 接口中的 query(MappedStatement, Object, RowBounds, ResultHandler) 方法和 close(boolean) 方法。
mybatis-config配置文件中添加拦截器
<plugins>
<plugin interceptor="design.Interceptor.DemoPlugin">
<!-- 对拦截器中的属性进行初始化 -->
<property name="logLevel" value="1"/>
</plugin>
</plugins>
MyBatis 会在初始化流程中解析 mybatis-config.xml 全局配置文件,其中的 节点就会被处理成相应的 Interceptor 对象,同时调用 setProperties() 方法完成配置的初始化,最后MyBatis 会将 Interceptor 对象添加到Configuration.interceptorChain 这个全局的 Interceptor 列表中保存。
拦截器注解的作用
自定义拦截器必须使用mybatis提供的注解来声明我们要拦截的类型对象。
Mybatis插件都要有Intercepts 注解来指定要拦截哪个对象哪个方法。我们知道,Plugin.wrap方法会返回四大接口对象的代理对象,会拦截所有的方法。在代理对象执行对应方法的时候,会调用InvocationHandler处理器的invoke方法。
在mybatis中可被拦截的类型有四种(按照拦截顺序)
- Executor:拦截执行器的方法。
- ParameterHandler:拦截参数的处理。
- ResultHandler:拦截结果集的处理。
- StatementHandler:拦截Sql语法构建的处理。
一个DEMO
针对一些使用单个实体类去接收返回结果的 mapper方法,我们拦截检测,如果没写 LIMIT 1 ,我们将自动帮忙填充,达到查找单条数据 效率优化的效果。
Mapper接口
public interface UserMapperPlugin {
User selectUserById(@Param("id") Integer userId, @Param("name") String name);
List<User> selectUserAll();
void insertUser(User user);
List<User> getUsersByName(@Param("name") String name);
User getUserByName(@Param("name") String name);
}
Mapper xml文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.apache.ibatis.test.UserMapperPlugin">
<!--设置domain类和数据库中表的字段一一对应,注意数据库字段和domain类中的字段名称不致,此处一定要!-->
<resultMap id="BaseResultMap" type="org.apache.ibatis.test.User">
<id column="ID" property="id" jdbcType="INTEGER"/>
<result column="NAME" property="name" jdbcType="CHAR"/>
<result column="DATA" property="data" jdbcType="CHAR"/>
</resultMap>
<!-- 查询单条记录 -->
<select id="selectUserById" resultMap="BaseResultMap">
SELECT *
FROM student
WHERE ID = #{id}
and name = #{name}
</select>
<!-- 查询所有记录 -->
<select id="selectUserAll" resultMap="BaseResultMap">
SELECT *
FROM student
</select>
<select id="getUsersByName" resultMap="BaseResultMap">
SELECT *
FROM student
where name = #{name}
</select>
<select id="getUserByName" resultMap="BaseResultMap">
SELECT *
FROM student
where name = #{name}
</select>
<!-- 插入单条记录 -->
<insert id="insertUser" parameterType="org.apache.ibatis.test.User">
insert into student (id, name, data)
values (#{id}, #{name}, #{data})
</insert>
</mapper>
Mybatis-config 配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<!-- 打印sql日志 -->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<plugins>
<plugin interceptor="org.apache.ibatis.test.MybatisInterceptor"/>
</plugins>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/evan?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="Qwe123!@#"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="UserMapper1.xml"/>
</mappers>
</configuration>
自定义插件
package org.apache.ibatis.test;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.*;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import java.lang.reflect.Method;
import java.util.*;
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class MybatisInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
//获取执行参数
Object[] objects = invocation.getArgs();
MappedStatement ms = (MappedStatement) objects[0];
//解析执行sql的map方法,开始自定义规则匹配逻辑
String mapperMethodAllName = ms.getId();
int lastIndex = mapperMethodAllName.lastIndexOf(".");
String nameSpace = mapperMethodAllName.substring(0, lastIndex);
String methodName = mapperMethodAllName.substring((lastIndex + 1));
Class<?> mapperClass = Class.forName(nameSpace);
Method[] methods = mapperClass.getMethods();
//返回类型
Class<?> returnType;
for (Method method : methods) {
if (method.getName().equals(methodName)) {
returnType = method.getReturnType();
if (returnType.isAssignableFrom(List.class)) {
System.out.println("返回类型是 List");
System.out.println("针对List 做一些操作");
} else if (returnType.isAssignableFrom(Set.class)) {
System.out.println("返回类型是 Set");
System.out.println("针对Set 做一些操作");
} else {
BoundSql boundSql = ms.getSqlSource().getBoundSql(objects[1]);
String oldSql = boundSql.getSql().toLowerCase(Locale.CHINA).replace("[\t\n\r]", " ");
if (!oldSql.contains("LIMIT")) {
String newSql = boundSql.getSql().toLowerCase(Locale.CHINA).replace("[\t\n\r]", " ") + " LIMIT 1";
BoundSql newBoundSql = new BoundSql(ms.getConfiguration(), newSql,
boundSql.getParameterMappings(), boundSql.getParameterObject());
MappedStatement newMs = newMappedStatement(ms, new MyBoundSqlSqlSource(newBoundSql));
for (ParameterMapping mapping : boundSql.getParameterMappings()) {
String prop = mapping.getProperty();
if (boundSql.hasAdditionalParameter(prop)) {
newBoundSql.setAdditionalParameter(prop, boundSql.getAdditionalParameter(prop));
}
}
Object[] queryArgs = invocation.getArgs();
queryArgs[0] = newMs;
System.out.println("打印新SQL语句" + newSql);
}
}
}
}
//继续执行逻辑
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
//获取代理权
if (target instanceof Executor) {
//如果是Executor(执行增删改查操作),则拦截下来
return Plugin.wrap(target, this);
} else {
return target;
}
}
/**
* 定义一个内部辅助类,作用是包装 SQL
*/
class MyBoundSqlSqlSource implements SqlSource {
private BoundSql boundSql;
public MyBoundSqlSqlSource(BoundSql boundSql) {
this.boundSql = boundSql;
}
@Override
public BoundSql getBoundSql(Object parameterObject) {
return boundSql;
}
}
private MappedStatement newMappedStatement(MappedStatement ms, SqlSource newSqlSource) {
MappedStatement.Builder builder = new
MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource, ms.getSqlCommandType());
builder.resource(ms.getResource());
builder.fetchSize(ms.getFetchSize());
builder.statementType(ms.getStatementType());
builder.keyGenerator(ms.getKeyGenerator());
if (ms.getKeyProperties() != null && ms.getKeyProperties().length > 0) {
builder.keyProperty(ms.getKeyProperties()[0]);
}
builder.timeout(ms.getTimeout());
builder.parameterMap(ms.getParameterMap());
builder.resultMaps(ms.getResultMaps());
builder.resultSetType(ms.getResultSetType());
builder.cache(ms.getCache());
builder.flushCacheRequired(ms.isFlushCacheRequired());
builder.useCache(ms.isUseCache());
return builder.build();
}
@Override
public void setProperties(Properties properties) {
//读取mybatis配置文件中属性
}
}
测试
@Test
public void test_selectUserByName() throws IOException {
String resource = "mybatis-config1.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 创建数据库会话
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
// 获取要调用的接口
UserMapperPlugin mapper = sqlSession.getMapper(UserMapperPlugin.class);
// 调用方法开始执行
//org.apache.ibatis.binding.MapperProxy.invoke
User users = mapper.getUserByName("evan");
System.out.println(users);
}
}
未使用插件之前
==> Preparing: SELECT * FROM student where name = ?
==> Parameters: evan(String)
<== Columns: id, name, data
<== Row: 1, evan, major
<== Row: 7, evan, major
<== Row: 10, evan, major
<== Total: 3
使用插件之后
Interceptor实现原理
介绍完 Interceptor 的加载和初始化原理之后,我们再来看 Interceptor 是如何拦截目标类中的目标方法的。
Configuration 中属性interceptorChain的初始化
其实就是解析Mybatis-config.xml中的标签
/**
* 解析<plugins>节点
* @param parent <plugins>节点
* @throws Exception
*/
private void pluginElement(XNode parent) throws Exception {
if (parent != null) { // <plugins>节点存在
for (XNode child : parent.getChildren()) { // 依次<plugins>节点下的取出每个<plugin>节点
// 读取拦截器类名
String interceptor = child.getStringAttribute("interceptor");
// 读取拦截器属性
Properties properties = child.getChildrenAsProperties();
// 实例化拦截器类
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
// 设置拦截器的属性
interceptorInstance.setProperties(properties);
// 将当前拦截器加入到拦截器链中
configuration.addInterceptor(interceptorInstance);
}
}
}
Configuration 中属性interceptorChain的使用
我们在获取SqlSesison一般都是通过 SqlSessionFactory.getSqlSession()方法获取的,在该方法中同时会创建Executor、ParameterHandler、ResultSetHandler、StatementHandler 等。
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private final Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
//...
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
// 获取mybatis-config.xml配置文件中配置的Environment对象
final Environment environment = configuration.getEnvironment();
// 获取TransactionFactory对象
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// 创建 Transaction 对象
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 创建执行器
// 根据配置创建Executor对象
final Executor executor = configuration.newExecutor(tx, execType);
// 创建DefaultSqlSession
// 产生一个DefaultSqlSession对象
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
// 如果打开事务出错,则关闭它
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
// 最后清空错误上下文
ErrorContext.instance().reset();
}
}
private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
try {
boolean autoCommit;
try {
autoCommit = connection.getAutoCommit();
} catch (SQLException e) {
// Failover to true, as most poor drivers
// or databases won't support transactions
autoCommit = true;
}
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
final Transaction tx = transactionFactory.newTransaction(connection);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
//...
public class SimpleExecutor extends BaseExecutor {
//...
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration(); //获得配置
//获得statementHandler里面有statement,来处理
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
stmt = prepareStatement(handler, ms.getStatementLog());
//最终是一个statement进行处理
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}
//...
}
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;
//...
}
该部分代码只是想说明四大组件是存在关联关系的,这里不做分析。
通过源码可以发现MyBatis 中 Executor、ParameterHandler、ResultSetHandler、StatementHandler 等与 SQL 执行相关的核心组件都是通过 Configuration.new*() 方法生成的。
//org.apache.ibatis.session.Configuration
public class Configuration {
/**
* 配置默认的执行器。
* SIMPLE 就是普通的执行器;
* REUSE 执行器会重用预处理语句(prepared statements);
* BATCH 执行器将重用语句并执行批量更新。
*/
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
/**
* 指定 MyBatis 应如何自动映射列到字段或属性。
* NONE 表示取消自动映射;
* PARTIAL 只会自动映射没有定义嵌套结果集映射的结果集。
* FULL 会自动映射任意复杂的结果集(无论是否嵌套)。
*/
protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
//拦截器链
protected final InterceptorChain interceptorChain = new InterceptorChain();
//...
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
//确保ExecutorType不为空(defaultExecutorType有可能为空)
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;
}
// 创建语句处理器
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// 创建具有路由功能的 StatementHandler
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
// 应用插件到 StatementHandler 上
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
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;
}
//...
}
查看源码可以发现, Mybatis框架在创建好这四大接口对象的实例后,都会调用InterceptorChain.pluginAll()方法。Configuration中的属性interceptorChain 是 Interceptor 构成的责任链,在其 interceptors 字段(List类型)中维护了 MyBatis 初始化过程中加载到的全部 Interceptor 对象,在其 pluginAll() 方法中,会调用每个 Interceptor 的 plugin() 方法创建目标类的代理对象。
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList<>();
/**
* 向所有的拦截器链提供目标对象,由拦截器链给出替换目标对象的对象
* @param target 目标对象,是MyBatis中支持拦截的几个类(ParameterHandler、ResultSetHandler、StatementHandler、Executor)的实例
* @return 用来替换目标对象的对象
*/
public Object pluginAll(Object target) {
// 依次交给每个拦截器完成目标对象的替换工作
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
//...
}
按照顺序执行插件的plugin方法,一层一层返回我们原对象(Executor/ParameterHandler/ResultSetHander/StatementHandler)的代理对象。
public interface Interceptor {
//intercept方法就是要进行拦截的时候要执行的方法
Object intercept(Invocation invocation) throws Throwable;
//plugin方法是插件用于封装目标对象的,通过该方法我们可以返回目标对象本身,也可以返回一个它的代理,可以决定是否要进行拦截进而决定要返回一个什么样的目标对象,
// 官方提供了示例:return Plugin.wrap(target, this);
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
//setProperties方法是在Mybatis进行配置插件的时候可以配置自定义相关属性,即:接口实现对象的参数配置
default void setProperties(Properties properties) {
// NOP
}
}
我们可以看到 plugin() 方法依赖 MyBatis 提供的 Plugin.wrap() 工具方法创建代理对象。
Plugin
public class Plugin implements InvocationHandler {
//要拦截的目标对象
private final Object target;
//目标方法被拦截后,要执行的逻辑就写在了该 Interceptor 对象的 intercept() 方法中。
private final Interceptor interceptor;
//记录了 @Signature 注解中配置的方法信息,也就是代理要拦截的目标方法信息。
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) {
// 获取自定义Interceptor实现类上的@Signature注解信息,
// 这里的getSignatureMap()方法会解析@Signature注解,得到要拦截的类以及要拦截的方法集合
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
// 检查当前传入的target对象是否为@Signature注解要拦截的类型,如果是的话,就
// 使用JDK动态代理的方式创建代理对象
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
// 创建JDK动态代理
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
// 这里使用的InvocationHandler就是Plugin本身
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());
// 如果当前方法需要被代理,则执行intercept()方法进行拦截处理
if (methods != null && methods.contains(method)) {
return interceptor.intercept(new Invocation(target, method, args));
}
// 如果当前方法不需要被代理,则调用target对象的相应方法
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
//@Intercepts、@Signature注解的解析
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;
}
private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
Set<Class<?>> interfaces = new HashSet<>();
while (type != null) {
for (Class<?> c : type.getInterfaces()) {
if (signatureMap.containsKey(c)) {
interfaces.add(c);
}
}
type = type.getSuperclass();
}
return interfaces.toArray(new Class<?>[0]);
}
}
通过源码可以发现Plugin实现了InvocationHandler接口。
invoke()
既然 Plugin 实现了 InvocationHandler 接口,我们自然需要关注其 invoke() 方法实现。在 invoke() 方法中,Plugin 会检查当前要执行的方法是否在 signatureMap 集合中,如果在其中的话,表示当前待执行的方法是我们要拦截的目标方法之一,也就会调用 intercept() 方法执行代理逻辑;如果未在其中的话,则表示当前方法不应被代理,直接执行当前的方法即可。
如果所有的条件都满足,也就是需要代理会调用 interceptor.intercept(new Invocation(target, method, args));
Interceptor.intercept() 方法的如参是Invocation类型,Invocation中封装了目标对象、目标方法以及目标方法的相关参数,在 Interceptor.intercept() 方法实现中,需要通过调用 Invocation.proceed() 方法完成目标方法的执行。
@Override
public Object intercept(Invocation invocation) throws Throwable {
// do something...
//继续执行逻辑
return invocation.proceed();
}
org.apache.ibatis.plugin.Invocation
public class Invocation {
//...
public Object proceed() throws InvocationTargetException, IllegalAccessException {
return method.invoke(target, args);
}
wrap()
在 wrap() 方法中,Plugin 工具类会解析传入的 Interceptor 实现的 @Signature 注解信息,并与当前传入的目标对象类型进行匹配,只有在匹配的情况下,才会生成代理对象,否则直接返回目标对象。
分页插件
一个Mybatis分页的插件,可以为以后开发做参考。
/**
* Mybatis - 通用分页插件(如果开启二级缓存需要注意)
*/
@Intercepts({
@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class}),
@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})
})
@Log4j
public class PageHelper implements Interceptor {
public static final ThreadLocal<Page> localPage = new ThreadLocal<Page>();
/**
* 开始分页
*
* @param pageNum
* @param pageSize
*/
public static void startPage(int pageNum, int pageSize) {
localPage.set(new Page(pageNum, pageSize));
}
/**
* 结束分页并返回结果,该方法必须被调用,否则localPage会一直保存下去,直到下一次startPage
*
* @return
*/
public static Page endPage() {
Page page = localPage.get();
localPage.remove();
return page;
}
public Object intercept(Invocation invocation) throws Throwable {
if (localPage.get() == null) {
return invocation.proceed();
}
if (invocation.getTarget() instanceof StatementHandler) {
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
MetaObject metaStatementHandler = SystemMetaObject.forObject(statementHandler);
// 分离代理对象链(由于目标类可能被多个插件拦截,从而形成多次代理,通过下面的两次循环
// 可以分离出最原始的的目标类)
while (metaStatementHandler.hasGetter("h")) {
Object object = metaStatementHandler.getValue("h");
metaStatementHandler = SystemMetaObject.forObject(object);
}
// 分离最后一个代理对象的目标类
while (metaStatementHandler.hasGetter("target")) {
Object object = metaStatementHandler.getValue("target");
metaStatementHandler = SystemMetaObject.forObject(object);
}
MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement");
//分页信息if (localPage.get() != null) {
Page page = localPage.get();
BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");
// 分页参数作为参数对象parameterObject的一个属性
String sql = boundSql.getSql();
// 重写sql
String pageSql = buildPageSql(sql, page);
//重写分页sql
metaStatementHandler.setValue("delegate.boundSql.sql", pageSql);
Connection connection = (Connection) invocation.getArgs()[0];
// 重设分页参数里的总页数等
setPageParameter(sql, connection, mappedStatement, boundSql, page);
// 将执行权交给下一个插件
return invocation.proceed();
} else if (invocation.getTarget() instanceof ResultSetHandler) {
Object result = invocation.proceed();
Page page = localPage.get();
page.setResult((List) result);
return result;
}
return null;
}
/**
* 只拦截这两种类型的
* <br>StatementHandler
* <br>ResultSetHandler
*
* @param target
* @return
*/
public Object plugin(Object target) {
if (target instanceof StatementHandler || target instanceof ResultSetHandler) {
return Plugin.wrap(target, this);
} else {
return target;
}
}
public void setProperties(Properties properties) {
}
/**
* 修改原SQL为分页SQL
*
* @param sql
* @param page
* @return
*/
private String buildPageSql(String sql, Page page) {
StringBuilder pageSql = new StringBuilder(200);
pageSql.append("select * from (");
pageSql.append(sql);
pageSql.append(" ) temp limit ").append(page.getStartRow());
pageSql.append(" , ").append(page.getPageSize());
return pageSql.toString();
}
/**
* 获取总记录数
*
* @param sql
* @param connection
* @param mappedStatement
* @param boundSql
* @param page
*/
private void setPageParameter(String sql, Connection connection, MappedStatement mappedStatement,
BoundSql boundSql, Page page) {
// 记录总记录数
String countSql = "select count(0) from (" + sql + ") temp";
PreparedStatement countStmt = null;
ResultSet rs = null;
try {
countStmt = connection.prepareStatement(countSql);
BoundSql countBS = new BoundSql(mappedStatement.getConfiguration(), countSql,
boundSql.getParameterMappings(), boundSql.getParameterObject());
setParameters(countStmt, mappedStatement, countBS, boundSql.getParameterObject());
rs = countStmt.executeQuery();
int totalCount = 0;
if (rs.next()) {
totalCount = rs.getInt(1);
}
page.setTotal(totalCount);
int totalPage = totalCount / page.getPageSize() + ((totalCount % page.getPageSize() == 0) ? 0 : 1);
page.setPages(totalPage);
} catch (SQLException e) {
log.error("Ignore this exception", e);
} finally {
try {
rs.close();
} catch (SQLException e) {
log.error("Ignore this exception", e);
}
try {
countStmt.close();
} catch (SQLException e) {
log.error("Ignore this exception", e);
}
}
}
/**
* 代入参数值
*
* @param ps
* @param mappedStatement
* @param boundSql
* @param parameterObject
* @throws SQLException
*/
private void setParameters(PreparedStatement ps, MappedStatement mappedStatement, BoundSql boundSql,
Object parameterObject) throws SQLException {
ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
parameterHandler.setParameters(ps);
}
@Data //采用lombok插件编译
public static class Page<E> {
private int pageNum;
private int pageSize;
private int startRow;
private int endRow;
private long total;
private int pages;
private List<E> result;
public Page(int pageNum, int pageSize) {
this.pageNum = pageNum;
this.pageSize = pageSize;
this.startRow = pageNum > 0 ? (pageNum - 1) * pageSize : 0;
this.endRow = pageNum * pageSize;
}
}
}