Mybatis苞米豆源码分析二-方法执行
执行具体过程(集成到spring)
- 找到扫包类ClassPathMapperScanner,和以往的扫包形式一样,扫描包下所有类, 并获得BeanDefinition
- 基于BeanDefinition,通过设置definition.setBeanClass,然后在spring 容器中通过getBean的方式获取Mapper对象(此时是基础对象下面要继续织入插件)
- Mapper对象只有简单持有sqlSession来做数据库操作的能力, 而Mybatis提供了插件的功能, 就需要已实例化的Mapper对象进行再次代理, 将插件能力用方法拦截的方式编织到进去(插件要编织代码具体执行位置依据实际情况)
Mybatis知识: 重重代理之后最终操作数据库执行链 Executor -> StatementHandler -> statement.excute()-> ResultHander.handleResultSets(statement)
//spring-mybatis扫包注解 实例化ClassPathMapperScanner 并设置所需属性值, 属spring 范畴,忽略掉,直接关注ClassPathMapperScanner @MapperScan("com.xiaofeng.audit.dao.mapper") public class MybatisPlusConfig { 忽略....... }
查看最关心的doScan与processBeanDefinitions方法
ClassPathMapperScanner
@Override public Set<BeanDefinitionHolder> doScan(String... basePackages) { //使用spring自带扫包方式 先得到所有定义类 Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages); if (beanDefinitions.isEmpty()) { logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration."); } else { // 具体Bean处理方法 接着往下看 processBeanDefinitions(beanDefinitions); } return beanDefinitions; } //实际是对定义类的一系列设置 private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) { 忽略若干行...... //definition.setBeanClass 很眼熟的代码, 设置FactoryBean 后续spring容器通过FactoryBean的getObject()方法得到具体代理对象 //getObject的实现在 MapperFactoryBean中实现, 接下来所有的东西都是围绕MapperFactoryBean的getObject来实现的 definition.setBeanClass(this.mapperFactoryBean.getClass()); 忽略若干行....... }
实例化Mapper对象
MapperFactoryBean
@Override public T getObject() throws Exception { //这里使用spring-mybatis集成 使用的是getSqlSession()=SqlSessionTemplate return getSqlSession().getMapper(this.mapperInterface); }
委托SqlSessionTemplate来实例化Mapper
SqlSessionTemplate
@Override public <T> T getMapper(Class<T> type) { //使用mybatis configuration来实例化Mapper return getConfiguration().getMapper(type, this); } @Override public Configuration getConfiguration() { //获取sqlSessionFactory中的configuration //在上一节MybatisPlusAutoConfiguration初始化过程中 sqlSessionFactory的configuration设置为苞米豆重写类 return this.sqlSessionFactory.getConfiguration(); }
继续调用方法getMapper
MybatisConfiguration
@Override public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mybatisMapperRegistry.getMapper(type, sqlSession); }
MybatisMapperRegistry
@Override public <T> T getMapper(Class<T> type, SqlSession sqlSession) { final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MybatisPlusMapperRegistry."); } try { //在这里会通过代理实例化一个Mapper对象 最终使用MapperProxy实例化代理对象 return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } }
直接看invoke方法,Mapper每个方法都会被此方法代理执行, jdk代理方式 不过多解释
MapperProxy
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } else if (isDefaultMethod(method)) { return invokeDefaultMethod(proxy, method, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } //此处为执行方法类 这个方法很重要, 后面查询时, 对于是否查询条目边界, 就在cachedMapperMethod方法中设置, 判断Mapper方法参数是否含有有RowBounds子类(举例:Page extends RowBounds) final MapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args); }
到这里 Mapper代理实例化方式和方法执行过程基本结束, 接下来看Mapper方法的具体执行, 接上面最后一行 mapperMethod.execute(sqlSession, args) 以查询列表为例查看源码
MapperMethod
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) { List<E> result; Object param = method.convertArgsToSqlCommandParam(args); if (method.hasRowBounds()) { RowBounds rowBounds = method.extractRowBounds(args); //通过看前面代码可只此处sqlSession的实现为Spring的 SqlSessionTemplate, 但其实SqlSessionTemplate并没有实现Sqlsession的功能,而是委托给Mybatis自带的DefaultSqlSession来完成操作 result = sqlSession.<E>selectList(command.getName(), param, rowBounds); } else { //通过看前面代码可只此处sqlSession的实现为Spring的 SqlSessionTemplate, 但其实SqlSessionTemplate并没有实现Sqlsession的功能,而是委托给Mybatis自带的DefaultSqlSession来完成操作 result = sqlSession.<E>selectList(command.getName(), param); } 忽略....... return result; }
实际执行者为DefaultSqlSession, 接着往下看selectList
DefaultSqlSession
@Override public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { try { //在上一节解析XML的时候 已经将所有的statement设置到configuration 此处直接取用 MappedStatement ms = configuration.getMappedStatement(statement); //executor 有两个实现类BaseExecutor 和 cachedExecutor(二级缓存), 为了方便代码追踪, 不开二级缓存,使用BaseExecutor来执行 return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); } catch (Exception e) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }
我们更关注的是从数据库取数据的执行过程, 直接跳到queryFromDatabase方法
BaseExecutor
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { 忽略缓存代码.... try { list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql); } finally { localCache.removeObject(key); } 忽略缓存代码.... return list; } //下面这段代码为此篇文章最为重点的代码 @Override public <E> List<E> doQuery(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; try { //啥也没干 不需要关心 flushStatements(); //获取 苞米豆Configuration Configuration configuration = ms.getConfiguration(); //这里是mybatis插件核心所在 非常重要 直接进入方法 StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameterObject, rowBounds, resultHandler, boundSql); Connection connection = getConnection(ms.getStatementLog()); stmt = handler.prepare(connection, transaction.getTimeout()); handler.parameterize(stmt); return handler.<E>query(stmt, resultHandler); } finally { closeStatement(stmt); } }
实例化具体Statement执行器
Configuration
//实例化Statement执行器 public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { //创建RoutingStatementHandler对象 用于执行Statement, 其实从名字可以看出RoutingStatementHandler 并不做真正的处理, 而是将处理过程交给其他基础StatementHandler实现类 StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql); //这里就是Mybatis的精髓所在了 将创建的RoutingStatementHandler对象再次代理, 添加插件执行功能, 进入interceptorChain.pluginAll 看具体如何代理的 statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler); return statementHandler; }
通过jdk代理方式 植入插件调用链
InterceptorChain
//将所有的插件interceptors 通过代理方式植入到目标对象中 public Object pluginAll(Object target) { for (Interceptor interceptor : interceptors) { // 从下面这段代码可以简单看出,要么给target通过反射方式设置属性,要么通过jdk重新代理当前target, 这里根据自己需要实现拦截器(也即插件) // 下面从苞米豆分页插件(PaginationInterceptor)为例子来看怎么实现 target = interceptor.plugin(target); } return target; }
分页插件织入
PaginationInterceptor
@Override public Object plugin(Object target) { if (target instanceof StatementHandler) { //具体包装交给Plugin工具类来做的 直接看工具类wrap方法 return Plugin.wrap(target, this); } return target; }
mybatis插件执行包装
Plugin
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) { //这段代码表明 代理方法实现在Plugin 直接进Plugin查看invoke方法 return Proxy.newProxyInstance( type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)); } return target; } @Override 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)) { //具体的方法执行Invocation在intercept中执行, 当然这取决于是否真的需要执行 return interceptor.intercept(new Invocation(target, method, args)); } //若方法不需要拦截 则直接执行方法 return method.invoke(target, args); } catch (Exception e) { throw ExceptionUtil.unwrapThrowable(e); } }