1,猜想
不修改代码如何增强功能
很容易能想到就是代理模式,这也是Mybatis插件的实现原理
多个插件怎么拦截
插件是层层拦截的,我们需要用到另一个设计模式-责任链模式
什么对象可以被拦截
2,插件实现原理
1,代理类在什么时候创建?实在解析配置的时候,还是在获取会话的时候创建,还是在调用的时候创建
2,核心对象被代理后,调用的流程是怎么执行的呢?怎么依次执行多个插件逻辑?在执行完插件后怎么执行原来的逻辑
代理类什么时候创建
对Executor拦截的代理类是openSession()的时候创建的
//创建 ExecutorExecutor executor = this.configuration.newExecutor(tx, execType)
代理怎么创建
遍历interceptorChain,使用Interceptor的实现类plugin()方法,对目标核心对象进行代理。
这个plugin方法是我们要实现的返回一个代理对象
public interface Interceptor { Object intercept(Invocation var1) throws Throwable; default Object plugin(Object target) { return Plugin.wrap(target, this); } default void setProperties(Properties properties) { } }
public static Object wrap(Object target, Interceptor interceptor) {
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
return interfaces.length > 0 ? Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)) : target;
}
被代理后,调用的流程?
如果被拦截的方法不为空,进入Plugin的invoke()方法,调用interceptor的intercept方法,也就走到了我们自己实现的拦截逻辑(比如:PageInterceptor的intercept方法)
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set<Method> methods = (Set)this.signatureMap.get(method.getDeclaringClass());
return methods != null && methods.contains(method) ? this.interceptor.intercept(new Invocation(this.target, method, args)) : method.invoke(this.target, args);
} catch (Exception var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
}
3,案例实现(慢sql检测)
package com.boe.itserviceportal.config.thread;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.plugin.*;import java.sql.Connection;
import java.util.Properties;
/**
* 慢sql查询插件
*/
@Intercepts({@Signature(type = StatementHandler.class, method ="prepare", args = {Connection.class, Integer.class})})
public class SlowSqlPlugin implements Interceptor {
private long slowTime;
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = (StatementHandler)invocation.getTarget();
BoundSql boundSql = statementHandler.getBoundSql();
long startTime = System.currentTimeMillis();
Object proceed = invocation.proceed();
long endTime = System.currentTimeMillis();
long f = endTime - startTime;
System.out.println("sql查询耗时" + f);
if (f > slowTime) {
System.out.println("本次数据库操作是慢查询,sql是:");
System.out.println(boundSql.getSql());
}
return proceed;
}@Override
public Object plugin(Object target) {
//触发intercept方法
return Plugin.wrap(target, this);
}@Override
public void setProperties(Properties properties) {
//获取我们定义的慢sql的时间阈值slowTime
this.slowTime = Long.parseLong(properties.getProperty("slowTime"));
}
}
package com.boe.itserviceportal;
import com.boe.itserviceportal.config.thread.SlowSqlPlugin;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.util.Properties;
/**
* @author 10522120c
*/
@SpringBootApplication
@EnableSwagger2
@MapperScan({"com.boe.itserviceportal.**.mapper"})
public class ItServicePortalApplication {public static void main(String[] args) {
SpringApplication.run(ItServicePortalApplication.class, args);
}//注入bean
@Bean
public SlowSqlPlugin sqlPlugin() {
SlowSqlPlugin slowSqlPlugin = new SlowSqlPlugin();
Properties properties = new Properties();
properties.setProperty("slowTime", "10");
slowSqlPlugin.setProperties(properties);
return slowSqlPlugin;
}
}
4,源码分析
到了intercept()方法,也就走到了我们自己实现的拦截逻辑(例如Pagelnterceptor的intercept()方法)。
注意参数是new出来的Invocation对象,它是对被拦截对象、被拦截方法、被拦截参数的一个封装。为什么要传这样一个参数呢?
public class Invocation {
private final Object target;
private final Method method;
private final Object[] args;
public Invocation(Object target, Method method, Object[] args) {
this.target = target;
this.method = method;
this.args = args;
}
当然,在代理逻辑执行完了之后,比如继续执行被代理对象(四大核心对象)的原方法,也就是说要有一行这样的代码:
return method.invoke(target, args);
或者拿到被代理的核心对象,继续执行它的方法(比如executor.query())。这个时候,我们要拿到被代理对象和它的参数,去哪里拿?
就是上面创建的Invocation对象。它简化了参数的传递,而且直接提供了一个proceed()方法,也就是说继续执行原方法可以写成:
return invocation.proceed();