mybatis插件
使用
-
注册插件
<plugins> <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin> </plugins>
编写插件类 实现org.apache.ibatis.plugin.Interceptor接口
-
添加注解
@Intercepts( { @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}), @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}), } ) public class InterceptorTwo implements Interceptor
- Signature
@Documented @Retention(RetentionPolicy.RUNTIME) @Target({}) public @interface Signature { //拦截的方法类型 Class<?> type(); //拦截的方法名字 String method(); //方法中的参数类型集合 Class<?>[] args(); }
-
Intercepts
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Intercepts { //返回需要拦截的方法签名列表 Signature[] value(); }
插件初始化流程
项目启动加载配置文件
解析配置文件
-
解析plugins标签
XMLConfigBuilder
private void parseConfiguration(XNode root) { //解析plus标签 pluginElement(root.evalNode("plugins")); }
-
循环插入插件类
XMLConfigBuilder
//解析拦截器类并插入list集合 private void pluginElement(XNode parent) throws Exception { if (parent != null) { //循环获取子类并插入一个List 配置文件中plugin的顺序与执行的顺序相反 // child = <plugin interceptor="com.github.pagehelper.PageInterceptor"/> for (XNode child : parent.getChildren()) { //获取interceptor属性 String interceptor = child.getStringAttribute("interceptor"); //获取参数 Properties properties = child.getChildrenAsProperties(); //这句话等同于Class.forName("class").newInstance()通过反射生成一个对象 Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance(); interceptorInstance.setProperties(properties); //List<Interceptor> interceptors = new ArrayList<>(); 集合插入对象 configuration.addInterceptor(interceptorInstance); } }
插件调用流程
-
获取session
SqlSession sqlSession1 = sqlSessionFactory.openSession();
-
新增执行器
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { //新建一个执行器 final Executor executor = configuration.newExecutor(tx, execType); return new DefaultSqlSession(configuration, executor, autoCommit); }
-
初始化执行器
public Executor newExecutor(Transaction transaction, ExecutorType executorType) { .... //通过循环代理 一层层的包装来实现调用链 executor = (Executor) interceptorChain.pluginAll(executor); return executor; }
-
动态生成拦截器代理
public Object pluginAll(Object target) { //先进后出 for (Interceptor interceptor : interceptors) { //动态生成代理类等于Proxy.newProxyInstance 像洋葱一样 代理了另一个代理类 target = interceptor.plugin(target); } return target; }
public interface Interceptor { Object intercept(Invocation invocation) throws Throwable; //default 关键字方法的默认实现 实现类无需实现这个方法 default Object plugin(Object target) { //静态工厂方法获取一个代理类 return Plugin.wrap(target, this); } }
Plugin implements InvocationHandler
//静态工厂方法 生成代理类 public static Object wrap(Object target, Interceptor interceptor) { //通过解析拦截器Intercepts注解与Signature注解 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 { //判断此拦截器需要拦截的方法是否与需要执行的方法相同 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); } }
插件调用链视图
精简代码核心流程
看一下删除了各种设计模式之后的代码 只是展示思路用
public void testInterceptor() {
Object target = 12;
List<Interceptor> interceptorList = new ArrayList<>();
interceptorList.add(new InterceptorOne());
interceptorList.add(new InterceptorTwo());
for(Interceptor interceptor : interceptorList){
//管理类
PluginInvocation pluginInvocation = new PluginInvocation(target, interceptor);
//代理类
target = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), pluginInvocation);
}
//模拟触发代理
System.out.println(target.equals(12));
}
InterceptorOne
public class InterceptorOne implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("执行拦截器 1");
return null;
}
}
InterceptorTwo
public class InterceptorTwo implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("执行拦截器2");
return null;
}
}
PluginInvocation
public class PluginInvocation implements InvocationHandler {
//被代理对象
private Object targetObject;
private Interceptor interceptor;
public PluginInvocation(Object o, Interceptor interceptor) {
this.targetObject = o;
this.interceptor = interceptor;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//执行拦截器逻辑 这里会有判断是否需要拦截的逻辑
interceptor.intercept(null);
//执行目标对象的方法
return method.invoke(targetObject, args);
}
}
pageHelper插件怎么分页的
什么对象可以被拦截?官网如下
MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
-
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
(对应插件调用流程-3.初始化执行器 的时候会用代理模式包装Executor对象)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)
应用场景
水平分表 权限控制 数据的加解密 分页插件