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