引言:今天在公司里测试遇到一个bug
先上代码:

private static SqlSessionFactory getSqlSessionFactory() throws Exception {
	if (sqlSessionFactory == null) {
			SqlSessionFactoryBean sqlSessionFactoryBean = SpringBeanUtils.getBean(SqlSessionFactoryBean.class);
			sqlSessionFactory = sqlSessionFactoryBean.getObject();
	}
	return sqlSessionFactory;
}

在执行的时候会抛出异常NoSuchBeanDefinitionException
为啥呢?因为SqlSessionFactory这个Bean在外部是通过java注入,而非通过xml注入的
修改以上代码如下:

private static SqlSessionFactory getSqlSessionFactory() throws Exception {
	if (sqlSessionFactory == null) {
		try {
			SqlSessionFactoryBean sqlSessionFactoryBean = SpringBeanUtils.getBean(SqlSessionFactoryBean.class);
			sqlSessionFactory = sqlSessionFactoryBean.getObject();

		} catch (NoSuchBeanDefinitionException e) {
			// 找不到SqlSessionFactoryBean 就找 SqlSessionFactory
			// SpringBoot时会存在这种情况
			sqlSessionFactory = SpringBeanUtils.getBean(SqlSessionFactory.class);
		}
	}
	return sqlSessionFactory;
}

测试没有报错了。后来仔细再研究了一下spring的getBean姿势:
发现以上的代码可以简写成如下方式:

private static SqlSessionFactory getSqlSessionFactory() throws Exception {
	return  sqlSessionFactory = SpringBeanUtils.getBean(SqlSessionFactory.class);
}

具体原因是啥呢?以下进行详细的探讨!

一:Spring中怎么获取Bean

在xml中定义sessionFactory的姿势:

<!-- 定义Spring与MyBatis整合的控制操作,此时数据库的连接对象取得由Spring负责 -->
<bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
	<property name="dataSource" ref="dataSource" />
	<!-- 配置所有资源文件的保存路径 此段也会作为node进行解析 可以配置array -->
	<!-- <property name="mapperLocations" value="classpath:sqlMapper/**/*.xml" /> -->
	<property name="mapperLocations">
		<array>
			<value>classpath:sqlMapper/**/*.xml</value>
		</array>
	</property>
</bean>

那么如何获取这个Bean呢?
在Spring容器中获取Bean有如下几种方式:

// 方式一:byName
this.applicationContext.getBean("sessionFactory");
// 方式二:byType
this.applicationContext.getBean(SqlSessionFactoryBean.class);
// 方式三:byNameAndType
this.applicationContext.getBean("sessionFactory", SqlSessionFactoryBean.class);
第一种方式(byName):

在这里插入图片描述
包括如下三个步骤:

1. 名称转换

根据传入的名称(name)解析出真正的id(beanDefinitionMap中的key),因为传入的名称可能是别名,也有可能是获取工厂Bean实际对象(名称前面添加&的方式)

// org.springframework.beans.factory.BeanFactoryUtils
public static String transformedBeanName(String name) {
	Assert.notNull(name, "'name' must not be null");
	String beanName = name;
	// 去掉name前面的&符号
	while (beanName.startsWith(BeanFactory.FACTORY_BEAN_PREFIX)) {
		beanName = beanName.substring(BeanFactory.FACTORY_BEAN_PREFIX.length());
	}
	return beanName;
}
public String canonicalName(String name) {
	String canonicalName = name;
	// Handle aliasing...  处理别名
	String resolvedName;
	do {
		// 根据保存的别名与ID映射解析出ID
		resolvedName = this.aliasMap.get(canonicalName);
		if (resolvedName != null) {
			canonicalName = resolvedName;
		}
	}
	while (resolvedName != null);
	return canonicalName;
}
2. 获取单例对象

如果已经解析过了,通常在容器初始化的过程中进行的,那么直接读取缓存就行了:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
	// 单例对象缓存中直接获取
	Object singletonObject = this.singletonObjects.get(beanName);
	if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
		synchronized (this.singletonObjects) {
			singletonObject = this.earlySingletonObjects.get(beanName);
			if (singletonObject == null && allowEarlyReference) {
				ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
				if (singletonFactory != null) {
					singletonObject = singletonFactory.getObject();
					this.earlySingletonObjects.put(beanName, singletonObject);
					this.singletonFactories.remove(beanName);
				}
			}
		}
	}
	return (singletonObject != NULL_OBJECT ? singletonObject : null);
}

在这里插入图片描述
如果没有加载过呢?比如将当前bean设置为懒加载模式,那么第一次获取时会进行Bean的初始化,初始化完成之后放到单例Bean缓存中。
为了方便,可以直接添加断点(如下图所示),以debug模式启动容器
在这里插入图片描述
流程如下:

  1. 解析名称(工厂Bean与别名)
  2. 获取单例(此时缓存不存在)
  3. 判断是否protype类型Bean缓存是否存在(无关)
  4. 到父容器中获取(没有父容器)
  5. 根据名称获取Bean定义,返回bean定义的类型为SqlSessionFactoryBean,其实这个过程就是从beanDefinitionMap中获取指定名称的Bean定义对象并包装(GenericBeanDefinition类型包装成RootBeanDefinition类型),当然也涉及到缓存的应用,如果此过程中无法获取到Bean定义,抛出的异常为NoSuchBeanDefinitionException,源码如下:
	@Override
public BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException {
	BeanDefinition bd = this.beanDefinitionMap.get(beanName);
	if (bd == null) {
		if (logger.isTraceEnabled()) {
			logger.trace("No bean named '" + beanName + "' found in " + this);
		}
		throw new NoSuchBeanDefinitionException(beanName);
	}
	return bd;
}

获取的Bean定义的beanClass为org.mybatis.spring.SqlSessionFactoryBean
在这里插入图片描述
6. 解析依赖Bean(此处为null)
7. 解析单例Bean 根据反射创建对象、解析依赖、填充依赖、初始化、缓存单例Bean等等,这个逻辑是Spring中单例Bean的初始化主要逻辑,此处不进入深究

// Create bean instance.
if (mbd.isSingleton()) {
	sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
		@Override
		public Object getObject() throws BeansException {
			try {
				return createBean(beanName, mbd, args);
			}
			catch (BeansException ex) {
				// Explicitly remove instance from singleton cache: It might have been put there
				// eagerly by the creation process, to allow for circular reference resolution.
				// Also remove any beans that received a temporary reference to the bean.
				destroySingleton(beanName);
				throw ex;
			}
		}
	});
	bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}

此时缓存的单例对象是什么呢?如下图所示:
在这里插入图片描述
获取再次根据名称查询时,都是从缓存中获取这个对象了。

3. 获取目标对象

根据返回的sharedInstance解析出对应名称的对象
在这里插入图片描述
解析的过程存在如下几个判断:
name包含&但是解析对象不是工厂Bean,直接抛出异常

// Don't let calling code try to dereference the factory if the bean isn't a factory.
if (BeanFactoryUtils.isFactoryDereference(name) && !(beanInstance instanceof FactoryBean)) {
	throw new BeanIsNotAFactoryException(transformedBeanName(name), beanInstance.getClass());
}

当然Bean不是工厂Bean,或者属于工厂Bean,但是名称中包含&,那么直接返回。

// 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)) {
	return beanInstance;
}

如果当前我们使用的名称为&sessionFactory,那么此处就直接返回了SqlSessionFactoryBean类型的对象

如果不是以上的类型,就说明是工厂Bean,但是名称不是以&开头,也就是说想获取工厂Bean的真实对象(getObject)

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);
}
return object;

最重要的逻辑在getObjectFromFactoryBean

protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) {
		// 工厂Bean是单例而且当前名称的Bean已经存在于singletonObjects单例缓存中
		if (factory.isSingleton() && containsSingleton(beanName)) {
			synchronized (getSingletonMutex()) {
				// 在FactoryBean对象缓存中获取 第一次返回null
				Object object = this.factoryBeanObjectCache.get(beanName);
				if (object == null) {
					// factory.getObject() 获取真实对象
					object = doGetObjectFromFactoryBean(factory, beanName);
					// Only post-process and store if not put there already during getObject() call above
					// (e.g. because of circular reference processing triggered by custom getBean calls)
					Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
					if (alreadyThere != null) {
						object = alreadyThere;
					}
					else {
						if (object != null && shouldPostProcess) {
							if (isSingletonCurrentlyInCreation(beanName)) {
								// Temporarily return non-post-processed object, not storing it yet..
								return object;
							}
							beforeSingletonCreation(beanName);
							try {
								// 此处会生成代理对象
								object = postProcessObjectFromFactoryBean(object, beanName);
							}
							catch (Throwable ex) {
								throw new BeanCreationException(beanName,
										"Post-processing of FactoryBean's singleton object failed", ex);
							}
							finally {
								afterSingletonCreation(beanName);
							}
						}
						if (containsSingleton(beanName)) {
							// 添加缓存 
							this.factoryBeanObjectCache.put(beanName, (object != null ? object : NULL_OBJECT));
						}
					}
				}
				return (object != NULL_OBJECT ? object : null);
			}
		}
		else {
			Object object = doGetObjectFromFactoryBean(factory, beanName);
			if (object != null && shouldPostProcess) {
				try {
					object = postProcessObjectFromFactoryBean(object, beanName);
				}
				catch (Throwable ex) {
					throw new BeanCreationException(beanName, "Post-processing of FactoryBean's object failed", ex);
				}
			}
			return object;
		}
	}
final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
private Object doGetObjectFromFactoryBean(final FactoryBean<?> factory, final String beanName)
		throws BeanCreationException {

	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();
		}
	}
	catch (FactoryBeanNotInitializedException ex) {
		throw new BeanCurrentlyInCreationException(beanName, ex.toString());
	}
	catch (Throwable ex) {
		throw new BeanCreationException(beanName, "FactoryBean threw exception on object creation", ex);
	}

	// Do not accept a null value for a FactoryBean that's not fully
	// initialized yet: Many FactoryBeans just return null then.
	if (object == null && isSingletonCurrentlyInCreation(beanName)) {
		throw new BeanCurrentlyInCreationException(
				beanName, "FactoryBean which is currently in creation returned null from getObject");
	}
	return object;
}
  1. 最后进行返回对象类型检查,如果不是指定类型尝试进行类型转换
if (requiredType != null && bean != null && !requiredType.isInstance(bean)) {
	try {
		return getTypeConverter().convertIfNecessary(bean, requiredType);
	}
	catch (TypeMismatchException ex) {
		if (logger.isDebugEnabled()) {
			logger.debug("Failed to convert bean '" + name + "' to required type '" +
					ClassUtils.getQualifiedName(requiredType) + "'", ex);
		}
		throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
	}
}

此时返回对象为:
在这里插入图片描述
所以通过byName(sessionFactory)获取的对象类型为

org.apache.ibatis.session.SqlSessionFactory

在这里插入图片描述
根据以上分析,也不难想到,如果将name改为&sessionFactory将会返回

org.mybatis.spring.SqlSessionFactoryBean

在这里插入图片描述

第二种方式(byType):
  1. 首先根据类型获取beanName,会遍历beanDefinitionNames和manualSingletonNames中的所有beanName,依次获取所有的Bean定义,判断是否为指定的目标类型(考虑性能问题?)
String[] candidateNames = getBeanNamesForType(requiredType);

复杂的遍历过程,找到指定类型的BeanName

private String[] doGetBeanNamesForType(ResolvableType type, boolean includeNonSingletons, boolean allowEagerInit) {
	List<String> result = new ArrayList<String>();

	// Check all bean definitions.
	for (String beanName : this.beanDefinitionNames) {
		// Only consider bean as eligible if the bean name
		// is not defined as alias for some other bean.
		if (!isAlias(beanName)) {
			try {
				RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
				// Only check bean definition if it is complete.
				if (!mbd.isAbstract() && (allowEagerInit ||
						(mbd.hasBeanClass() || !mbd.isLazyInit() || isAllowEagerClassLoading()) &&
								!requiresEagerInitForType(mbd.getFactoryBeanName()))) {
					// In case of FactoryBean, match object created by FactoryBean.
					boolean isFactoryBean = isFactoryBean(beanName, mbd);
					BeanDefinitionHolder dbd = mbd.getDecoratedDefinition();
					boolean matchFound =
							(allowEagerInit || !isFactoryBean ||
									(dbd != null && !mbd.isLazyInit()) || containsSingleton(beanName)) &&
							(includeNonSingletons ||
									(dbd != null ? mbd.isSingleton() : isSingleton(beanName))) &&
							isTypeMatch(beanName, type);
					if (!matchFound && isFactoryBean) {
						// In case of FactoryBean, try to match FactoryBean instance itself next.
						beanName = FACTORY_BEAN_PREFIX + beanName;
						matchFound = (includeNonSingletons || mbd.isSingleton()) && isTypeMatch(beanName, type);
					}
					if (matchFound) {
						result.add(beanName);
					}
				}
			}
			catch (CannotLoadBeanClassException ex) {
				if (allowEagerInit) {
					throw ex;
				}
				// Probably a class name with a placeholder: let's ignore it for type matching purposes.
				if (logger.isDebugEnabled()) {
					logger.debug("Ignoring bean class loading failure for bean '" + beanName + "'", ex);
				}
				onSuppressedException(ex);
			}
			catch (BeanDefinitionStoreException ex) {
				if (allowEagerInit) {
					throw ex;
				}
				// Probably some metadata with a placeholder: let's ignore it for type matching purposes.
				if (logger.isDebugEnabled()) {
					logger.debug("Ignoring unresolvable metadata in bean definition '" + beanName + "'", ex);
				}
				onSuppressedException(ex);
			}
		}
	}

	// Check manually registered singletons too.
	for (String beanName : this.manualSingletonNames) {
		try {
			// In case of FactoryBean, match object created by FactoryBean.
			if (isFactoryBean(beanName)) {
				if ((includeNonSingletons || isSingleton(beanName)) && isTypeMatch(beanName, type)) {
					result.add(beanName);
					// Match found for this bean: do not match FactoryBean itself anymore.
					continue;
				}
				// In case of FactoryBean, try to match FactoryBean itself next.
				beanName = FACTORY_BEAN_PREFIX + beanName;
			}
			// Match raw bean instance (might be raw FactoryBean).
			if (isTypeMatch(beanName, type)) {
				result.add(beanName);
			}
		}
		catch (NoSuchBeanDefinitionException ex) {
			// Shouldn't happen - probably a result of circular reference resolution...
			if (logger.isDebugEnabled()) {
				logger.debug("Failed to check manually registered singleton with name '" + beanName + "'", ex);
			}
		}
	}

	return StringUtils.toStringArray(result);
}

如果是工厂Bean类型,会在查找到的beanName前面添加上&符号,表示这是个工厂Bean对象
在这里插入图片描述

// 由于解析过程需要遍历所有的Bean定义,所以此处进行了缓存
Map<Class<?>, String[]> cache =
			(includeNonSingletons ? this.allBeanNamesByType : this.singletonBeanNamesByType);
	String[] resolvedBeanNames = cache.get(type);
	if (resolvedBeanNames != null) {
		return resolvedBeanNames;
	}
	resolvedBeanNames = doGetBeanNamesForType(ResolvableType.forRawClass(type), includeNonSingletons, true);
	if (ClassUtils.isCacheSafe(type, getBeanClassLoader())) {
		cache.put(type, resolvedBeanNames);
	}

缓存情况:
在这里插入图片描述
2. 根据返回的名称数据解析

if (candidateNames.length == 1) {
	String beanName = candidateNames[0];
	return new NamedBeanHolder<T>(beanName, getBean(beanName, requiredType, args));
}

此处直接根据名称和对象类型获取对象类型了,首先根据名称&sessionFactory获取,最后再根据类型进行检查(上面根据名称获取的过程不需要进行类型检查操作)
在这里插入图片描述
包装成NamedBeanHolder类型对象
最后再返回包装类型中的实例

if (namedBean != null) {
	return namedBean.getBeanInstance();
}

在这里插入图片描述

第三种方式(byNameAndType):

其实在上面byType方式中就间接使用了这种方式

this.applicationContext.getBean("&sessionFactory", SqlSessionFactoryBean.class);

在这里插入图片描述
这种方式此处不进行详述,其实与byType在获取到beanName之后的逻辑是一样的。
但是此处对于新手而言会犯如下的错误:

this.applicationContext.getBean("sessionFactory", SqlSessionFactoryBean.class);

此时就会抛出如下异常:

org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'sessionFactory' is expected to be of type 'org.mybatis.spring.SqlSessionFactoryBean' but was actually of type 'org.apache.ibatis.session.defaults.DefaultSqlSessionFactory'

	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:384)

大致意思就是根据名称sessionFactory获取的Bean类型不是期望的类型SqlSessionFactoryBean,实际类型为DefaultSqlSessionFactory,这和上面的分析没什么出入。只是初学者或者对Spring不了解的人会疑惑。
异常就是在获取Bean返回之前进行类型检查时抛出来的,如下图所示:
在这里插入图片描述
以上详细的说明了根据名称、根据类型和根据名称与类型获取Bean的过程。

二:深入探讨类型获取

接下来分析如下场景:
可以通过按照类型SessionFactory获取容器中的对象吗?

this.applicationContext.getBean(SqlSessionFactory.class);

答案是可以的
在这里插入图片描述
那么这又是如何做到的呢?
还是在遍历所有Bean定义的时候,判断的,主要代码如下(上面也有):

RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
// Only check bean definition if it is complete.
if (!mbd.isAbstract() && (allowEagerInit ||
		(mbd.hasBeanClass() || !mbd.isLazyInit() || isAllowEagerClassLoading()) &&
				!requiresEagerInitForType(mbd.getFactoryBeanName()))) {
	// In case of FactoryBean, match object created by FactoryBean.
	// 判断是否工厂类型的Bean
	boolean isFactoryBean = isFactoryBean(beanName, mbd);
	BeanDefinitionHolder dbd = mbd.getDecoratedDefinition();
	// allowEagerInit为true  mbd.isSingleton()也为true 因此执行isTypeMatch
	boolean matchFound =
			(allowEagerInit || !isFactoryBean ||
					(dbd != null && !mbd.isLazyInit()) || containsSingleton(beanName)) &&
			(includeNonSingletons ||
					(dbd != null ? mbd.isSingleton() : isSingleton(beanName))) &&
			isTypeMatch(beanName, type);		
	if (!matchFound && isFactoryBean) {
		// In case of FactoryBean, try to match FactoryBean instance itself next.
		beanName = FACTORY_BEAN_PREFIX + beanName;
		matchFound = (includeNonSingletons || mbd.isSingleton()) && isTypeMatch(beanName, type);
	}
	if (matchFound) {
		result.add(beanName);
	}

在isTypeMatch中进行详细的匹配
在这里插入图片描述
对应的Bean类型
在这里插入图片描述
与上面目标不一致 但是工厂类型Bean有不同的逻辑
在这里插入图片描述
此时当前类型与目标类型可以匹配上了
因此此处可以找到对应的工厂Bean.
在这里插入图片描述

三:java配置中的问题

如果不通过xml进行sqlSessionFactory的配置,而是通过java的方式,如下:

@ComponentScan("com.xquant.platform.component.simpleapp")
@org.springframework.context.annotation.Configuration
public static class ContextConfiguration {

	@Bean
	public DataSource dataSource() {
		DruidDataSource dataSource = new DruidDataSource();
		dataSource.setDriverClassName("com.mysql.jdbc.Driver");
		dataSource.setUrl("jdbc:mysql://localhost:3306/sakila?allowMultiQueries=true&useUnicode=true&characterEncoding=utf8&autoReconnect=true");
		dataSource.setUsername("root");
		dataSource.setPassword("root");
		return dataSource;
	}

	@Bean
	public PlatformTransactionManager transactionalManagerOne() {
		return new DataSourceTransactionManager(dataSource());
	}

	private static final ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();

	@Bean
	public SqlSessionFactory sqlSessionFactory() throws Exception {
		SqlSessionFactoryBean ss = new SqlSessionFactoryBean();
		ss.setDataSource(dataSource());
//			ss.setMapperLocations(new Resource[] { new ClassPathResource("sqlMapper/RoleMapper.xml") });
		ss.setMapperLocations(resourceResolver.getResources("classpath:sqlMapper/**/*.xml"));
		return (SqlSessionFactory) ss.getObject();
	}

	@Bean
	public MapperScannerConfigurer mapperScannerConfigurer() {
		MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
		mapperScannerConfigurer.setBasePackage("com.xquant.platform.component.simpleapp.dao");
		return mapperScannerConfigurer;
	}
}

那么此时执行如下代码:

@Test
public void test1DeleteRole() {
    Object sessionFactory = this.applicationContext.getBean(SqlSessionFactory.class);
    Object sqlSessionFactoryBean = this.applicationContext.getBean(SqlSessionFactoryBean.class);
}

就会抛出异常如下:

org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.mybatis.spring.SqlSessionFactoryBean' available

	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:352)

根据异常栈查看源码如下:

@Override
public <T> T getBean(Class<T> requiredType, Object... args) throws BeansException {
	NamedBeanHolder<T> namedBean = resolveNamedBean(requiredType, args);
	if (namedBean != null) {
		return namedBean.getBeanInstance();
	}
	BeanFactory parent = getParentBeanFactory();
	if (parent != null) {
		return parent.getBean(requiredType, args);
	}
	throw new NoSuchBeanDefinitionException(requiredType);
}

主要的原因就在于根据类型去bean容器中无法查找到符合要求的bean定义。
在这里插入图片描述
查看此时的beanDefinitionMap:
在这里插入图片描述
然后查看此时的bean定义的beanClass是啥
在这里插入图片描述
是不是很意外??类型为null ?

sqlSessionFactory -> 
{ConfigurationClassBeanDefinitionReader$ConfigurationClassBeanDefinition@2567} 
"Root bean: class [null]; scope=; abstract=false; lazyInit=false; 
autowireMode=3; dependencyCheck=0; autowireCandidate=true; 
primary=false; 
factoryBeanName=IRoleServiceConfigTest.ContextConfiguration; 
factoryMethodName=sqlSessionFactory; initMethodName=null; destroyMethodName=(inferred); 
defined in com.xquant.platform.component.simpleapp.service.IRoleServiceConfigTest$ContextConfiguration"

在这个Bean定义里面完全找不到任何关于SqlSessionFactory这个类型的一些信息,除了factoryMethodName,那按照SqlSessionFactory是如何查找的呢?
在这里插入图片描述
从上面不难看出,在遍历每一个beanName的时候,会去单例缓存中查找到这个Bean,此时得到的是已经初始化好了的Bean,所以类型是知道的,然后再判断一下是否为所需要的类型。
在这里插入图片描述
匹配完成,得到符合要求的BeanName,然后再根据beanName查到,最后再检查类型,返回结果。一切都通了,但是,对的,但是呢?那么如果没有初始化(单例缓存中不存在)呢?
修改Bean定义

@Lazy   // 懒加载模式
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
    SqlSessionFactoryBean ss = new SqlSessionFactoryBean();
    ss.setDataSource(dataSource());
//			ss.setMapperLocations(new Resource[] { new ClassPathResource("sqlMapper/RoleMapper.xml") });
    ss.setMapperLocations(resourceResolver.getResources("classpath:sqlMapper/**/*.xml"));
    return (SqlSessionFactory) ss.getObject();
}

通过这种方式然后再去debug发现不行,主要原因是其他非懒加载的bean依赖于这个Bean.

那么再采用另一种方式,按照如下方式打一个断点:
条件为:

type != null && "SqlSessionFactory".equals(type.getSimpleName()) 

在这里插入图片描述
只要是根据类型查找这个Bean的话都会走这里的。然后以debug模式启动容器。
果然在启动的时候,进入了这个断点
在这里插入图片描述
查看左下角的调用栈信息:
在这里插入图片描述
可以看到此时在实例化MyBatis的接口类的时候会去初始化SqlSessionFactory,为啥呢?
查看roleMapper的Bean定义:

Root bean: class [org.mybatis.spring.mapper.MapperFactoryBean]; 
scope=singleton; abstract=false; lazyInit=false; autowireMode=2; 
dependencyCheck=0; autowireCandidate=true; primary=false; 
factoryBeanName=null; factoryMethodName=null; initMethodName=null; 
destroyMethodName=null; 
defined in file [D:\20190919\appcontainer\simple-app\target\classes\com\xquant\platform\component\simpleapp\dao\RoleMapper.class]

类型为:MapperFactoryBean
查看这个类的UML图
在这里插入图片描述
从以上图中不难看出原因了吧?此处不继续分析了。总之在容器初始化的时候我们的SqlSessionFactory进行了初始化,其他Bean查找依赖的Bean也是通过类型进行查找的。好了,那么下面就分析如果单例缓存中不存在指令类型的Bean是如何进行的。
在这里插入图片描述
在获取Bean定义的时候 看到了两个属性 好像有点用
在这里插入图片描述
此时单例缓存不存在,无法根据实例化好的对象判断类型
在这里插入图片描述
解析typesToMatch
在这里插入图片描述
解析beanType
在这里插入图片描述
此时再引入解析Bean类型的复杂逻辑,通常我们印象中通过beanClass来判断bean类型的,或者如上面提到的根据实例化好的对象判断bean类型,其实判断bean类型是相当复杂的,但是为啥不在一开始就去细看这块逻辑呢?还是BB一下,学习源代码学习的是思想,当你有疑问的时候去看,而不是为了看而看,通过解决疑问学习是最好的方式。好啦,我们此时来看看:

protected Class<?> predictBeanType(String beanName, RootBeanDefinition mbd, Class<?>... typesToMatch) {
	// 首选看targetType
	Class<?> targetType = mbd.getTargetType();
	if (targetType != null) {
		return targetType;
	}
	if (mbd.getFactoryMethodName() != null) {
		return null;
	}
	return resolveBeanClass(mbd, beanName, typesToMatch);
}

首先看bean定义中的targetType,如果为空,则继续解析

protected Class<?> resolveBeanClass(final RootBeanDefinition mbd, String beanName, final Class<?>... typesToMatch)
		throws CannotLoadBeanClassException {

	try {
		// 看beanClass属性
		if (mbd.hasBeanClass()) {
			return mbd.getBeanClass();
		}
		if (System.getSecurityManager() != null) {
			return AccessController.doPrivileged(new PrivilegedExceptionAction<Class<?>>() {
				@Override
				public Class<?> run() throws Exception {
					return doResolveBeanClass(mbd, typesToMatch);
				}
			}, getAccessControlContext());
		}
		else {
			return doResolveBeanClass(mbd, typesToMatch);
		}
	}
	catch (PrivilegedActionException pae) {
		ClassNotFoundException ex = (ClassNotFoundException) pae.getException();
		throw new CannotLoadBeanClassException(mbd.getResourceDescription(), beanName, mbd.getBeanClassName(), ex);
	}
	catch (ClassNotFoundException ex) {
		throw new CannotLoadBeanClassException(mbd.getResourceDescription(), beanName, mbd.getBeanClassName(), ex);
	}
	catch (LinkageError err) {
		throw new CannotLoadBeanClassException(mbd.getResourceDescription(), beanName, mbd.getBeanClassName(), err);
	}
}

其次看beanClass属性,没有再继续解析

private Class<?> doResolveBeanClass(RootBeanDefinition mbd, Class<?>... typesToMatch)
		throws ClassNotFoundException {

	ClassLoader beanClassLoader = getBeanClassLoader();
	ClassLoader classLoaderToUse = beanClassLoader;
	if (!ObjectUtils.isEmpty(typesToMatch)) {
		// When just doing type checks (i.e. not creating an actual instance yet),
		// use the specified temporary class loader (e.g. in a weaving scenario).
		ClassLoader tempClassLoader = getTempClassLoader();
		if (tempClassLoader != null) {
			classLoaderToUse = tempClassLoader;
			if (tempClassLoader instanceof DecoratingClassLoader) {
				DecoratingClassLoader dcl = (DecoratingClassLoader) tempClassLoader;
				for (Class<?> typeToMatch : typesToMatch) {
					dcl.excludeClass(typeToMatch.getName());
				}
			}
		}
	}
	// 根据beanClassName判断类型
	String className = mbd.getBeanClassName();
	if (className != null) {
		Object evaluated = evaluateBeanDefinitionString(className, mbd);
		if (!className.equals(evaluated)) {
			// A dynamically resolved expression, supported as of 4.2...
			if (evaluated instanceof Class) {
				return (Class<?>) evaluated;
			}
			else if (evaluated instanceof String) {
				return ClassUtils.forName((String) evaluated, classLoaderToUse);
			}
			else {
				throw new IllegalStateException("Invalid class name expression result: " + evaluated);
			}
		}
		// When resolving against a temporary class loader, exit early in order
		// to avoid storing the resolved Class in the bean definition.
		if (classLoaderToUse != beanClassLoader) {
			return ClassUtils.forName(className, classLoaderToUse);
		}
	}
	return mbd.resolveBeanClass(beanClassLoader);
}

第三种,根据beanClassName属性判断Bean的类型.
其实此时是第一种:看一下如下方法:

// org.springframework.beans.factory.support.RootBeanDefinition
public Class<?> getTargetType() {
	if (this.resolvedTargetType != null) {
		return this.resolvedTargetType;
	}
	return (this.targetType != null ? this.targetType.resolve() : null);
}

还记不记得上面看到过的这个resolvedTargetType,对的,此时返回这个类型了。
在这里插入图片描述
在返回这个类型之前,还可以通过后置处理进行处理一下(知道就好,不要深究,一般也不到随意去扩展这个点,毕竟不是哪个点扩展都是好的,会导致逻辑混乱):
在这里插入图片描述
现在已经得到typesToMatch和beanType了,如下图所示
在这里插入图片描述
可以自己想一想该如何去匹配呢?首先还是考虑bean类型是不是工厂Bean,如果是,需要看看这个工厂Bean创造的那个对象是不是我们需要的,这就是前面xml配置中通过SqlSessionFactory类型可以查找到Bean的原因。

// Check bean class whether we're dealing with a FactoryBean.
if (FactoryBean.class.isAssignableFrom(beanType)) {
	if (!BeanFactoryUtils.isFactoryDereference(name)) {
		// If it's a FactoryBean, we want to look at what it creates, not the factory class.
		beanType = getTypeForFactoryBean(beanName, mbd);
		if (beanType == null) {
			return false;
		}
	}
}

当然,现在走的不是这个逻辑,因为beanType不是FactoryBean类型的。
在这里插入图片描述
在这里插入图片描述
匹配的流程就此结束了,找到了beanName接下来就简单了,不再详述。
从以上的流程可以看到查找一个bean的类型,就是通过bean定义里面的一些关键参数来进行的,包括

// org.springframework.beans.factory.support.AbstractBeanDefinition
private volatile Object beanClass;
/**
 * Specify the class for this bean.
 */
public void setBeanClass(Class<?> beanClass) {
	this.beanClass = beanClass;
}
/**
 * Specify the bean class name of this bean definition.
 */
@Override
public void setBeanClassName(String beanClassName) {
	this.beanClass = beanClassName;
}
// org.springframework.beans.factory.support.RootBeanDefinition
volatile ResolvableType targetType;

/** Package-visible field for caching the determined Class of a given bean definition */
volatile Class<?> resolvedTargetType;

/** Package-visible field for caching the return type of a generically typed factory method */
volatile ResolvableType factoryMethodReturnType;

详细逻辑参考源码

org.springframework.beans.factory.support.AbstractBeanFactory#predictBeanType

另外如果细心的读者也可以开始思考为RootBeanDefinition与GenericBeanDefinition的区别了。

四:总结

  1. Spring中可以通过名称、类型来获取Bean,根据类型比根据名称更消耗性能,因为要遍历所有的bean定义进行类型匹配,所以Spring在这里使用了缓存,这很重要。
  2. 由于xml和java配置方式的差异,在BeanDefinition中的属性就有些不一样了,也就导致了在xml配置方式下可以找到SqlSessionFactoryBean,而在java配置模式下无法通过SqlSessionFactoryBean进行类型查找了。
  3. 无论是xml模式,还是java模式,都可以通过类型查到到SqlSessionFactory,因为针对于类型为工厂Bean的可以匹配自己和真实getObject的那个对象。
  4. 如果是你来设计,会不会做到这种兼容呢?
    最后再上两张图,xml配置模式beanDefinitionMap:
    在这里插入图片描述
    java配置模式beanDefinitionMap:
    在这里插入图片描述
Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐