Mybatis 插件原理
在实际开发过程中, 我们经常使用的 Mybaits 插件就是分页插件了, 通过分页插件我们可以在不用写 count 语句和 limit 的情况下就可以获取分页后的数据, 给我们开发带来很大
的便利. 除了分页, 插件使用场景主要还有更新数据库的通用字段, 分库分表, 加解密等的处理.
这篇博客主要讲 Mybatis 插件原理, 下一篇博客会设计一个 Mybatis 插件实现的功能就是每当新增数据的时候不用数据库自增 ID 而是通过该插件生成雪花 ID, 作为每条数据的主键.
一, JDK 动态代理 + 责任链设计模式
Mybatis 的插件其实就是个拦截器功能. 它利用 JDK 动态代理和责任链设计模式的综合运用. 采用责任链模式, 通过动态代理组织多个拦截器, 通过这些拦截器你可以做一些
你想做的事. 所以在讲 Mybatis 拦截器之前我们先说说 JDK 动态代理 + 责任链设计模式. 有关 JDK 动态代理的原理, 可以参考我之前写的一篇博客:[java 设计模式] --- 代理模式
1,JDK 动态代理案例
- public class MyProxy {
- /**
- * 一个接口
- */
- public interface HelloService{
- void sayHello();
- }
- /**
- * 目标类实现接口
- */
- static class HelloServiceImpl implements HelloService{
- @Override
- public void sayHello() {
- System.out.println("sayHello......");
- }
- }
- /**
- * 自定义代理类需要实现 InvocationHandler 接口
- */
- static class HWInvocationHandler implements InvocationHandler {
- /**
- * 目标对象
- */
- private Object target;
- public HWInvocationHandler(Object target){
- this.target = target;
- }
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- System.out.println("------ 插入前置通知代码 -------------");
- // 执行相应的目标方法
- Object rs = method.invoke(target,args);
- System.out.println("------ 插入后置处理代码 -------------");
- return rs;
- }
- public static Object wrap(Object target) {
- return Proxy.newProxyInstance(target.getClass().getClassLoader(),
- target.getClass().getInterfaces(),new HWInvocationHandler(target));
- }
- }
- public static void main(String[] args) {
- HelloService proxyService = (HelloService) HWInvocationHandler.wrap(new HelloServiceImpl());
- proxyService.sayHello();
- }
- }
运行结果
------ 插入前置通知代码 -------------
sayHello......
------ 插入后置处理代码 -------------
2, 优化
上面代理的功能是实现了, 但是有个很明显的缺陷, 就是 HWInvocationHandler 是动态代理类, 也可以理解成是个工具类, 我们不可能会把业务代码写到写到到 invoke 方法里,
不符合面向对象的思想, 可以抽象一下处理. 可以设计一个 Interceptor 接口, 需要做什么拦截处理实现接口就行了.
- public interface Interceptor {
- /**
- * 具体拦截处理
- */
- void intercept();
- }
intercept() 方法就可以处理各种前期准备了
- public class LogInterceptor implements Interceptor {
- @Override
- public void intercept() {
- System.out.println("------ 插入前置通知代码 -------------");
- }
- }
- public class TransactionInterceptor implements Interceptor {
- @Override
- public void intercept() {
- System.out.println("------ 插入后置处理代码 -------------");
- }
- }
代理对象也做一下修改
- public class HWInvocationHandler implements InvocationHandler {
- private Object target;
- private List<Interceptor> interceptorList = new ArrayList<>();
- public TargetProxy(Object target,List<Interceptor> interceptorList) {
- this.target = target;
- this.interceptorList = interceptorList;
- }
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- // 处理多个拦截器
- for (Interceptor interceptor : interceptorList) {
- interceptor.intercept();
- }
- return method.invoke(target, args);
- }
- public static Object wrap(Object target,List<Interceptor> interceptorList) {
- HWInvocationHandler targetProxy = new HWInvocationHandler(target, interceptorList);
- return Proxy.newProxyInstance(target.getClass().getClassLoader(),
- target.getClass().getInterfaces(),targetProxy);
- }
- }
现在可以根据需要动态的添加拦截器了, 在每次执行业务代码 sayHello()之前都会拦截, 看起来高级一点, 来测试一下
- public class Test {
- public static void main(String[] args) {
- List<Interceptor> interceptorList = new ArrayList<>();
- interceptorList.add(new LogInterceptor());
- interceptorList.add(new TransactionInterceptor());
- HelloService target = new HelloServiceImpl();
- Target targetProxy = (Target) TargetProxy.wrap(target,interceptorList);
- targetProxy.sayHello();
- }
- }
运行结果
------ 插入前置通知代码 -------------
------ 插入后置处理代码 -------------
sayHello......
3, 再优化
上面的动态代理确实可以把代理类中的业务逻辑抽离出来, 但是我们注意到, 只有前置代理, 无法做到前后代理, 所以还需要在优化下. 所以需要做更一步的抽象,
把拦截对象信息进行封装, 作为拦截器拦截方法的参数, 把拦截目标对象真正的执行方法放到 Interceptor 中完成, 这样就可以实现前后拦截, 并且还能对拦截
对象的参数等做修改. 设计一个 Invocation 对象.
- public class Invocation {
- /**
- * 目标对象
- */
- private Object target;
- /**
- * 执行的方法
- */
- private Method method;
- /**
- * 方法的参数
- */
- private Object[] args;
- // 省略 getset
- public Invocation(Object target, Method method, Object[] args) {
- this.target = target;
- this.method = method;
- this.args = args;
- }
- /**
- * 执行目标对象的方法
- */
- public Object process() throws Exception{
- return method.invoke(target,args);
- }
- }
Interceptor 拦截接口做修改
- public interface Interceptor {
- /**
- * 具体拦截处理
- */
- Object intercept(Invocation invocation) throws Exception;
- }
Interceptor 实现类
- public class TransactionInterceptor implements Interceptor {
- @Override
- public Object intercept(Invocation invocation) throws Exception{
- System.out.println("------ 插入前置通知代码 -------------");
- Object result = invocation.process();
- System.out.println("------ 插入后置处理代码 -------------");
- return result;
- }
- }
Invocation 类就是被代理对象的封装, 也就是要拦截的真正对象. HWInvocationHandler 修改如下:
- public class HWInvocationHandler implements InvocationHandler {
- private Object target;
- private Interceptor interceptor;
- public TargetProxy(Object target,Interceptor interceptor) {
- this.target = target;
- this.interceptor = interceptor;
- }
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- Invocation invocation = new Invocation(target,method,args);
- return interceptor.intercept(invocation);
- }
- public static Object wrap(Object target,Interceptor interceptor) {
- HWInvocationHandler targetProxy = new HWInvocationHandler(target, interceptor);
- return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),targetProxy);
- }
- }
测试类
- public class Test {
- public static void main(String[] args) {
- HelloService target = new HelloServiceImpl();
- Interceptor transactionInterceptor = new TransactionInterceptor();
- HelloService targetProxy = (Target) TargetProxy.wrap(target,transactionInterceptor);
- targetProxy.sayHello();
- }
- }
运行结果
------ 插入前置通知代码 -------------
sayHello......
------ 插入后置处理代码 -------------
4, 再再优化
上面这样就能实现前后拦截, 并且拦截器能获取拦截对象信息. 但是测试代码的这样调用看着很别扭, 对应目标类来说, 只需要了解对他插入了什么拦截就好.
再修改一下, 在拦截器增加一个插入目标类的方法.
- public interface Interceptor {
- /**
- * 具体拦截处理
- */
- Object intercept(Invocation invocation) throws Exception;
- /**
- * 插入目标类
- */
- Object plugin(Object target);
- }
- public class TransactionInterceptor implements Interceptor {
- @Override
- public Object intercept(Invocation invocation) throws Exception{
- System.out.println("------ 插入前置通知代码 -------------");
- Object result = invocation.process();
- System.out.println("------ 插入后置处理代码 -------------");
- return result;
- }
- @Override
- public Object plugin(Object target) {
- return TargetProxy.wrap(target,this);
- }
- }
这样目标类仅仅需要在执行前, 插入需要的拦截器就好了, 测试代码:
- public class Test {
- public static void main(String[] args) {
- HelloService target = new HelloServiceImpl();
- Interceptor transactionInterceptor = new TransactionInterceptor();
- // 把事务拦截器插入到目标类中
- target = (HelloService) transactionInterceptor.plugin(target);
- target.sayHello();
- }
- }
运行结果
------ 插入前置通知代码 -------------
sayHello......
------ 插入后置处理代码 -------------
5, 多个拦截器如何处理
到这里就差不多完成了, 那我们再来思考如果要添加多个拦截器呢, 怎么搞?
- public class Test {
- public static void main(String[] args) {
- HelloService target = new HelloServiceImpl();
- Interceptor transactionInterceptor = new TransactionInterceptor();
- target = (HelloService) transactionInterceptor.plugin(target);
- LogInterceptor logInterceptor = new LogInterceptor();
- target = (HelloService)logInterceptor.plugin(target);
- target.sayHello();
- }
- }
运行结果
------ 插入前置通知代码 -------------
------ 插入前置通知代码 -------------
sayHello......
------ 插入后置处理代码 -------------
------ 插入后置处理代码 -------------
6, 责任链设计模式
其实上面已经实现的没问题了, 只是还差那么一点点, 添加多个拦截器的时候不太美观, 让我们再次利用面向对象思想封装一下. 我们设计一个 InterceptorChain 拦截器链类
- public class InterceptorChain {
- private List<Interceptor> interceptorList = new ArrayList<>();
- /**
- * 插入所有拦截器
- */
- public Object pluginAll(Object target) {
- for (Interceptor interceptor : interceptorList) {
- target = interceptor.plugin(target);
- }
- return target;
- }
- public void addInterceptor(Interceptor interceptor) {
- interceptorList.add(interceptor);
- }
- /**
- * 返回一个不可修改集合, 只能通过 addInterceptor 方法添加
- * 这样控制权就在自己手里
- */
- public List<Interceptor> getInterceptorList() {
- return Collections.unmodifiableList(interceptorList);
- }
- }
其实就是通过 pluginAll() 方法包一层把所有的拦截器插入到目标类去而已. 测试代码:
- public class Test {
- public static void main(String[] args) {
- HelloService target = new HelloServiceImpl();
- Interceptor transactionInterceptor = new TransactionInterceptor();
- LogInterceptor logInterceptor = new LogInterceptor();
- InterceptorChain interceptorChain = new InterceptorChain();
- interceptorChain.addInterceptor(transactionInterceptor);
- interceptorChain.addInterceptor(logInterceptor);
- target = (Target) interceptorChain.pluginAll(target);
- target.sayHello();
- }
- }
这里展示的是 JDK 动态代理 + 责任链设计模式, 那么 Mybatis 拦截器就是基于该组合进行开发.
二, Mybatis Plugin 插件概念
1, 原理
Mybatis 的拦截器实现机制跟上面最后优化后的代码非常的相似. 它也有个代理类 Plugin(就是上面的 HWInvocationHandler)这个类同样也会实现了 InvocationHandler 接口,
当我们调用 ParameterHandler,ResultSetHandler,StatementHandler,Executor 的对象的时候,, 就会执行 Plugin 的 invoke 方法, Plugin 在 invoke 方法中根据
@Intercepts 的配置信息 (方法名, 参数等) 动态判断是否需要拦截该方法. 再然后使用需要拦截的方法 Method 封装成 Invocation, 并调用 Interceptor 的 proceed 方法.
这样我们就达到了拦截目标方法的结果. 例如 Executor 的执行大概是这样的流程:
拦截器代理类对象 ->拦截器 ->目标方法
Executor.Method->Plugin.invoke->Interceptor.intercept->Invocation.proceed->method.invoke.
2, 如何自定义拦截器?
1) Interceptor 接口
首先 Mybatis 官方早就想到我们开发会有这样的需求, 所以开放了一个 org.apacheibatis.plugin.Interceptor 这样一个接口. 这个接口就是和上面 Interceptor 性质是一样的
- public interface Interceptor {
- // 当 plugin 函数返回代理, 就可以对其中的方法进行拦截来调用 intercept 方法
- Object intercept(Invocation invocation) throws Throwable;
- //plugin 方法是拦截器用于封装目标对象的, 通过该方法我们可以返回目标对象本身, 也可以返回一个它的代理.
- Object plugin(Object target);
- // 在 Mybatis 配置文件中指定一些属性
- void setProperties(Properties properties);
- }
2)自定义拦截器
这里的 ExamplePlugin 和上面的 LogInterceptor 和 TransactionInterceptor 性质是一样的
- @Intercepts({@Signature( type= Executor.class, method = "update", args ={MappedStatement.class,Object.class})})
- public class ExamplePlugin implements Interceptor {
- public Object intercept(Invocation invocation) throws Throwable {
- return invocation.proceed();
- }
- public Object plugin(Object target) {
- return Plugin.wrap(target, this);
- }
- public void setProperties(Properties properties) {
- }
- }
3), 全局 xml 配置
最后如果你使用的是 Mybatis.xml 也就是 Mybatis 本身单独的配置, 你可以需要在这里配置相应的拦截器名字等.
如果你使用的是 spring 管理的 Mybatis, 那么你需要在 Spring 配置文件里面配置注册相应的拦截器.
这样一个自定义 mybatis 插件流程大致就是这样了.
3,Mybatis 四大接口
竟然 Mybatis 是对四大接口进行拦截的, 那我们要先要知道 Mybatis 的四大接口对象 Executor, StatementHandle, ResultSetHandler, ParameterHandler.
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) MyBatis 的执行器, 用于执行增删改查操作;
ParameterHandler (getParameterObject, setParameters) 处理 SQL 的参数对象;
ResultSetHandler (handleResultSets, handleOutputParameters) 处理 SQL 的返回结果集;
StatementHandler (prepare, parameterize, batch, update, query) 拦截 Sql 语法构建的处理
上图 Mybatis 框架的整个执行过程.
三, Mybatis Plugin 插件源码
经过上面的分析, 再去看 Mybastis Plugin 源码的时候就很轻松了.
这几个也就对应上面的几个, 只不过添加了注解, 来判断是否拦截指定方法.
1, 拦截器链 InterceptorChain
- public class InterceptorChain {
- private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
- public Object pluginAll(Object target) {
- // 循环调用每个 Interceptor.plugin 方法
- 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,Configuration
通过初始化配置文件把所有的拦截器添加到拦截器链中.
- public class Configuration {
- protected final InterceptorChain interceptorChain = new InterceptorChain();
- // 创建参数处理器
- 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;
- }
- // 创建结果集处理器
- 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;
- }
- // 创建语句处理器
- 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;
- // 这句再做一下保护, 囧, 防止粗心大意的人将 defaultExecutorType 设成 null?
- executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
- Executor executor;
- // 然后就是简单的 3 个分支, 产生 3 种执行器 BatchExecutor/ReuseExecutor/SimpleExecutor
- 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(默认就是有缓存), 装饰者模式, 所以默认都是返回 CachingExecutor
- if (cacheEnabled) {
- executor = new CachingExecutor(executor);
- }
- // 此处调用插件, 通过插件可以改变 Executor 行为
- executor = (Executor) interceptorChain.pluginAll(executor);
- return executor;
- }
- }
从代码可以看出 Mybatis 在实例化 Executor,ParameterHandler,ResultSetHandler,StatementHandler 四大接口对象的时候调用 interceptorChain.pluginAll()方法插入
进去的. 其实就是循环执行拦截器链所有的拦截器的 plugin() 方法, mybatis 官方推荐的 plugin 方法是 Plugin.wrap() 方法, 这个类就是我们上面的 TargetProxy 类.
3,Plugin
这里的 Plugin 就是我们上面的自定义代理类 TargetProxy 类
- public class Plugin implements InvocationHandler {
- public static Object wrap(Object target, Interceptor interceptor) {
- // 从拦截器的注解中获取拦截的类名和方法信息
- Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
- // 取得要改变行为的类(ParameterHandler|ResultSetHandler|StatementHandler|Executor)
- Class<?> type = target.getClass();
- // 取得接口
- Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
- // 产生代理, 是 Interceptor 注解的接口的实现类才会产生代理
- 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());
- // 是 Interceptor 实现类注解的方法才会拦截处理
- if (methods != null && methods.contains(method)) {
- // 调用 Interceptor.intercept, 也即插入了我们自己的逻辑
- return interceptor.intercept(new Invocation(target, method, args));
- }
- // 最后还是执行原来逻辑
- return method.invoke(target, args);
- } catch (Exception e) {
- throw ExceptionUtil.unwrapThrowable(e);
- }
- }
- // 取得签名 Map, 就是获取 Interceptor 实现类上面的注解, 要拦截的是那个类 (Executor,ParameterHandler, ResultSetHandler,StatementHandler) 的那个方法
- private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
- // 取 Intercepts 注解, 例子可参见 ExamplePlugin.java
- Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
- // issue #251
- // 必须得有 Intercepts 注解, 没有报错
- if (interceptsAnnotation == null) {
- throw new PluginException("No @Intercepts annotation was found in interceptor" + interceptor.getClass().getName());
- }
- //value 是数组型, Signature 的数组
- Signature[] sigs = interceptsAnnotation.value();
- // 每个 class 里有多个 Method 需要被拦截, 所以这么定义
- 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()]);
- }
- }
4,Interceptor 接口
- public interface Interceptor {
- // 拦截
- Object intercept(Invocation invocation) throws Throwable;
- // 插入
- Object plugin(Object target);
- // 设置属性(扩展)
- void setProperties(Properties properties);
- }
思路 这么下来思路就很清晰了, 我们通过实现 Interceptor 类实现自定义拦截器, 然后把它放入 InterceptorChain(拦截器链)中, 然后通过 JDK 动态代理来实现依次拦截处理.
致谢
非常感谢一篇博客, 它讲的循序渐进, 让我不仅仅对 JDK 动态代理 + 责任链模式有更好的理解, 而且在代码设计上也有很大的启发, 确实受益很大. 非常感谢!
Mybatis Plugin 插件 (拦截器) 原理分析 https://www.jianshu.com/p/b82d0a95b2f3
我相信, 无论今后的道路多么坎坷, 只要抓住今天, 迟早会在奋斗中尝到人生的甘甜. 抓住人生中的一分一秒, 胜过虚度中的一月一年!(6)
来源: https://www.cnblogs.com/qdhxhz/p/11390778.html