Case
請看下面的IOC實例:
1)AaaService實現AaaaInterface接口
2)在BaaService中Autowired AaaService
Code
java
//1.AaaInterface spring
package com.test; sql
public interface AaaInterface { express
void method1(); apache
} app
//2.AaaService maven
package com.test; ide
public class AaaService implements AaaInterface { post
@Override
public void method1() {
System.out.println("hello");
}
public void method2() {
System.out.println("hello");
}
}
//3.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.springframework.beans.factory.support.DefaultListableBeanFactory@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中,經過<aop>對全部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進行動態代理的<aop>配置註釋後,從新啓動容器,便可正常啓動了。
可是這裏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>