mybatis 源码解析之初始化流程

一、本文介绍

 本文只专注于mybaits初始化流程,旨在帮助各位弄清楚mybatis初始化过程中都进行那些工作,这些初始化工作在那些类中。并没有对每项初始化工作进行详细的介绍,如果有这方面的需求请移步别处,避免耽误您的时间。只要各位学会了怎么去看mybatis源码,那我相信如果以后想要深入的学习细节的实现会变得非常简单。好了,话不多说,下面进入正题。

二、简单介绍一下mybatis基础配置 

MybatisConfig 类是mybatis 的基础配置类

public class MybatisConfig  implements ApplicationContextAware {
    private static PropertiesUtil propertiesUtil;
    private ApplicationContext applicationContext;
    static {
        propertiesUtil = new PropertiesUtil("jdbc.properties");
    }

    /**
     * 配置连接池
     * @return
     */
    @Bean
    public DruidDataSource dataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl(propertiesUtil.getProperty("db.url"));
        dataSource.setUsername(propertiesUtil.getProperty("db.username"));
        dataSource.setPassword(propertiesUtil.getProperty("db.password"));
        dataSource.setDriverClassName(propertiesUtil.getProperty("db.driverName"));
        dataSource.setMaxActive(Integer.parseInt(propertiesUtil.getProperty("db.maxActive")));
        return dataSource;
    }

    /**
     * 配置sqlfactory
     * @return
     * @throws Exception
     */
    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource());
        Resource[] resources = applicationContext.getResources("classpath:mappers/**/*.xml");
        factoryBean.setMapperLocations(resources);
        //设置分页的插件
        Interceptor[] interceptors = new Interceptor[]{new MyBatisPageInterceptopr()};
        factoryBean.setPlugins(interceptors);
        return factoryBean.getObject();
    }

    /**
     * 配置全局事务
     * @return
     */
    @Bean
    public DataSourceTransactionManager transactionManager(){
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource());
        return transactionManager;
    }
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

Dao 类是访问数据库的工具类 

@Repository
public class Dao  extends SqlSessionDaoSupport {
    /**
     * 设置工厂类
     *
     * @param sqlSessionFactory sql工厂类
     */
    @Autowired
    public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
        super.setSqlSessionFactory(sqlSessionFactory);
    }
    public  T get(String prefix,String key,Object params){
        return getSqlSession().selectOne(prefix+key,params);
    }
    public void insert(String prefix,String key,Object params){
        getSqlSession().insert(prefix+key,params);
    }
    public void update(String prefix,String key,Object params){
        getSqlSession().update(prefix+key,params);
    }
    public void delete(String prefix,String key,Object params){
        getSqlSession().delete(prefix+key,params);
    }
    public  List getList(String prefix, String key, Object params) {
        return this.getSqlSession().selectList(prefix + key, params);
    }

    /**
     * 获取分页的数据
     * @param prefix
     * @param key
     * @param params
     * @param page
     * @return
     */
    public Page page(String prefix, String key, Map params, Page page) {
        params.put("page",page);
        page.setList(this.getSqlSession().selectList(prefix + key, params));
        return page;
    }
MyBatisPageInterceptopr 是自定义的分页拦截器,如果想要学习这块的知识,请自行百度
@Intercepts(@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class}))
public class MyBatisPageInterceptopr  implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler =getActuralHandlerObject(invocation);
        MetaObject metaStatementHandler = SystemMetaObject.forObject(statementHandler);
        String sql = statementHandler.getBoundSql().getSql();
        //检查是否是select语句,如果不是则直接放行
        if(checkIsSelectFalg(sql)){
            return invocation.proceed();
        }
        BoundSql boundSql = statementHandler.getBoundSql();
        Object paramObject = boundSql.getParameterObject();
        Page page = getPageParam(paramObject);
        //如果参数是空则直接放行
        if(page==null){
            return invocation.proceed();
        }
        //获取页码
        Integer pageNum = page.getPageNum();
        //获取每页条数
        Integer pageSize = page.getPageSize();
        int total = getTotal(invocation,metaStatementHandler,boundSql);
        //将动态获取到的分页参数会天道pageParam中
        setTotleToParam(page,total,pageSize);
        return updateSql2Limit(invocation,metaStatementHandler,pageNum,pageSize);
    }

    /**
     * 生成代理对象
     * @param o
     * @return
     */
    @Override
    public Object plugin(Object o) {
        return Plugin.wrap(o, this);
    }

    @Override
    public void setProperties(Properties properties) {

    }
    private StatementHandler getActuralHandlerObject(Invocation invocation){
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        //获取statementHandler的代理类
        MetaObject metaStatementHandler = SystemMetaObject.forObject(statementHandler);
        Object  object = null;
        //分离代理链,目标可能被多个拦截器拦截,分离出最原始的目标类
        while (metaStatementHandler.hasGetter("h")){
            object = metaStatementHandler.getValue("h");
            metaStatementHandler = SystemMetaObject.forObject(object);
        }
        if(object==null){
            return  statementHandler;
        }
        return (StatementHandler)object;
    }

    /**
     * 判断是否是select语句
     * @param sql
     * @return
     */
    private boolean checkIsSelectFalg(String sql){
        return !sql.trim().toLowerCase().contains("select");
    }

    /**
     * 获取分页参数
     * @param parameObject
     * @return
     */
    private Page getPageParam(Object parameObject){
        if(parameObject==null){
            return null;
        }
        Page page =null;
        if(parameObject instanceof Map){
            Map params = (Map)parameObject;
            for(Map.Entry entry:params.entrySet()){
                if(entry.getValue() instanceof Page){
                    return (Page)entry.getValue();
                }
            }
        }else if(parameObject instanceof Page){
            page = (Page)parameObject;
        }
        return page;
    }

    /**
     * 获取总条数
     * @param invocation
     * @param metaStatementHandler
     * @param boundSql
     * @return
     */
    private int getTotal(Invocation invocation, MetaObject metaStatementHandler, BoundSql boundSql){
        //获取mapper文件中当前语句的配置信息
        MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement");
       //获取所有的配置
        Configuration configuration = mappedStatement.getConfiguration();
        //获取当前查询的sql
        String sql = (String) metaStatementHandler.getValue("delegate.boundSql.sql");
        String countSql = "select count(*) as total from ("+sql+")$_paging";
        //获取connection 连接对象,用于和执行countsql
        Connection conn = (Connection) invocation.getArgs()[0];
        PreparedStatement ps = null;
        int total = 0;
        try{
            //预编译统计总记录数的sql
            ps = conn.prepareStatement(countSql);
            //构建统计的BoundSql
            BoundSql countBoundSql = new BoundSql(configuration,countSql,boundSql.getParameterMappings(),boundSql.getParameterObject());
            //构建paramterHandler ,用于设置统计的sql参数
            ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement,boundSql.getParameterObject(),countBoundSql);
            //设置总数的sql参数
            parameterHandler.setParameters(ps);
            //执行查询语句
            ResultSet rs = ps.executeQuery();
            while (rs.next()){
                total = rs.getInt("total");
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if(ps!=null){
                try {
                    ps.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
        return total;
    }

    /**
     * 设置条数参数到Page对象中
     * @param page
     * @param total
     * @param pageSize
     */
    private void setTotleToParam(Page page,int total,int pageSize){
        page.setTotalCount(total);
        page.setTotalPage(total%pageSize==0?total/pageSize:(total/pageSize+1));
    }

    /**
     * 修改原始的sql语句为分页sql语句
     * @param invocation
     * @param metaStatementHandler
     * @param pageNum
     * @param pageSize
     * @return
     */
    private Object updateSql2Limit(Invocation invocation,MetaObject metaStatementHandler,int pageNum,int pageSize) throws InvocationTargetException, IllegalAccessException, SQLException {
        String sql = (String)metaStatementHandler.getValue("delegate.boundSql.sql");
        //构建分页的sql语句
        String limitSql = "select * from ("+sql+")$_paging_table limit ?,?";
        //修改当前要执行的sql语句
        metaStatementHandler.setValue("delegate.boundSql.sql",limitSql);
        //相当于调用prepare方法,预编译的sql并且加入参数,但是少了分页的两个参数,他返回一个ps
        PreparedStatement ps = (PreparedStatement) invocation.proceed();
        //设置分页的两个参数
        int count = ps.getParameterMetaData().getParameterCount();
        ps.setInt(count-1,(pageNum-1)*pageSize);
        ps.setInt(count,pageSize);
        return ps;
    }

jdbc.properties 配置信息

db.url=jdbc:mysql://localhost:3306/pms?prepStmtCacheSize=517&cachePrepStmts=true&autoReconnect=true&characterEncoding=utf-8&allowMultiQueries=true
db.username=root
db.password=huc123456
db.driverName = com.mysql.jdbc.Driver
db.maxActive=20

service 请求实例

@Service
public class LevelService {
    private static final String CLASSNAME = LevelService.class.getName()+".";
    @Autowired
    private Dao dao;

    public Object listLevel(Map params,Page page){
        return dao.page("","listLevels",params,page);
    }
    public LevelBean getLevelById(Long id){
        return dao.get(CLASSNAME,"getLevelById",id);
    }
    public List getLevelList(Map params){
        return dao.getList(CLASSNAME,"listLevels",params);
    }
}

三、mybatis的初始化流程介绍

首先先看mybatisConfig 中对于 SqlSessionFactory的配置

 /**
     * 配置sqlfactory
     * @return
     * @throws Exception
     */
    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        //设置连接池信息
        factoryBean.setDataSource(dataSource());
        //设置mapper文件路劲信息
        Resource[] resources = applicationContext.getResources("classpath:mappers/**/*.xml");
        factoryBean.setMapperLocations(resources);
        //设置分页的插件
        Interceptor[] interceptors = new Interceptor[]{new MyBatisPageInterceptopr()};
        factoryBean.setPlugins(interceptors);
        //生成SqlSessionFactory实例
        return factoryBean.getObject();
    }

在此处我们能看到生成SqlSessionFactory的入口,那就是SqlSessionFactoryBean.getObject()方法,下面我们看一下这个方法


    public SqlSessionFactory getObject() throws Exception {
        //如果sqlSessionFactory为空,则进行初始化
        if (this.sqlSessionFactory == null) {
            this.afterPropertiesSet();
        }

        return this.sqlSessionFactory;
    }

然后看 afterPropertiesSet()方法

public void afterPropertiesSet() throws Exception {
        Assert.notNull(this.dataSource, "Property 'dataSource' is required");
        Assert.notNull(this.sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
        this.sqlSessionFactory = this.buildSqlSessionFactory();
    }

这个方法前面是断言,继续后续的 buildSqlSessionFactory()方法

    protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
        XMLConfigBuilder xmlConfigBuilder = null;
        Configuration configuration;
        //如果configuration 不为空,则直接获取configuration
        if (this.configLocation != null) {
            xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), (String)null, this.configurationProperties);
            configuration = xmlConfigBuilder.getConfiguration();
        } else {
            if (logger.isDebugEnabled()) {
                logger.debug("Property 'configLocation' not specified, using default MyBatis Configuration");
            }
            //定义configuration,它构造方法里面初始化了默认的参数,可以点进去看一下
            configuration = new Configuration();
            configuration.setVariables(this.configurationProperties);
        }
        //设置相应的myabtis自己的工厂类
        if (this.objectFactory != null) {
            configuration.setObjectFactory(this.objectFactory);
        }

        if (this.objectWrapperFactory != null) {
            configuration.setObjectWrapperFactory(this.objectWrapperFactory);
        }
        //设置typeAliases
        String[] typeHandlersPackageArray;
        String[] arr$;
        int len$;
        int i$;
        String packageToScan;
        if (StringUtils.hasLength(this.typeAliasesPackage)) {
            typeHandlersPackageArray = StringUtils.tokenizeToStringArray(this.typeAliasesPackage, ",; \t\n");
            arr$ = typeHandlersPackageArray;
            len$ = typeHandlersPackageArray.length;

            for(i$ = 0; i$ < len$; ++i$) {
                packageToScan = arr$[i$];
                configuration.getTypeAliasRegistry().registerAliases(packageToScan, this.typeAliasesSuperType == null ? Object.class : this.typeAliasesSuperType);
                if (logger.isDebugEnabled()) {
                    logger.debug("Scanned package: '" + packageToScan + "' for aliases");
                }
            }
        }

        int len$;
        if (!ObjectUtils.isEmpty(this.typeAliases)) {
            Class[] arr$ = this.typeAliases;
            len$ = arr$.length;

            for(len$ = 0; len$ < len$; ++len$) {
                Class typeAlias = arr$[len$];
                configuration.getTypeAliasRegistry().registerAlias(typeAlias);
                if (logger.isDebugEnabled()) {
                    logger.debug("Registered type alias: '" + typeAlias + "'");
                }
            }
        }
        //设置自定义的插件
        if (!ObjectUtils.isEmpty(this.plugins)) {
            Interceptor[] arr$ = this.plugins;
            len$ = arr$.length;

            for(len$ = 0; len$ < len$; ++len$) {
                Interceptor plugin = arr$[len$];
                configuration.addInterceptor(plugin);
                if (logger.isDebugEnabled()) {
                    logger.debug("Registered plugin: '" + plugin + "'");
                }
            }
        }
        //设置typeHandlers 类型处理器
        if (StringUtils.hasLength(this.typeHandlersPackage)) {
            typeHandlersPackageArray = StringUtils.tokenizeToStringArray(this.typeHandlersPackage, ",; \t\n");
            arr$ = typeHandlersPackageArray;
            len$ = typeHandlersPackageArray.length;

            for(i$ = 0; i$ < len$; ++i$) {
                packageToScan = arr$[i$];
                configuration.getTypeHandlerRegistry().register(packageToScan);
                if (logger.isDebugEnabled()) {
                    logger.debug("Scanned package: '" + packageToScan + "' for type handlers");
                }
            }
        }

        if (!ObjectUtils.isEmpty(this.typeHandlers)) {
            TypeHandler[] arr$ = this.typeHandlers;
            len$ = arr$.length;

            for(len$ = 0; len$ < len$; ++len$) {
                TypeHandler typeHandler = arr$[len$];
                configuration.getTypeHandlerRegistry().register(typeHandler);
                if (logger.isDebugEnabled()) {
                    logger.debug("Registered type handler: '" + typeHandler + "'");
                }
            }
        }

        if (xmlConfigBuilder != null) {
            try {
                xmlConfigBuilder.parse();
                if (logger.isDebugEnabled()) {
                    logger.debug("Parsed configuration file: '" + this.configLocation + "'");
                }
            } catch (Exception var23) {
                throw new NestedIOException("Failed to parse config resource: " + this.configLocation, var23);
            } finally {
                ErrorContext.instance().reset();
            }
        }

        if (this.transactionFactory == null) {
            this.transactionFactory = new SpringManagedTransactionFactory();
        }
        //设置依赖的环境
        Environment environment = new Environment(this.environment, this.transactionFactory, this.dataSource);
        configuration.setEnvironment(environment);
        if (this.databaseIdProvider != null) {
            try {
                configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
            } catch (SQLException var22) {
                throw new NestedIOException("Failed getting a databaseId", var22);
            }
        }
        //根据配置的mapper路径 逐个解析mapper文件
        if (!ObjectUtils.isEmpty(this.mapperLocations)) {
            Resource[] arr$ = this.mapperLocations;
            len$ = arr$.length;

            for(i$ = 0; i$ < len$; ++i$) {
                Resource mapperLocation = arr$[i$];
                if (mapperLocation != null) {
                    try {
                        XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), configuration, mapperLocation.toString(), configuration.getSqlFragments());
                    // 尽心mapper文件的解析
                        xmlMapperBuilder.parse();
                    } catch (Exception var20) {
                        throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", var20);
                    } finally {
                        ErrorContext.instance().reset();
                    }

                    if (logger.isDebugEnabled()) {
                        logger.debug("Parsed mapper file: '" + mapperLocation + "'");
                    }
                }
            }
        } else if (logger.isDebugEnabled()) {
            logger.debug("Property 'mapperLocations' was not specified or no matching resources found");
        }

        return this.sqlSessionFactoryBuilder.build(configuration);
    }

上面的这个方法先构建了Configuration,然后创建了sqlSessionFactory,我们先看一下mapper文件的解析,也就是xmlMapperBuilder.parse()方法

    public void parse() {
        //如果该mapper没有加载则加载mapper文件
        if (!this.configuration.isResourceLoaded(this.resource)) {
            //加载mapper文件 
            this.configurationElement(this.parser.evalNode("/mapper"));
            this.configuration.addLoadedResource(this.resource);
            this.bindMapperForNamespace();
        }

        this.parsePendingResultMaps();
        this.parsePendingChacheRefs();
        this.parsePendingStatements();
    }

主要看configurationElement()方法,里面定义了解析mapper文件的流程

private void configurationElement(XNode context) {
        try {
            String namespace = context.getStringAttribute("namespace");
            if (namespace.equals("")) {
                throw new BuilderException("Mapper's namespace cannot be empty");
            } else {
                //读取命名空间
                this.builderAssistant.setCurrentNamespace(namespace);
                //读取缓存的引用
                this.cacheRefElement(context.evalNode("cache-ref"));
                //读取缓存配置
                this.cacheElement(context.evalNode("cache"));
                //解析parameterMap配置
                this.parameterMapElement(context.evalNodes("/mapper/parameterMap"));
                //解析resultMap配置
                this.resultMapElements(context.evalNodes("/mapper/resultMap"));
                //解析定义的sql标签
                this.sqlElement(context.evalNodes("/mapper/sql"));
                //解析定义select|insert|update|delete 标签信息
                this.buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
            }
        } catch (Exception var3) {
            throw new BuilderException("Error parsing Mapper XML. Cause: " + var3, var3);
        }
    }

这里我们主要看一下解析select|insert|update|delete 标签的方法,如果有兴趣,其他的方法可自己研究一下

    private void buildStatementFromContext(List list) {
        if (this.configuration.getDatabaseId() != null) {
            this.buildStatementFromContext(list, this.configuration.getDatabaseId());
        }

        this.buildStatementFromContext(list, (String)null);
    }

继续看里面的buildStatementFromContext()方法

 private void buildStatementFromContext(List list, String requiredDatabaseId) {
        //遍历mapper的select,update,delete 标签进行解析
        Iterator i$ = list.iterator();

        while(i$.hasNext()) {
            XNode context = (XNode)i$.next();
            XMLStatementBuilder statementParser = new XMLStatementBuilder(this.configuration, this.builderAssistant, context, requiredDatabaseId);

            try {
                statementParser.parseStatementNode();
            } catch (IncompleteElementException var7) {
                this.configuration.addIncompleteStatement(statementParser);
            }
        }

    }

具体解析方法是statementParser.parseStatementNode()

public void parseStatementNode() {
        //这里解析标签上的一些属性信息
        String id = this.context.getStringAttribute("id");
        String databaseId = this.context.getStringAttribute("databaseId");
        if (this.databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
            Integer fetchSize = this.context.getIntAttribute("fetchSize");
            Integer timeout = this.context.getIntAttribute("timeout");
            String parameterMap = this.context.getStringAttribute("parameterMap");
            String parameterType = this.context.getStringAttribute("parameterType");
            Class parameterTypeClass = this.resolveClass(parameterType);
            String resultMap = this.context.getStringAttribute("resultMap");
            String resultType = this.context.getStringAttribute("resultType");
            String lang = this.context.getStringAttribute("lang");
            LanguageDriver langDriver = this.getLanguageDriver(lang);
            Class resultTypeClass = this.resolveClass(resultType);
            String resultSetType = this.context.getStringAttribute("resultSetType");
            StatementType statementType = StatementType.valueOf(this.context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
            ResultSetType resultSetTypeEnum = this.resolveResultSetType(resultSetType);
            String nodeName = this.context.getNode().getNodeName();
            SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
            boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
            boolean flushCache = this.context.getBooleanAttribute("flushCache", !isSelect);
            boolean useCache = this.context.getBooleanAttribute("useCache", isSelect);
            boolean resultOrdered = this.context.getBooleanAttribute("resultOrdered", false);
            XMLIncludeTransformer includeParser = new XMLIncludeTransformer(this.configuration, this.builderAssistant);
            includeParser.applyIncludes(this.context.getNode());
            this.processSelectKeyNodes(id, parameterTypeClass, langDriver);
            //这里着重介绍一下,sqlSource里面封装的标签定义的sql的基本sql语句以及参数的配置
            //如果没有myabtis的动态sql标签,如等,则这里返回的是RawSqlSource,如果
            //是动态sql语句,则返回的是DynamicSqlSource。sqlsource主要用来生成BoundSql
            //后期执行sql语句会用到这个类
            SqlSource sqlSource = langDriver.createSqlSource(this.configuration, this.context, parameterTypeClass);
            String resultSets = this.context.getStringAttribute("resultSets");
            String keyProperty = this.context.getStringAttribute("keyProperty");
            String keyColumn = this.context.getStringAttribute("keyColumn");
            String keyStatementId = id + "!selectKey";
            keyStatementId = this.builderAssistant.applyCurrentNamespace(keyStatementId, true);
            Object keyGenerator;
            if (this.configuration.hasKeyGenerator(keyStatementId)) {
                keyGenerator = this.configuration.getKeyGenerator(keyStatementId);
            } else {
                keyGenerator = this.context.getBooleanAttribute("useGeneratedKeys", this.configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
            }
            //这个方法里面就是构造了MappedStatement,并将其添加到configuration中
            //MappedStatement,这里面封装了mapper里面的所有配置,是myabtis
            //核心类之一。
            this.builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, (KeyGenerator)keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
        }
    }

好了,到这里 configuration大体的初始化流程就清除了,那我们返回之前的SqlSessionFactoryBean.buildSqlSessionFactory()方法里面的 this.sqlSessionFactoryBuilder.build(configuration)

public SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config);
    }

最终就是构建了一个DefaultSqlSessionFactory。

到这里myabtis大体的初始化流程就结束了,其实整体而言myabtis初始化并不复杂,主要是构建Configuration。Configuration里面保存了myabtis所有的配置信息,其中包括 类型解析器,类型别名,resultMap ,拦截器,mapperstatement,还有一些默认的处理器,所以各位有时间需要看一下这个类里面大体的属性。

你可能感兴趣的:(mybatis 源码解析之初始化流程)