一、看完本篇文章你会知道
1、Mapper是如何被扫描进来的
2、Mapper是如何被实例化的
二、Spring加载Mapper
1、在spring启动的时候,会执行refresh刷新方法,刷新里面会有一个invokeBeanFactoryPostProcessors方法,他调用ConfigurationClassPostProcessor后置处理器去加载各个Bean
2、ConfigurationClassPostProcessor会在Bean中找到是Configuration类型的Bean,判断这个Bean是否包含@Import注解。比如Springboot启动类中,@SpringBootApplication就是Configuration类型的Bean,而@MapperScan又包含@Import注解,所以就会@Import注解的值转成Bd存到Spring容器中
@SpringBootApplication(exclude = MybatisAutoConfiguration.class)
@ServletComponentScan
@MapperScan("com.xiaour.spring.boot.mapper")
public class Application {
.......
}
//看下MapperScan长什么样
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({MapperScannerRegistrar.class})
public @interface MapperScan {
.......
}
注意MapperScan包含下面注解,MapperScannerRegistrar会在下面讲到
@Import({MapperScannerRegistrar.class})
1、执行MapperScannerRegistrar回调
那么spring是怎么处理@Import注解
private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited){
......
String annName = annotation.getMetadata().getClassName();
if (!annName.startsWith("java") && !annName.equals(Import.class.getName())) {
collectImports(annotation, imports, visited);//通过递归的方式调用collectImports
}
//把里面的value太那几到集合中
imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
......
}
SpringBoot的@Import塞的是MapperScannerRegistrar.clss,那么MapperScannerRegistrar就会被当做Bd被注入到Spring的容器中
在所有的Bd加载到容器里面后,就会执行下面的load方法。
this.reader.loadBeanDefinitions(configClasses)
Load所有Bd的信息,此时MapperScannerRegistrar已经被加载到Spring的容器中,Load会主动触发MapperScannerRegistrar类的回调方法。
MapperScannerRegistrar类是ImportBeanDefinitionRegistrars类的子类,ImportBeanDefinitionRegistrars类有个接口方法registerBeanDefinitions
public interface ImportBeanDefinitionRegistrar {
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
}
方法registerBeanDefinitions是在MapperScannerRegistrar类里面实现的。
看下MapperScannerRegistrar的registerBeanDefinitions方法做了什么
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
......
//获取注解@MapperScan的全部属性
AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
.....
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
//拿到Mapper所在路径,并且扫描该路径下的所有Mapper
basePackages = annoAttrs.getStringArray("basePackages");
......
scanner.doScan(StringUtils.toStringArray(basePackages));
}
从Configuration类型的Bd中拿到@MapperScan注解属性,从属性里面读取basePackages的值,basePackages配的就是需要扫描Mapper的路径,在调用scanner.doScan方法,把该路径下的Mapper读取出来转成Bd。
2、Mapper转成MapperFactoryBean
Bd所对应的BeanName是userInfoMapper,但是类型却是MapperFactoryBean。那么UserInfoMapper这个Db是什么时候被存成了MapperFactoryBean类型的呢,其实下面代码就可以看出来了
MapperScannerRegistrar的回调方法registerBeanDefinitions下面有个doScan方法,就是把扫描路径下的Mapper转成Bb,
scanner.doScan(StringUtils.toStringArray(basePackages));
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);//把扫描路径下的Mapper转成Db
....
this.processBeanDefinitions(beanDefinitions);//进一步处理Db
....
}
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
Iterator var3 = beanDefinitions.iterator();
while(var3.hasNext()) {
BeanDefinitionHolder holder = (BeanDefinitionHolder)var3.next();
GenericBeanDefinition definition = (GenericBeanDefinition)holder.getBeanDefinition();
//这个才是Mapper的真实类型UserInfoMapper.clss
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());
//这里存的是虚拟类型MapperFactoryBean.clss
definition.setBeanClass(this.mapperFactoryBean.getClass());
//设置属性addToConfig的事,addToConfig是做什么的
definition.getPropertyValues().add("addToConfig", this.addToConfig);
}
}
可以看到上面的代码,原来是虚设了一个BeanClass类型
definition.setBeanClass(this.mapperFactoryBean.getClass());
那么MapperFactoryBean什么,我们看下MapperFactoryBean的属性,MapperFactoryBean有一个属性字段mapperInterface,mapperInterface存的就是UserInfoMapper.class
//mapperInterface存的是UserInfoMapper,mapperInterface才是真正类型
private Class<T> mapperInterface;
private boolean addToConfig = true;
三、Spring实例化Mapper
1、获取sqlSessionFactory
因为Spring整合Mybatis那就需要我们配置sqlSessionFactory,Mapper实例化的时候会用到SqlSessionFactory
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource());
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources(("classpath*:mapper/*.xml")));
return sqlSessionFactoryBean.getObject();
}
@Bean(name = "dataSource")
public DruidDataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(dataSourceProperties.getUrl());
dataSource.setDriverClassName(dataSourceProperties.getDriverClassName());
dataSource.setUsername(dataSourceProperties.getUsername());
dataSource.setPassword(dataSourceProperties.getPassword());
return dataSource;
}
DruidDataSource是为了创建SqlSessionFactory做的准备工作,那么SqlSessionFactory方法加了@Bean,就也会被Spring加入到IOC容器中,所以在实例化的时候,也会包含实例化SqlSessionFactory。
先实例化sqlSessionFactory,那么对于sqlSessionFactory的实例化spring是怎么做的,直接调用加了@Bean注解的方法,因为加了@Bean的方法返回值就是我们要创建的Bean类型
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
....
return sqlSessionFactoryBean.getObject();
}
注意这里是通过spring的SqlSessionFactoryBean来得到SqlSessionFactory,所以对于@Bean注解的bean是不是就不是走代理,直接走带有@Bean的方法?
2、创建SqlSessionTemplate
下面开始创建Bean
sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
destroySingleton(beanName);
throw ex;
}
}
});
//上面是执行bean的准备工作,下面代码才会开始创建Bean的实例
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
beanName就是userInfoMapper
mbd就是userInfoMapper的Db对象
在createBean方法创建UserInfoMapper对象的时候,里面会触发下面方法
populateBean(beanName, mbd, instanceWrapper);
作用:在完成Bean实例化后,Spring会给UserInfoMapper这个Bean注入它依赖的Bean(因为这里需要创建SqlSessionTemplate,刚开始也是看了半天不明白SqlSessionTemplate是怎么被实例化的)
那么就需要找到哪些属性需要注入进去,spirng通过递归的方式一层一层遍历MapperFactoryBean以及父类,然后找到自己和父类的public修饰符的set方法,来变向的给类中的属性赋值。那么怎么去执行这个set方法呢,首先拿到需要执行的setxxx方法,然后通过代理的方式
本类+父类一层一层往上遍历来得到public修复的方法,注意是方法不是属性。
Method methodList[] = getPublicDeclaredMethods(beanClass);
比如MapperFactoryBean的父类是SqlSessionDaoSupport,SqlSessionDaoSupport有setSqlSessionFactory方法,那么是调用setSqlSessionFactory方法的时候,里面会实例化sqlSessionTemplete
0 = "xxxx.session.SqlSession org.mybatis.spring.support.SqlSessionDaoSupport.getSqlSession()"
1 = "xxxx.SqlSessionDaoSupport.setSqlSessionFactory(org.apache.ibatis.session.SqlSessionFactory)"
2 = "xxxx.SqlSessionDaoSupport.setSqlSessionTemplate(org.mybatis.spring.SqlSessionTemplate)"
writeMethod.invoke(getWrappedInstance(), value);
- writeMethod 表示需要执行的setxxx方法
- getWrappedInstance()是MapperFactoryBean.clss类型
- value就是sqlSessionFactory对象
当执行setSqlSessionFactory方法的时候,看下会做哪些操作
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
....
this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
....
}
会通过sqlSessionFactory去创建SqlSessionTemplate,这个时候就会触发创建SqlSessionTemplate逻辑
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[]{SqlSession.class},
new SqlSessionTemplate.SqlSessionInterceptor());
}
再看下代理sqlSessionProxy对象是怎么产生的
this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[]{SqlSession.class},
new SqlSessionTemplate.SqlSessionInterceptor());
三个参数
1、获取类加载器
2、SqlSession类型,目标对象类型,也就是被代理对象的类型
3、代理对象。SqlSessionInterceptor代理对象实现了InvocationHandler方法,所以逻辑都在Invoke方法里面
private class SqlSessionInterceptor implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
.....
Object result = method.invoke(sqlSession, args);
.....
}
}
小结:在每个mapper实例化的时候,会先实例化SqlSessionTemplate对象,每个Mapper有个对应的SqlSessionTemplate,每个SqlSessionTemplate都有属于自己的sqlSessionProxy。
3、开始实例化Mapper
sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
destroySingleton(beanName);
throw ex;
}
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
createBean代码执行完了之后,这个时候还没创建UserInfoMapper实例,上面一步返回sharedInstance对象,是MapperFactoryBean类型,getObjectForBeanInstance才是开始实例化UserInfoMapper,在实例化UserInfoMapper就要用到这个MapperFactoryBean对象
getObjectForBeanInstance会调用MapperFactoryBean的getObject方法,也就是sharedInstance.getObject()方法,来得到UserInfoMapper的实例(注意:FactoryBean和BeanFactory什么区别)
public class MapperFactoryBean<T> extends xxx implements xxx<T> {
private Class<T> mapperInterface;
private boolean addToConfig = true;
public T getObject() throws Exception {
return this.getSqlSession().getMapper(this.mapperInterface);
}
}
MapperFactoryBean和MapperProxyFactory属性很相似
1、MapperFactoryBean属于spring-mybatis
2、MapperProxyFactory属于mybatis
上面getObject方法里面this.getSqlSession()拿到的就是上面创建的sqlSessionTemplate,
public class SqlSessionTemplate implements xxx {
public <T> T getMapper(Class<T> type) {
return this.getConfiguration().getMapper(type, this);
}
}
public class DefaultSqlSessionFactory implements xxx {
public Configuration getConfiguration() {
return this.configuration;
}
}
最终还是通过DefaultSqlSessionFactory来调用getMapper方法,所以会想之前Mybatis没有整合Spring的源码,也是通过DefaultSqlSessionFactory来得到SqlSession,再通过得到的SqlSession来得到MapperProxyFactory,在通过MapperProxyFactory调用mapperProxyFactory.newInstance(sqlSession)方法来实例化UserInfoMaper的bean,所以Spring只是包装了一下Mybatis,底层走的还是Mybatis的逻辑
关于Mybatis的源码介绍请看我的这篇文章跳转
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return this.mapperRegistry.getMapper(type, sqlSession);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//得到我们前面一开始创建的MapperProxyFactory
MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new ..
} else {
return mapperProxyFactory.newInstance(sqlSession);
}
}
看下mapperProxyFactory.newInstance(sqlSession);做了什么
public T newInstance(SqlSession sqlSession) {
MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
return this.newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}
先创建一个MapperProxy,再看下MapperProxy长什么样子,实现了InvocationHandler接口,所以要重写invoke方法,所以MapperProxy这个经常被Mybatis当做代理类,然后走到这个类型的invoke方法
public class MapperProxy<T> implements InvocationHandler, Serializable {
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
//实现了InvocationHandler接口,所以需要重写Invoker方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
MapperMethod mapperMethod = this.cachedMapperMethod(method);
return mapperMethod.execute(this.sqlSession, args);
}
}
当我们执行UserInfoMapper的任何方法,都会走到MapperProxy的invoke方法里面去,甚至包括toString方法。看下Invoke里面做了啥
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
MapperMethod mapperMethod = this.cachedMapperMethod(method);
return mapperMethod.execute(this.sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
//通过Method从Cache里面拿,一开始是null,后续再进来,就直接从缓存里面拿
MapperMethod mapperMethod = (MapperMethod)this.methodCache.get(method);
if (mapperMethod == null) {
//通过mybatis配置+userMapper接口里面的方法绑定起来,返回对应的Method
mapperMethod = new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());
this.methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
看下MapperMethod有哪些东西,SqlCommand sql扫描
public class MapperMethod {
private final MapperMethod.SqlCommand command;
private final MapperMethod.MethodSignature method;
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new MapperMethod.SqlCommand(config, mapperInterface, method);
this.method = new MapperMethod.MethodSignature(config, mapperInterface, method);
}
}
通过:mapperInterface.getName() + “.” + methodName = Id生成Id,通过id从集合mappedStatements里面得到
MappedStatement
protected final Map<String, MappedStatement> mappedStatements;
mappedStatements.get(id)
MappedStatement有什么用,下面会说道
通过上面得到的mapperMethod,然后存到methodCache中。后面需要用的时候,就直接从缓存里面取就好了。
执行sql
mapperMethod.execute(this.sqlSession, args);
上面写到了mapperMethod包含的属性,以及方法,那么执行sql都是围绕mapperMethod来进行的。
execute里面会调用方法
Object result = sqlSession.selectOne(this.command.getName(), param);
private final SqlSession sqlSessionProxy;
public <T> T selectOne(String statement, Object parameter) {
return this.sqlSessionProxy.selectOne(statement, parameter);
}
前面说了sqlSessionProxy对象是由创建sqlSessionTemplate对象的时候创建的
public class SqlSessionTemplate implements SqlSession, DisposableBean {
private final SqlSession sqlSessionProxy
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
......
this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[]{SqlSession.class},
new SqlSessionTemplate.SqlSessionInterceptor());
}
}
sqlSessionProxy是sqlSession类型,是由SqlSessionInterceptor类代理了,当执行sqlSession的任意方法时,都会走到代理类SqlSessionInterceptor的invoker方法里面,
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//先得到SqlSession
SqlSession sqlSession = SqlSessionUtils.getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
try {
Object result = method.invoke(sqlSession, args);//执行目标方法
if (!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
sqlSession.commit(true);//如果没有开启事务,那么就会自动提交
}
} catch (Throwable var11) {
....
} finally {
if (sqlSession != null) {
SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
return unwrapped;
}
看下上面SqlSessionUtils.getSqlSession如何拿到Session
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
........
SqlSession session = sessionFactory.openSession(executorType);
return session;
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
........
Environment environment = this.configuration.getEnvironment();
TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
Transaction tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
Executor executor = this.configuration.newExecutor(tx, execType);
DefaultSqlSession defaultSqlSession = new DefaultSqlSession(this.configuration, executor, autoCommit);
return defaultSqlSession
}
上面代码
1、创建事务Transaction
2、通过事务Transaction创建executor,在通过得到的executor执行器创建DefaultSqlSession
(上面两步和Mybatis的源码是一样的)
Object result = method.invoke(sqlSession, args);//执行目标方法
if (!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
sqlSession.commit(true);//如果没有开启事务,那么就会自动提交
}
由上面这串代码就知道事务是怎么做的,如果sql执行成功,默认时自动提交,如果执行失败,那么在执行到finally的时候,就会自动关闭,也就是说,没有提交,所以之前的sql不会生效,需要用户手动提交
注意:
Bean的作用域问题
- singleton作用域:SpringIoC容器中只会存在一个共享的Bean实例,返回该Bean的同一实例。singleton是Spring中的缺省作用域。
- prototype作用域:每次对该Bean请求,比如将其注入到另一个Bean中,或者调用getBean()方法,都会创建一个新的Bean实例
- prototype作用域的Bean,Spring不能对该Bean的整个生命周期负责。该Bean创建后交由调用者负责销毁对象回收资源
因为我们平时开发的时候,mapper是会注入Service,所以在实例化Service的时候,就会把Service依赖的Bean创建好,这个时候Mapper就会被创建,那么就会从DB集合中找到name=userInfoMapper的Db,但其实存的是类型是MapperFactoryBean
如果是注入到Controller里面,因为Controller是属于多例的只有在请求的时候才会创建,那么如果把Mapper注入到Controller里面也就是只有当Controller创建的时候,才会去区创建Mapper的实例,这一点和注入到Service有点不太一样。
四、总结
可以看到在实例化之前,MapperFactoryBean就是存在Spring中了,只不过实例化的时候,多了2个属性sqlSession = SqlSessionTemplate,externalSqlSession = false