2014-07-09 82 views
2

請參閱下面的代碼以獲取更多信息。這會在name()方法中拋出NullPointerException(標記爲下面的註釋)。我的理解是,Spring按順序讀取@Configuration,所以首先自動接線namePrinter,然後nameProvider。由於在name()中使用,因此需要爲NamePrinter構造函數自動佈線,因此可以解釋NPE。我也知道兩種解決方案:自動裝配訂單NullPointerException

  • 一種是按照預期的順序進行訂購。我認爲這應該是Spring的工作。
  • 另一種是通過參數自動裝配,即:

    @Bean 
    public Name name(NameProvider nameProvider) { 
        return nameProvider.getName(); 
    } 
    

    我也覺得這應該是春天的工作,並希望避免這種情況在有很多參數的情況下 - 使用配置@Autowired看起來更在這些情況下我可讀。

一些問題:

  • 對於上述的解決2 - 爲什麼春天治療方法參數和自動裝配Autowired類變量不同?
  • 是否有合法的原因Spring不會按照依賴順序自動裝入bean,而是選擇源文件定義順序?
  • 你遇到過這樣的問題嗎?如果是這樣,你在特定情況下如何解決它們?希望我忽略這裏的東西,可以是有益的

版本:

  • 的Java 1.7
  • 春4.0.5.RELEASE

代碼:

import org.springframework.beans.factory.annotation.Autowired; 
import org.springframework.context.annotation.AnnotationConfigApplicationContext; 
import org.springframework.context.annotation.Bean; 
import org.springframework.context.annotation.ComponentScan; 
import org.springframework.context.annotation.Configuration; 
import org.springframework.stereotype.Component; 

interface Name { 
    String getName(); 
} 

@Component 
class NameProvider { 
    public Name getName() { 
     return new Name() { 
     @Override 
     public String getName() { 
      return "Foo"; 
     } 
     }; 
    } 
} 

@Component 
class NamePrinter { 
    private final Name name; 

    @Autowired 
    public NamePrinter(Name name) { 
     this.name = name; 
    } 

    public void print() { 
     System.out.println(name.getName()); 
    } 
} 

@Configuration 
@ComponentScan(basePackageClasses = { SpringAutowiringPrbConfig.class }) 
class SpringAutowiringPrbConfig { 
    @Autowired 
    private NamePrinter namePrinter; 
    @Autowired 
    private NameProvider nameProvider; 

    @Bean 
    public Name name() { 
     return nameProvider.getName(); // NullPointerException here 
    } 
} 

public class SpringAutowiringPrb { 
    public static void main(String[] args) { 
     AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
     SpringAutowiringPrbConfig.class); 
     NamePrinter namePrinter = context.getBean(NamePrinter.class); 
     namePrinter.print(); 
     context.close(); 
    } 
} 
+0

+1優秀的問題。 –

回答

1

的行爲:

這是一個圓形的例子。 Spring的@Configuration類在調用@Bean方法之前確定解析@Autowired目標。 但是,如果部分@Autowired解析過程需要調用@Bean方法,那麼您必須能夠處理它。

在你的榜樣,你有

@Autowired 
private NamePrinter namePrinter; 
@Autowired 
private NameProvider nameProvider; 

看來,在這種情況下(這必須做與反思,以及如何Field對象從Class對象檢索),即春季嘗試解析namePrinter領域第一。爲了執行該決議,Spring必須實例化並初始化Bean。因此,它會調用

@Autowired 
public NamePrinter(Name name) { 
    this.name = name; 
} 

要做到這一點,它需要一個Name豆注入到構造。要獲得Name豆,它需要調用

@Bean 
public Name name() { 
    return nameProvider.getName(); // NullPointerException here 
} 

然後又回到了這裏。 nameProvider尚未處理,因此null尚未處理。

您可以查看堆棧跟蹤,以查看在NPE發生時Spring是否正在自動裝配某些東西。


對於上述的解決2 - 爲什麼春天治療方法 參數和自動裝配Autowired類變量不同?

它並不是真的。當春天需要自動裝配

@Bean 
public Name name(NameProvider nameProvider) { 
    return nameProvider.getName(); 
} 

它檢查NameProvider bean定義以及如果存在初始化豆。然後它可以將它注入到構造函數中。以前,它沒有任何關於該方法內可以使用什麼對象的提示。 Spring使用的反射不能查看方法的主體。它只能看它的定義。

是否有正當理由春天不下令豆被 自動連接的依賴順序,而是選擇採用源文件 定義命令?

Spring使用反射來獲取一個ClassField對象。尤其是,它使用Class#getDeclaredFields()其中指出

返回的數組不排序中的元素,並且不以任何 特定順序。

所以這只是(非)幸運namePrinter第一。

你遇到過這樣的問題嗎?如果是這樣,你是如何解決他們在 你的特殊情況?希望我忽略了這裏的一些東西,可以是 有用

分析你的代碼並嘗試充實任何循環依賴。考慮使用@DependsOn,雖然它在這裏沒有幫助。

還有選擇開水@Component(及其專業化),並採取一切方法,包括@Bean方法。

你可以有

@Bean 
public NamePrinter namePrinter() { 
    return new NamePrinter(name()); 
} 

@Bean 
public NameProvider nameProvider() { 
    return new NameProvider(); 
} 

@Bean 
public Name name() { 
    return nameProvider().getName(); 
} 

這工作,因爲春季爲您創建@Configuration類的自定義子類(和實例),其中可以截取到@Bean方法的調用,緩存調用的結果,並提供在未來的所有調用中都會產生相同的結果。

+0

謝謝Sotirios! 「Spring使用的反射不能看待方法的主體。「好吧,這是有道理的。結合攔截字段訪問不是Java允許的事實(參見[這個答案](http://stackoverflow.com/a/4207523/3821009)),我猜Spring沒有什麼可以做的。「分析你的代碼並試圖充實任何循環依賴關係。」注意,這裏沒有循環依賴關係 - NamePrinter依賴於Name,而Name依賴於NameProvider,除了上述解決方案之外,你是否看到有什麼更簡單的?如果沒有,我會按照上面的方法做, –

+0

@levantpied對不起,我的意思是_circular依賴關係_就像導致這個問題的東西一樣,還有一個替代方案(我已經添加了一個編輯器)而不是使用'@ Component'和組件掃描,你可以用'@ Bean'顯式地聲明你的bean,注意你的另一個解決方案(方法參數)基本上是一樣的,你應該總是喜歡做方法和構造器注入 –

+0

謝謝Sotirios - a gree,這與第二個解決方案類似,但稍微冗長些。我認爲給@Bean註釋方法添加一個參數將是最簡潔的。 –