一. 初始化SqlSessionFactory
使用过mybatis的人都知道,mybatis中要操作需要开启SqlSession,而mybatis采用了工厂模式来生成SqlSession,要将mybatis继承到spring中,首先我们需要初始化SqlSessionFactory
// 数据源配置,采用dbcp2连接池
@Bean
public BasicDataSource dataSource(){
BasicDataSource basicDataSource = new BasicDataSource();
basicDataSource.setUrl("jdbc:oracle:thin:@(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=192.268.252.20)(PORT=1521)))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=abc)))");
basicDataSource.setPassword("123456");
basicDataSource.setUsername("demouser");
basicDataSource.setDriverClassName(OracleDriver.class.getName());
return basicDataSource;
}
// 此处会一次调用getObject -> afterPropertiesSet() -> buildSqlSessionFactory()创建sqlSessionFactory
// 如果需要指定配置文件可以通过以下方法指定:
// sqlSessionFactoryBean.setConfigLocation(new ClassPathResource(""));
// sqlSessionFactoryBean.setConfiguration(new org.apache.ibatis.session.Configuration());
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource());
return sqlSessionFactoryBean.getObject();
}
SqlSession采用工厂模式产生,而SqlSessionFactory也可以通过SqlSessionFactoryBean来产生,SqlSessionFactoryBean采用了类似建造者模式的方式产生SqlSessionFactory,让我们看看他类图。
这里我们new出SqlSessionFactoryBean,然后通过getObject获取SqlSessionFactory,该方法先构建解析配置类,然后利用sqlSessionFactoryBuilder建造SqlSessionFactory。
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
final Configuration targetConfiguration;
// 如果是采用java方式注入的Configuration
XMLConfigBuilder xmlConfigBuilder = null;
if (this.configuration != null) {
targetConfiguration = this.configuration;
if (targetConfiguration.getVariables() == null) {
targetConfiguration.setVariables(this.configurationProperties);
} else if (this.configurationProperties != null) {
targetConfiguration.getVariables().putAll(this.configurationProperties);
}
}
// 如果指定的是xml配置文件路径
else if (this.configLocation != null) {
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
targetConfiguration = xmlConfigBuilder.getConfiguration();
}
// 如果没有指定任何配置文件
else {
LOGGER.debug(
() -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
targetConfiguration = new Configuration();
Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);
}
// ..... 其余代码省略
if (xmlConfigBuilder != null) {
try {
xmlConfigBuilder.parse();
LOGGER.debug(() -> "Parsed configuration file: '" + this.configLocation + "'");
} catch (Exception ex) {
throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
} finally {
ErrorContext.instance().reset();
}
}
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 xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("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.");
}
return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}
利用解析好的配置文件创建DefaultSqlSessionFactory
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
二. Mapper实例化
上篇讲到,spring会将所有标注有@Mapper的接口注册成BeanDefinition,而这些BeanDefinition在Spring初始化过程中会被实例化,代码入口为AbstractApplicationContext类中的finishBeanFactoryInitialization方法。
spring会循环遍历所有已经有的BeanDefinition,然后依次做实例化
for (String beanName : beanNames) {
// 获取合并后的beanDefinition,此时已经有合并后的beanDefinition了,因为在执行bean工厂后置处理器时,会调用一个getBeanNamesForType,为了得到一个bean是否时属于某一个type,必须拿到合并后的beanDefinition
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
// 判断是否不是抽象的,是否是单例的,是否是非懒加载的
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
// 判断是否是工厂bean,我们这里的MapperFactoryBean就是继承自spring工厂bean
if (isFactoryBean(beanName)) {
// 获取工厂bean,FACTORY_BEAN_PREFIX=&,工厂beanName特有的标识
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);
}
}
}
由于在定义beanDefinition时添加了PropertyValue,因此实例化过程中会进行属性注入,分两个步骤,实例化时注入mapperInterface,属性注入时注入sqlSessionTemplate,addToConfig
2.1. 实例化,bean后置处理器推断构造方法
因为MapperFactoryBean有两个构造函数,一个为无参构造函数,一个为有参构造函数,首先会通过bean后置处理器推断构造函数
// Candidate constructors for autowiring?
// 第三次调用后置处理器,判断bean的构造器
// 1.唯一非默认构造函数,返回改构造函数
// 2.多个且有一个为@Autowired(required = true),返回该构造函数
// 3.多个@Autowired(required = false), 返回多个构造函数
// 4.唯一一个默认构造函数,返回null
// 根据以上结论推断构造器返回null
Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
return autowireConstructor(beanName, mbd, ctors, args);
}
但构造BeanDefinition中添加了构造方法参数,因此上述条件成立,根据参数数量等条件推断出构造函数,实例化对象
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
在autowireConstructor(beanName, mbd, ctors, args)方法中会完成实例化及mapperInterface的注入
2.1. 属性注入
实例化完成后会进行属性注入
// 属性注入
populateBean(beanName, mbd, instanceWrapper);
BeanDefinition中配置了注入方式为自动byType注入,并且属性中添加过addToConfig,因此,该值将会自动注入到属性中
definition.getPropertyValues().add("addToConfig", this.addToConfig);
if (!explicitFactoryUsed) {
LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
调用autowireByType注入addToConfig,并且推断出其他需要且能被注入的属性,能被注入的属性一定是已有的bean
if (resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
autowireByType(beanName, mbd, bw, newPvs);
}
通过后置处理器找出需要注入的bean,封装成PropertyValue
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof InstantiationAwareBeanPostProcessor) {
InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
// 第7次调用后置处理器,查到到需要用的PropertyValue
PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
if (pvsToUse == null) {
if (filteredPds == null) {
filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
}
pvsToUse = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
if (pvsToUse == null) {
return;
}
}
pvs = pvsToUse;
}
}
完成属性设置
if (pvs != null) {
applyPropertyValues(beanName, mbd, bw, pvs);
}
至此所有属性注入完成!!
三. 初始化MapperFactoryBean
bean实例化完成后如果有需要,还要对其进行初始化,因为MapperFactoryBean实现了InitializingBean,因此此处会调用他的afterPropertiesSet方法。
// 1.执行各种Aware接口,BeanNameAware,BeanClassLoaderAware,BeanFactoryAware
// 2. 如果bean为空执行bean初始化前后置处理器
// 3. 执行初始化方法
// 4 如果bean还为空执行bean初始化后后置处理器
exposedObject = initializeBean(beanName, exposedObject, mbd);
程序入口:
try {
// 执行实现了InitializingBean的afterPropertiesSet()
invokeInitMethods(beanName, wrappedBean, mbd);
}
程序执行到DaoSupport的初始化方法
@Override
public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
// Let abstract subclasses check their configuration.
checkDaoConfig();
// Let concrete implementations initialize themselves.
try {
initDao();
}
catch (Exception ex) {
throw new BeanInitializationException("Initialization of DAO failed", ex);
}
}
而checkDaoConfig由MapperFactoryBean重写了,此时就与我们之前注入SqlSessionTemplate对接上了,因此这里我们需要使用SqlSession获取到Configuration,然后再用Configuration来注册Mapper。
@Override
protected void checkDaoConfig() {
super.checkDaoConfig();
notNull(this.mapperInterface, "Property 'mapperInterface' is required");
Configuration configuration = getSqlSession().getConfiguration();
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
// 注册Mapper的核心代码
configuration.addMapper(this.mapperInterface);
} catch (Exception e) {
logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
throw new IllegalArgumentException(e);
} finally {
ErrorContext.instance().reset();
}
}
}
以下是注册Mapper核心代码:
public <T> void addMapper(Class<T> type) {
// 判断类型是否是接口,不是就不添加
if (type.isInterface()) {
// 判断是否是已经存在的Mapper
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
// 往缓存中添加MapperProxyFactory
knownMappers.put(type, new MapperProxyFactory<>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
// 解析接口中的信息,把各种@Select,@Insert解析为Statement,以便后续直接调用,解析过程这里暂时不分析
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
下面我们来看看,knownMappers中添加的这个MapperProxyFactory
// 由jdk动态代理生成代理Mapper的的动态代理对象,该对象实现了我们的Mapper接口
// 其中三个参数说明以下
// mapperInterface.getClassLoader():我们Mapper接口的类加载器
// new Class[] { mapperInterface}:动态代理对象需要实现的接口
// mapperProxy: 实现了InvocationHandler的类,并重写了invoke方法,后续执行Mapper接口方法时就会被动态转调到该方法上,后面我们重点来说这个方法
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
// new实例,实际调用newInstance(MapperProxy<T> mapperProxy)方法
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
MapperProxy的invoke方法
@Override
// 参数说明一下:
// proxy:代理对象
// method: 调用的方法
// args: 方法参数
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
// 如果该方法所属类是Object,那么直接调用
return method.invoke(this, args);
} else if (method.isDefault()) {
// 如果是接口的default方法
if (privateLookupInMethod == null) {
return invokeDefaultMethodJava8(proxy, method, args);
} else {
return invokeDefaultMethodJava9(proxy, method, args);
}
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 否则获取封装后的方法并执行
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
封装的方法,如果方法已经存在,则返回缓存的方法,如果没有,则新建并缓存
private MapperMethod cachedMapperMethod(Method method) {
return methodCache.computeIfAbsent(method,
k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
封装执行方法,后续就可以利用该方法的execute方法调用数据库
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
以上是我对Spring集成mybatis的总结,刚开始对Spring源码进行系统学习,不求给大家看,只求自己以后忘了能有个记录的地方,如果有理解不对或者逻辑混乱的地方,欢迎指正!!谢谢!