Cannot find class: ${jdbc.driver}—配置了sqlSessionFactoryBeanName也报错之问题分析_Tomcat, WebLogic及J2EE讨论区_Weblogic技术|Tuxedo技术|中间件技术|Oracle论坛|JAVA论坛|Linux/Unix技术|hadoop论坛_联动北方技术论坛  
网站首页 | 关于我们 | 服务中心 | 经验交流 | 公司荣誉 | 成功案例 | 合作伙伴 | 联系我们 |
联动北方-国内领先的云技术服务提供商
»  游客             当前位置:  论坛首页 »  自由讨论区 »  Tomcat, WebLogic及J2EE讨论区 »
总帖数
2
每页帖数
101/1页1
返回列表
0
发起投票  发起投票 发新帖子
查看: 2453 | 回复: 1   主题: Cannot find class: ${jdbc.driver}—配置了sqlSessionFactoryBeanName也报错之问题分析        下一篇 
曹先丰
注册用户
等级:中士
经验:208
发帖:72
精华:0
注册:2012-1-9
状态:离线
发送短消息息给曹先丰 加好友    发送短消息息给曹先丰 发消息
发表于: IP:您无权察看 2015-4-16 9:16:51 | [全部帖] [楼主帖] 楼主

MyBatis中一个sqlSessionFactory代表一个数据源,那么配置多个数据源只需要注入多个sqlSessionFactory即可。
首先需要说明的是,用mybatis-spring-1.1.0貌似无法配置多个数据源(这里说的不对,应该是无法在配置数据源中使用${..}占位符),这里大概折腾了我一整天的时间。后来才想到可能是版本问题,于是换了最新的1.2.2版,问题就迎刃而解了。
下面记录一下分析这个问题的过程:
首先我在Spring的配置文件中配置了org.springframework.beans.factory.config.PropertyPlaceholderConfigurer,这个Bean中的作为一个BeanFactoryPostProcessor会在载入所有BeanDefinition后运行,然后利用指定的properties文件来替换BeanDefinition中定义的${...}占位符,Spring的配置文件如下:

  1. <?xml version="1.0" encoding="UTF-8"?> 
  2. <beans xmlns="http://www.springframework.org/schema/beans" 
  3.  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" 
  4.  xsi:schemaLocation="http://www.springframework.org/schema/beans 
  5.  http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> 
  6.  <bean id="pmsDatasource" class="org.apache.ibatis.datasource.pooled.PooledDataSource"> 
  7.  <property name="driver" value="${pms.driver}" /> 
  8.  <property name="url" value="${pms.url}" /> 
  9.  <property name="username" value="${pms.username}" /> 
  10.  <property name="password" value="${pms.password}" /> 
  11.  </bean> 
  12.  <bean id="pmsSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> 
  13.  <property name="dataSource" ref="pmsDatasource" /> 
  14.  </bean> 
  15.  <bean id="pmsMapperConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer"> 
  16.  <property name="basePackage" value="com.westsoft.pms.dao" /> 
  17.  <property name="sqlSessionFactoryBeanName" value="pmsSqlSessionFactory"></property> 
  18.  </bean> 
  19.  <bean id="kftDatasource" class="org.apache.ibatis.datasource.pooled.PooledDataSource"> 
  20.  <property name="driver" value="${kft.driver}" /> 
  21.  <property name="url" value="${kft.url}" /> 
  22.  <property name="username" value="${kft.username}" /> 
  23.  <property name="password" value="${kft.password}" /> 
  24.  </bean> 
  25.  <bean id="kftSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> 
  26.  <property name="dataSource" ref="kftDatasource" /> 
  27.  </bean> 
  28.  <bean id="kftMapperConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer"> 
  29.  <property name="basePackage" value="com.westsoft.kft.dao" /> 
  30.  <property name="sqlSessionFactoryBeanName" value="kftSqlSessionFactory"></property> 
  31.  </bean> 
  32.  <bean id="propertyPlaceholderConfigurer" 
  33.  class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> 
  34.  <property name="locations"> 
  35.  <list> 
  36.  <value>classpath:/META-INF/config/kft-ds.properties</value> 
  37.  <value>classpath:/META-INF/config/pms-ds.properties</value> 
  38.  </list> 
  39.  </property> 
  40.  </bean> 
  41. </beans> 


但是启动容器后,却报错:Cannot find class: ${kft.driver}
因为记得几个月前也弄过这个,但是没成功,后来我就改成不读properties文件,而是直接写在xml中了.问了度娘,上面铺天盖地的都是说不要注入MapperScannerConfigurer的sqlSessionFactory属性,而应该使用sqlSessionFactoryBeanName。可问题是我这里已经使用sqlSessionFactoryBeanName,显然不是这个问题。可是度娘上只有这么一个答案,真是略坑啊~难道没人用1.0.0的版本?好吧,那么我做第一人,若以后有人碰到诸如此类的问题,就不用像我搞这么久了~
虽然度娘上没给出解决答案,但是分析的还是有道理的:说是因为MapperScannerConfigurer初始化的时候同时也初始化了SqlSessionFactoryBean,并且由于MapperScannerConfigurer要先于PropertyPlaceholderConfigurer初始化,那么此时对于datasource中配置的占位符无法被替换,所以也就导致出现了上面的错误。
根据上面的思路,跟踪MapperScannerConfigurer的源码看看:

  1. public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException { 
  2.        processPropertyPlaceHolders(); 
  3.       
  4.        Scanner scanner = new Scanner(beanDefinitionRegistry); 
  5.        scanner.setResourceLoader(this.applicationContext); 
  6.       
  7.        scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)); 
  8. }


发现MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,这类Bean会在BeanDefinition被全部载入后,BeanPostProcessor前初始化。并且在初始化时,会执行postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry)方法。
其中后面几行我们看的明白,是自动扫描Mapper的。而第一行执行了processPropertyPlaceHolders()方法,看了代码注释,这个方法就是设计出来防止MapperScannerConfigurer先于PropertyPlaceholderConfigurer初始化而无法转换${..}的。但是为什么没起作用呢?我们来看看源码:

  1. private void processPropertyPlaceHolders() { 
  2.        Map<String, PropertyResourceConfigurer> prcs = applicationContext.getBeansOfType(PropertyResourceConfigurer.class); 
  3.       
  4.        if (!prcs.isEmpty() && applicationContext instanceof GenericApplicationContext) { 
  5.              BeanDefinition mapperScannerBean = ((GenericApplicationContext) applicationContext) 
  6.              .getBeanFactory().getBeanDefinition(beanName); 
  7.             
  8.              // PropertyResourceConfigurer does not expose any methods to explicitly perform 
  9.              // property placeholder substitution. Instead, create a BeanFactory that just 
  10.              // contains this mapper scanner and post process the factory. 
  11.              DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); 
  12.              factory.registerBeanDefinition(beanName, mapperScannerBean); 
  13.             
  14.              for (PropertyResourceConfigurer prc : prcs.values()) { 
  15.                    prc.postProcessBeanFactory(factory); 
  16.              } 
  17.             
  18.              PropertyValues values = mapperScannerBean.getPropertyValues(); 
  19.             
  20.              this.basePackage = updatePropertyValue("basePackage", values); 
  21.              this.sqlSessionFactoryBeanName = updatePropertyValue("sqlSessionFactoryBeanName", values); 
  22.              this.sqlSessionTemplateBeanName = updatePropertyValue("sqlSessionTemplateBeanName", values); 
  23.        } 


首先这个方法不管从名字或者意义上来看,我们都不难才想到他是用来得到所有定义的PropetyResourceConfigurer类。
其次,注意到上面的条件了吗?这里需要applicationContext是GenericApplicationContext才起作用,而我们这里的applicationContext的实现类是XmlWebApplicationContext,所以这一步完全不起作用(不对,应该说还起了副作用,因为上面的报错就是和这个方法有关)可以找到这个方法最终其实是交给DefaultListableBeanFactory的getBeansOfType(...)来实现的:

  1. public <T> Map<String, T> getBeansOfType(Class<T> type, boolean includeNonSingletons, boolean allowEagerInit) 
  2.  throws BeansException { 
  3.       
  4.        String[] beanNames = getBeanNamesForType(type, includeNonSingletons, allowEagerInit); 
  5.        Map<String, T> result = new LinkedHashMap<String, T>(beanNames.length); 
  6.        for (String beanName : beanNames) { 
  7.              try { 
  8.                    result.put(beanName, getBean(beanName, type)); 
  9.              } 
  10.              catch (BeanCreationException ex) { 
  11.                    Throwable rootCause = ex.getMostSpecificCause(); 
  12.                    if (rootCause instanceof BeanCurrentlyInCreationException) { 
  13.                          BeanCreationException bce = (BeanCreationException) rootCause; 
  14.                          if (isCurrentlyInCreation(bce.getBeanName())) { 
  15.                                if (this.logger.isDebugEnabled()) { 
  16.                                      this.logger.debug("Ignoring match to currently created bean '" + beanName + "': " + 
  17.                                      ex.getMessage()); 
  18.                                } 
  19.                                onSuppressedException(ex); 
  20.                                // Ignore: indicates a circular reference when autowiring constructors. 
  21.                                // We want to find matches other than the currently created bean itself. 
  22.                                continue; 
  23.                          } 
  24.                    } 
  25.                    throw ex; 
  26.              } 
  27.        } 
  28.        return result; 



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

  1. public String[] getBeanNamesForType(Class<?> type, boolean includeNonSingletons, boolean allowEagerInit) { 
  2.        if (type == null || !allowEagerInit) { 
  3.              return this.doGetBeanNamesForType(type, includeNonSingletons, allowEagerInit); 
  4.        } 
  5.        Map<Class<?>, String[]> cache = includeNonSingletons ? 
  6.        this.nonSingletonBeanNamesByType : this.singletonBeanNamesByType; 
  7.        String[] resolvedBeanNames = cache.get(type); 
  8.        if (resolvedBeanNames != null) { 
  9.              return resolvedBeanNames; 
  10.        } 
  11.        resolvedBeanNames = this.doGetBeanNamesForType(type, includeNonSingletons, allowEagerInit); 
  12.        cache.put(type, resolvedBeanNames); 
  13.        return resolvedBeanNames; 


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

  1. private String[] doGetBeanNamesForType(Class<?> type, boolean includeNonSingletons, boolean allowEagerInit) { 
  2.        List<String> result = new ArrayList<String>(); 
  3.       
  4.        // Check all bean definitions. 
  5.        String[] beanDefinitionNames = getBeanDefinitionNames(); 
  6.        for (String beanName : beanDefinitionNames) { 
  7.              // Only consider bean as eligible if the bean name 
  8.              // is not defined as alias for some other bean. 
  9.              if (!isAlias(beanName)) { 
  10.                    try { 
  11.                          RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); 
  12.                          // Only check bean definition if it is complete. 
  13.                          if (!mbd.isAbstract() && (allowEagerInit || 
  14.                          ((mbd.hasBeanClass() || !mbd.isLazyInit() || this.allowEagerClassLoading)) && 
  15.                          !requiresEagerInitForType(mbd.getFactoryBeanName()))) { 
  16.                                // In case of FactoryBean, match object created by FactoryBean. 
  17.                                boolean isFactoryBean = isFactoryBean(beanName, mbd); 
  18.                                boolean matchFound = (allowEagerInit || !isFactoryBean || containsSingleton(beanName)) && 
  19.                                (includeNonSingletons || isSingleton(beanName)) && isTypeMatch(beanName, type); 
  20.                                if (!matchFound && isFactoryBean) { 
  21.                                      // In case of FactoryBean, try to match FactoryBean instance itself next. 
  22.                                      beanName = FACTORY_BEAN_PREFIX + beanName; 
  23.                                      matchFound = (includeNonSingletons || mbd.isSingleton()) && isTypeMatch(beanName, type); 
  24.                                } 
  25.                                if (matchFound) { 
  26.                                      result.add(beanName); 
  27.                                } 
  28.                          } 
  29.                    } 
  30.                    catch (CannotLoadBeanClassException ex) { 
  31.                          if (allowEagerInit) { 
  32.                                throw ex; 
  33.                          } 
  34.                          // Probably contains a placeholder: let's ignore it for type matching purposes. 
  35.                          if (this.logger.isDebugEnabled()) { 
  36.                                this.logger.debug("Ignoring bean class loading failure for bean '" + beanName + "'", ex); 
  37.                          } 
  38.                          onSuppressedException(ex); 
  39.                    } 
  40.                    catch (BeanDefinitionStoreException ex) { 
  41.                          if (allowEagerInit) { 
  42.                                throw ex; 
  43.                          } 
  44.                          // Probably contains a placeholder: let's ignore it for type matching purposes. 
  45.                          if (this.logger.isDebugEnabled()) { 
  46.                                this.logger.debug("Ignoring unresolvable metadata in bean definition '" + beanName + "'", ex); 
  47.                          } 
  48.                          onSuppressedException(ex); 
  49.                    } 
  50.              } 
  51.        } 
  52.       
  53.        // Check singletons too, to catch manually registered singletons. 
  54.        String[] singletonNames = getSingletonNames(); 
  55.        for (String beanName : singletonNames) { 
  56.              // Only check if manually registered. 
  57.              if (!containsBeanDefinition(beanName)) { 
  58.                    // In case of FactoryBean, match object created by FactoryBean. 
  59.                    if (isFactoryBean(beanName)) { 
  60.                          if ((includeNonSingletons || isSingleton(beanName)) && isTypeMatch(beanName, type)) { 
  61.                                result.add(beanName); 
  62.                                // Match found for this bean: do not match FactoryBean itself anymore. 
  63.                                continue; 
  64.                          } 
  65.                          // In case of FactoryBean, try to match FactoryBean itself next. 
  66.                          beanName = FACTORY_BEAN_PREFIX + beanName; 
  67.                    } 
  68.                    // Match raw bean instance (might be raw FactoryBean). 
  69.                    if (isTypeMatch(beanName, type)) { 
  70.                          result.add(beanName); 
  71.                    } 
  72.              } 
  73.        } 
  74.       
  75.        return StringUtils.toStringArray(result); 

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

  1. public boolean isTypeMatch(String name, Class<?> targetType) throws NoSuchBeanDefinitionException { 
  2.  String beanName = transformedBeanName(name); 
  3.  Class<?> typeToMatch = (targetType != null ? targetType : Object.class); 
  4.  // Check manually registered singletons. 
  5.  Object beanInstance = getSingleton(beanName, false); 
  6.  if (beanInstance != null) { 
  7. <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定义的时候是用下面这样的形式:

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


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

  1. protected Class<?> getTypeForFactoryBean(String beanName, RootBeanDefinition mbd) { 
  2.        if (!mbd.isSingleton()) { 
  3.              return null; 
  4.        } 
  5.        try { 
  6.              FactoryBean<?> factoryBean = doGetBean(FACTORY_BEAN_PREFIX + beanName, FactoryBean.class, null, true); 
  7.              return getTypeForFactoryBean(factoryBean); 
  8.        } 
  9.        catch (BeanCreationException ex) { 
  10.              // Can only happen when getting a FactoryBean. 
  11.              if (logger.isDebugEnabled()) { 
  12.                    logger.debug("Ignoring bean creation exception on FactoryBean type check: " + ex); 
  13.              } 
  14.              onSuppressedException(ex); 
  15.              return null; 
  16.        } 


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

  1. protected <T> T doGetBean( 
  2.  final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly) 
  3.  throws BeansException { 
  4. lt;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()方法加上了一个条件。也就是说,默认是不执行的:

  1. public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { 
  2.        if (this.processPropertyPlaceHolders) { 
  3.              processPropertyPlaceHolders(); 
  4.        } 
  5.       
  6.        ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry); 
  7.        scanner.setAddToConfig(this.addToConfig); 
  8.        scanner.setAnnotationClass(this.annotationClass); 
  9.        scanner.setMarkerInterface(this.markerInterface); 
  10.        scanner.setSqlSessionFactory(this.sqlSessionFactory); 
  11.        scanner.setSqlSessionTemplate(this.sqlSessionTemplate); 
  12.        scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName); 
  13.        scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName); 
  14.        scanner.setResourceLoader(this.applicationContext); 
  15.        scanner.setBeanNameGenerator(this.nameGenerator); 
  16.        scanner.registerFilters(); 
  17.        scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)); 
  18.  } 

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

--转自 北京联动北方科技有限公司




赞(0)    操作        顶端 
hei_nihao
注册用户
等级:少校
经验:1279
发帖:19
精华:0
注册:2015-4-17
状态:离线
发送短消息息给hei_nihao 加好友    发送短消息息给hei_nihao 发消息
发表于: IP:您无权察看 2015-4-20 8:59:31 | [全部帖] [楼主帖] 2  楼

北京联动北方科技有限公司北京联动北方科技有限公司北京联动北方科技有限公司

刚才还以为你是ibatIS,弄错了原来是mybatius和spring整合的。。。。

该贴被hei_nihao编辑于2015-4-20 9:03:18


赞(0)    操作        顶端 
总帖数
2
每页帖数
101/1页1
返回列表
发新帖子
请输入验证码: 点击刷新验证码
您需要登录后才可以回帖 登录 | 注册
技术讨论