Mybatis源码分析之插件(plugins)源码详解
来源:互联网 发布:淘宝装饰店铺教程 编辑:程序博客网 时间:2024/06/05 22:57
插件的实现就是使用动态代理。将mybytis四大对象(Executor、StatementHandler 、ParameterHandler、ResultHandler)在构造以后利用动态代理悄悄的将其代理,插入一些自己的逻辑,这里的动态代理是对实现类的动态代理,而不是像Dao层接口这种的直接对一个接口类的动态代理,理解起来也相对容易些。
插件实现过程中的一些关键类
(1)Interceptor 自定义插件接口类,所有Mybatis插件必须实现这个类,统一管理,注册在mybatis_config里,会在初始化时候就加载到Configuration类里。(2)InterceptorChain 插件的缓存类,所有的插件都初始化到这个类中的一个缓存list中,四大对象构造时候将其包装
(3)Plugin Mybatis提供的一个实现InvocationHandler类,方便应用,统一到这里的invoke方法处理
比较简单, 基本上就这么三个关键类。
下面用一个经常使用的Mybats的分页插件为例子讲解插件从初始化到代理四大对象的整个过程:
@Intercepts(value= {@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class}), }) public class PageInterceptor implements Interceptor {@Overridepublic Object intercept(Invocation invocation) throws Throwable { System.out.println("PageInterceptor -- intercept"); if (invocation.getTarget() instanceof StatementHandler) { StatementHandler statementHandler = (StatementHandler) invocation.getTarget(); MetaObject metaStatementHandler = MetaObject.forObject(statementHandler, new DefaultObjectFactory(), new DefaultObjectWrapperFactory()); MappedStatement mappedStatement=(MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement"); String selectId=mappedStatement.getId(); System.out.println(selectId); if(selectId.matches(".*Page$")) { BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql"); // 分页参数作为参数对象parameterObject的一个属性 String sql = boundSql.getSql(); Demo co=(Demo)(boundSql.getParameterObject()); // 重写sql String countSql=concatCountSql(sql); String pageSql=concatPageSql(sql,co); System.out.println("重写的 count sql :"+countSql); System.out.println("重写的 select sql :"+pageSql); Connection connection = (Connection) invocation.getArgs()[0]; java.sql.PreparedStatement countStmt = null; ResultSet rs = null; int totalCount = 0; try { countStmt = connection.prepareStatement(countSql); rs = countStmt.executeQuery(); if (rs.next()) { totalCount = rs.getInt(1); } } catch (SQLException e) { System.out.println("Ignore this exception"+e); } finally { try { rs.close(); countStmt.close(); } catch (SQLException e) { System.out.println("Ignore this exception"+ e); } } metaStatementHandler.setValue("delegate.boundSql.sql", pageSql); //绑定count co.setTotalRecord(totalCount); } } return invocation.proceed();}@Overridepublic Object plugin(Object target) { if (target instanceof StatementHandler) { return Plugin.wrap(target, this); } else { return target; } }@Overridepublic void setProperties(Properties properties) {// TODO Auto-generated method stub} public String concatCountSql(String sql){ StringBuffer sb=new StringBuffer("select count(*) from "); sql=sql.toLowerCase(); if(sql.lastIndexOf("order")>sql.lastIndexOf(")")){ sb.append(sql.substring(sql.indexOf("from")+4, sql.lastIndexOf("order"))); }else{ sb.append(sql.substring(sql.indexOf("from")+4)); } return sb.toString(); } public String concatPageSql(String sql,Page co){ StringBuffer sb=new StringBuffer(); sb.append(sql); sb.append(" limit ").append((co.getCurrentPage()-1)*co.getPageSize()).append(" , ").append(co.getPageSize()); return sb.toString(); } public void setPageCount(){ }}例子也比较容易理解,实现Mybatis的插件接口,重写他的三个方法,plugin方法实现代理,intercept插入自己的操作,setProperties注入一些自己需要的属性。注解的意思是type表示准备要代理的类,method 代理类中准备要代理方法,args 代理方法的参数。
主要流程:
1.注册interceptor,会在初始化的时候实例化
2.sqlSession中4大对象在实例化的时候会调用pluginAll方法,该方法中会用interceptot创建代理
3.具体的,首先调用interceptor的plugin方法,该方法一般会使用Plugin类的wrap方法,Plugin类实现了invocationHandler,wrap中完成了代理创建
4.Plugin的invoke方法会调用intercptor的intercept方法,植入自己的逻辑,完成插件
5.执行完成以后,调用proceed方法,回归到原来的方法调用中
下面开始源代码的分析:
<plugins><plugin interceptor="com.yanzh.PageInterceptor"></plugin></plugins>第一步将插件注册到Configuration。前面的初始化不说了,直接看进入到对插件节点的解析
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); } } }看一下这个方法,拿到plugins节点,一次性可以注册多个plugin子节点。取到插件以后,将其利用反射实例化,插件的第三个方法会在这里被调用,然后直接注册到了configuration里了。
//Configuration里的注册方法,实际上是将其注入到一个interceptorChain里public void addInterceptor(Interceptor interceptor) { interceptorChain.addInterceptor(interceptor); }//interceptorChain里的方法,放到了interceptors缓存里public void addInterceptor(Interceptor interceptor) { interceptors.add(interceptor); }interceptors是一个list,private final List<Interceptor> interceptors = new ArrayList<Interceptor>(); 初始化就结束了,最终就是把插件注入到一个拦截器链的缓存List里。
第二步看缓存中的拦截器是如何被执行的
具体dao方法的执行流程就不讲解了,直接进入插件的相关过程,在sqlSessionFactory获取sqlSession的过程中调用openSession方法,这个流程中会初始化Executor对象。final Executor executor = configuration.newExecutor(tx, execType);就是这句(在DefaultSqlSessionFactory类中)
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); } executor = (Executor) interceptorChain.pluginAll(executor); return executor; }函数的前面是Mybatis几种不同的执行器,初始化的时候可以自己设置,如果不设置,默认即是SimpleExecutor,这些都与插件没有关系,看return前的最后一句,构建出Executor对象以后,不是立即返回,而是经历了pluginAll,玄机就在这里,看函数名也是添加所有插件。
public Object pluginAll(Object target) { for (Interceptor interceptor : interceptors) { target = interceptor.plugin(target); } return target; }看到没有,这里就是把初始化时候缓存的拦截器都拿出来,一层层的包裹这个target对象(Executor),有几个拦截器就代理几次。最终会返回一个被包裹的对象,已经不是本身的Executor的对象了。被代理就是发生在plugin方法里。
直接利用分页拦截器实例讲解,大多数都是这种模式。
@Overridepublic Object plugin(Object target) { if (target instanceof StatementHandler) { return Plugin.wrap(target, this); } else { return target; } }分页拦截器拦截的是StatementHandler对象,原理是一样的,假如此处的判断条件是target instanceof Executro.直接利用mybatis提供的动态代理工具类来包装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; }首先将@signature注解封装在一个map中,然后取到被代理类的.class类型,接着获取被代理类实现的接口,下面就是动态代理实现的真面目了,取接口,取类类型都是为代理准备参数。此处返回的就是一个被代理过的Executor对象。封装注解的函数就不说明了, 其实就是一个解析注解的过程,不熟悉的可以看下自定义注解,就是反射的知识,很容易看懂。那么自己准备要插入的逻辑(intercept函数)是怎么注入到代理中的呢。关键就在Plugin类的invoke方法中。
再啰嗦几句,分析一下动态代理的几个要素,首先被代理类(target,也就是Executor),代理类实现的接口(getAllInterface方法返回了,jdk动态代理必须有接口类),实现InvocationHandler的类(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); } }先取到注解中要拦截的方法,判断这次调用的被代理的方法是否在被拦截方法之列,不在的话直接反射调用被代理方法,在就调用拦截器的intercept方法,并且将被代理对象,方法,参数都封装在一个Invocation类中。
看一下分页拦截器的方法,简单说一下。
public Object intercept(Invocation invocation) throws Throwable { System.out.println("PageInterceptor -- intercept"); if (invocation.getTarget() instanceof StatementHandler) { StatementHandler statementHandler = (StatementHandler) invocation.getTarget(); MetaObject metaStatementHandler = MetaObject.forObject(statementHandler, new DefaultObjectFactory(), new DefaultObjectWrapperFactory()); MappedStatement mappedStatement=(MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement"); String selectId=mappedStatement.getId(); System.out.println(selectId); if(selectId.matches(".*Page$")) { BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql"); // 分页参数作为参数对象parameterObject的一个属性 String sql = boundSql.getSql(); Demo co=(Demo)(boundSql.getParameterObject()); // 重写sql String countSql=concatCountSql(sql); String pageSql=concatPageSql(sql,co); System.out.println("重写的 count sql :"+countSql); System.out.println("重写的 select sql :"+pageSql); Connection connection = (Connection) invocation.getArgs()[0]; java.sql.PreparedStatement countStmt = null; ResultSet rs = null; int totalCount = 0; try { countStmt = connection.prepareStatement(countSql); rs = countStmt.executeQuery(); if (rs.next()) { totalCount = rs.getInt(1); } } catch (SQLException e) { System.out.println("Ignore this exception"+e); } finally { try { rs.close(); countStmt.close(); } catch (SQLException e) { System.out.println("Ignore this exception"+ e); } } metaStatementHandler.setValue("delegate.boundSql.sql", pageSql); //绑定count co.setTotalRecord(totalCount); } } return invocation.proceed();}文中经过各种手段从被拦截对象中取到了即将要执行的sql语句和占位符参数,然后调用concatPageSql将分页参数append上去,偷天换日,最后一句又将换过的sql归还到原执行过程,invocation.proceed(),注意这句必须调用,不然归回不到原过程了,里面就是一个元方法的反射调用。
到这里一整套Mybatis的插件就走完了。sqlSession下的四大对象都是这么一个流程。
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql); statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler); return statementHandler; }public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) { ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql); parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler); return parameterHandler; }public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql) { ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds); resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler); return resultSetHandler; }一模一样的流程。插件整体比较简单,整个过程就是一个动态代理的实现过程,只要对动态代理了解,基本上没什么问题,不过还需要一些dao接口执行流程的熟悉知识,不然可能会感到一头雾水。
- Mybatis源码分析之插件(plugins)源码详解
- 【Mybatis】mybatis插件源码分析
- WhatWeb源码分析之lib/plugins.rb
- MyBatis 分页插件详解(带源码)
- Mybatis3源码分析(18)-插件(plugins)拦截器
- Mybatis源码分析之插件责任链、动态代理
- Mybatis源码学习笔记(六)配置简介之objectFactory、plugins、mappers
- Mybatis源码分析之缓存
- Mybatis源码分析 之 Configration
- 【MyBatis源码分析】插件实现原理
- SharpDevelop源码分析之插件
- Mybatis源码分析之执行完整分析
- MyBatis 教程 - MyBatis插件(Plugins)开发
- Mybatis源码分析(一)- Configuration配置文件详解
- Mybatis源码分析(一)
- mybatis之XML解析源码分析
- MyBatis源码分析之MappedStatemenet,SqlSource,DynamicContext
- Mybatis源码分析 之 sql解析
- centos7搭建DHCP简要服务器
- Java实现八大排序算法
- java__for找出数组中最大,最小值
- leetcode 575. Distribute Candies
- [RK3288][Android6.0] AT24C02驱动分析及功能增加小结
- Mybatis源码分析之插件(plugins)源码详解
- grails 中返回 json
- 火柴棍
- Codeforces 807E Prairie Partition 贪心思维+二分
- 实现数据源为List<ImageView>的简单的ViewPage效果
- Java集合框架(3)——HashMap
- oracle 查询取第二行值 rank 与 ROWNUM
- 手机APP测试
- 嵌入式软件教程2.1