-
MyBatis插件插件机制简介
MyBatis插件其实就是为使用者提供的自行拓展拦截器,主要是为了可以更好的满足业务需要。
在MyBatis中提供了四大核心组件对数据库进行处理,分别是Executor、Statement Handler、ParameterHandler及ResultSetHandler,同时也支持对这四大组件进行自定义扩展拦截,用来增强核心对象的功能。其本质上是使用底层的动态代理来实现的,即程序运行时执行的都是代理后的对象。
MyBatis允许拦截的方法如下:
- 执行器Executor (update、query、commit、rollback等方法);
- SQL语法构建器StatementHandler (prepare、parameterize、batch、updates query等方 法);
- 参数处理器ParameterHandler (getParameterObject、setParameters方法);
- 结果集处理器ResultSetHandler (handleResultSets、handleOutputParameters等方法);
- 拦截器参数简介
- 拦截器注解
@Intercepts(//可配置多个@Signature,均使用此类进行拦截增强 { @Signature(//指定需要拦截的类、方法及方法参数 type = Executor.class,//需要拦截接口 method = "update",//需要拦截方法名称 args = {MappedStatement.class, Object.class}//拦截方法的请求参数 ) } )
- 实现Interceptor接口,并实现方法
public Object intercept(Invocation invocation)//每次拦截到都会执行此方法,方法内写增强逻辑 invocation//代理对象,可以获取目标方法、请求参数、执行结果等 invocation.proceed() //执行目标方法
public Object plugin(Object target) Plugin.wrap(target,this)//包装目标对象,为目标对象创建代理对象,将当前生成的代理对象放入拦截器链中
public void setProperties(Properties properties)//获取配置文件中的插件参数,插件初始化时调用一次
- 拦截器注解
- 自定义插件
- 新建MyExecuter类,实现Interceptor接口
package com.rangers.plugin; import org.apache.ibatis.executor.Executor; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.plugin.*; import java.lang.reflect.Method; import java.util.Properties; /** * @Author Rangers * @Description 自定义Executor update方法 * @Date 2021-03-11 **/ @Intercepts({ @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class} ) }) public class MyExecuter implements Interceptor { // 接收插件的配置参数 private Properties properties = new Properties(); // 增强逻辑写在此方法中 @Override public Object intercept(Invocation invocation) throws Throwable { // 打印插件的配置参数 System.out.println("插件的配置参数:"+properties.toString()); // 获取目标方法Method对象 Method method = invocation.getMethod(); // 获取目标方法的请求参数 与args列表一一对应 String reqParam = invocation.getArgs()[1].toString(); System.out.println("方法名称:"+method.getName()+" 请求参数:"+ reqParam); // 执行目标方法 Object result = invocation.proceed(); System.out.println("方法名称:"+method.getName()+" 执行结果:"+result); return result; } /** * @Author Rangers * @Description **/ @Override public Object plugin(Object target) { //System.out.println("需要包装的目标对象:"+target+" 目标对象类型"+ target.getClass()); // 主要是将当前生成的代理对象放入拦截器链中,包装目标对象,为目标对象创建代理对象 return Plugin.wrap(target,this); } /** * @Author Rangers * @Description 获取配置文件中的插件属性参数,插件初始化时调用一次 **/ @Override public void setProperties(Properties properties) { // 将配置参数进行接收 this.properties = properties; } }
- 主配置文件添加标签
<plugins> <!--指定拦截器类--> <plugin interceptor="com.rangers.plugin.MyExecuter"> <!--配置拦截器 属性参数--> <property name="param1" value="value1"/> <property name="param2" value="value2"/> <property name="param3" value="value3"/> </plugin> </plugins>
- 新建MyExecuter类,实现Interceptor接口
-
插件原理
a、在Executor、StatementHandler、ParameterHandler及ResultSetHandler四大对象创建时,并不是直接返回的,而是中间多了一步interceptorChain.pluginAll()(均在Configuration类中进行创建)。
- Executor—interceptorChain.pluginAll(executor);
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; }
- StatementHandler—interceptorChain.pluginAll(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 = (StatementHandler) interceptorChain.pluginAll(statementHandler); return statementHandler; }
- ParameterHandler—interceptorChain.pluginAll(parameterHandler);
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) { ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql); parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler); return parameterHandler; }
- ResultSetHandler—interceptorChain.pluginAll(resultSetHandler)
b、interceptorChain.pluginAll()调用的就是实现了Interceptor接口的plugin()方法,plugin()方法又通过Plugin.wrap(target,this)为目标对象创建一个Plugin的代理对象,添加到拦截链interceptorChain中。具体代码如下: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 Object pluginAll(Object target) { for (Interceptor interceptor : interceptors) { // 调用实现Interceptor接口的plugin方法 target = interceptor.plugin(target); } return target; }
@Override public Object plugin(Object target) { // 调用Plugin的wrap()方法,创建代理对象 return Plugin.wrap(target,this); }
c、Plugin实现了 InvocationHandler接口,因此它的invoke方法会拦截所有的方法调用。invoke()方法会 对所拦截的方法进行检测,以决定是否执行插件逻辑。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 { // 根据组件对象Class从signatureMap中获取到需要拦截的方法set集合 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注解数组 Signature[] sigs = interceptsAnnotation.value(); // signatureMap存放所有拦截到方法,key为四大组件的Class,value为组件对应的方法set集合 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; }
-
PageHelper插件
PageHelper是MyBaits框架使用最广泛的第三方物理分页插件,分页助手PageHelper是将分页的复杂操作进行封装,使用简单的方式即可获得分页的相关数据。
使用步骤:
- 添加依赖
<dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.1.8</version> </dependency> <dependency> <groupId>com.github.jsqlparser</groupId> <artifactId>jsqlparser</artifactId> <version>1.2</version> </dependency>
- 配置插件
<plugin interceptor="com.github.pagehelper.PageInterceptor"> </plugin>
- 使用分页
@org.junit.Test public void testPagehealper() { PageHelper.startPage(1, 2); List<User> users = userDao.findAll(); if (users != null && users.size() > 0) { for (User user : users) { System.out.println(user.toString()); } PageInfo<User> pageInfo = new PageInfo<>(users); System.out.println("总条数:" + pageInfo.getTotal()); System.out.println("总页数:" + pageInfo.getPages()); System.out.println("当前页:" + pageInfo.getPageNum()); System.out.println("每页显万长度:" + pageInfo.getPageSize()); System.out.println("是否第一页:" + pageInfo.isIsFirstPage()); System.out.println("是否最后一页:" + pageInfo.isIsLastPage()); } }
- 添加依赖