前面有几篇单独讲Mybatis的,单独使用Mybatis,有以下几步:
- 配置Mybatis-config文件,类似于下面:
<configuration>
<properties>
<property name="org.apache.ibatis.parsing.PropertyParser.enable-default-value" value="true"/> <!-- 启用默认值特性 -->
</properties>
<typeAliases>
<!-- 设置别名 -->
<typeAlias alias="User" type="anla.learn.mybatis.hello.model.auto.User" />
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1/df"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mappers/UserMapper.xml"/>
</mappers>
</configuration>
- 在上面配置的
Mapper
中配置对应的UserMapper.xml
,配置好NameSpace
。 - 使用
SqlSessionFactory
打开一个Session
,并使用Mapper
。
try (SqlSession session = sqlSessionFactory.openSession()) {
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.selectUser(1L);
System.out.println(user);
}
Mybatis提供了基于Spring的继承包,将Mapper 放入到了Spring 容器中统一管理,本篇文章以此分析。
Mybatis-Spring
使用Mybatis-Spring 时,有以下几个步骤:
- 声明一个
SqlSessionFactory
bean,可以使用SqlSessionFactoryBean
待为生成 - 声明mapper location位置。
- 可以使用
@Bean
方式声明一个Mapper
@Bean
public UserMapper userMapper() throws Exception {
SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory());
return sqlSessionTemplate.getMapper(UserMapper.class);
}
- 或者通过
@MapperScan
扫描Mapper类。
本文重点看待2个逻辑
MapperScan
工作机制SqlSessionFactoryBean
原理
MapperScan 工作机制
在 MapperScan
注解定义为:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
...
}
MapperScan
使用 Import
引入了 MapperScannerRegistrar
bean。另一方面, MapperScan
仅在 MapperScannerRegistrar
解析并使用。
MapperScannerRegistrar
MapperScannerRegistrar
定义为:
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
...
}
ImportBeanDefinitionRegistrar
将在 invokeBeanFactoryPostProcessors
中被同步调用:
ConfigurationClassPostProcessor
对所有 已注册bean的@Conponent
、Configuration
注解进行扫描- 扫描到
MybatisCommonConfiguration
,因为它上面有MapperScan
注解 - 继而会对 加载 其 @Import 注解内容,并使用registry.registerBeanDefinition放入容器中,这一步在:
ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForConfigurationClass
- 而后将对所有
@Import
内容执行其registerBeanDefinitions
private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) {
registrars.forEach((registrar, metadata) ->
registrar.registerBeanDefinitions(metadata, this.registry));
}
- 最终就到了
MapperScannerRegistrar
的registerBeanDefinitions
中。
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes mapperScanAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
if (mapperScanAttrs != null) {
registerBeanDefinitions(mapperScanAttrs, registry, generateBaseBeanName(importingClassMetadata, 0));
}
}
从上面一小节分析来看, @MapperScan
目的是为了引入 MapperScannerRegistrar
,而最终是到了 registerBeanDefinitions
方法中。
该方法将 读取 @MapperScan
所有参数,并向Spring 注入一个 MapperScannerConfigurer
类型 BeanDefinition
。
void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) {
// 使用 BeanDefinitionBuilder 进行构建
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
builder.addPropertyValue("processPropertyPlaceHolders", true);
// 获取 annotationClass,即只扫描 有特定注解的类。否则扫描所有接口
Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
if (!Annotation.class.equals(annotationClass)) {
builder.addPropertyValue("annotationClass", annotationClass);
}
// 父类,即只有某一个父类下类才会被注入
Class<?> markerInterface = annoAttrs.getClass("markerInterface");
if (!Class.class.equals(markerInterface)) {
builder.addPropertyValue("markerInterface", markerInterface);
}
// bena 命名器
Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
if (!BeanNameGenerator.class.equals(generatorClass)) {
builder.addPropertyValue("nameGenerator", BeanUtils.instantiateClass(generatorClass));
}
// 自定义MapperFactoryBean
Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass);
}
// 自定义SqlSessionFtemplate
String sqlSessionTemplateRef = annoAttrs.getString("sqlSessionTemplateRef");
if (StringUtils.hasText(sqlSessionTemplateRef)) {
builder.addPropertyValue("sqlSessionTemplateBeanName", annoAttrs.getString("sqlSessionTemplateRef"));
}
// 自定义sqlSessionFactory
String sqlSessionFactoryRef = annoAttrs.getString("sqlSessionFactoryRef");
if (StringUtils.hasText(sqlSessionFactoryRef)) {
builder.addPropertyValue("sqlSessionFactoryBeanName", annoAttrs.getString("sqlSessionFactoryRef"));
}
// 将所有的factory加入到一起
List<String> basePackages = new ArrayList<>();
basePackages.addAll(
Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));
basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText)
.collect(Collectors.toList()));
basePackages.addAll(Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName)
.collect(Collectors.toList()));
// 判断是否懒加载
String lazyInitialization = annoAttrs.getString("lazyInitialization");
if (StringUtils.hasText(lazyInitialization)) {
builder.addPropertyValue("lazyInitialization", lazyInitialization);
}
builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));
// 注册bean
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}
MapperScannerConfigurer
现在 MapperScannerConfigurer
注入进去了,就有一个问题了,那它何时被调用呢?其功能是啥?
public class MapperScannerConfigurer
implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
}
从上面可以获取以下两个信息:
- 是
BeanDefinitionRegistryPostProcessor
类型bean,即在 refresh时候,其postProcessBeanDefinitionRegistry
会在PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors
被调用。具体可以看前面文章中对ConfigurationClassPostProcessor
分析。 - 是
InitializingBean
类型,即在 该 beanBeanDefinitionRegistryPostProcessor
实例化后,设置值时候,其afterPropertiesSet
方法会被调用。
下面从上面两个点看其内部处理逻辑。
postProcessBeanDefinitionRegistry
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
// 如果 BeanDefinitionRegistries 在refresh对 BeanFactoryPostProcessors 处理前调用,则需要设置一些属性,否则会报错。
processPropertyPlaceHolders();
}
// 设置 ClassPathMapperScanner 相关属性
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
scanner.setAddToConfig(this.addToConfig);
scanner.setAnnotationClass(this.annotationClass);
scanner.setMarkerInterface(this.markerInterface);
scanner.setSqlSessionFactory(this.sqlSessionFactory);
scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
scanner.setResourceLoader(this.applicationContext);
scanner.setBeanNameGenerator(this.nameGenerator);
scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
if (StringUtils.hasText(lazyInitialization)) {
scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
}
// 注册过滤类性FilterType
scanner.registerFilters();
// 进行扫描
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
- 判断是否在 容器 处理
BeanFactoryPostProcessors
前调用,是,则需要设置一些属性 - 构造
ClassPathMapperScanner
并 对所有basePackage
进行扫描
ClassPathMapperScanner
中扫描则是对Spring 的 ClassPathBeanDefinitionScanner
进行一层封装:
public int scan(String... basePackages) {
// 获取当前bean数量
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
// 扫描
doScan(basePackages);
if (this.includeAnnotationConfig) {
// 如果需要,注册一遍默认的processors
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
最终,Mybatis对 doScan进行了一层包装,不重复造轮子,还是通过Spring 进行扫描:
@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;
}
doScan
方法前面有类似分析,即扫描初所需要所有bean,主要看 processBeanDefinitions
方法:
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
// 对bean进行解析
definition = (GenericBeanDefinition) holder.getBeanDefinition();
// 获取bean名字
String beanClassName = definition.getBeanClassName();
LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
+ "' mapperInterface");
// 使用 mapperFactoryBeanClass 代替该bean,即这个bean,有名无实
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
definition.setBeanClass(this.mapperFactoryBeanClass);
// 添加属性
definition.getPropertyValues().add("addToConfig", this.addToConfig);
boolean explicitFactoryUsed = false;
if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
definition.getPropertyValues().add("sqlSessionFactory",
new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionFactory != null) {
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
explicitFactoryUsed = true;
}
if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
if (explicitFactoryUsed) {
LOGGER.warn(
() -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate",
new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionTemplate != null) {
if (explicitFactoryUsed) {
LOGGER.warn(
() -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
}
if (!explicitFactoryUsed) {
LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
// 设置为按类型注入
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
definition.setLazyInit(lazyInitialization);
}
}
从 processBeanDefinitions
获取到了重要的信息:
- Mybatis会将所有beanClass 设置为
mapperFactoryBeanClass
- 配置 部分 属性
- 将该bean 设置为 按类型注入
这就牵扯到了 FactoryBean
类型的初始化:
先看看FactoryBean的定义:
public interface FactoryBean<T> {
// 返回bean实例
@Nullable
T getObject() throws Exception;
// 返回class类型
@Nullable
Class<?> getObjectType();
// 是否为单例,默认是true
default boolean isSingleton() {
return true;
}
}
举个例子来说,比如要将 SimpleDateFormat
放入Spring bean容器中管理,那么可以用 MapperFactoryBean
实现:
public class DemoDateFormatFactoryBean implements FactoryBean<DateFormat>, InitializingBean {
private DateFormat dateFormat = new SimpleDateFormat();
@Override
public DateFormat getObject() throws Exception {
return dateFormat;
}
@Override
public Class<?> getObjectType() {
return dateFormat.getClass();
}
@Override
public boolean isSingleton() {
return true;
}
@Override
public void afterPropertiesSet() throws Exception {
dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// Asia/Shanghai
dateFormat.setTimeZone(TimeZone.getTimeZone(ZoneId.SHORT_IDS.get("CTT")));
}
}
先看看 MapperFactoryBean
的定义:
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
很明显是装饰器模式,所有操作都交给了 SqlSessionDaoSupport
执行。MapperFactoryBean
只做了一层包装。
直接看 geObject
方法:
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
熟悉的代码,从 SqlSession 中获取对应的Mapper。
这样就完成了 将 Mybatis的Mapper ,统一交给Spring 管理的操作。
实际就是套了一层MapperFactoryBean。
FactoryBean初始化
在 Spring refresh最后阶段,会将所有bean都执行一遍getBean, FactoryBean类型 的bean 会立刻通过getBean获取,而实际的bean 并不会一开始就执行getBean初始化,而是使用时才会被初始化:
@Override
public void preInstantiateSingletons() throws BeansException {
if (logger.isTraceEnabled()) {
logger.trace("Pre-instantiating singletons in " + this);
}
// Iterate over a copy to allow for init methods which in turn register new bean definitions.
// While this may not be part of the regular factory bootstrap, it does otherwise work fine.
List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
// Trigger initialization of all non-lazy singleton beans...
for (String beanName : beanNames) {
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
if (isFactoryBean(beanName)) {
// 利用& 符号工厂bean
Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
if (bean instanceof FactoryBean) {
final FactoryBean<?> factory = (FactoryBean<?>) bean;
boolean isEagerInit;
if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>)
((SmartFactoryBean<?>) factory)::isEagerInit,
getAccessControlContext());
}
else {
// 判断是否需要立即加载
isEagerInit = (factory instanceof SmartFactoryBean &&
((SmartFactoryBean<?>) factory).isEagerInit());
}
if (isEagerInit) {
getBean(beanName);
}
}
}
else {
getBean(beanName);
}
}
}
// 实例化以及初始化后执行 afterSingletonsInstantiated
for (String beanName : beanNames) {
Object singletonInstance = getSingleton(beanName);
if (singletonInstance instanceof SmartInitializingSingleton) {
final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
if (System.getSecurityManager() != null) {
AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
smartSingleton.afterSingletonsInstantiated();
return null;
}, getAccessControlContext());
}
else {
smartSingleton.afterSingletonsInstantiated();
}
}
}
}
在 getBean
时候,如果判定 为FactoryBean类型,则最终使用getObject 返回实例对象,判断方式如下:
@Override
public boolean isFactoryBean(String name) throws NoSuchBeanDefinitionException {
String beanName = transformedBeanName(name);
Object beanInstance = getSingleton(beanName, false);
if (beanInstance != null) {
return (beanInstance instanceof FactoryBean);
}
// No singleton instance found -> check bean definition.
if (!containsBeanDefinition(beanName) && getParentBeanFactory() instanceof ConfigurableBeanFactory) {
// No bean definition found in this factory -> delegate to parent.
return ((ConfigurableBeanFactory) getParentBeanFactory()).isFactoryBean(name);
}
// 上面是直接instanceof,而如果都没有则去beanFactory中mergedBeanDefinitions 寻找
return isFactoryBean(beanName, getMergedLocalBeanDefinition(beanName));
}
上面是直接instanceof,而如果都没有则去beanFactory中mergedBeanDefinitions 寻找
protected RootBeanDefinition getMergedLocalBeanDefinition(String beanName) throws BeansException {
// Quick check on the concurrent map first, with minimal locking.
RootBeanDefinition mbd = this.mergedBeanDefinitions.get(beanName);
if (mbd != null) {
return mbd;
}
return getMergedBeanDefinition(beanName, getBeanDefinition(beanName));
}
而bean注册时,beanFactory
会缓存下自己和parent bean definition
相关信息在 mergedBeanDefinitions
中。
觉得博主写的有用,不妨关注博主公众号: 六点A君。
哈哈哈,一起研究Spring: