@(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

示例的调用序列图

图片太大,可能需要另开一个页面单独看

MyBatis源码分析(2)—— Plugin原理的更多相关文章

  1. MyBatis源码分析(各组件关系+底层原理

    MyBatis源码分析MyBatis流程图 下面将结合代码具体分析. MyBatis具体代码分析 SqlSessionFactoryBuilder根据XML文件流,或者Configuration类实例 ...

  2. Mybatis源码分析--关联表查询及延迟加载原理(二)

    在上一篇博客Mybatis源码分析--关联表查询及延迟加载(一)中我们简单介绍了Mybatis的延迟加载的编程,接下来我们通过分析源码来分析一下Mybatis延迟加载的实现原理. 其实简单来说Myba ...

  3. MyBatis 源码分析 - 缓存原理

    1.简介 在 Web 应用中,缓存是必不可少的组件.通常我们都会用 Redis 或 memcached 等缓存中间件,拦截大量奔向数据库的请求,减轻数据库压力.作为一个重要的组件,MyBatis 自然 ...

  4. Mybatis源码分析之Cache二级缓存原理 (五)

    一:Cache类的介绍 讲解缓存之前我们需要先了解一下Cache接口以及实现MyBatis定义了一个org.apache.ibatis.cache.Cache接口作为其Cache提供者的SPI(Ser ...

  5. MyBatis 源码分析 - 插件机制

    1.简介 一般情况下,开源框架都会提供插件或其他形式的拓展点,供开发者自行拓展.这样的好处是显而易见的,一是增加了框架的灵活性.二是开发者可以结合实际需求,对框架进行拓展,使其能够更好的工作.以 My ...

  6. MyBatis 源码分析 - 配置文件解析过程

    * 本文速览 由于本篇文章篇幅比较大,所以这里拿出一节对本文进行快速概括.本篇文章对 MyBatis 配置文件中常用配置的解析过程进行了较为详细的介绍和分析,包括但不限于settings,typeAl ...

  7. MyBatis 源码分析-项目总览

    MyBatis 源码分析-项目总览 1.概述 本文主要大致介绍一下MyBatis的项目结构.引用参考资料<MyBatis技术内幕> 此外,https://mybatis.org/mybat ...

  8. 精尽MyBatis源码分析 - 插件机制

    该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

  9. 精尽MyBatis源码分析 - MyBatis-Spring 源码分析

    该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

随机推荐

  1. 统计文件种类数+获取子shell返回值的其它方法

    前言 只是作为一个shell的小小练习和日常统计用,瞎折腾的过程中也是摸到了获取子shell返回值的几种方法: 肯定还有别的方法,跟进程间的通信相关,希望你能提出建议和补充,谢谢~ 完整程序: #! ...

  2. linux下 mysql数据库的备份和还原

    1.备份 [root@CentOS ~]# mysqldump -u root -p mysql > ~/mysql.sql #把数据库mysql备份到家目录下命名为mysql.sql Ente ...

  3. Centos 7 Docker、docker-compose、Registrator、Consul、Consul Template和Nginx实现高可扩展的Web框架

    安装所需软件 Docker Docker-compose 配置docker-compose.yml文件内容如下: #load balancer will automatically update th ...

  4. 国内经典BI系统架构分析

    谈起商业智能BI,也许大家并不陌生,但你是否了解国内的各类BI系统架构? 自国内商业智能发展以来,就系统结构方面已经历了多次优化性的变革.目前国内商业智能BI系统的经典架构的模式包括数据层.业务层和应 ...

  5. Oracle 数据库基础——安装

    一.数据库基础知识 1.概念 数据库全称数据库管理系统,简称DBMS,是一种在计算机中,针对数据进行管理.存储.共享的一种技术. 2.分类 数据库的发展过程中,按逻辑模型可分为以下几种: 3.关系型数 ...

  6. 最新JavaScript、Ajax典藏级学习资料下载分类汇总 (2011年12月21日更新)

    其他网站开发相关资料            超强HTML和xhtml,CSS精品学习资料下载汇总                                               最新htm ...

  7. ASP.NET MVC - 探究应用程序文件夹

    为了学习 ASP.NET MVC,我们将构建一个 Internet 应用程序. 第 2 部分:探究应用程序文件夹. MVC 文件夹 一个典型的 ASP.NET MVC Web 应用程序的文件夹内容如下 ...

  8. node基础09:第2个node web服务器

    1.同时输出文字与图片 在前几个小课程中,我会学会了 从服务器中读取文字字符,并且向浏览器中输出 从服务器中读取图片文件,并且向浏览器中输出 这节课中,我学会了同时向浏览器输出文字,图片.对此,我感到 ...

  9. jquery 停止动画 stop的几种用法

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  10. 9个让人印象深刻的网站 JS 视觉效果

    网页设计已经提升到一个整体新的水平,Flash 渐渐失去了地位,逐渐被 HTML/JavaScript/CSS 所超越,而且一样可以实现出 Flash 复杂的特效. 本文介绍 8 个让人印象深刻的网站 ...