从源码角度来分析实现原理,不涉及到具体使用例子。
/**
* @author Clinton Begin
*/
public interface Interceptor {
Object intercept(Invocation invocation) throws Throwable;
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
default void setProperties(Properties properties) {
// NOP
}
}
大多数的实现都是实现了intercept方法,对于下面两个方法都没有怎么关注过。那么下面就来看看他是什么样子
Plugin实现InvocationHandler,那么这里肯定有jdk的动态代理起作用。这里肯定要看他invoke方法。等会再说
再看它里面有三个参数
private final Object target; //原始对象
private final Interceptor interceptor; //拦截器
private final Map<Class<?>, Set<Method>> signatureMap; //
再看wrap
方法之前,先看看warp
方法里面getSignatureMap
private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
//拿到拦截器上Intercepts注解
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注解
Signature[] sigs = interceptsAnnotation.value();
//这个map里面保存着 需要拦截的class和这个class里面需要拦截的method。
Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();
for (Signature sig : sigs) {
Set<Method> methods = MapUtil.computeIfAbsent(signatureMap, sig.type(), k -> new HashSet<>());
try {
//得到Signature里面指定的方法,通过 type和args,method。
//这里就是通过反射来获取指定类上面的具体方法,并且缓存起来。
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
方法
public static Object wrap(Object target, Interceptor interceptor) {
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
// 得到 查找type的所有的接口,是否包含在signatureMap里面,将包含的返回回来。注意,这的接口是查找所有的接口,包括父接口
// signatureMap上面不是已经缓存了,需要拦截的类和具体方法的集合嗎。那么这里就需要遍历目标类以及他的父接口,看看目标类中那些接口中的那些方法需要利用代理。
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
//这里就很熟悉了,直接new代理。
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
作为代理必要看看看invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//生成了代理对象之后。在调用方法的时候就会调用invoke方法,通过前面缓存的signatureMap来判断当前调用的方法是否需要
// 利用拦截器。
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
if (methods != null && methods.contains(method)) {
//直接调用代理对象的方法。注意,这里传递过去的 实例可是未包装的对象,不是proxy。这个proxy其实是代理对象,如果
// 直接调用proxy的话,就会造成栈溢出。
return interceptor.intercept(new Invocation(target, method, args));
}
//这里在调用方法的时候也用的是原来的对象
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
这个方法只是用来设置属性的。下面会讲
首先要知道,拦截器是代码写完之后,如果在mybatis里面是需要配置在xml文件中,如果在spring或者Springboot中就可以交给spring去管理,也可以交给mybatis来管理。
在这里只是单纯看在Mybatis中是怎么做的。
<plugins>
<plugin interceptor="org.apache.ibatis.session.mybatis.MyInterceptor">
<property name="name" value="myInterceptor"/>
plugin>
plugins>
只要从经典的new SqlSessionFactoryBuilder().build(reader);
开始看,就可以看到下面的代码
private void parseConfiguration(XNode root) {
try {
// issue #117 read properties first
propertiesElement(root.evalNode("properties")); //解析properties标签,并把他放在 parser和config的 Variables 里面
Properties settings = settingsAsProperties(root.evalNode("settings"));//加载setting标签
loadCustomVfs(settings); //lcnote 这里的vfs是啥?怎么用
loadCustomLogImpl(settings);
typeAliasesElement(root.evalNode("typeAliases"));
//从这里开始,都是解析具体的标签,new出对象,将标签下面的属性设置进去,
// 从解析的这里基本也能看出mybatis里面重要的几个点,首先是objectFactory,objectFactory。objectFactory,plugins
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectFactory"));
reflectorFactoryElement(root.evalNode("objectFactory"));
//这里就具体设置setting标签了
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
//lcnote 这里是重点,解析mapper文件,
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
上面就是mybatis解析配置文件的重要代码
,这里只看 pluginElement(root.evalNode("plugins"))
// 这里会直接new出来实现了Interceptor接口的类,并且把interceptor标签下面的属性都调用setProperties设置进去,并且把这个类添加到 Interceptor里面
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
String interceptor = child.getStringAttribute("interceptor");
Properties properties = child.getChildrenAsProperties();//这个就是property标签里面的元素,这里会变为一个 Properties
// 重点就是下面这里,解析到一个,通过构造方法直接创建出来,注意,这里是无参构造。
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
//设置属性,这里调用就是 拦截器里面的setProperties方法。设置属性了
interceptorInstance.setProperties(properties);
//添加到拦截器集合中,注意Mybatis中一个很重要的对象 configuration。这个对象基本上包含了Mybatis中的所有。
configuration.addInterceptor(interceptorInstance);
}
}
}
看到这里,也就明白了,在解析配置文件,解析到 plugins
标签的时候创建出来的。通过无参构造。
有一个简单的方式,直接可以看到,之前说了configuration
对象中包含了Mybatis运行时候的所有的东西,并且从上面的代码也可以看出,将所有的代理对象添加到了 configuration.addInterceptor(interceptorInstance);
里面。那么只要看看在哪里调用就好了,很简单。直接看。
四个地方
SIMPLE, REUSE, BATCH
,还有一种CachingExecutor,这只是代理。真正的实现还是委托给了前三种)在创建这四个对象的时候,调用interceptorChain.pluginAll(parameterHandler)
通过上面的分析,可以知道,这里会创建代理对象。如果拦截器配置的话。通过之前的JDK可以知道,如果是动态代理对象的话,在每一个方法调用的时候就会调用InvocationHandler
,同样的这里也会这样。意思就是调用所有方法的时候都有可能用到拦截器。
Intercepts
注解呢?想想Jdk的动态代理,只要是动态代理对象,在调用方法的时候都会调用InvocationHandler
。所有的方法都会,那这不是很好吧?想要的是,在指定的方法上面做拦截,而不是全部。这就是Intercepts
和Signature
注解的作用。从这两个注解就可以解析出,什么接口上的什么方法需要应用拦截器。从而达到目的。
说到这里,我想到了cglib,
cglib和jdk的代理不一样的地方,除了jdk的实现是接口,cglib可以给类增强,但是两者是实现本质上都是字节码,都是生成了一个新的类,然后继承要代理的类或者实现了要代理的接口。我觉得两者最大的区别就是cglib可以支持多个MethodInterceptor并且可以通过CallbackFilter来指定每个方法应该执行那个MethodInterceptor。
Mybatis拦截器就到这里了,如有不正确的地方,欢迎指出。谢谢。