spring default-autowire属性引发的 No unique bean 异常

问题:
      在项目中需要加入一个新的数据源, 由于采用mybatis框架. 所以需要定义两个org.mybatis.spring.SqlSessionFactoryBean(SessionFactory). 此时,原有继承自org.mybatis.spring.support.SqlSessionDaoSupport的dao, 无法工作. 抛异常如下:
Java代码   收藏代码
  1. org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'b2cArriveMappingDao' defined in file [......\B2cArriveMappingDao.class]: Unsatisfied dependency expressed through bean property 'sqlSessionTemplate': : No unique bean of type [org.mybatis.spring.SqlSessionTemplate] is defined: expected single matching bean but found 2: [sqlSession, vacationSqlSession]; ......  
  2. ......  
  3. Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [org.mybatis.spring.SqlSessionTemplate] is defined: expected single matching bean but found 2: [sqlSession, vacationSqlSession]  
  4. .....  

 

     

查找原因:

   1. 起初怀疑是SqlSessionDaoSupport的问题,认为他是不支持多数据源的.后来看到网上的一篇文章说 mybatis-spring:1.2.0以后为了更方便的支持多数据源,取消了SqlSessionDaoSupport.  setSqlSessionFactory()的 @Autowired  标注.
  2.既然SqlSessionDaoSupport  没有@ Autowired  ,在项目中的BaseSqlSessionDao使用了@ Autowired+@Qualifier  进行了注入.那么为什么还会找到两个呢? 显然这个annotation没有起作用吗...这个问题纠结了一下午,后来,偶然间发现了在配置文件中有一个属性很别致.
Xml代码   收藏代码
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2.   
  3.       <beans xmlns="http://www.springframework.org/schema/beans"  
  4.   
  5.          ...... /spring-context-3.0.xsd" default-autowire="byType">  
  6.   
  7.       </beans>  
  
  3.在去掉 default-autowire属性后,程序正常运行.不在报上述异常了....why?
  4.后来参考了一些资料,知道了其中的知识点.即,
    1. 如果指定 default-autowire属性,在加载 bean 的时候, 会按照byName/byType进行注入. 这里,需要注入的属性,是符合javabean的任何类,即setXXX的方法,都会进行注入,无论有没有通过xml/annotation来定义的.
    2. 完成之后,再进行 @ Autowired+@Qualifier  的注入.
    所以就能解释上述的疑问了.虽然SqlSessionDaoSupport 中没有显式的进行注入, 但是 有setSqlSessionFactory()方法,并且符合javabean.那么就会按照type进行注入,所以找到两个,抛出异常.具体代码如下:   
Java代码   收藏代码
  1. org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean:1080  
  2.   
  3.       if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME ||  
  4.         mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) {  
  5.        MutablePropertyValues newPvs = new MutablePropertyValues(pvs);  
  6.        // Add property values based on autowire by name if applicable.  
  7.        if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME) {  
  8.         autowireByName(beanName, mbd, bw, newPvs);  
  9.        }  
  10.        // Add property values based on autowire by type if applicable.  
  11.        if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) {  
  12.         autowireByType(beanName, mbd, bw, newPvs);  
  13.        }  
  14.        pvs = newPvs;  
  15.       }  
  16.   
  17.     .....  
  18.     for (BeanPostProcessor bp : getBeanPostProcessors()) {  
  19.          if (bp instanceof InstantiationAwareBeanPostProcessor) {  
  20.           InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;  
  21.     //@Autowired的处理类为AutowiredAnnotationBeanPostProcessor, 它是InstantiationAwareBeanPostProcessor 的子类.  
  22.           pvs = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);  
  23.           if (pvs == null) {  
  24.            return;  
  25.           }  
  26.          }  
  27.         }  

 

    5. 也就是说,如果指定了 default-autowire ,并且指定了 @ Autowired, 那么这个属性有可能会被注入两次. 并且如果有一次不成功,那么就挂了.
    6. 在DefaultListableBeanFactory.doResolveDependency() 执行的时候, 还会处理一个primary属性, 可以注意一下. 如果存在一个type有多个bean的时候,而且注入是只需要一个, 那么可以通过primary=true来指定一个优先被加载的bean,来解决上面的问题.
Java代码   收藏代码
  1. Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);  
  2.      if (matchingBeans.isEmpty()) {  
  3.       if (descriptor.isRequired()) {  
  4.        raiseNoSuchBeanDefinitionException(type, "", descriptor);  
  5.       }  
  6.       return null;  
  7.      }  
  8.      if (matchingBeans.size() > 1) {  
  9.       String primaryBeanName = determinePrimaryCandidate(matchingBeans, descriptor);  
  10.       if (primaryBeanName == null) {  
  11.        throw new NoSuchBeanDefinitionException(type, "expected single matching bean but found " +  
  12.          matchingBeans.size() + ": " + matchingBeans.keySet());  
  13.       }  
  14.       if (autowiredBeanNames != null) {  
  15.        autowiredBeanNames.add(primaryBeanName);  
  16.       }  
  17.       return matchingBeans.get(primaryBeanName);  
  18.      }  
  19.      // We have exactly one match.  
  20.      Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next();  
  21.      if (autowiredBeanNames != null) {  
  22.       autowiredBeanNames.add(entry.getKey());  
  23.      }  
  24.      return entry.getValue();  
  
       
     其中的执行逻辑是:
    1. 首先根据类型找到所有可以满足条件的bean
    2. 判断bean长度,如果没有,则根据@autowired中的required属性进行判断是否抛出异常(默认为true)
    3. 如果多于一个,则尝试寻找最优的那一个,如果最优的未找到,则抛出异常
    4. 如果只有一个,则直接使用此bean
7. primary属性,对 @ Autowired的处理起效,对default-autowire处理时同样起效.
8.  除了primary属性显式的设置优先级,  还有有个隐式的优先级,在 determinePrimaryCandidate方法中,会检查 @ Autowired的属性名称,如果属性名称和bean的alias一样,则会优先加载.不会报错.

 解决办法:

  方案1. 去掉 default-autowire ="byType",使用 @ Autowired+@Qualifier.
 方案 2. 在多个同type的bean中,指定一个bean标注primary=true.
 
参考:
http://www.iflym.com/index.php/code/201211070001.html
http://www.oschina.net/code/piece_full?code=21550#35968
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值