面试-springboot-mybatis启动流程

一、看完本篇文章你会知道

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);
  1. writeMethod 表示需要执行的setxxx方法
  2. getWrappedInstance()是MapperFactoryBean.clss类型
  3. 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的作用域问题

  1. singleton作用域:SpringIoC容器中只会存在一个共享的Bean实例,返回该Bean的同一实例。singleton是Spring中的缺省作用域。
  2. prototype作用域:每次对该Bean请求,比如将其注入到另一个Bean中,或者调用getBean()方法,都会创建一个新的Bean实例
  3. 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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

信仰_273993243

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值