首页 > 代码库 > Cannot find class: ${jdbc.driver}——配置了sqlSessionFactoryBeanName也报错之问题分析
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=http://www.mamicode.com/"${pms.driver}" />>但是启动容器后,却报错: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=http://www.mamicode.com/"org.mybatis.spring.sample.mapper.UserMapper" />>所以当要获取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。
Cannot find class: ${jdbc.driver}——配置了sqlSessionFactoryBeanName也报错之问题分析