Cannot find class: ${jdbc.driver}——配置了sqlSessionFactoryBeanName也报错之问题分析

MyBatis中一个sqlSessionFactory代表一个数据源,那么配置多个数据源只需要注入多个sqlSessionFactory即可。

首先需要说明的是,用mybatis-spring-1.1.0貌似无法配置多个数据源(这里说的不对,应该是无法在配置数据源中使用${..}占位符),这里大概折腾了我一整天的时间。后来才想到可能是版本问题,于是换了最新的1.2.2版,问题就迎刃而解了。

下面记录一下分析这个问题的过程:

首先我在Spring的配置文件中配置了org.springframework.beans.factory.config.PropertyPlaceholderConfigurer,这个Bean中的作为一个BeanFactoryPostProcessor会在载入所有BeanDefinition后运行,然后利用指定的properties文件来替换BeanDefinition中定义的${...}占位符,Spring的配置文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
		http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

	<bean id="pmsDatasource" class="org.apache.ibatis.datasource.pooled.PooledDataSource">
		<property name="driver" value="${pms.driver}" />
		<property name="url" value="${pms.url}" />
		<property name="username" value="${pms.username}" />
		<property name="password" value="${pms.password}" />
	</bean>
	<bean id="pmsSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="dataSource" ref="pmsDatasource" />
	</bean>
	<bean id="pmsMapperConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
		<property name="basePackage" value="com.westsoft.pms.dao" />
		<property name="sqlSessionFactoryBeanName" value="pmsSqlSessionFactory"></property>
	</bean>

	<bean id="kftDatasource" class="org.apache.ibatis.datasource.pooled.PooledDataSource">
		<property name="driver" value="${kft.driver}" />
		<property name="url" value="${kft.url}" />
		<property name="username" value="${kft.username}" />
		<property name="password" value="${kft.password}" />
	</bean>
	<bean id="kftSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="dataSource" ref="kftDatasource" />
	</bean>
	<bean id="kftMapperConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
		<property name="basePackage" value="com.westsoft.kft.dao" />
		<property name="sqlSessionFactoryBeanName" value="kftSqlSessionFactory"></property>
	</bean>

	<bean id="propertyPlaceholderConfigurer"
		class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
		<property name="locations">
			<list>
				<value>classpath:/META-INF/config/kft-ds.properties</value>
				<value>classpath:/META-INF/config/pms-ds.properties</value>
			</list>
		</property>
	</bean>
</beans>

但是启动容器后,却报错:Cannot find class: ${kft.driver}

因为记得几个月前也弄过这个,但是没成功,后来我就改成不读properties文件,而是直接写在xml中了,算是一种逃避吧

不过这次,我不打算放过它。问了度娘,上面铺天盖地的都是说不要注入MapperScannerConfigurer的sqlSessionFactory属性,而应该使用sqlSessionFactoryBeanName。可问题是我这里已经使用sqlSessionFactoryBeanName,显然不是这个问题。可是度娘上只有这么一个答案,真是略坑啊~难道没人用1.0.0的版本?好吧,那么我做第一人,若以后有人碰到诸如此类的问题,就不用像我搞这么久了~

虽然度娘上没给出解决答案,但是分析的还是有道理的:说是因为MapperScannerConfigurer初始化的时候同时也初始化了SqlSessionFactoryBean,并且由于MapperScannerConfigurer要先于PropertyPlaceholderConfigurer初始化,那么此时对于datasource中配置的占位符无法被替换,所以也就导致出现了上面的错误。

根据上面的思路,跟踪MapperScannerConfigurer的源码看看:

  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
    processPropertyPlaceHolders();

    Scanner scanner = new Scanner(beanDefinitionRegistry);
    scanner.setResourceLoader(this.applicationContext);

    scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }

发现MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,这类Bean会在BeanDefinition被全部载入后,BeanPostProcessor前初始化。并且在初始化时,会执行postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry)方法。

其中后面几行我们看的明白,是自动扫描Mapper的。而第一行执行了processPropertyPlaceHolders()方法,看了代码注释,这个方法就是设计出来防止MapperScannerConfigurer先于PropertyPlaceholderConfigurer初始化而无法转换${..}的。但是为什么没起作用呢?我们来看看源码:

  private void processPropertyPlaceHolders() {
    Map<String, PropertyResourceConfigurer> prcs = applicationContext.getBeansOfType(PropertyResourceConfigurer.class);

    if (!prcs.isEmpty() && applicationContext instanceof GenericApplicationContext) {
      BeanDefinition mapperScannerBean = ((GenericApplicationContext) 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.postProcessBeanFactory(factory);
      }

      PropertyValues values = mapperScannerBean.getPropertyValues();

      this.basePackage = updatePropertyValue("basePackage", values);
      this.sqlSessionFactoryBeanName = updatePropertyValue("sqlSessionFactoryBeanName", values);
      this.sqlSessionTemplateBeanName = updatePropertyValue("sqlSessionTemplateBeanName", values);
    }
  }

首先这个方法不管从名字或者意义上来看,我们都不难才想到他是用来得到所有定义的PropetyResourceConfigurer类。

其次,注意到上面的条件了吗?这里需要applicationContext是GenericApplicationContext才起作用,而我们这里的applicationContext的实现类是XmlWebApplicationContext,所以这一步完全不起作用(不对,应该说还起了副作用,因为上面的报错就是和这个方法有关)可以找到这个方法最终其实是交给DefaultListableBeanFactory的getBeansOfType(...)来实现的:

	public <T> Map<String, T> getBeansOfType(Class<T> type, boolean includeNonSingletons, boolean allowEagerInit)
			throws BeansException {

		String[] beanNames = getBeanNamesForType(type, includeNonSingletons, allowEagerInit);
		Map<String, T> result = new LinkedHashMap<String, T>(beanNames.length);
		for (String beanName : beanNames) {
			try {
				result.put(beanName, getBean(beanName, type));
			}
			catch (BeanCreationException ex) {
				Throwable rootCause = ex.getMostSpecificCause();
				if (rootCause instanceof BeanCurrentlyInCreationException) {
					BeanCreationException bce = (BeanCreationException) rootCause;
					if (isCurrentlyInCreation(bce.getBeanName())) {
						if (this.logger.isDebugEnabled()) {
							this.logger.debug("Ignoring match to currently created bean '" + beanName + "': " +
									ex.getMessage());
						}
						onSuppressedException(ex);
						// Ignore: indicates a circular reference when autowiring constructors.
						// We want to find matches other than the currently created bean itself.
						continue;
					}
				}
				throw ex;
			}
		}
		return result;
	}

这里先去寻找相应type的beanName,继续跟踪getBeanNameForType(...):

	public String[] getBeanNamesForType(Class<?> type, boolean includeNonSingletons, boolean allowEagerInit) {
		if (type == null || !allowEagerInit) {
			return this.doGetBeanNamesForType(type, includeNonSingletons, allowEagerInit);
		}
		Map<Class<?>, String[]> cache = includeNonSingletons ?
				this.nonSingletonBeanNamesByType : this.singletonBeanNamesByType;
		String[] resolvedBeanNames = cache.get(type);
		if (resolvedBeanNames != null) {
			return resolvedBeanNames;
		}
		resolvedBeanNames = this.doGetBeanNamesForType(type, includeNonSingletons, allowEagerInit);
		cache.put(type, resolvedBeanNames);
		return resolvedBeanNames;
	}

后两个boolean参数在这里都是true,且在cache中没有该bean,继续跟踪doGetBeanNamesForType(...)

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

		// Check all bean definitions.
		String[] beanDefinitionNames = getBeanDefinitionNames();
		for (String beanName : 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() || this.allowEagerClassLoading)) &&
									!requiresEagerInitForType(mbd.getFactoryBeanName()))) {
						// In case of FactoryBean, match object created by FactoryBean.
						boolean isFactoryBean = isFactoryBean(beanName, mbd);
						boolean matchFound = (allowEagerInit || !isFactoryBean || containsSingleton(beanName)) &&
								(includeNonSingletons || 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 contains a placeholder: let's ignore it for type matching purposes.
					if (this.logger.isDebugEnabled()) {
						this.logger.debug("Ignoring bean class loading failure for bean '" + beanName + "'", ex);
					}
					onSuppressedException(ex);
				}
				catch (BeanDefinitionStoreException ex) {
					if (allowEagerInit) {
						throw ex;
					}
					// Probably contains a placeholder: let's ignore it for type matching purposes.
					if (this.logger.isDebugEnabled()) {
						this.logger.debug("Ignoring unresolvable metadata in bean definition '" + beanName + "'", ex);
					}
					onSuppressedException(ex);
				}
			}
		}

		// Check singletons too, to catch manually registered singletons.
		String[] singletonNames = getSingletonNames();
		for (String beanName : singletonNames) {
			// Only check if manually registered.
			if (!containsBeanDefinition(beanName)) {
				// 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);
				}
			}
		}

		return StringUtils.toStringArray(result);
	}

可以看到要得到 指定类型的类,其实最后是通过遍历所有的beanDefinition来的。其中判断type是否一致是用isTypeMatch(...)

	public boolean isTypeMatch(String name, Class<?> targetType) throws NoSuchBeanDefinitionException {
		String beanName = transformedBeanName(name);
		Class<?> typeToMatch = (targetType != null ? targetType : Object.class);

		// Check manually registered singletons.
		Object beanInstance = getSingleton(beanName, false);
		if (beanInstance != null) {
<pre name="code" class="java">			<span style="font-family: Arial, Helvetica, sans-serif;">//省略部分代码.....</span>

else {

			<span style="font-family: Arial, Helvetica, sans-serif;">//省略部分代码.....</span>

// Check bean class whether we‘re dealing with a FactoryBean.if (FactoryBean.class.isAssignableFrom(beanClass)) {if (!BeanFactoryUtils.isFactoryDereference(name)) {// If it‘s a FactoryBean, we want to look at what it creates,
not the factory class.Class<?> type = getTypeForFactoryBean(beanName, mbd);return (type != null && typeToMatch.isAssignableFrom(type));}else {return typeToMatch.isAssignableFrom(beanClass);}}else {return !BeanFactoryUtils.isFactoryDereference(name) &&typeToMatch.isAssignableFrom(beanClass);}}}


在我模拟的时候发现在mapper类进来的时候会造成sqlSessionFactory的初始化,这是为什么呢?

由于mapper被MyBatis代理了,mapper定义的时候是用下面这样的形式:

<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
  <property name="mapperInterface" value="org.mybatis.spring.sample.mapper.UserMapper" />
  <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

所以当要获取userMapper的类型时,由于判断其是FactoryBean,且不是Dereference(不是以‘&‘开头去取FactoryBean的类型),就去执行getTypeForFactoryBean(beanName, mbd)这个方法:

	protected Class<?> getTypeForFactoryBean(String beanName, RootBeanDefinition mbd) {
		if (!mbd.isSingleton()) {
			return null;
		}
		try {
			FactoryBean<?> factoryBean = doGetBean(FACTORY_BEAN_PREFIX + beanName, FactoryBean.class, null, true);
			return getTypeForFactoryBean(factoryBean);
		}
		catch (BeanCreationException ex) {
			// Can only happen when getting a FactoryBean.
			if (logger.isDebugEnabled()) {
				logger.debug("Ignoring bean creation exception on FactoryBean type check: " + ex);
			}
			onSuppressedException(ex);
			return null;
		}
	}

继续跟进doGetBean(...)方法查看:

	protected <T> T doGetBean(
			final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
			throws BeansException {

<pre name="code" class="java">			<span style="font-family: Arial, Helvetica, sans-serif;">//省略部分代码.....</span>

}else { //省略部分代码.....// Create bean instance.if (mbd.isSingleton()) {// 总算找到了,这里是会去创建sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {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);}//省略部分代码.....}

			<span style="font-family: Arial, Helvetica, sans-serif;">//省略部分代码.....</span>

return
(T) bean;}


所以关键原因是因为要得到FactoryBean的类型(其实是得到创建的Object的类型),那么先要去初始化它再通过getObjectType方法来得到其类型。于是就造成了sqlSessionFactory提前初始化了!另外,分析过程应该是倒推的,而不是正推~

而看了下mybatis-spring-1.2.2的源码,发现MapperScannerConfigurer初始化中的processPropertyPlaceHolders()方法加上了一个条件。也就是说,默认是不执行的:

 public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
    if (this.processPropertyPlaceHolders) {
      processPropertyPlaceHolders();
    }

    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();
    scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }

并且可以看到,它提供了更多配置项用于自动发现Mapper。

时间: 2025-01-16 08:23:09

Cannot find class: ${jdbc.driver}——配置了sqlSessionFactoryBeanName也报错之问题分析的相关文章

ThinkPHP pdo连接Oracle的配置写法,提示报错

'DB_TYPE' => 'pdo', // 数据库类型 'DB_USER' => 'user101', // 用户名 'DB_PWD' => '[email protected]#$%', // 密码 'DB_PREFIX' => 'TB_', // 数据库表前缀 'DB_DSN' => 'oci:dbname=127.0.0.1:1158/orcl;charset=utf8', ThinkPHP 3.2.2 连接Oracle 的配置写法,结果提示内容: 由于目前PDO暂时

关于MySql升级JDBC架包导致时区问题报错(The server time zone value &#39;?й???????&#39; is unrecognized or represents more than one time zone)

报错信息: The server time zone value '?й???????' is unrecognized or represents more than one time zone. You must configure either the server or JDBC driver (via the serverTimezone configuration property) to use a more specifc time zone value if you want

AppiumDriver driver = new AppiumDriver 的AppiumDriver报错问题解决

AppiumDriver  driver = new AppiumDriver 的AppiumDriver报错,是因为使用的appium-client版本太高导致的,使用1.6.1的版本是没有问题.亲测通过 资源下载:http://download.csdn.net/detail/yang_guang_1989/9478442

MyEclipse10+Flash Builder4+BlazeDS+Tomcat7配置J2EE Web项目报错(一)

1.错误描述 usage: java org.apache.catalina.startup.Catalina [ -config {pathname} ] [ -nonaming ] { -help | start | stop } 2014-6-23 20:15:55 org.apache.catalina.core.AprLifecycleListener init 信息: Loaded APR based Apache Tomcat Native library 1.1.29 using

LAMP之PHP安装 配置jpeglib 依赖库报错解决方法

问题显现 配置 php参数时> --with-jpeg-dir \ configure: WARNING: you should use --build, --host, --targetconfigure: WARNING: invalid host type: checking for grep that handles long lines and -e... /bin/grepchecking for egrep... /bin/grep -Echecking for a sed tha

mybatis配置文件,注意标签配置顺序。否则报错The content of element type &quot;configuration&quot; must match &quot;(properties?,settings?,...怎么解决

感谢原作者http://www.cnblogs.com/zhoumingming/p/5417014.html 注意每个标签必须按照顺序写,不然就会提示错误 顺序是 <?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd

struts2 ,web.xml中配置为/*.action,运行报错Invalid &lt;url-pattern&gt; /*.action in filter mapp

首先,修改成: <filter-mapping>  <filter-name>struts2</filter-name>  <url-pattern>/*</url-pattern></filter-mapping> 是可以的. 引起此错误的原因如下: 这个对filter的基础知识的理解:容器只认 全名匹配,路径匹配,扩展名匹配./*.action  又是路径匹配,有时扩展名匹配. 容器没办法区分 解决方法:写*.action <

spring 管理事务配置时,结果 报错: No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one here这个异常

java.lang.IllegalStateException: No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one here这个异常 这个错误,网上说的原因一大推,反正我这个出的问题 是因为 虽然我在 spring 里把事务都配置好了,结果运行就出现这个错误,看看配置都没什么问题,都是网上 案例 照 写编码的,还是错的,结果发现是因为 我

为什么基于Windows Server 2008 R2的网络负载均衡(NLB)配置的时候总会报错&ldquo;主机不可访问&rdquo;?

配置基于Windows的网络负载均衡是很容易的,操作也很简单,点点鼠标基本上就能完成,但是在进行节点(真实服务器)操作的过程中有时候会遇到一些主机不可访问的报错信息.这个又是为什么呢? Figure 1在其中一台节点上配置好了NLB后刷新一次就是报错一次,让人用的很不踏实呢 出错时候会在描述里面呈现[主机不可访问.连接到"XXX主机名"时出错]. 在Windows里面和主机名有关的一些网络设置有DNS解析.WINS解析,本地HOSTS文件解析这几种. 由于所在环境没有内网的DNS以及W