2021-07-29

深入源码理解Spring整合MyBatis原理

写在前面

聊一聊MyBatis的核心概念、Spring相关的核心内容,主要结合源码理解Spring是如何整合MyBatis的。(结合右侧目录了解吧)

MyBatis相关核心概念粗略回顾

SqlSessionFactory

创建SqlSession的工厂

SqlSession

sql请求的会话,通过SqlSessionFactory获取。

 String resource = "mybatis-config.

上述代码就是单独使用MyBatis的时候的API例子。读取mybatis-config.

Mapper

public interface UserMapper { User findByUserId(Integer userId);}
<mapper namepsace = "com.deepz.mybatis.user.UserMapper">  <select id = "findByUserId" resultType="User">   ... </select></mapper>

以上就是咱们熟悉的MyBatis使用代码了,一个Mapper接口对应的就有一个

Spring相关核心概念粗略回顾

FactoryBean接口

是一种特殊的SpringBean,对应的真实实例是FactoryBean接口中getObject()方法的返回值,用于自定义复杂的Bean生成。

FactoryBean类型的Bean的获取

AbstractBeanFactory#doGetBean

Object sharedInstance = getSingleton(beanName); // 从三级缓存中根据beanName获取SpringBeanif (sharedInstance != null && args == null) {    // 如果SpringBean不为空则说明命中缓存,直接获取SpringBean实例即可	if (logger.isDebugEnabled()) {		if (isSingletonCurrentlyInCreation(beanName)) {			logger.debug("Returning eagerly cached instance of singleton bean '" + beanName +					"' that is not fully initialized yet - a consequence of a circular reference");		}		else {			logger.debug("Returning cached instance of singleton bean '" + beanName + "'");		}	}	bean = getObjectForBeanInstance(sharedInstance, name, beanName, null); // 获取SpringBean真实实例(针对FactoryBean)}

AbstractBeanFactory#getObjectForBeanInstance

// Now we have the bean instance, which may be a normal bean or a FactoryBean.// If it's a FactoryBean, we use it to create a bean instance, unless the// caller actually wants a reference to the factory.if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) { // 如果不是FactoryBean类型则直接返回	return beanInstance;}// 后续代码针对FactoryBean类型的SpringBean处理Object object = null;if (mbd == null) {  object = getCachedObjectForFactoryBean(beanName); // 从缓存中获取}if (object == null) {	// Return bean instance from factory.	FactoryBean<?> factory = (FactoryBean<?>) beanInstance;	// Caches object obtained from FactoryBean if it is a singleton.	if (mbd == null && containsBeanDefinition(beanName)) {		mbd = getMergedLocalBeanDefinition(beanName);	}	boolean synthetic = (mbd != null && mbd.isSynthetic());	object = getObjectFromFactoryBean(factory, beanName, !synthetic); // 获取FactoryBean真实实例}return object;

FactoryBeanRegistrySupport#doGetObjectFromFactoryBean
FactoryBeanRegistrySupport#getObjectFromFactoryBean最终会调到如下方法,通过FactoryBean的getObject()获取FactoryBean的真实实例

Object object;try {	if (System.getSecurityManager() != null) {		AccessControlContext acc = getAccessControlContext();		try {			object = AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {				@Override				public Object run() throws Exception {						return factory.getObject();					}				}, acc);		}		catch (PrivilegedActionException pae) {			throw pae.getException();		}	}	else {		object = factory.getObject(); // 显示调用FactoryBean#getObject	}}

InitializingBean接口

在SpringBean的生命周期中,Bean的初始化环节Spring会调用AbstractAutowireCapableBeanFactory#invokeInitMethods() 回调实现了InitializingBean接口的Bean的InitializingBean#afterPropertiesSet()

关于SpringBean生命周期欢迎移步笔者的相关总结随笔《深入源码理解SpringBean生命周期》

protected void invokeInitMethods(String beanName, final Object bean, RootBeanDefinition mbd)			throws Throwable {	boolean isInitializingBean = (bean instanceof InitializingBean); // Bean实现了InitializingBean接口	if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {		if (logger.isDebugEnabled()) {			logger.debug("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");		}		if (System.getSecurityManager() != null) {			try {				AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {					@Override					public Object run() throws Exception {						((InitializingBean) bean).afterPropertiesSet();						return null;					}				}, getAccessControlContext());			}			catch (PrivilegedActionException pae) {				throw pae.getException();			}		}		else {			((InitializingBean) bean).afterPropertiesSet(); // 显式回调InitializingBean的afterPropertiesSet()方法		}	}	if (mbd != null) {		String initMethodName = mbd.getInitMethodName();		if (initMethodName != null && !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&				!mbd.isExternallyManagedInitMethod(initMethodName)) {			invokeCustomInitMethod(beanName, bean, mbd);		}	}}

BeanDefinitionRegistryPostProcessor接口

可以看到BeanDefinitionRegistryPostProcessor继承了BeanFactoryPostProcessor,那么该接口的实现类一定会在Spring应用上下文生命周期中回调相关接口方法。

public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {	/**	 * Modify the application context's internal bean definition registry after its	 * standard initialization. All regular bean definitions will have been loaded,	 * but no beans will have been instantiated yet. This allows for adding further	 * bean definitions before the next post-processing phase kicks in.	 * @param registry the bean definition registry used by the application context	 * @throws org.springframework.beans.BeansException in case of errors	 */	void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;}

BeanFactoryPostProcessor接口

这个接口是干什么的?它有自己的抽象方法,在Spring应用上下文生命周期的"invokeBeanFactoryPostProcessors"环节会回调相关方法。(这里不是重点不过多聊)

我们这次关注的重点是它的子接口-BeanDefinitionRegistryPostProcessor,Spring在上述环节中对该接口做了特殊处理,回调了它的独有方法。(如上面的代码段所示)

BeanDefinitionRegistryPostProcessor接口回调

AbstractApplicationContext#refresh()方法即为Spring应用上下文的生命周期的刷新入口,可以看到在比较前置的环节就会先处理BeanFactoryProcessor类型的Bean。

追进源码后发现最后会在PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors()方法中循环处理所有BeanFactoryProcessor,其中如果是BeanDefinitionRegistryPostProcessor则会回调BeanDefinitionRegistryPostProcessor的独有方法,值得注意的是该方法的入参是一个BeanDefinitionRegistry

(关于BeanDefinitionRegistry有必要在单独段落做介绍,所以请先移步下一段落吧)

在了解了这几个接口后,我们汇总一下就明白了BeanDefinitionRegistryPostProcessor的能力了。它可以在Spring应用上下文前期先被实例化且回调相关接口方法,向Spring容器注册或移除BeanDefinition甚至可以在get出一个BeanDefinition后直接修改内部属性,让Bean变成你想要的模样。

BeanDefinitionRegistry接口

BeanDefinition是什么?

BeanDefinition是Spring实例化一个Bean的依据,它的内部维护了一个Bean的各种属性,如BeanClass、BeanName、lazyInit(是否懒加载)、primary、scope等等。

而Spring在实例化一个Bean的时候需要先从一个Map中根据beanName获取到对应的BeanDefinition才能去按需实例化SpringBean,如下。

DefaultListableBeanFactory#getBeanDefinition()

看看这个Map的定义
BeanDefinition

相信大家也猜到了,上面提到的就是维护这个Map的。
BeanDefinitionRegistry接口的方法如下:

可以看到该接口的能力就是维护Spring容器中的BeanDefinition。

Spring整合MyBatis正片

有了上面的回顾后,关于Spring整合MyBatis的秘密就很容易揭晓了。

在跟进源码前,我们先思考下如果是你,你会怎么将MyBatis整合进来?达到效果:通过@Autowired将Mapper注入进来便可以直接使用。

首先,我们有了Spring,第一个要干掉的就是SqlSessionFactory的维护了,我们要想办法读取"mybatis-config.

其次,同样的,我们不可能每次都通过sqlSession.getMapper()来获取我们需要的Mapper代理实例,所以第二个要干掉的就是Mapper的维护,我们同样要想办法将所有的Mapper处理成SpringBean交给SpringIOC,这样我们就能够将SpringBean依赖注入到任何地方了。

思考过后,我们来看看Spring是怎么做的吧。当我们需要结合Spring使用MyBatis的时候,第一步便是添加一个mybatis-spring的Jar到项目里来,那么秘密都在这里了。

MyBatis-Spring

如下便是MyBatis-Spring这个Jar的项目结构了,应该能看到大家使用的时候熟悉的组件吧。如MapperScan注解。眼尖的伙伴应该能看到几个类:SqlSessionFactoryBean、MapperFactoryBean、MapperScannerConfigurer、SpringManagedTransaction,这几个类将会是接下来探讨的重点。
MyBatis-Spring

SqlSessionFactoryBean

刚刚我们聊到了,首先需要干掉的就是SqlSessionFactory的低端维护方式。我们先看看SqlSessionFactory这个类的继承树。

SqlSessionFactoryBean继承树
可以看到它实现了FactoryBean,这就意味着它一定有一个getObject()方法,用于返回交给Spring管理的实例;
它还实现了InitializingBean,这就意味着在这个Bean的初始化时,Spring会回调它的afterPropertiesSet()方法。(Spring事件本次不讨论)

将SqlSessionFactory交给Spring管理-FactoryBean#getObject()

我们先看看,这个SqlSessionFactoryBean交给Spring管理的对象是怎样构建的。

 /** * {@inheritDoc} */ @Override public SqlSessionFactory getObject() throws Exception { if (this.sqlSessionFactory == null) { // 如果sqlSessionFactory为空,则显式调用afterPropertiesSet()方法  afterPropertiesSet(); } return this.sqlSessionFactory;  // 返回sqlSessionFactory } @Override public Class<? extends SqlSessionFactory> getObjectType() { return this.sqlSessionFactory == null ? SqlSessionFactory.class : this.sqlSessionFactory.getClass(); }

可以看到源码中首先是判空,如果为空则显式调用afterPropertiesSet()方法,最后将sqlSessionFactory返回。那么可以猜出,afterPropertiesSet()方法大概率是构造sqlSessionFactory的了。

SqlSessionFactory的构建

首先看看afterPropertiesSet()方法

 /** * {@inheritDoc} */ @Override public void afterPropertiesSet() throws Exception { notNull(dataSource, "Property 'dataSource' is required"); notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required"); state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),    "Property 'configuration' and 'configLocation' can not specified with together"); this.sqlSessionFactory = buildSqlSessionFactory(); // 核心逻辑在buildSqlSessionFactory()中。 }

跟进看看buildSqlSessionFactory()方法,请重点看标了注释的,其他的可以不用太关注细节,主要是一些配置的初始化工作。

 protected SqlSessionFactory buildSqlSessionFactory() throws IOException { Configuration configuration; 

总体就是读取MyBatis的配置,初始化Configuration全局配置对象,根据整合的配置创建出SqlSessionFactory。

经过了上述步骤,SqlSessionFactory就可以被交给Spring管理了,解决了第一个问题,可以从Spring上下文中获取到SqlSessionFactory这个Bean了。

ClassPath

Mapper的代理对象-MapperFactoryBean

接下来讨论Spring如何实现通过@Autowired将Mapper注入进来后就能直接使用的问题。

首先思考一下,如何将一个类交给Spring管理?@Component系列注解?而MyBatis是一个个Interface而不是Class,在上面加注解是没用的,我们需要的是将MyBatis对Mapper生成的代理对象交给Spring管理。那该怎么做呢?Spring的做法是将Mapper一对一地包装成了MapperFactoryBean,而MapperFactoryBean维护了Mapper的类型,通过该类型获取Mapper代理实例。

MapperFactoryBean的继承树
可以看到这个MapperFactoryBean同样实现了FactoryBean接口,那么按照惯例我们看看它的getObject()做了什么。

 // Mapper接口类型 private Class<T> mapperInterface; /** * {@inheritDoc} */ @Override public T getObject() throws Exception { // 与MyBatis单独使用类似,都是通过sqlSession调用getMapper()方法获取对应的Mapper。 // 需要注意的是入参是一个接口类型,而出参是MyBatis生成的代理对象 return getSqlSession().getMapper(this.mapperInterface); } /** * {@inheritDoc} */ @Override public Class<T> getObjectType() { return this.mapperInterface; // Object的类型 }

可以看出Spring将Mapper包装成了MapperFactoryBean,其中的mapperInterface字段就是Mapper的类型,在交给Spring管理的时候依旧是通过sqlSession.getMapper(Class type)返回Mapper的代理对象的。

那么Mapper对应的模型有了,是不是还缺点什么?是的,我们需要扫描所有的Mapper,将他们包装成MapperFactoryBean(如UserMapper,就需要有一个MapperFactoryBean,其中mapperInterface字段是UserMapper.class)。这个重要的任务,Spring交给了我们接下来要聊的MapperScannerConfigurer了,通过类名就能感知到关键字:[Mapper、扫描、配置]

Mapper的扫描与配置-MapperScannerConfigurer

MapperScannerConfigurer

老规矩,看看几个核心接口的方法都做了什么。

afterPropertiesSet()

 /** * {@inheritDoc} */ @Override public void afterPropertiesSet() throws Exception { // 几乎啥也没干,就断言了个扫描包路径不为空。 下一个! notNull(this.basePackage, "Property 'basePackage' is required"); }

扫描Mapper并注册BeanDefinition

可以看到MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,文章开头的Spring技术回顾聊到该接口的postProcessBeanDefinitionRegistry()方法会在Spring容器启动的时候在较早的时机被回调。

 private String basePackage; private boolean addToConfig = true; private SqlSessionFactory sqlSessionFactory; private SqlSessionTemplate sqlSessionTemplate; private String sqlSessionFactoryBeanName; private String sqlSessionTemplateBeanName; private Class<? extends Annotation> annotationClass; private Class<?> markerInterface; private ApplicationContext applicationContext; private String beanName; private boolean processPropertyPlaceHolders; private BeanNameGenerator nameGenerator; /** * {@inheritDoc} * * @since 1.0.2 */ @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { if (this.processPropertyPlaceHolders) {  // 解析并更新Spring配置文件中MapperScannerConfigurer相关的配置  processPropertyPlaceHolders(); } //创建类路径Mapper扫描器,并配置基本信息如扫描的注解(过滤条件)等。 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.registerFilters(); //根据配置好的信息去扫描basePackage字段中指定的包及其子包 scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)); }

我们来看看processPropertyPlaceHolders()做了什么。[可以跳过,不重要]

 /* * BeanDefinitionRegistries are called early in application startup, before * BeanFactoryPostProcessors. This means that PropertyResourceConfigurers will not have been * loaded and any property substitution of this class' properties will fail. To avoid this, find * any PropertyResourceConfigurers defined in the context and run them on this class' bean * definition. Then update the values. */ // 上面Spring官方的注释的意思如下:BeanDefinitionRegistriy在Spring启动的时候回调地太早了,在BeanFactoryPostProcessors之后(PropertyResourceConfigurer实现了BeanFactoryProcessor) // 方法调用到此处的时候,相关的配置信息还没被载入进来,都是空,会有问题。所以我们要提前主动触发(getBeanOfType与getBean逻辑一致,都是先拿,拿不到就实例化再存入三级缓存)PropertyResourceConfigurer的实例化,这样相关的配置就能够被载入进来了。 private void processPropertyPlaceHolders() { // 先主动触发该类型的Bean的实例化。 Map<String, PropertyResourceConfigurer> prcs = applicationContext.getBeansOfType(PropertyResourceConfigurer.class); if (!prcs.isEmpty() && applicationContext instanceof ConfigurableApplicationContext) {  BeanDefinition mapperScannerBean = ((ConfigurableApplicationContext) applicationContext)   .getBeanFactory().getBeanDefinition(beanName);  // PropertyResourceConfigurer does not expose any methods to explicitly perform  // property placeholder substitution. Instead, create a BeanFactory that just  // contains this mapper scanner and post process the factory.  DefaultListableBeanFactory factory = new DefaultListableBeanFactory();  factory.registerBeanDefinition(beanName, mapperScannerBean);   for (PropertyResourceConfigurer prc : prcs.values()) {  prc.postProcessBeanFa......

原文转载:http://www.shaoqun.com/a/893400.html

跨境电商:https://www.ikjzd.com/

parser:https://www.ikjzd.com/w/680

mav:https://www.ikjzd.com/w/2414

智邦:https://www.ikjzd.com/w/2376


写在前面聊一聊MyBatis的核心概念、Spring相关的核心内容,主要结合源码理解Spring是如何整合MyBatis的。(结合右侧目录了解吧)MyBatis相关核心概念粗略回顾SqlSessionFactory创建SqlSession的工厂SqlSessionsql请求的会话,通过SqlSessionFactory获取。Stringresource="mybatis-config.上
rfq:https://www.ikjzd.com/w/251
topia:https://www.ikjzd.com/w/2741
它是全国第三大的博览中心,面积相当于17个足球场,武汉人的骄傲:http://www.30bags.com/a/241640.html
它是西藏海拔最低大湖,雪山桃花美不胜收,有"小瑞士"之美誉:http://www.30bags.com/a/246109.html
它曾是三朝古都,从山西煤都华丽变成旅游之城,今空气好古迹丰富:http://www.30bags.com/a/227509.html
塌方的急救方法 :http://www.30bags.com/a/411047.html
换妻 老婆还是别人的好?:http://lady.shaoqun.com/a/107395.html
随着车子不断晃动一进一出 男朋友在车里㖭刺激:http://lady.shaoqun.com/a/247999.html
为什么有的女生在被侵犯的时候不逃不喊,看完不可言喻的夏天才恍然大悟:http://lady.shaoqun.com/a/428540.html
产后多久可以做爱?医生告诉你最好的时间,你必须注意这些事项:http://lady.shaoqun.com/a/428541.html
会写性爱日记吗?:http://lady.shaoqun.com/a/428542.html
与异性相处:男女有性,为什么女人不拒绝,看完你就明白了:http://lady.shaoqun.com/a/428543.html

No comments:

Post a Comment