代碼
1、組件
現在很常見的就是不管如何先定義接口,如下所示:
package com.sishuok;
public interface Interface {
public void sayHello();
}
然後定義實現,真的有必要嗎?思考下。
package com.sishuok;
public class Impl implements Interface {
@Override
public void sayHello() {
System.out.println("hello");
}
}
Bean用於注入Impl,其實此處是錯誤的,因爲定義了接口,應該注入接口的。
package com.sishuok;
public class Bean {
private Impl impl;
public Bean() {
}
public Bean(final Impl impl) {
this.impl = impl;
}
}
2、需要一個切面
怎麼實現無所謂,就是爲了生成代理對象,重現問題。
package com.sishuok;
public class Aspect {
public void before() {
System.out.println("==before");
}
}
3、 配置文件
<bean id="aspect" class="com.sishuok.Aspect"/>
<aop:config>
<aop:aspect ref="aspect">
<aop:before method="before" pointcut="execution(* com.sishuok.Impl.*(..))"/>
</aop:aspect>
</aop:config>
<bean id="impl" class="com.sishuok.Impl"/>
<bean id="b" class="com.sishuok.Bean">
<constructor-arg name="impl" ref="impl"/>
</bean>
配置文件很簡單。 一個AOP切面會橫切Impl,即生成Impl代理,而Bean會注入Impl。
4、測試類
測試類很簡單,只需要加載配置文件即可。
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath:spring-config.xml"})
public class BeanIT {
@Autowired
private Bean bean;
@Test
public void test() {
System.out.println("=hello test");
}
}
整段代碼很簡單,應該能猜到是嘛問題。
拋出的異常
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'b' defined in class path resource [spring-config.xml]: Could not resolve matching constructor (hint: specify index/type/name arguments for simple parameters to avoid type ambiguities)
這個異常讓人疑惑,因爲它告訴你說可能是構造器的問題,所以可能把我們帶坑裏看不到真實問題。
主要原因是:
我們實際定義了兩個構造器:
1、一個空參的:public Bean()
2、一個帶Impl參數的:public Bean(final Impl impl)
如果把第一個構造器刪除就會得到真實的異常:
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'b' defined in class path resource [spring-config.xml]: Unsatisfied dependency expressed through constructor argument with index 0 of type [com.sishuok.Impl]: Could not convert constructor argument value of type [$Proxy8] to required type [com.sishuok.Impl]: Failed to convert value of type '$Proxy8 implementing com.sishuok.Interface,org.springframework.aop.SpringProxy,org.springframework.aop.framework.Advised' to required type 'com.sishuok.Impl'; nested exception is java.lang.IllegalStateException: Cannot convert value of type [$Proxy8 implementing com.sishuok.Interface,org.springframework.aop.SpringProxy,org.springframework.aop.framework.Advised] to required type [com.sishuok.Impl]: no matching editors or conversion strategy found
很明顯是注入問題,見到$Proxy8 我們就能猜到是JDK動態代理(這個在《spring的二次代理原因及如何排查》說過),即它不能轉換爲實際的com.sishuok.Impl,問題很明顯了.
此處還有另一個問題:關於按照構造器參數名注入時,具體參考《【第三章】 DI 之 3.1 DI的配置使用 ——跟我學spring3》中的構造器注入部分。
解決方案
1、要麼構造器注入接口,即public Bean(final Interface impl)
2、要麼使用CGLIB類代理
總結
1、知道自己使用的是啥編程風格:
- 先定義接口,然後實現;如UserController---->UserService(接口 其實現是UserServiceImpl),這種情況大多數人都使用接口注入即可
- 我喜歡直接定義實現,如UserController--->UserService(實現),因爲這是一個模塊內部的操作,幹嘛定義接口呢?可擴展?想想自己擴展過嗎?如果實在想要可擴展我一般是這樣:UserApi UserApiImpl 可參考我的es腳手架
2、知道自己使用的啥代理:
- JDK動態代理 還是 CGLIB代理, 做到心中有數,儘量別混用。當然最保險的方式就是使用CGLIB代理。
明確自己的風格,從一而終,不要爲了玩玩都用上,Spring已經很龐大且複雜了,,使用Spring出問題最多的就是AOP部分,遇到AOP問題。可參考《請不要再使用低級別的AOP API》
我喜歡
- 給自己使用的無需定義接口;即一個模塊內部的都是封裝的,定義接口並不會得到很多好處,變過幾次實現?? “優先面向接口編程,而非實現” 不是必須,是優先;
- 給朋友(第三方)使用的定義接口;即要公開的功能,因爲接口就是個契約,就是溝通用的;
- 優先使用setter注入,除非必要才使用構造器注入;
- 使用CGLIB代理,這樣基本不會出現AOP代理注入不了或一些隱晦的問題;
- 優先使用Spring提供的XML標籤簡化功能定義,如<aop:config>、<task:executor>等,而不要使用低層次API;
- 儘量使用XML風格的事務,而不是註解風格;
- 按照配置的內容分多配置文件存放配置,不要一股腦的放在一起,就像不分包那樣;
- 可配置部分(如db數據)還是放到XML中,不要什麼都註解;
- 使用Spring profile 或 maven profile分環境測試(如開發環境、測試環境、正式機環境);
官方文檔還是要看的,當然剛開始可能比較困難,但是堅持幾次以後就輕鬆了,出問題先看文檔,接着翻javadoc,基本能搞定,當然讀一讀源碼對日常開發的調錯還是很有幫助的。