Plugin
首先mybatisPlugin是针对于mybatis四大组件(Statementhandler、resultsethandler、parameterHandler、executor)做增强操作的。
MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)
通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可。
以pageHlper拦截器为例
@SuppressWarnings("rawtypes")
@Intercepts(@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}))
public class PageHelper implements Interceptor
根据注解构建signatureMap
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[] sigs = interceptsAnnotation.value();
Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();
for (Signature sig : sigs) {
Set<Method> methods = signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>());
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;
}
key为自定义拦截器接口 ,参数时是方法参数
多个拦截器就会代理多次,对代理类再进行代理。
public interface Interceptor {
//执行目标方法
Object intercept(Invocation invocation) throws Throwable;
//执行pluginAll调用
Object plugin(Object target);
//xml配置的初始化参数值
void setProperties(Properties properties);
}
我们要实现逻辑的方法是intercept
对于plugin方法,可以自定义一些场景
@Override
public Object plugin(Object target) {
if (target instanceof Executor) {
return Plugin.wrap(target, this);
}
return target;
}
例子
@Intercepts({@Signature(type = Executor.class, method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
public Object intercept(Invocation invocation) throws Throwable {
Object[] args = invocation.getArgs();
MappedStatement ms = (MappedStatement) args[0];
Object parameterObject = null;
try {
parameterObject = args[1];
}catch (Exception e){
System.out.println(e);
}
// id为执行的mapper方法的全路径名,如com.mapper.UserMapper
String id = ms.getId();
String sqlCommandType = ms.getSqlCommandType().toString();
if (!sqlCommandType.equals(SqlCommandType.SELECT.toString())) {
return invocation;
}
BoundSql boundSql = ms.getBoundSql(parameterObject);
String origSql = boundSql.getSql();
String newSql = getNewSql(id, origSql);
if (newSql.equals(origSql)) {
return invocation;
}
// 重新new一个查询语句对象
BoundSql newBoundSql = new BoundSql(ms.getConfiguration(), newSql,
boundSql.getParameterMappings(), boundSql.getParameterObject());
// 把新的查询放到statement里
MappedStatement newMs = newMappedStatement(ms, new BoundSqlSqlSource(newBoundSql));
for (ParameterMapping mapping : boundSql.getParameterMappings()) {
String prop = mapping.getProperty();
if (boundSql.hasAdditionalParameter(prop)) {
newBoundSql.setAdditionalParameter(prop, boundSql.getAdditionalParameter(prop));
}
}
Object[] queryArgs = invocation.getArgs();
queryArgs[0] = newMs;
logger.info("由于数据权限,SQL被改写 originSql: {},newSql: {}", origSql, newSql);
return invocation;
}
获取查询sql并改写
原理分析
再创建四大核心对象时会对对象做代理操作
例如
![image.png](https://img-blog.csdnimg.cn/img_convert/52d370b36badb80659bc7efe791f144e.png#clientId=u30d5b9a5-e98a-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=515&id=ub7d803a1&margin=[object Object]&name=image.png&originHeight=772&originWidth=1181&originalType=binary&ratio=1&rotation=0&showTitle=false&size=93254&status=done&style=none&taskId=udbc4c837-4a55-4a70-8932-6d854c219a4&title=&width=787.3333333333334)
代理类Plugin
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;
}
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[] sigs = interceptsAnnotation.value();
Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();
for (Signature sig : sigs) {
Set<Method> methods = signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>());
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;
}
wrap方法会根据注解信息选择性的进行代理操作
package org.apache.ibatis.plugin;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.ibatis.reflection.ExceptionUtil;
/**
* @author Clinton Begin
*/
public class Plugin implements InvocationHandler {
@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);
}
}
}
内部逻辑就是获取签名信息,执行时。匹配目标方法和注解中的方法参数,如果匹配上,那么执行拦截器interceptor。