@(MyBatis)[Plugin]
MyBatis源码分析——Plugin原理
Plugin原理
Plugin的实现采用了Java的动态代理,应用了责任链设计模式
InterceptorChain
拦截器链,用于保存从配置文件解析后的所有拦截器
插件链的创建
在Configuration解析配置文件的时候,XMLConfigBuilder.parseConfiguration
中会调用pluginElement
解析插件信息并实例化后,保存到插件链中
// /configuration/plugins节点
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);
}
}
}
// Configuration.addInterceptor
public void addInterceptor(Interceptor interceptor) {
interceptorChain.addInterceptor(interceptor);
}
public class InterceptorChain {
// 所有拦截器实例
private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
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);
}
}
插件拦截
在MyBatis中,只能拦截四种接口的实现类:
-
Executor
-
ParameterHandler
-
ResultSetHandler
-
StatementHandler
每种类型的拦截方式都是一样的,这里取executor为例:
在创建SqlSession的时候,会需要创建Executor实现类,在创建时,会调用插件链的加载插件功能:executor = (Executor) interceptorChain.pluginAll(executor);
,该方法会形成一个调用链。// 依次调用每个插件的plugin方法,如果该插件无需拦截target,则直接返回target public Object pluginAll(Object target) { for (Interceptor interceptor : interceptors) { target = interceptor.plugin(target); } return target; }
Plugin
插件代理的实现,这里应用了Java Dynamic Proxy
public class Plugin implements InvocationHandler {
// 需要被代理的实例
private Object target;
// 拦截器实例
private Interceptor interceptor;
// 拦截器需要拦截的方法摘要,这里Class键为Executor等上述的四个
// 值为需要被拦截的方法
private Map<Class<?>, Set<Method>> signatureMap;
// 此类不能直接创建,需要通过静态方法wrap来创建代理类
private Plugin(Object target, Interceptor interceptor,
Map<Class<?>, Set<Method>> signatureMap) {
this.target = target;
this.interceptor = interceptor;
this.signatureMap = signatureMap;
}
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;
}
// 在代理类中调用
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);
}
}
// 获取需要被拦截的方法摘要
private static Map<Class<?>, Set<Method>> getSignatureMap(
Interceptor interceptor) {
// 先获取拦截器实现类上的注解,提取需要被拦截的方法
/* 注解示例:@Intercepts(value={@Signature(args={Void.class},method="query",type=Void.class)})*/
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;
}
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()]);
}
}
示例:
插件配置
在mybatis.xml配置文件:
<plugins>
<plugin interceptor="com.jabnih.analysis.mybatis.interceptor.InterceptorDemo1"/>
<plugin interceptor="com.jabnih.analysis.mybatis.interceptor.InterceptorDemo2"/>
</plugins>
插件实现
@Intercepts(value={@Signature(args={MappedStatement.class,Object.class},method="update",type=Executor.class)})
public class InterceptorDemo1 implements Interceptor {
private Logger logger = LoggerFactory.getLogger(InterceptorDemo1.class);
@Override
public Object intercept(Invocation invocation) throws Throwable {
logger.debug(InterceptorDemo1.class.getName());
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
@Intercepts(value={@Signature(args={MappedStatement.class,Object.class},method="update",type=Executor.class)})
public class InterceptorDemo2 implements Interceptor {
private Logger logger = LoggerFactory.getLogger(InterceptorDemo2.class);
@Override
public Object intercept(Invocation invocation) throws Throwable {
logger.debug(InterceptorDemo2.class.getName());
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
main方法
public static void main(String args[]) throws Exception {
String resource = "mybatis.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = sqlSessionFactory.openSession();
ProductMapper productMapper = session.getMapper(ProductMapper.class);
productMapper.updatePriceById("ANV01", BigDecimal.valueOf(6.99));
}
输出结果
2016-07-09 16:59:00 [DEBUG]-[Thread: main]-[com.jabnih.analysis.mybatis.interceptor.InterceptorDemo2.intercept()]:
com.jabnih.analysis.mybatis.interceptor.InterceptorDemo2
2016-07-09 16:59:00 [DEBUG]-[Thread: main]-[com.jabnih.analysis.mybatis.interceptor.InterceptorDemo1.intercept()]:
com.jabnih.analysis.mybatis.interceptor.InterceptorDemo1
示例的调用序列图
图片太大,可能需要另开一个页面单独看