MyBatis是一款不完全的ORM框架,将sql语句从Java代码中抽取出来,降低了sql语句与代码之间的耦合度,同时了许多的标签提高了我们编写sql语句的灵活性,输入输出映射能将我们的表字段与JavaBean中的属性字段进行一一的映射,当然Mybatis框架的优秀性远远不止这些。该篇博文主要讲的是Mybatis如何进行自定插件拦截器,以及其内部的简单原理,不会做过多的源码分析。OK,下面就开始吧!
MyBatis中的四大对象
在编写自定义拦截器之前,我们先来简单介绍一下MyBatis内部其中的四大对象的作用以及其创建过程。
-
Executor:该对象主要是用来执行我们的sql语句的。
-
StatementHandler:该对象主要是用来预编译我们的sq语句l,将输入参数用问号占位符的形式替换。
-
ParameterHandler:该对象主要是将参数设置进我们预编好的sql语句。
-
ResultSetHandler:该对象主要是处理结果集映射。
下面跟进源码看看这四个对象是如何创建的。1、Executor对象的创建
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
//这里的executorType可以在mybatis的全局文件中设置,默认为SIMPLE
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
//根据executorType的值进行不同的构造方式
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用interceptorChain.pluginAll()进行封装。
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
2、StatementHandler对象的创建
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用interceptorChain.pluginAll()进行封装。
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
3、ParameterHandler对象和ResultSetHandler对象的创建,这两个对象其实在StatementHandler对象创建的时候就在其构造器中创建好的了
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
//关键一步:将我们创建好的ParameterHandler用interceptorChain.pluginAll()进行封装。
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用interceptorChain.pluginAll()进行封装。
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
return resultSetHandler;
}
从上述的四大对象的创建过成中不难看出都有这么的一段类似的代码
xxx = (Xxx) interceptorChain.pluginAll(xxx);(其中的xxx代表具体的某个对象)
不得不说这段代码设计得很巧妙,这是MyBatis设计者为我们方便扩展框架本身的功能而设计出来的。我们的自定义插件拦截器就是基于这段代码进行开发的。
下面就来看看这段代码做了什么事情。
//截取InterceptorChain类下的代码片段
//获取配置文件中配置的所有拦截器
private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
public Object pluginAll(Object target) {
/*将我们的目标对象(这里为四大对象中其中之一)用interceptor.plugin(target)进行包装,
这里包装后的结果为返回目标对象的代理对象,
既然是代理对象我们就能在执行目标方法之前之后做自己的一些逻辑处理,这里也体现出了AOP的编程思想
*/
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
这里MyBatis中的四大对象的创建就讲述完了,明白四大对象的创建共性——如果我们自定义了拦截器,最后都是将其进行包装并返回代理对象。为什么要讲述这些呢?因为了解这些对象的创建过程更有利于我们手动编写自定拦截器以及拦截的原理,甚至实现自己分页插件。
MyBatis自定义插件拦截器
上述代码中的List集合的泛型为Interceptor,这个Interceptor就是mybatis提供给我们使用的一个拦截器接口,实现这个拦截器接口就能够对我们的目标类下的目标方法进行拦截,从而对我们的目标方法进行增强。
不过要注意的是MyBatis只能拦截Executor、StatementHandler、ParameterHandler、ResultSetHandler这四大对象下的方法
public interface Interceptor {
// 拦截到目标方法后要做的事情
Object intercept(Invocation invocation) throws Throwable;
//根据目标对象返回其代理对象
Object plugin(Object target);
//将配置拦截器时的参数设置进来
void setProperties(Properties properties);
}
自定义插件拦截器的步骤:
- 创建一个拦截器类实现Interceptor,实现里面的方法。
- 为这个拦截器注册方法签名,这个方法签名主要是来告诉mybatis这个拦截器类是用来拦截哪个类下的哪个方法的。
- 将编写好的拦截器类注册到mybatis的全局配置文件中。
案例代码
/**
* 完成插件签名:
* 告诉mybatis当前插件用来拦截哪个对象的哪个方法
* @author 陈先生
* type:要拦截的类
* method:拦截类下的哪个方法
* args:这个方法的方法参数
*/
@Intercepts({
@Signature(type=StatementHandler.class,method="parameterize",args=java.sql.Statement.class)
})
public Object intercept(Invocation invocation) throws Throwable {
Object target = invocation.getTarget();
System.out.println("拦截到的目标对象:"+target);
System.out.println("拦截到的目标方法:"+invocation.getMethod());
System.out.println("拦截到的目标方法下的参数:"+invocation.getArgs());
MetaObject metaObject = SystemMetaObject.forObject(target);
Object value = metaObject.getValue("parameterHandler.parameterObject");
System.out.println("原来sql语句中的参数为:"+value);
//修改原来sql语句中的参数,将其改成查询id为2的用户
metaObject.setValue("parameterHandler.parameterObject", 2);
//执行原来目标对象的目标方法。
Object proceed = invocation.proceed();
System.out.println("修改后的参数:"+metaObject.getValue("parameterHandler.parameterObject"));
return proceed;
}
/*
*包装目标对象,就是为我们的目标对象创建代理对象
* wrap():使用当前拦截器包装我们的目标对象,产生代理对象
*/
@Override
public Object plugin(Object target) {
System.out.println("plugin is running....");
//使用Plugin下的wrap()方法就能直接返回代理对象
//当然也可以自己自己使用Proxy类来手动编写
Object wrap = Plugin.wrap(target, this);
//返回代理对象
//四大对象在创建返回之前,都会先调用这个方法产生代理对象后再返回,
//之后我们的四大对象在使用的时候就是调用的代理对象下的方法。
return wrap;
}
//将插件注册时的property属性设置进来
@Override
public void setProperties(Properties properties) {
System.out.println("获取到的信息:"+properties);
}
}
<plugin interceptor="com.zhku.Interceptor.MyInterceptor">
<!-- 设置拦截器参数 -->
<property name="password" value="123456"/>
</plugin>
@Test
public void findUserByIdMapperTest() throws Exception{
//创建userMapper的对象
SqlSession session = this.getSqlSessionFactory().openSession();
UserMapper userMapper = session.getMapper(UserMapper.class);
User user = userMapper.findUserById(1);//查询id为1的用户
System.out.println(user);
}
通过控制台可以清楚的看出我们的自定义拦截器已经起到了作用,并且成功的将原来输入参数1偷偷的改成了2,说明通过这个拦截器我们可以更改sql语句的相关内容,甚至是重写sql语句,现在很流行的PageHelper分页插件就是基于这样的原理进行设计的,其本身也是实现这个接口,但是其内部方法的设计原理要复杂的多。
总结
- MyBatis的四大对象的创建过程中都不是直接返回的,而是进行 interceptorChain.pluginAll()进行包装后返回的。
- MyBatis框架提供的Interceptor接口只能用来拦截这四大对象下的方法。
- 编写好自定义拦截器后记得将其设置到全局配置文件中。
- 四大对象:Executor、StatementHandler、ParameterHandler、ResultSetHandler