1. 程式人生 > >spring自動載入,使用實現類無法載入,使用介面卻可以的原因

spring自動載入,使用實現類無法載入,使用介面卻可以的原因

參考:

Case

請看下面的IOC例項: 1)AaaService實現AaaaInterface介面 2)在BaaService中Autowired AaaService

Code

AaaInterface

package com.test;  
public interface AaaInterface {  
    void method1();  
}  

AaaService

package com.test;  
public class AaaService implements AaaInterface {  

    @Override  
    public void
method1() { System.out.println("hello"); } public void method2() { System.out.println("hello"); } }

BbbService

package com.test;  

import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.stereotype.Service;  
public class
BbbService { @Autowired private AaaService aaaService; public void method2(){ System.out.println("hello method2"); } }

Spring XML配置

<?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:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" 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.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd">
<context:property-placeholder location="classpath*:conf.properties"/> <!--掃描除Controller外的Bean,Controller在MVC層配置--> <context:component-scan base-package="com.test"/> <!--資料來源--> <bean id="dataSource" class="org.springframework.jdbc.datasource.SingleConnectionDataSource" p:driverClassName="${jdbc.driverClassName}" p:url="${jdbc.url}" p:username="${jdbc.username}" p:password="${jdbc.password}"/> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" p:dataSource-ref="dataSource"/> <!-- 通過AOP配置的事務管理增強 --> <aop:config > <aop:pointcut id="serviceMethod" expression="execution(public * com.test..*Service.*(..))"/> <aop:advisor pointcut-ref="serviceMethod" advice-ref="txAdvice"/> </aop:config> <tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> <tx:method name="*"/> </tx:attributes> </tx:advice> <!-- //////////////////////////////////////////////// --> </beans>

啟動Spring容器

package com.test;  

import org.springframework.context.ApplicationContext;  
import org.springframework.context.support.ClassPathXmlApplicationContext;  

public class MainStarter {  
    public static void main(String[] args) {  
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");  
        BbbService bbbService = applicationContext.getBean("bbbService", BbbService.class);  
    }  
}  

結果報了以下這堆異常:

2013-7-25 13:54:08 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry destroySingletons  
資訊: Destroying singletons in org.s[email protected]687bc899: defining beans [org.springframework.context.support.PropertySourcesPlaceholderConfigurer#0,aaaService,bbbService,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,dataSource,txManager,org.springframework.aop.config.internalAutoProxyCreator,serviceMethod,org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor#0,txAdvice,org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor]; root of factory hierarchy  
//重要的資訊在這兒!!  
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'bbbService': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.test.AaaService com.test.BbbService.aaaService; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.test.AaaService] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}  
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:288)  
    at   
    ... 50 more  

上面的異常告訴我們,BbbService Bean建立不了,因為無法Autowired其aaaService的成員變數,說在Spring容器中不存在這個AaaService的Bean.

分析

以上是Spring IoC中一個經典的錯誤,其原因是Spring對Bean的動態代理引起的

  1. 由於在applicationContext.xml中,通過對所有Service進行事務增強,因此Spring容器會對所有所有XxxService的Bean進行動態代理;
  2. 預設情況下,Spring使用基於介面的代理,也即: a)如果Bean類有實現介面,那麼Spring自動使用基於介面的代理建立該Bean的代理例項; b)如果Bean類沒有實現介面,那麼則使用基於子類擴充套件的動態代理(即CGLib代理)
  3. 由於我們的AaaService實現了AaaInterface,所以Spring在生成AaaService類的動態代理Bean時,採用了 基於介面的動態代理,該動態代理例項只實現AaaInterface,且該例項不能強制轉換成AaaService。換句話說,AaaService生成的Bean不能賦給AaaService的例項,而只能賦給AaaInterface的例項
  4. 因此,當BbbService希望注入AaaService的成員時,Spring找不到對應的Bean了(因為只有AaaInterface的Bean).

解決

方法1

  既然AaaService Bean是被Spring動態代理後改變成了型別,那如果取消引起動態代理的配置,使Spring不會對AaaService動態代理,那麼AaaService的Bean型別就是原始的AaaService了:

<beans>  
...  
    <!-- 把以下的配置註釋掉 -->  
 <!--    <aop:config >  
        <aop:pointcut id="serviceMethod"  
                      expression="execution(public * com.test..*Service.*(..))"/>  
        <aop:advisor pointcut-ref="serviceMethod" advice-ref="txAdvice"/>  
    </aop:config>  

    <tx:advice id="txAdvice" transaction-manager="txManager">  
        <tx:attributes>  
            <tx:method name="*"/>  
        </tx:attributes>  
    </tx:advice>-->  
    <!-- //////////////////////////////////////////////// -->  
</beans>  

  引起Spring對AaaService進行動態代理的配置註釋後,重新啟動容器,即可正常啟動了。

  但是這裡AaaService的方法就不會被事務增強了,這將違背了我們的初衷,因此這種解決方法僅是為了讓大家瞭解到引起IOC問題的根源所在,並沒有真正解決問題

方法2

  將BbbService的AaaService成員改成AaaInterface:

@Service("bbbService")  
public class BbbService {  

    @Autowired  
    private AaaInterface aaaService;//注意這兒,成員型別從AaaService更改為AaaInterface  

    public void method2(){  
        System.out.println("hello method2");  
    }  
}  

  既然AaaService Bean被植入事務增強動態代理後就變成了AaaInterface的例項,那麼幹脆我們更改BbbService的成員屬性型別,也是可以解決問題的。

  但是這樣的話,只能呼叫介面中擁有的方法 ,在AaaService中定義的方法(如method2())就呼叫不到了,因為這個代理後的Bean不能被強制轉換成AaaService。

  因此,就引出了我們的終極解決辦法,請看方法3:

方法3:終極解決辦法

  剛才我們說“Spring在預設情況下,對實現介面的Bean採用基於介面的代理”,我們可否改變Spring這一“預設的行為”呢?答案是肯定的,那就是通過proxy-target-class=”true”這個屬性,Spring植入增強時,將不管Bean有沒有實現介面,統統採用基於擴充套件子類的方式進行動態代理,也即生成的動態代理是AaaService的子類,那當然就可以賦給AaaService有例項了:

<!-- 注意這兒的proxy-target-class="true" -->  
<aop:config proxy-target-class="true">  
    <aop:pointcut id="serviceMethod"  
                  expression="execution(public * com.test..*Service.*(..))"/>  
    <aop:advisor pointcut-ref="serviceMethod" advice-ref="txAdvice"/>  
</aop:config>  

<tx:advice id="txAdvice" transaction-manager="txManager">  
    <tx:attributes>  
        <tx:method name="*"/>  
    </tx:attributes>  
</tx:advice>  

注意,你需要將CGLib包放到類路徑下,因為Spring用它來動態生成代理!以下是我這個小例子的Maven:

<?xml version="1.0" encoding="UTF-8"?>  
<project xmlns="http://maven.apache.org/POM/4.0.0"  
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">  
    <modelVersion>4.0.0</modelVersion>  

    <groupId>onlyTest</groupId>  
    <artifactId>onlyTest</artifactId>  
    <version>1.0-SNAPSHOT</version>  

    <properties>  
       <spring.version>3.2.3.RELEASE</spring.version>  
       <mysql.version>5.1.25</mysql.version>  
    </properties>  

    <dependencies>  

        <dependency>  
            <groupId>org.springframework</groupId>  
            <artifactId>spring-context</artifactId>  
            <version>${spring.version}</version>  
        </dependency>  

        <dependency>  
            <groupId>org.springframework</groupId>  
            <artifactId>spring-context-support</artifactId>  
            <version>${spring.version}</version>  
        </dependency>  

        <dependency>  
            <groupId>org.springframework</groupId>  
            <artifactId>spring-aop</artifactId>  
            <version>${spring.version}</version>  
        </dependency>  

        <dependency>  
            <groupId>org.springframework</groupId>  
            <artifactId>spring-jdbc</artifactId>  
            <version>${spring.version}</version>  
        </dependency>  

        <dependency>  
            <groupId>mysql</groupId>  
            <artifactId>mysql-connector-java</artifactId>  
            <version>${mysql.version}</version>  
        </dependency>  

        <dependency>  
            <groupId>org.aspectj</groupId>  
            <artifactId>aspectjweaver</artifactId>  
            <version>1.6.10</version>  
        </dependency>  

    </dependencies>  

</project>