Spring容器中获取SqlSessionFactoryBean类型Bean失败
引言:今天在公司里测试遇到一个bug先上代码:private static SqlSessionFactory getSqlSessionFactory() throws Exception {if (sqlSessionFactory == null) {SqlSessionFactoryBean sqlSessionFactoryBean = SpringBeanUtils.g...
引言:今天在公司里测试遇到一个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模式启动容器
流程如下:
- 解析名称(工厂Bean与别名)
- 获取单例(此时缓存不存在)
- 判断是否protype类型Bean缓存是否存在(无关)
- 到父容器中获取(没有父容器)
- 根据名称获取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;
}
- 最后进行返回对象类型检查,如果不是指定类型尝试进行类型转换
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):
- 首先根据类型获取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的区别了。
四:总结
- Spring中可以通过名称、类型来获取Bean,根据类型比根据名称更消耗性能,因为要遍历所有的bean定义进行类型匹配,所以Spring在这里使用了缓存,这很重要。
- 由于xml和java配置方式的差异,在BeanDefinition中的属性就有些不一样了,也就导致了在xml配置方式下可以找到SqlSessionFactoryBean,而在java配置模式下无法通过SqlSessionFactoryBean进行类型查找了。
- 无论是xml模式,还是java模式,都可以通过类型查到到SqlSessionFactory,因为针对于类型为工厂Bean的可以匹配自己和真实getObject的那个对象。
- 如果是你来设计,会不会做到这种兼容呢?
最后再上两张图,xml配置模式beanDefinitionMap:
java配置模式beanDefinitionMap:
更多推荐
所有评论(0)