插件配置的解析和插件功能的实现
我们看下文档对插件功能的介绍:
插件(plugins)
MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)
这些类中方法的细节可以通过查看每个方法的签名来发现,或者直接查看 MyBatis 的发行包中的源代码。假设你想做的不仅仅是监控方法的调用,那么你应该很好的了解正在重写的方法的行为。因为如果在试图修改或重写已有方法的行为的时候,你很可能在破坏 MyBatis 的核心模块。这些都是更低层的类和方法,所以使用插件的时候要特别当心。
通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定了想要拦截的方法签名即可。
// ExamplePlugin.java
@Intercepts({@Signature(
type= Executor.class,
method = “update”,
args = {MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {
public Object intercept(Invocation invocation) throws Throwable {
return invocation.proceed();
}
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
public void setProperties(Properties properties) {
}
}
上面的插件将会拦截在 Executor 实例中所有的“update”方法调用,这里的 Executor 是负责低层映射语句执行的内部对象。
NOTE 覆盖配置类
除了用插件来修改 MyBatis 核心行为之外,还可以通过完全覆盖配置类来达到目的。只需继承后覆盖其中的每个方法,再把它传递到 sqlSessionFactoryBuilder.build(myConfig) 方法即可。再次重申,这可能会严重影响 MyBatis 的行为,务请慎之又慎。
可以看出,通过插件的方式,可以影响mybatis在整个执行链路中的行为。
相比于插件功能的实现,插件配置的解析相对于来说就太简单了。
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).newInstance();
interceptorInstance.setProperties(properties);
configuration.addInterceptor(interceptorInstance);
}
}
}
resolveClass(interceptor)这个方法,以后会常看到,根据传入的字符串类名返回相应的Class对象。并且支持别名方式。有兴趣可以看下实现代码,没有几行。
整个解析的流程只是简单地读取插件配置的类信息,转化为Interceptor 后,设置上配置上的属性。然后加到configuration的全局interceptors变量中。
插件从配置文件中解析出来了
那插件又是怎样执行的呢?
我们得找一个切入点。
还记得我们之前在查看执行过程时有看到类似这样的代码。
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
...
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
就是在创建一些执行器(包括newStatementHandler,newParameterHandler,newResultSetHandler和newExecutor方法,都在Configuration中可以找到)的时候,会执行
interceptorChain.pluginAll(executor).方法。
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
可以看到,这个方法是依次执行interceptor的plugin方法。这里的target就是相关的业务的实例,比如这里的newExecutor的target就是一个Executor。请注意,这里做了一件很有趣的事情,把interceptor.plugin(target)返回的结果又赋给了target。为什么这样做,我们看下plugin方法做了什么。
不过这里的plugin方法就不是源码了。因为interceptor是我们自己定义的。
我们就按照官方的例子建一个plugin
@Intercepts({@Signature(
type = Executor.class,
method = "update",
args = {MappedStatement.class, Object.class})})
public class ExamplePlugin implements Interceptor {
public Object intercept(Invocation invocation) throws Throwable {
return invocation.proceed();
}
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
public void setProperties(Properties properties) {
}
}
先简单看下,这个类包含了注解,和三个方法。第二个方法plugin就是pluginAll的执行方法,第三个方法就是解析配置文件时,设置属性文件的方法。
我们看下Plugin.wrap(target, this)的实现,看他是怎么对target做处理的。
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;
}
看下getSignatureMap这个方法
private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
if (interceptsAnnotation == null) { // issue #251
throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
}
Signature[] sigs = interceptsAnnotation.value();
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;
}
我们可以看到,这个就是读取我们自定义的插件的注解部分信息,然后放到map中。
对应的是以下注解的部分。
@Intercepts({@Signature(
type = Executor.class,
method = “update”,
args = {MappedStatement.class, Object.class})})
再回来,看下Plugin.wrap的这部分代码片段。
Class<?> type = target.getClass();
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
拿到注解的type(Executor.class),然后new Plugin 并代理它。返回了代理对象。
这里还有个逻辑
private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
Set<Class<?>> interfaces = new HashSet<Class<?>>();
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()]);
}
这里的type是target的type。
这个方法是获取要代理的对象要提供哪些接口。这个方法循环查找该TYPE实现的接口,然后再取父类,再次循环。
再回来看下下面语句。
target = interceptor.plugin(target);
plugin方法返回的其实是target的代理对象targetProxy1,再把targetProxy1赋值给target对象,如果有第二个插件,那么就会再次执行(外面有个循环)plugin方法,那么就会以targetProxy1作为代理对象,创建targetProxy2对象。多个插件就依次下去,最终形成一个插件任务责任链。
这样,比如SimpleExecutor执行了update方法,实际上是执行了最后的代理对象targetProxyN的invoke的方法。
我们来看看这个代理对象的invoke方法。(这个对象是Plugin哦)
org.apache.ibatis.plugin.Plugin#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);
}
}
可以看到逻辑还是比较简单的,从signatureMap中取出要拦截的方法集合,然后一一对比,如果能匹配上,就执行自定义拦截器的intercept方法。就是拦截器三个方法的第一个。如果匹配不上,就正常执行原方法(不额外执行,自然没拦截了)
public Object intercept(Invocation invocation) throws Throwable {
//下一个拦截器
return invocation.proceed();
}
执行下一个拦截器的实现,很简单,就执行target的相应方法,这里的target如果是代理对象,那么就还会执行一次invoke,一直到原本的对象为止。
这就是整个执行的任务链过程了。
public Object proceed() throws InvocationTargetException, IllegalAccessException {
return method.invoke(target, args);
}