從一而終只使用一種Spring編程風格

代碼

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,基本能搞定,當然讀一讀源碼對日常開發的調錯還是很有幫助的。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章