2017-07-07 47 views
1

這有點難以解釋...希望問題的領域一點都不含糊......如何指示Spring來自動裝配在緊密耦合的對象鏈

你可以看看代碼的想法...

ClassA.java

public class ClassA { 
    @Autowired 
    InterA abcd; 
    public void dododo() { 
     abcd.doit(); 
    } 
} 

ClassB.java

@Component 
public class ClassB implements InterA { 
    @Override 
    public void doit() { 
     System.out.println("hoo hoo"); 
    } 
} 

ClassC.java

@Component("classc") 
public class ClassC { 
    public void doFromAbove() { 
     ClassA cls = new ClassA(); 
     cls.dododo(); 
    } 
} 

接口InterA.java

public interface InterA { 
    public void doit(); 
} 

配置ClassConfig.java(在同一封裝的其他Java類文件)

@Configuration 
@ComponentScan 
public class ClassConfig { 
} 

主要方法

public static void main(String[] args) { 
    try(AbstractApplicationContext appctx = new AnnotationConfigApplicationContext(ClassConfig.class)) { 
     ClassC obj = (ClassC) appctx.getBean("classc"); 
     obj.doFromAbove(); 
    } 
} 

當我執行的主要方法,在ClassA的自動裝配Autowired場「ABCD」沒有得到注入,導致NullPointerException

當我宣佈ClassA作爲它僅@Component並得到它的豆...間接自動裝配沒有發生

我應該從ClassC去耦ClassA並使一切鬆耦合?

是否有任何簡單的註釋可以用來告訴Spring自動注入@Autowired字段,即使對象是以緊密耦合的方式創建的?

注意 請不要告訴我使用的ApplicationContext在ClassC創造ClassA豆。

任何Spring Geek誰可以找到答案?

+2

我認爲在這種情況下Spring 101可能是合適的。因此,讓我們從那裏開始 - 如果您在應用程序中的任何位置使用'new' **,那麼您的錯誤是__錯誤。在Spring應用程序中,只有**的地方應該看到'new',它在配置類中向Spring解釋如何創建bean。它被稱爲_Dependency Injection_框架 - 您絕對必須** _inject dependencies_才能使用它。 –

+1

此外,任何告訴你在'ClassC'中使用'ApplicationContext'來創建'ClassA'_'的bean的人也不理解DI。你永遠不應該這樣做。正確的做法是注入'Provider '並在需要時調用'get'。然後你告訴Spring'ClassA'是'PROTOTYPE'的作用域,所以每次創建一個新的實例。正如我所說 - 春天101. –

+0

@BoristheSpider,我得到你的漂移......問題是我正在經歷的是將傳統的Java項目轉換爲Spring ...所以我想也許我們可以混合匹配緊密耦合的對象鬆散耦合的彈簧注入對象來完成春季轉換/遷移過程,代碼修改更少...我所描述的例子只有一個緊耦合......在一個層面上...應用Spring正確的方式似乎是一個很大的現有項目的痛苦 –

回答

1

經過激烈的谷歌搜索,Spring Documentation Skimming,我確信有更多可能的解決方案來解決這個難題。

可能的解決方案:

  1. 使用JSR 330 Provider<T>@Autowired
  2. 使用​​與初始化代碼在getObject()(但由工廠返回的豆是不是Spring管理和由此的任何自動裝配Autowired場原型類將返回NullPointerException)
  3. 使用查找方法注入(包括CGLIB庫)(我不喜歡 這個,因爲它修改編譯的co德和聽起來像它創建bean對象的抽象類 )(Java的純度受到侵犯)
  4. 實現ApplicationContextAware接口和獲取上下文 對象(不推薦)
  5. 的Autowire ApplicationContext和使用getBean()(不推薦)
上述中

最巧妙的辦法就是JSR330提供

ClassA的

@Component("classa") 
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) 
public class ClassA implements InterB { 
    private static int counter=0; 

    private int objectid = 0; 
    @Autowired 
    InterA abcd; 

    public ClassA() { 
     super(); 
     this.objectid = ++counter; 
    } 

    @Override 
    public void dododo() { 
     System.out.println("instance number "+objectid++); 
     abcd.doit(); 
    } 
} 

ClassB的

@Component 
public class ClassB implements InterA { 
    @Override 
    public void doit() { 
     System.out.println("hoo hoo"); 
    } 

} 

ClassC

@Component("classc") 
public class ClassC { 

    @Autowired 
    Provider<InterB> classAPrototypeobj; 

    public void doFromAbove() { 
     //you can do a for loop here and get a set of objects for use 
     InterB cls = (InterB) classAPrototypeobj.get(); 
     InterB cls1 = (InterB) classAPrototypeobj.get(); 
     cls.dododo(); 
     cls1.dododo(); 
     System.out.println(cls); 
     System.out.println(cls1); 
    } 
} 

現在,它完美的作品和初始化的對象是Spring管理太...

注: JSR330的依賴在Maven的pom.xml中進行設置

<dependency> 
    <groupId>javax.inject</groupId> 
    <artifactId>javax.inject</artifactId> 
    <version>1</version> 
</dependency> 
+0

完美 - 在研究上做得很好。這是完全正確的方法。你不應該需要投射,因爲'Provider'是通用的。 –

1

的問題是在ClassC

ClassA cls = new ClassA(); 

如果調用的ClassA這樣的構造,春天不會做它的魔力。如果需要帶注入字段的ClassA實例,請向Spring請求一個實例(使用注入或getBean())。

(爲避免讓null字段假設注入,我建議使用構造函數注入。)

+1

Ctor injection每次調用doFromAbove時都不會創建新的實例,除非你進入範圍代理的領域。 –

+0

與野外注射不一樣嗎? –

+0

是的,但我的觀點是「_to解決這些問題,我建議使用構造函數注入_」不是一個有用的聲明。 –

0

豆(無論是通過XML或類似@Component)註解是Spring容器內聲明Spring管理 - Spring會照顧他們的,將確保當您通過ApplicationContext.getBean(),他們的依賴是要求他們,他們存在也注入。

當你自己創建一個實例(cls = new ClassA())時,該實例不是Spring管理的,Spring將不會對它做任何事情。事實上,Spring不會(也不能)知道對象存在。

有些混亂可能與您註釋的類與Spring的註解幹 - 但它確實對象(實例),它們實際上是在Java中使用;即使該類被註釋了,註解也只會應用於由Spring創建和管理的實例。

0

如果啓用加載時織入,並且使對象的每個新實例成爲託管彈簧組件,那麼您可以使用@Configurable,以便使用新的語句。

除此之外,你可以創建一個原型範圍的bean定義和一個引用該bean的工廠bean,這意味着它每次都會給你一個新的bean,所以你可以注入工廠並只調用新的get方法實例。

+0

小心給我一個'@ Configurable'和FactoryBeans的簡單例子嗎?如果可能的話,請使用問題中的示例代碼,這樣可以使任何人都受益。這可能是我一直在尋找的答案......但我需要確保......如果這樣做,我會獎勵+50賞金 –

+0

這是一個獨立的基本命令行java程序,帶有'public靜態無效主'thingy ...將LTW甚至在它上面工作?我認爲它的工作原理只有當它的一個web應用程序加載在像tomcat這樣的容器中時......但是我可能是錯的......啓發請... –

0

爲什麼不使用@Lookup註解?根據接受的答案,我假設您需要每次在ClassC的新實例ClassA

@Component("classA") 
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) 
public class ClassA { 
    @Autowired 
    InterA abcd; 

    private ObjectA objA; 

    private ObjectB objB; 

    public ClassA(objA, objB) { 
     this.objA = objA; 
     this.objB = objB; 
    } 

    public void dododo() { 
     abcd.doit(); 
    } 
} 

@Component("classc") 
public class ClassC { 
    public void doFromAbove() { 
     ClassA cls = getNewInstanceOfClassA(objA, objB); 
     cls.dododo(); 
    } 

    @Lookup("classA") 
    private getNewInstanceOfClassA(ObjectA objA, ObjectB objB) { 
     //Spring creates a runtime implementation of this method 
     return null; 
    } 
} 

其餘的類實現保持不變。爲了清楚地說明注入構造函數的參數,我在實現中包含了objA和objB。

所以主要方法獲得classC的spring bean並調用doFromAbove()方法。這又調用getNewInstanceOfClassA方法,該方法返回類型爲classA的spring bean,其構造函數參數爲objA,objB。由於我們將其註釋爲prototype bean,因此每次調用此方法時都會得到一個新的classA實例。您無需實施getNewInstanceOfClassA方法。 Spring在運行時添加它自己的代碼。

本質上,你的問題歸結爲在單例bean中注入一個原型bean。查找註釋是解決該問題的最佳方法。

+0

除了@Lookup(正如我在回答點編號(3)中提到的)使用CGLib來更改Java中間字節碼的子類。(Java的純度被違反)現在你可能會覺得可以擴展java類並返回一個bean,它是我想要的子類。但它感覺有點不對 –

+0

Spring擴展了您用子類創建的Bean類並覆蓋了該方法......它看起來不正確... –

+0

我不確定你的意思是否違反了Java的純度。如果CGLib添加字節碼,你有什麼缺點?您可以使用Lookup註釋以更簡潔明瞭的方式做你想做的事(獲取原型bean)。Spring在許多其他地方使用CGLib。事務註釋,spring-data-jpa,配置註釋和許多其他彈簧功能在內部使用CGLib或JDK動態代理。 – yaswanth