较真儿学源码系列-MyBatis整合Spring核心流程源码分析

        MyBatis-Spring版本:2.1.0-SNAPSHOT。


1 简介

        在我们使用MyBatis时,大部分情况下都不是单独使用的,都是需要和Spring进行集成后再使用。那么下面就来看下MyBatis是如何与Spring进行集成的(我之前写过对MyBatis源码进行分析的文章《较真儿学源码系列-MyBatis核心流程源码分析》,建议看一下,因为我下面的分析都是基于此之上再进行分析的)。


2 SqlSessionFactoryBean

        我们在单独使用MyBatis的时候,是通过SqlSessionFactoryBuilder的build方法生成的SqlSessionFactory,而在和Spring集成后,是通过SqlSessionFactoryBean来生成的:

@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
    SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
    factoryBean.setDataSource(dataSource());
    factoryBean.setMapperLocations(new ClassPathResource("org/mybatis/spring/demo/xml/UserMapper.xml"));
    return factoryBean.getObject();
}

        可以看到,这里将数据源和mapper文件传给SqlSessionFactoryBean了。SqlSessionFactoryBean本身是个FactoryBean,熟悉Spring源码的同学应该知道,FactoryBean的getObject方法的返回值会被做成bean放进IoC容器中(我之前有写过对Spring源码的解析《较真儿学源码系列-Spring IoC核心流程源码分析》,感兴趣的话可以查看)。那么下面就来看下其getObject方法的实现:

/**
 * SqlSessionFactoryBean:
 */
@Override
public SqlSessionFactory getObject() throws Exception {
    if (this.sqlSessionFactory == null) {
        afterPropertiesSet();
    }

    return this.sqlSessionFactory;
}

/**
 * 第7行代码处:
 */
@Override
public void afterPropertiesSet() throws Exception {
    notNull(dataSource, "Property 'dataSource' is required");
    notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
    state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
        "Property 'configuration' and 'configLocation' can not specified with together");

    this.sqlSessionFactory = buildSqlSessionFactory();
}

protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
    //...

    if (xmlConfigBuilder != null) {
        try {
            //之前在分析MyBatis的过程中已经解释过了,通过XMLConfigBuilder的parse方法来解析配置文件的内容,放到Configuration里
            xmlConfigBuilder.parse();
            LOGGER.debug(() -> "Parsed configuration file: '" + this.configLocation + "'");
        } catch (Exception ex) {
            throw new IOException("Failed to parse config resource: " + this.configLocation, ex);
        } finally {
            ErrorContext.instance().reset();
        }
    }

    /*
    这里会把SpringManagedTransactionFactory赋值给transactionFactory,所以最终用到的Transaction会是Spring事务管理器创建的Transaction;
    最终用到的connection会是事务管理器创建好的连接
     */
    targetConfiguration.setEnvironment(new Environment(this.environment,
        this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
        this.dataSource));

    if (this.mapperLocations != null) {
        if (this.mapperLocations.length == 0) {
            LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
        } else {
            for (Resource mapperLocation : this.mapperLocations) {
                if (mapperLocation == null) {
                    continue;
                }
                try {
                    //之前也说过,创建XMLMapperBuilder并调用其parse方法,会对mapper文件进行解析
                    XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                    targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
                    xmlMapperBuilder.parse();
                } catch (Exception e) {
                    throw new IOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
                } finally {
                    ErrorContext.instance().reset();
                }
                LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
            }
        }
    } else {
        LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");
    }

    //调用build方法,将Configuration放进SqlSessionFactoryBuilder中
    return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}

        至此就明白了SqlSessionFactoryBean的作用就是将MyBatis的配置信息放到Configuration对象中。


3 Spring如何去管理Mapper接口

3.1 MapperFactoryBean

         如果想把一个类交给Spring容器来管理的话,我们只需要在这个类上加上@Component或@Service或@Controller注解即可。但是在MyBatis中我们却不能在这样做,因为mapper是接口。如果是接口或者抽象类的话,是不能被Spring所实例化的:

/**
 * ClassPathScanningCandidateComponentProvider:
 */
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
    AnnotationMetadata metadata = beanDefinition.getMetadata();
	return (metadata.isIndependent() && (metadata.isConcrete() ||
        (metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));
}

/**
 * ClassMetadata:
 * 第6行代码处:
 */
default boolean isConcrete() {
    return !(isInterface() || isAbstract());
}

        这个时候就需要MapperFactoryBean登场了,它会对mapper接口进行包装:

@Bean
public MapperFactoryBean userMapper() throws Exception {
    MapperFactoryBean mapperFactoryBean = new MapperFactoryBean(UserMapper.class);
    mapperFactoryBean.setSqlSessionFactory(sqlSessionFactory());
    return mapperFactoryBean;
}

        同理,因为是个FactoryBean,所以查看其getObject方法实现:

/**
 * MapperFactoryBean:
 */
@Override
public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
}

/**
 * SqlSessionTemplate:
 */
@Override
public <T> T getMapper(Class<T> type) {
    return getConfiguration().getMapper(type, this);
}

        可以看到,最终就是通过调用sqlSession的getMapper方法来创建出代理类,之前在分析MyBatis的文章中已经讲过了。

3.2 并发控制

        这里需要对SqlSessionTemplate单独做一些分析。我们之前对MyBatis源码进行分析的时候,如果细心的话可以发现DefaultSqlSession乃至整个MyBatis源码中都很少有加锁的控制。那么可以认为DefaultSqlSession是线程不安全的(这点其实没毛病,MyBatis的宗旨就是简单轻量化)。那么现在与Spring集成后,就不得不考虑这点了。SqlSessionTemplate是mybatis-spring项目单独封装的类,下面来看下它的构造器:

/**
 * SqlSessionTemplate:
 */
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
    this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
}

public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
    this(sqlSessionFactory, executorType,
        new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));
}

public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
                          PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
    notNull(executorType, "Property 'executorType' is required");

    this.sqlSessionFactory = sqlSessionFactory;
    this.executorType = executorType;
    this.exceptionTranslator = exceptionTranslator;
    //这里是在动态代理,生成代理类。重点关注下最后一个参数,之后在调用sqlSessionProxy的方法时会调用到SqlSessionInterceptor的invoke方法
    this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
        new Class[]{SqlSession.class}, new SqlSessionInterceptor());
}

/**
 * SqlSessionInterceptor:
 */
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    //重点看下是如何获取到sqlSession的
    SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
        SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
    try {
        //目标方法的调用
        Object result = method.invoke(sqlSession, args);
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
            // force commit even on non-dirty sessions because some databases require
            // a commit/rollback before calling close()
            sqlSession.commit(true);
        }
        return result;
    } catch (Throwable t) {
        //...
    } finally {
        if (sqlSession != null) {
            closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
    }
}

/**
 * SqlSessionUtils:
 * 第33行代码处:
 */
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
                                       PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
    notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);

    /*
    获取同一个线程之前有没有创建过sqlSession
    Spring事务相关的获取资源的方法,这里不详细去看了,底层就是在使用ThreadLocal
     */
    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);

    //通过sqlSessionHolder获取sqlSession
    SqlSession session = sessionHolder(executorType, holder);
    if (session != null) {
        //如果之前存在sqlSession的话,就直接返回
        return session;
    }

    LOGGER.debug(() -> "Creating a new SqlSession");
    //之前不存在sqlSession,说明当前线程是第一次获取,此时通过openSession方法获取到sqlSession
    session = sessionFactory.openSession(executorType);

    //并放进ThreadLocal中(如果事务同步激活的话)
    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

    return session;
}

        可以看到,Mybatis集成Spring后,是通过ThreadLocal来保证的并发安全(我之前写过对ThreadLocal进行源码分析的文章《较真儿学源码系列-ThreadLocal(逐行源码带你分析作者思路)》,感兴趣的话可以查看)。这样的话,每个线程都有自己单独的sqlSession。

        需要注意的一点是:如果事务没有开启的话,ThreadLocal中是不会放入值的。这样造成的结果就是每次请求(注意这里的措辞,不是同一个线程)都是new出来的新的sqlSession。这样虽然不会有并发问题,但是会使缓存失效。还记得MyBatis的一级缓存是sqlSession级别的吗?所以这也就是为什么没有开启事务,MyBatis一级缓存失效的原因。因为每次请求都会创建一个新的sqlSession,不管在不在同一个线程内。之前sqlSession的缓存都作废了。

        至于为什么会这么做,为什么事务不开启的时候不往ThreadLocal里存值。猜测是为了Spring事务的概念。如果一个方法里面有多条查询SQL的语句的话,那么可以认为这几条查询语句是相互独立的,而如果在方法上加上@Transactional注解的话,则代表这里面的所有查询SQL是一个整体,所以需要共用同一个sqlSession、共用一级缓存( 我之前有写过对Spring事务的源码解析《较真儿学源码系列-Spring事务管理核心流程源码分析》,感兴趣的话可以查看)。

3.3 @MapperScan

        前面讲解了通过MapperFactoryBean包装的方式来创建mapper接口的bean,但是我们不可能每次创建出一个mapper接口,就手动用MapperFactoryBean包装一下,再注入到Spring容器中。这个时候我们就会用到@MapperScan注解,以此来提供统一的注册mapper接口的方式。查看其实现:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {

    //...

}

        @MapperScan注解上会引用到MapperScannerRegistrar类,因其实现了ImportBeanDefinitionRegistrar接口,所以我们只需要看其registerBeanDefinitions方法的实现就行了:

/**
 * MapperScannerRegistrar:
 */
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    //获取@MapperScan注解上的配置信息
    AnnotationAttributes mapperScanAttrs = AnnotationAttributes
        .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    if (mapperScanAttrs != null) {
        registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
            generateBaseBeanName(importingClassMetadata, 0));
    }
}

/**
 * 第10行代码处:
 */
void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
                             BeanDefinitionRegistry registry, String beanName) {

    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
    //...

    // for spring-native
    builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);

    //这里是将MapperScannerConfigurer注册为一个bean定义
    registry.registerBeanDefinition(beanName, builder.getBeanDefinition());

}

        可以看到,最后是注册了MapperScannerConfigurer。MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,那么接下来就来看下其postProcessBeanDefinitionRegistry接口的实现:

/**
 * MapperScannerConfigurer:
 */
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) {
        processPropertyPlaceHolders();
    }

    //创建扫描器,可以对mapper进行通用的扫描
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    //...
    scanner.registerFilters();
    //进行扫描
    scanner.scan(
        StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}

/**
 * ClassPathMapperScanner:
 * 第13行代码处:
 */
public void registerFilters() {
    boolean acceptAllInterfaces = true;

    //...

    if (acceptAllInterfaces) {
        // default include filter that accepts all classes
        /*
        这里的作用是添加一个includeFilter,使其可以跳过ClassPathScanningCandidateComponentProvider的isCandidateComponent(MetadataReader)方法
        的校验,之后能跳到ClassPathMapperScanner改写的isCandidateComponent(AnnotatedBeanDefinition)方法里
         */
        addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
    }

    // exclude package-info.java
    addExcludeFilter((metadataReader, metadataReaderFactory) -> {
        String className = metadataReader.getClassMetadata().getClassName();
        return className.endsWith("package-info");
    });
}

/**
 * ClassPathBeanDefinitionScanner:
 * 第15行代码处:
 */
public int scan(String... basePackages) {
	int beanCountAtScanStart = this.registry.getBeanDefinitionCount();

	doScan(basePackages);

	// Register annotation config processors, if necessary.
	if (this.includeAnnotationConfig) {
		AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
	}

	return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}

/**
  * ClassPathMapperScanner:
  * 第51行代码处:
  */
 @Override
 public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    //调用原有的扫描逻辑(在其中会调用到isCandidateComponent方法,也就是会跳到ClassPathMapperScanner改写的isCandidateComponent(AnnotatedBeanDefinition)方法里)
    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 {
        //这里是ClassPathMapperScanner自己改写的内容
        processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
}

/**
 * 第68行代码处:
 */
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
    //这里就可以看到:ClassPathMapperScanner改写了Spring默认的实现,使mapper接口可以被注册到Spring容器中
    return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
}

/**
 * 第75行代码处:
 */
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    AbstractBeanDefinition definition;
    BeanDefinitionRegistry registry = getRegistry();
    //遍历所有的mapper
    for (BeanDefinitionHolder holder : beanDefinitions) {
        definition = (AbstractBeanDefinition) holder.getBeanDefinition();
        boolean scopedProxy = false;
        //...
        String beanClassName = definition.getBeanClassName();
        LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
            + "' mapperInterface");

        // the mapper interface is the original class of the bean
        // but, the actual class of the bean is MapperFactoryBean
        //给MapperFactoryBean的构造器传参,参数为mapper接口的Class对象
        definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
        try {
            // for spring-native
            definition.getPropertyValues().add("mapperInterface", Resources.classForName(beanClassName));
        } catch (ClassNotFoundException ignore) {
            // ignore
        }

        //bean定义的类型需要从原始mapper的类型改写为MapperFactoryBean(本行代码和上面给构造器传参的代码的顺序不能换)
        definition.setBeanClass(this.mapperFactoryBeanClass);

        //...

        if (!explicitFactoryUsed) {
            LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
            definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
        }

        definition.setLazyInit(lazyInitialization);

        if (scopedProxy) {
            continue;
        }

        //...

    }
}

        上面最终会把类型为MapperFactoryBean的bean定义注册进去,之后的操作就是调用MapperFactoryBean的getObject方法了,之前在分析MyBatis的过程中已经讲过了。


原创不易,未得准许,请勿转载,翻版必究

VS 平台C#实现 1. 实验内容 用基本增量算法和Bresenham算法画直线 2.实验目的 1)理解在显示器上画图与在纸上画图的本质区别; 2)掌握直线的光栅扫描转换过程; 3)掌握不同算法绘制直线的思路和优缺点。 3. 实验要求 1)将像素网格表现出来,建立网格坐标系; 2)用橡皮筋的形式输入参数; 3)鼠标移动时,显示鼠标当前位置; 4)显示判别式的计算过程和下一点的选择策略; 5)记录生成点的坐标,建议用表的形式; 6)图形生成过程可以重复进行。 1. 实验内容 用正负法和Bresenham算法画圆弧 2.实验目的 1)掌握圆及圆弧的光栅扫描转换过程; 2)掌握不同算法绘制圆弧的技巧和优缺点。 3. 实验要求 1)将像素网格表现出来,建立网格坐标系; 2)用橡皮筋的形式输入参数; 3)鼠标移动时,显示鼠标当前位置; 4)显示判别式的计算过程和下一点的选择策略; 5)记录生成点的坐标,建议用表的形式; 6)图形生成过程可以重复进行。 1. 实验内容 用Cohen-SutherLand算法和liang _barsky算法进行线段裁剪 2.实验目的 1)理解裁剪的相关概念 2)掌握直线段的一般裁剪过程; 3)理解并掌握Cohen-SutherLand 算法的编码思想; 4)理解并掌握Liang_Barsky算法的参数化裁剪思想; 3. 实验要求 1)将像素网格表现出来,建立网格坐标系; 2)用橡皮筋的形式输入剪裁线段和裁剪窗口; 3)鼠标移动时,显示鼠标当前位置; 4)对于线段裁剪,线段被窗口的四条边裁剪的过程要显示出来; 6)裁剪过程可以重复进行。 1. 实验内容 用Sutherland-Hodgman算法进行多边形裁剪 2.实验目的 1)理解多边形裁剪与直线段裁剪的区别; 2)掌握多边形的裁剪过程; 3)理解并掌握Sutherland-Hodgman算法的裁剪思想。 3. 实验要求 1)将像素网格表现出来,建立网格坐标系; 2)用橡皮筋的形式输入剪裁多边形和裁剪窗口; 3)鼠标移动时,显示鼠标当前位置; 4)多边形被窗口的四条边裁剪的过程以及多边形顶点增删的过程要显示出来; 5)裁剪过程可以重复进行。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值