spring boot mybatis 加载过程
其实最简单的使用mybatis是这样
创建spring boot 项目
maven加载mabatis
创建数据源配置
建个文件夹
建个接口文件用@Mapper注解
ok了
@Mapper
public interface UserDao {
@Select("select * from t_user limit 1")
public List<Map> getUserList();
}
然后就可以在其他类里注入这个Dao并调用了
是不是 很简单
那spring boot 和 mabatis 是怎么实现的,让这个接口可以用呢?
1,先看下我们用maven都引入哪些关于mybatis的包
maven配置
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
共引入的4个包
- org.mybatis.spring.boot:mybatis-spring-boot-autoconfigure:2.1.4
- org.mybatis.spring.boot:mybatis-spring-boot-starter:2.1.4
- org.mybatis:mybatis:3.5.6
- org.mybatis:mybatis-spring:2.0.6
光看名字就知道了
org.mybatis.spring.boot:mybatis-spring-boot-autoconfigure:2.1.4
这个就是自动配置的包,符合spring-boot自动配置约定
看下spring.fatories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
两个自动配置类,会被spring-boot在启动时扫描到
- MybatisLanguageDriverAutoConfiguration:这个是加载不同语言脚本用的例如php脚本,我们这篇文章不关心
- MybatisAutoConfiguration:主要看这个
2,MybatisAutoConfiguration
@Configuration
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties({MybatisProperties.class})
@AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class})
public class MybatisAutoConfiguration implements InitializingBean {
private static final Logger logger = LoggerFactory.getLogger(MybatisAutoConfiguration.class);
private final MybatisProperties properties;
private final Interceptor[] interceptors;
private final TypeHandler[] typeHandlers;
private final LanguageDriver[] languageDrivers;
private final ResourceLoader resourceLoader;
private final DatabaseIdProvider databaseIdProvider;
private final List<ConfigurationCustomizer> configurationCustomizers;
public MybatisAutoConfiguration(MybatisProperties properties, ObjectProvider<Interceptor[]> interceptorsProvider, ObjectProvider<TypeHandler[]> typeHandlersProvider, ObjectProvider<LanguageDriver[]> languageDriversProvider, ResourceLoader resourceLoader, ObjectProvider<DatabaseIdProvider> databaseIdProvider, ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider) {
this.properties = properties;
this.interceptors = (Interceptor[])interceptorsProvider.getIfAvailable();
this.typeHandlers = (TypeHandler[])typeHandlersProvider.getIfAvailable();
this.languageDrivers = (LanguageDriver[])languageDriversProvider.getIfAvailable();
this.resourceLoader = resourceLoader;
this.databaseIdProvider = (DatabaseIdProvider)databaseIdProvider.getIfAvailable();
this.configurationCustomizers = (List)configurationCustomizersProvider.getIfAvailable();
}
。。。
}
@Configuration 和 InitializingBean 接口
请参照:spring 生命周期 扩展点
请他注解请自己去查
但 看名字也知道了,主要是要在DataSource加载完后,才会加载这个类
不过这个类有点特别,它的属性没有用注解的方式注入
而是用构造函数,但我们又没有用显示的@Bean注解才构造它
是怎么实现的呢?
是因为在
spring 4.3之后,引入了一个新特性:当构造方法的参数为单个构造参数时,可以不使用@Autowired进行注解
同样是在Spring 4.3版本中,不仅隐式的注入了单构造参数的属性。还引入了ObjectProvider接口。
具体请参照# ObjectProvider使用说明
到目前为止好像没有发生什么
再看看它还有什么方法
// afterPropertiesSet 是实现的 InitializingBean 接口 会在属性加载完调用
// 去检查 mybatis.config-location 指定的配置文件,我们没用上(什么配置都没有)
public void afterPropertiesSet() { this.checkConfigFileExists(); }
private void checkConfigFileExists() {。。。}
// 初始化SqlSessionFactory
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) {
。。。
this.applyConfiguration(factory);
。。。
}
// 根据配置文件 初始化一些配置,我们没用上(什么配置都没有)
private void applyConfiguration(SqlSessionFactoryBean factory) {。。。}
// 初始化 SqlSessionTemplate,你可以手动调用这个类执行sql
// 但它并没有做别的
@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {。。。}
// 重点来了 这也是一个配置类
// 看名字也知道了,MapperScanner
// 但这个类其实没干啥,只不过在MapperFactoryBean.class, MapperScannerConfigurer.class 不存在时,记录log
// 真正做事的是AutoConfiguredMapperScannerRegistrar
@Configuration
@Import({MybatisAutoConfiguration.AutoConfiguredMapperScannerRegistrar.class})
@ConditionalOnMissingBean({MapperFactoryBean.class, MapperScannerConfigurer.class})
public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {
public MapperScannerRegistrarNotFoundConfiguration() {
}
public void afterPropertiesSet() {
MybatisAutoConfiguration.logger.debug("Not found configuration for registering mapper bean using @MapperScan, MapperFactoryBean and MapperScannerConfigurer.");
}
}
public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar {。。。}
先不管@Configuration为何嵌套
@Configuration可以参照:@Configuration 详解
3,AutoConfiguredMapperScannerRegistrar
首先要注意一件事
MapperFactoryBean.class, MapperScannerConfigurer.class 他们很重要,
故名思意啊
- MapperFactoryBean.class:生产Mapper实例,我们只定义了接口
- MapperScannerConfigurer.class:扫描Mapper定义
看下源码
public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar {
private BeanFactory beanFactory;
public AutoConfiguredMapperScannerRegistrar() {
}
// 主要是这个方法,实现至ImportBeanDefinitionRegistrar
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {。。。}
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
}
实现了两个接口
- BeanFactoryAware:用来注入BeanFactory
- ImportBeanDefinitionRegistrar:@Import注解用,可以先于@Configuration加载
registerBeanDefinitions方法
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
if (!AutoConfigurationPackages.has(this.beanFactory)) {
MybatisAutoConfiguration.logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.");
} else {
MybatisAutoConfiguration.logger.debug("Searching for mappers annotated with @Mapper");
// spring-boot扫描的包
List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
if (MybatisAutoConfiguration.logger.isDebugEnabled()) {
packages.forEach((pkg) -> {
MybatisAutoConfiguration.logger.debug("Using auto-configuration base package '{}'", pkg);
});
}
// 构建MapperScannerConfigurer定义,并加入spring容器
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
builder.addPropertyValue("processPropertyPlaceHolders", true);
builder.addPropertyValue("annotationClass", Mapper.class);
builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages));
BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class);
Set<String> propertyNames = (Set)Stream.of(beanWrapper.getPropertyDescriptors()).map(FeatureDescriptor::getName).collect(Collectors.toSet());
if (propertyNames.contains("lazyInitialization")) {
builder.addPropertyValue("lazyInitialization", "${mybatis.lazy-initialization:false}");
}
if (propertyNames.contains("defaultScope")) {
builder.addPropertyValue("defaultScope", "${mybatis.mapper-default-scope:}");
}
registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
}
}
这个方法其实就做了一件事
构建MapperScannerConfigurer定义并加入到spring容器中,并注入了一些属性
这样在spring容器加载类的时候就可以实例化它了
为什么要这么做,而不是@Component注解来加载MapperScannerConfigurer
个人理解
主要是为了,注入一些特别的属性
4,MapperScannerConfigurer
定义
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
。。。
}
实现了4个接口
请参照:spring 生命周期 扩展点
其他函数不介绍了
主要是这个
// spring 生命周期函数 实现至BeanDefinitionRegistryPostProcessor
// 会在spring容器扫描完@Component后执行
// 请注意@Mapper是Mybatis的注解,并没有组合@Component,所以不会被spring容器加载
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
// 这个依然是给 mapperScannerBean 注入属性,看名字也知道
if (this.processPropertyPlaceHolders) {
this.processPropertyPlaceHolders();
}
// 看名字 就知道要开始 扫描mapper了
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
scanner.setAddToConfig(this.addToConfig);
// 扫描@Mapper标注的类
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);
// 其他的属性 不一一介绍,关键是这个看使用的的FactoryBean
scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
if (StringUtils.hasText(this.lazyInitialization)) {
scanner.setLazyInitialization(Boolean.valueOf(this.lazyInitialization));
}
if (StringUtils.hasText(this.defaultScope)) {
scanner.setDefaultScope(this.defaultScope);
}
scanner.registerFilters();
// 扫描指定包,如果不指定就是 main函数所在包 与 spring 扫描的一样
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n"));
}
5,ClassPathMapperScanner
继承自:ClassPathBeanDefinitionScanner
重写了:doScan
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
// 上面提到的,事实上我们没有传入mapperFactoryBeanClass,所以这里用的是MapperFactoryBean
public void setMapperFactoryBeanClass(Class<? extends MapperFactoryBean> mapperFactoryBeanClass) {
this.mapperFactoryBeanClass = mapperFactoryBeanClass == null ? MapperFactoryBean.class : mapperFactoryBeanClass;
}
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
// 调用父类spring的扫描器
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
LOGGER.warn(() -> {
return "No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.";
});
} else {
// 如果有Mapper,这个时候Mapper已经在spring容器中了
this.processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
// 修改Mapper定义
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
BeanDefinitionRegistry registry = this.getRegistry();
Iterator var4 = beanDefinitions.iterator();
while(var4.hasNext()) {
BeanDefinitionHolder holder = (BeanDefinitionHolder)var4.next();
AbstractBeanDefinition definition = (AbstractBeanDefinition)holder.getBeanDefinition();
// 我们的Mapper通常都是单例的,所以这个是 false
boolean scopedProxy = false;
if (ScopedProxyFactoryBean.class.getName().equals(definition.getBeanClassName())) {
definition = (AbstractBeanDefinition)Optional.ofNullable(((RootBeanDefinition)definition).getDecoratedDefinition()).map(BeanDefinitionHolder::getBeanDefinition).orElseThrow(() -> {
return new IllegalStateException("The target bean definition of scoped proxy bean not found. Root bean definition[" + holder + "]");
});
scopedProxy = true;
}
String beanClassName = definition.getBeanClassName();
LOGGER.debug(() -> {
return "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName + "' mapperInterface";
});
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
// 指定Mapper的class为MapperFactoryBean。为生成实例做准备
// 因为我们Mapper是接口,不能生成实例
definition.setBeanClass(this.mapperFactoryBeanClass);
definition.getPropertyValues().add("addToConfig", this.addToConfig);
definition.setAttribute("factoryBeanObjectType", beanClassName);
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(() -> {
return "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(() -> {
return "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.";
});
}
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
}
if (!explicitFactoryUsed) {
LOGGER.debug(() -> {
return "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.";
});
definition.setAutowireMode(2);
}
definition.setLazyInit(this.lazyInitialization);
if (!scopedProxy) {
if ("singleton".equals(definition.getScope()) && this.defaultScope != null) {
definition.setScope(this.defaultScope);
}
if (!definition.isSingleton()) {
BeanDefinitionHolder proxyHolder = ScopedProxyUtils.createScopedProxy(holder, registry, true);
if (registry.containsBeanDefinition(proxyHolder.getBeanName())) {
registry.removeBeanDefinition(proxyHolder.getBeanName());
}
// 如果不是单例模式的时候 重新注册
registry.registerBeanDefinition(proxyHolder.getBeanName(), proxyHolder.getBeanDefinition());
}
}
}
}
}
6,MapperFactoryBean
上面的步骤是吧我们的Mapper注册到spring容器了
还没有实例化
但是,我们的Mapper是接口,没有办法直接实例化
这个时候MapperFactoryBean 就上场了
MapperFactoryBean
继承自:SqlSessionDaoSupport 这个辅助类先不看
实现了:FactoryBean,这个接口的作用是,当spring创建实现了FactoryBean接口的Bean时,当你将这个类型注入到其他对象时,实际上返回的是getObject方法返回的对象,返回的类型是getObjectType返回的类型
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
// 实现自 FactoryBean
// 这个方法是真正生成 Mapper实例的
public T getObject() throws Exception {
// 这个 this.getSqlSession() 是SqlSessionDaoSupport 的方法,返回的sqlSessionTemplate
// sqlSessionTemplate在最初的MybatisAutoConfiguration 实例化过
return this.getSqlSession().getMapper(this.mapperInterface);
}
// 实现自 FactoryBean
public Class<T> getObjectType() {
return this.mapperInterface;
}
}
看下
SqlSessionTemplate
public class SqlSessionTemplate implements SqlSession, DisposableBean {
public <T> T getMapper(Class<T> type) {
return this.getConfiguration().getMapper(type, this);
}
public Configuration getConfiguration() {
// sqlSessionFactory 在最初的MybatisAutoConfiguration 实例化过
return this.sqlSessionFactory.getConfiguration();
}
}
然后 Configuration
public class Configuration {
protected final MapperRegistry mapperRegistry;
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return this.mapperRegistry.getMapper(type, sqlSession);
}
}
然后 MapperRegistry
public class MapperRegistry {
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
// 终于new对象了
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception var5) {
throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
}
}
}
}
然后MapperProxyFactory
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return this.mapperInterface;
}
public Map<Method, MapperMethodInvoker> getMethodCache() {
return this.methodCache;
}
protected T newInstance(MapperProxy<T> mapperProxy) {
// 通过java的动态代理 创建Mappe对象
// 动态代理就不讲了
return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
return this.newInstance(mapperProxy);
}
}
到上面为止,Mapper类的实例化已经完成了
7,接口调用
MapperProxy
实现了InvocationHandler:java动态代理的Handler
public class MapperProxy<T> implements InvocationHandler, Serializable {
// 真正执行方法是这个代理方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 这个判断没弄明白,不知道 什么情况可以直接调用原类的方法,这边走的是后者
return Object.class.equals(method.getDeclaringClass()) ? method.invoke(this, args) : this.cachedInvoker(method).invoke(proxy, method, args, this.sqlSession);
} catch (Throwable var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
}
private MapperProxy.MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
try {
MapperProxy.MapperMethodInvoker invoker = (MapperProxy.MapperMethodInvoker)this.methodCache.get(method);
// 如果执行过就缓存起来
return invoker != null ? invoker : (MapperProxy.MapperMethodInvoker)this.methodCache.computeIfAbsent(method, (m) -> {
// 判断是否是默认方法,我们的都不是
if (m.isDefault()) {
try {
return privateLookupInMethod == null ? new MapperProxy.DefaultMethodInvoker(this.getMethodHandleJava8(method)) : new MapperProxy.DefaultMethodInvoker(this.getMethodHandleJava9(method));
} catch (InstantiationException | InvocationTargetException | NoSuchMethodException | IllegalAccessException var4) {
throw new RuntimeException(var4);
}
} else {
// PlainMethodInvoker是个内部类 很简单
return new MapperProxy.PlainMethodInvoker(new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration()));
}
});
} catch (RuntimeException var4) {
Throwable cause = var4.getCause();
throw (Throwable)(cause == null ? var4 : cause);
}
}
private static class PlainMethodInvoker implements MapperProxy.MapperMethodInvoker {
private final MapperMethod mapperMethod;
public PlainMethodInvoker(MapperMethod mapperMethod) {
this.mapperMethod = mapperMethod;
}
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
return this.mapperMethod.execute(sqlSession, args);
}
}
interface MapperMethodInvoker {
Object invoke(Object var1, Method var2, Object[] var3, SqlSession var4) throws Throwable;
}
}
MapperMethod
public class MapperMethod {
private final MapperMethod.SqlCommand command;
private final MapperMethod.MethodSignature method;
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
// 生成sql 对象
this.command = new MapperMethod.SqlCommand(config, mapperInterface, method);
// 方法签名
this.method = new MapperMethod.MethodSignature(config, mapperInterface, method);
}
// 最终调用的是sqlSession 的方法,也就是
// 最初在MybatisAutoConfiguration里生成的 sqlSessionTemplate
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
Object param;
switch(this.command.getType()) {
case INSERT:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
break;
case UPDATE:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
break;
case DELETE:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
break;
case SELECT:
if (this.method.returnsVoid() && this.method.hasResultHandler()) {
this.executeWithResultHandler(sqlSession, args);
result = null;
} else if (this.method.returnsMany()) {
result = this.executeForMany(sqlSession, args);
} else if (this.method.returnsMap()) {
result = this.executeForMap(sqlSession, args);
} else if (this.method.returnsCursor()) {
result = this.executeForCursor(sqlSession, args);
} else {
param = this.method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(this.command.getName(), param);
if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + this.command.getName());
}
if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
} else {
return result;
}
}
}