Mybatis源码阅读之--插件实现原理分析
前言:
mybatis为了方便用户在使用过程中,对某些特定的执行点进行添加自己的处理,提供了插件功能,这些插件可以在一个语句执行过程中的特定点进行拦截。
使用方法:
- 新建一个类实现Interceptor接口
- 在类上使用@Intercepts和@Signature注解指定在哪些地方进行拦截
- 在mybatis配置文件中使用
添加插件
// 使用注解指定在哪些地方进行拦截
// 可以拦截多个接口
@Intercepts({
@Signature(
type=Execution.class,
method="udpate",
args={MappedStatement.class, Object.class}
)})
public class ExamplePlugin implements Inteceptor {
// 可以设置一些属性
private Properties properties = new Properties();
@Override
public Object intercept(Invocation invocation) throws Throwable {
// implement pre-processing if needed
Object returnObject = invocation.proceed();
// implement post-processing if needed
return returnObject;
}
@Override
public void setProperties(Properties properties) {
this.properties = properties;
}
}
以上代码意思是在Execution执行的update(MappedStatement, Object)这个方法出进行拦截
可以拦截的类:
- Executor
- StatementHandler
- ResultSetHandler
- ParameterHandler
以上接口的所有公共方法都可以进行拦截
plugin的初始化动作通过XMLConfigBuilder进行plugin对象的创建与注册
// XMLConfigBuilder类
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
String interceptor = child.getStringAttribute("interceptor");
Properties properties = child.getChildrenAsProperties();
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
interceptorInstance.setProperties(properties);
// 在这里添加了各种plugin,plugin的用法参见https://mybatis.org/mybatis-3/configuration.html#plugins
configuration.addInterceptor(interceptorInstance);
}
}
}
主要过程:
- 获取plugin的interceptor属性
- 获取定义的各个properties
- 解析类并创建对象(使用无参构造方法进行构造)
- 向interceptor对象中设置属性
- 将interceptor添加至configuration对象中--此步骤主要时间interceptor放入interceptorChain中
// Configuration类
public void addInterceptor(Interceptor interceptor) {
interceptorChain.addInterceptor(interceptor);
}
InterceptorChain类的代码如下:
public class InterceptorChain {
// 存放所有的插件,任何插件都是要实现Interceptor接口
private final List<Interceptor> interceptors = new ArrayList<>();
public Object pluginAll(Object target) {
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);
}
}
其使用一个List来存储所有的Interceptor
以上就完成了所有Interceptor的注册功能,那么Mybatis是如何将这些Interceptor植入到相应地方呢?上文中说了四个类可以进行拦截,这四个类的对象都是在Configuration中进行创建的。
- Executor
// Configuration类
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);
}
// 将所有的interceptor包含在executor中
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
创建Executor的最后一步就是将所有的Interceptor进行植入,这里是调用InterceptorChain进行植入的,上文中已经列出了InterceptorChain的代码,这里再次列出其pluginAll方法,以方便查看
// InterceptorChain类
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
此方法的入口是目标对象,然后调用每个interceptor的plugin进行植入,Interceptor接口提供了plugin方法的默认实现
// Interceptor接口中
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
这里是调用Plugin.wrap方法进行植入
public class Plugin implements InvocationHandler {
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);
if (interceptsAnnotation == null) {
throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
}
Signature[] sigs = interceptsAnnotation.value();
// 一个class对应多个method
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;
}
private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
Set<Class<?>> interfaces = new HashSet<>();
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()]);
}
}
执行流程:
- 解析@Intercepts注解,一个Intercepts可以有多个Signature,而一个Signature指定了一个类中的一个方法,因此Intercepts可以指定一个类中的多个方法和多个类中的多个方法
并返回map,map表示此拦截器拦截了哪些类的哪些方法 - 根据targe对象的类型,多的其实现的所有接口,对每个接口进行判断,看其是否在上一步返回的map中
- 如果此interceptor拦截的接口中拥有目标对象中实现的接口,那么就为目标对象新建一个代理对象(使用JDK动态代理的方式)并返回,否则不创建代理直接返回目标对象
我们看到为目标对象创建代理对象时,InvocationHandler使用的是new Proxy(),这里的Proxy实现了InvocationHandler接口,
而interfaces是target实现了,并且在Intercepts中配置了的所有接口,也就是说如果target实现了接口A,但是Intercepts中没有A的Signature,那么队里对象也不会实现接口A。(思考如果遇到这种情况,新建的代理对象可以代表target吗?)
进一步去看Proxy的invoke方法(调用targe对象的任何方法都会先执行invoke):
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);
}
}
这里我们看到,如果当前调用的方法在Intercepts中指定了,那么就调用interceptor.intercept方法,也就是我们自己实现的方法,此方法接受一个封装号的Invocation对象,否则直接执行目标对象的方法。
Invocation是Mybatis封装的一个方法调用,我们可以通过此对象完成原有方法的执行,以及获取一些方法调用的信息(调用的是哪个方法,以及方法的参数等)
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;
}
public Object getTarget() {
return target;
}
public Method getMethod() {
return method;
}
public Object[] getArgs() {
return args;
}
// 调用此方法完成目标对象相应方法的执行
public Object proceed() throws InvocationTargetException, IllegalAccessException {
return method.invoke(target, args);
}
}
总结:
mybatis插件功能允许我们在Mybatis执行过程中某些特定的地方植入一些自己的处理,
例如我们想在Execution.update方法中加上一些日志的功能,那么就可以用插件功能实现,其使用了Java动态代理实现。