在上一篇文章中,我簡單解釋了什麼是Spring,什麼是bean和context。現在是時候嘗試一下了。我將使用 IntelliJ IDEA Enterprise Edition 來完成它。但我的所有示例也應該適用於免費的 IntelliJ IDEA 社區版。在屏幕截圖中,如果您看到我有一些您沒有的窗口,請不要擔心 — 這對這個項目並不重要 :) 首先,創建一個空的 Maven 項目。我在此鏈接的文章中展示瞭如何執行此操作。閱讀“現在是我們將 Maven 項目轉換為 Web 項目的時候了。 ”——之後,文章展示瞭如何創建 Web 項目,但我們現在不需要它。在src/main/java文件夾,創建一個包(在我的例子中,我稱之為 "
en.codegym.info.fatfaggy.animals
。你可以隨意調用它。只是不要忘記在所有正確的地方用你的包名替換我的包名。現在創建類Main
並添加一個方法
public static void main(String[] args) {
...
}
之後,打開 pom.xml 文件並添加該dependencies
部分。現在轉到Maven 存儲庫並找到最新穩定版本的Spring 上下文。將我們找到的內容放入該dependencies
部分。我在另一篇 CodeGym 文章中更詳細地描述了這個過程(請參閱標題為“在 Maven 中連接依賴項”的部分)。然後 Maven 自己會找到並下載必要的依賴項。最後,您應該得到這樣的結果: 在左側的窗口中,您可以看到帶有包和Main
類的項目結構。中間的窗口顯示了 pom.xml 如何尋找我。我還添加了一個屬性部分。此部分告訴 Maven 我在源文件中使用的 Java 版本以及要編譯的版本。這只是為了讓 IDEA 不會警告我我正在使用舊版本的 Java。這是可選的 :) 右邊的窗口清楚地表明,即使我們只連接了 spring-context 模塊,它也會自動拉入 spring-core、spring-beans、spring-aop 和 spring-expression 模塊。我們本可以分別連接每個模塊,在 pom.xml 文件中為每個模塊寫出一個明確版本的依賴關係,但現在我們對它們的現狀很滿意。現在創建entities
包並在其中創建 3 個類:Cat
, Dog
, Parrot
。讓我們給每隻動物起個名字(private String name
- 你可以在那裡硬編碼一些值)。getter/setter 是公開的。現在我們繼續討論Main
類和main()
方法,我們這樣寫:
public static void main(String[] args) {
// Create an empty Spring context that will look for its own beans based on the annotations in the specified package
ApplicationContext context =
new AnnotationConfigApplicationContext("en.codegym.info.fatfaggy.animals.entities");
Cat cat = context.getBean(Cat.class);
Dog dog = (Dog) context.getBean("dog");
Parrot parrot = context.getBean("parrot-polly", Parrot.class);
System.out.println(cat.getName());
System.out.println(dog.getName());
System.out.println(parrot.getName());
}
首先,我們創建一個上下文對象,告訴構造函數要在哪個包中查找 bean。換句話說,Spring 將遍歷這個包並嘗試找到標有特殊註釋的類,表明它們是 beans。然後它創建這些類的對象並將它們放在上下文中。之後,我們從這個上下文中得到一隻貓。我們調用上下文對像給我們一個bean(對象),指明我們想要的對象的類(順便說一句,我們也可以指定接口,而不僅僅是類)。之後,Spring 返回一個請求類的對象,然後我們將其保存在一個變量中。接下來,我們要求 Spring 為我們提供一個名為“dog”的 bean。當 Spring 創建一個Dog
object,它給對像一個標準名稱(除非創建的 bean 已經明確分配了一個名稱),這是類名,但首字母小寫。在本例中,我們的類名為Dog
,因此 bean 的名稱為“dog”。如果我們BufferedReader
在那裡需要一個對象,那麼 Spring 會將其命名為“bufferedReader”。而且因為 Java 不能 100% 確定我們想要哪個類,它返回一個Object
對象,然後我們手動將其轉換為所需的類型,即Dog
. 明確指示類的選項更方便。第三個選項是通過類名和 bean 名獲取 bean。上下文可能有一個類的多個 bean。為了指示我們需要的特定 bean,我們指示它的名稱。因為我們在這裡也明確指出了類,所以我們不再需要執行強制轉換。 重要的!如果Spring找到了幾個符合我們要求的bean,那麼它就不能確定給我們哪個bean,所以會拋出異常。因此,為避免這種情況,您應該嘗試盡可能具體地告訴 Spring 您需要哪個 bean。如果 Spring 搜索其上下文,但未能找到符合我們要求的單個 bean,那麼它也會拋出異常。最後,我們簡單地顯示我們動物的名字來驗證我們真的得到了我們需要的對象。但是,如果我們現在運行該程序,我們會發現 Spring 不高興 — 它無法在其上下文中找到我們需要的動物。這是因為它還沒有創建這些 bean。正如我之前所說,當 Spring 掃描類時,它會查找自己的 Spring 註釋。如果 Spring 沒有找到這些註解,那麼它就不會 不要認為這些類對應於它需要創建的 beans。解決這個問題只需要添加@Component
我們每個動物類前面的註釋。
@Component
public class Cat {
private String name = "Oscar";
...
}
但還有更多。如果我們需要顯式地告訴 Spring 這個類的 bean 應該有一個特定的名稱,我們在註釋後的括號中指明名稱。例如,要告訴 Spring 將名稱“ parrot-polly
”賦予鸚鵡 bean,這是我們將在方法中用來獲取這只鸚鵡的名稱main
,我們應該這樣做:
@Component("parrot-polly")
public class Parrot {
private String name = "Polly";
...
}
這就是自動配置 的重點。您編寫您的類,用必要的註釋標記它們,並告訴 Spring 包含您的類的包。這是框架將運行以查找註釋和創建這些類的對象的包。順便說一句,Spring 不僅會查找@Component
註解,還會查找繼承此註解的所有其他註解。比如 , @Controller
, @RestController
, @Service
,@Repository
等等,我們會在以後的文章中介紹。現在我們將嘗試使用基於 Java 的配置來做同樣的事情。首先,刪除@Component
我們班級的註釋。為了使事情更具挑戰性,假設我們沒有編寫這些類,所以我們不能輕易修改它們,這意味著我們不能添加註釋。就像這些類被打包在某個庫中一樣。在這種情況下,我們無法編輯這些類以使其被 Spring 識別。但是我們需要這些類的對象!這裡我們需要基於 Java 的配置來創建對象。首先,創建一個名稱類似於configs
. 在這個包中,創建一個普通的 Java 類,如MyConfig
,並用@Configuration
註解標記它。
@Configuration
public class MyConfig {
}
現在我們需要調整main()
方法,改變我們創建上下文的方式。我們可以明確指出哪個類具有我們的配置:
ApplicationContext context =
new AnnotationConfigApplicationContext(MyConfig.class);
如果我們有幾個創建 bean 的不同類,並且我們想同時連接其中的幾個,我們只需將它們全部顯示在那裡,用逗號分隔:
ApplicationContext context =
new AnnotationConfigApplicationContext(MyConfig.class, MyAnotherConfig.class);
如果我們有太多它們並且我們想同時連接它們,那麼我們只需指出它們包含的包的名稱:
ApplicationContext context =
new AnnotationConfigApplicationContext("en.codegym.info.fatfaggy.animals.configs");
在這種情況下,Spring 將遍歷包並找到所有標有@Configuration
註解的類。好吧,如果我們有一個非常大的程序,其中配置分為不同的包,那麼我們只需指出包含配置的包名稱的逗號分隔列表:
ApplicationContext context =
new AnnotationConfigApplicationContext("en.codegym.info.fatfaggy.animals.database.configs",
"en.codegym.info.fatfaggy.animals.root.configs",
"en.codegym.info.fatfaggy.animals.web.configs");
或者它們都通用的包的名稱:
ApplicationContext context =
new AnnotationConfigApplicationContext("en.codegym.info.fatfaggy.animals");
您可以隨心所欲地進行操作,但在我看來,第一個選項(僅指示具有配置的類)最適合我們的程序。在創建上下文時,Spring 會查找標有@Configuration
註解的類,並創建這些類的自己的對象。它嘗試調用標有@Bean
註解的方法,這意味著這些方法返回 Spring 將添加到上下文中的 beans(對象)。現在,我們將使用基於 Java 的配置為類中的貓、狗和鸚鵡創建 bean。這很簡單:
@Bean
public Cat getCat() {
return new Cat();
}
在這裡,我們手動創建我們的貓並將其交給 Spring,然後 Spring 將我們的對象保存在它的上下文中。由於我們沒有顯式地為我們的 bean 命名,Spring 將為其命名為與方法名稱相同的名稱。在我們的例子中,貓豆將被稱為“ getCat
”。但是因為我們使用類而不是名稱來在方法中獲取 cat bean main
,所以 bean 的名稱對我們來說並不重要。同樣,創建一個 dog bean,請記住 Spring 會將方法名稱賦予該 bean。要顯式命名我們的鸚鵡 bean,我們只需在註釋後的括號中指明它的名稱@Bean
:
@Bean("parrot-polly")
public Object weNeedMoreParrots() {
return new Parrot();
}
如您所見,我在這裡指定了一個Object
返回類型並為該方法指定了一個任意名稱。這不會影響 bean 的名稱,因為我們在這裡明確指定了名稱。不過,最好還是指示一個或多或少有意義的返回值和方法名稱。這樣做只是為了在一年後重新打開項目時幫自己一個忙。:) 現在考慮我們需要一個 bean 來創建另一個 bean的情況。例如,假設我們希望 cat bean 中的貓的名字是鸚鵡的名字加上字符串“-killer”。沒問題!
@Bean
public Cat getCat(Parrot parrot) {
Cat cat = new Cat();
cat.setName(parrot.getName() + "-killer");
return cat;
}
這裡Spring會看到為了創建這個bean,框架需要傳入之前創建的parrot bean。因此,它將安排必要的方法調用鏈:首先,調用創建鸚鵡的方法,然後框架將新的鸚鵡傳遞給創建貓的方法。這就是依賴注入發揮作用的地方:Spring 本身將所需的鸚鵡 bean 傳遞給我們的方法。如果 IDEA 對變量不滿意parrot
,請不要忘記將鸚鵡創建方法的返回類型從 更改Object
為Parrot
。此外,基於 Java 的配置讓您可以運行任何 Java 代碼在你的 bean 創建方法中。你真的可以做任何事情:創建其他輔助對象,調用任何其他方法,甚至是那些沒有用 Spring 註釋標記的方法,創建循環,布爾條件——任何想到的!這在自動配置中並非都是可能的,而在 XML 配置中更是如此。 現在讓我們考慮一個更有趣的問題。多態性和接口 :) 我們將創建一個WeekDay
接口並創建 7 個實現該接口的類:Monday
, Tuesday
, Wednesday
, Thursday
, Friday
, Saturday
, Sunday
。我們將為接口提供一個String getWeekDayName()
方法,該方法將返回相應類的星期幾的名稱。換句話說,Monday
類將返回“Monday
" 等。在啟動應用程序時,假設我們的任務是將對應於星期幾的 bean 放入上下文中。不是所有實現該接口的類的 bean — 只有我們需要的一個 bean。您WeekDay
可以這樣做:
@Bean
public WeekDay getDay() {
DayOfWeek dayOfWeek = LocalDate.now().getDayOfWeek();
switch (dayOfWeek) {
case MONDAY: return new Monday();
case TUESDAY: return new Tuesday();
case WEDNESDAY: return new Wednesday();
case THURSDAY: return new Thursday();
case FRIDAY: return new Friday();
case SATURDAY: return new Saturday();
default: return new Sunday();
}
}
這裡的返回類型是我們的接口。該方法返回實現該接口的類之一的真實對象,具體取決於星期幾。現在我們可以在方法中執行以下操作main()
:
WeekDay weekDay = context.getBean(WeekDay.class);
System.out.println("Today is " + weekDay.getWeekDayName() + "!");
對我來說,程序告訴我今天是星期天 :) 我相信如果我明天運行程序,上下文將包含一個完全不同的對象。請注意,我們只是使用接口獲取 bean:context.getBean(WeekDay.class)
。Spring 將在其上下文中搜索實現該接口的 bean,並將其返回。然後事實證明我們的WeekDay
變量以 Sunday 對象結束,並且當我們使用這個變量時,熟悉的多態性概念適用。:) 現在簡單介紹一下組合方法,其中一些 bean 由 Spring 自動創建,一些通過掃描包中帶有註釋的類創建@Component
,另一些則通過基於 Java 的配置創建。當我們考慮這一點時,我們將返回到原始版本,其中Cat
、Dog
和Parrot
類標有註釋@Component
。假設我們想通過讓 Spring自動掃描包來為我們的動物創建 bean entities
,但我們也想創建一個帶有星期幾的 bean,就像我們剛剛所做的那樣。您需要做的就是@ComponentScan
在類級別添加註釋MyConfig
,我們在創建上下文時指出main()
,並在括號中指出需要掃描的包並自動創建必要類的bean:
@Configuration
@ComponentScan("en.codegym.info.fatfaggy.animals.entities")
public class MyConfig {
@Bean
public WeekDay getDay() {
DayOfWeek dayOfWeek = LocalDate.now().getDayOfWeek();
switch (dayOfWeek) {
case MONDAY: return new Monday();
case TUESDAY: return new Tuesday();
case WEDNESDAY: return new Wednesday();
case THURSDAY: return new Thursday();
case FRIDAY: return new Friday();
case SATURDAY: return new Saturday();
default: return new Sunday();
}
}
}
創建上下文時,Spring 發現它需要處理該類MyConfig
。它進入該類並發現它需要掃描“ en.codegym.info.fatfaggy.animals.entities
”包並創建這些類的 bean,然後執行該類MyConfig
的getDay()
方法並將一個WeekDay
bean 添加到上下文中。在該main()
方法中,我們現在可以訪問所需的所有 bean:動物對象和帶有星期幾的 bean。如果您需要讓 Spring 也獲取一些 XML 配置文件,您可以自己進行網絡搜索以找到解釋:) 總結:
- 嘗試使用自動配置
- 自動配置時,指明需要創建bean的類所在的包名
- 這些類標有
@Component
註解 - Spring遍歷所有這些類,創建對象,並將它們放入上下文中;
- 如果由於某種原因自動配置不適合我們,我們使用基於 Java 的配置
- 在這種情況下,我們創建一個普通的 Java 類,其方法返回我們需要的對象。
@Configuration
如果我們要掃描整個包而不是在創建上下文時用配置指示特定類,我們會用註解標記這個類 - 此類返回 bean 的方法用註解
@Bean
標記
@ComponentScan
。
GO TO FULL VERSION