1. Spring手動裝配基礎(chǔ)
??在Spring
中,手動裝配通常是指通過XML
配置文件明確指定Bean
及其依賴,或者在代碼中直接使用new
關(guān)鍵字創(chuàng)建對象并設(shè)定依賴關(guān)系。
??然而,隨著Spring 2.0
引入注解,以及Spring 3.0
全面支持注解驅(qū)動開發(fā),這個過程變得更加自動化。例如,通過使用@Component + @ComponentScan
,Spring
可以自動地找到并創(chuàng)建bean
,通過@Autowired
,Spring
可以自動地注入依賴。這種方式被稱為 “自動裝配”。
??對于手動裝配,最常見的場景可能是在不使用Spring
的上下文的單元測試或者簡單的POJO
類中,通過new
關(guān)鍵字直接創(chuàng)建對象和設(shè)定依賴關(guān)系。比如下面這段代碼:
public class Main {
public static void main(String[] args) {
ServiceA serviceA = new ServiceA();
ServiceB serviceB = new ServiceB(serviceA);
//...
}
}
??在這個例子中,我們顯式地創(chuàng)建了ServiceA
和ServiceB
的對象,并將ServiceA
的對象作為依賴傳遞給了ServiceB
。這就是一個典型的手動裝配的例子。
??需要注意的是,手動裝配的使用通常是有限的,因為它需要開發(fā)者顯式地在代碼中管理對象的創(chuàng)建和依賴關(guān)系,這在大型應(yīng)用中可能會變得非常復(fù)雜和難以管理。因此,Spring
的自動裝配機制(例如@Autowired
注解,或者@Configuration
和@Bean
的使用)通常是更常見和推薦的方式。
2. Spring框架中的模塊裝配
??模塊裝配就是將我們的類或者組件注冊到Spring
的IoC
(Inversion of Control
,控制反轉(zhuǎn))容器中,以便于Spring
能夠管理這些類,并且在需要的時候能夠為我們自動地將它們注入到其他的組件中。
在Spring
框架中,有多種方式可以實現(xiàn)模塊裝配,包括:
-
基于Java的配置:通過使用
@Configuration
和@Bean
注解在Java
代碼中定義的Bean
。這是一種聲明式的方式,我們可以明確地控制Bean
的創(chuàng)建過程,也可以使用@Value
和@PropertySource
等注解來處理配置屬性。 -
基于XML的配置:
Spring
也支持通過XML
配置文件定義Bean
,這種方式在早期的Spring
版本中更常見,但現(xiàn)在基于Java
的配置方式更為主流。 -
基于注解的組件掃描:通過使用
@Component
、@Service
、@Repository
、@Controller
等注解以及@ComponentScan
來自動檢測和注冊Bean
。這是一種隱式的方式,Spring
會自動掃描指定的包來查找?guī)в羞@些注解的類,并將這些類注冊為Bean
。 -
使用@Import:這是一種顯式的方式,可以通過它直接注冊類到
IOC
容器中,無需這些類帶有@Component
或其他特殊注解。我們可以使用它來注冊普通的類,或者注冊實現(xiàn)了ImportSelector
或ImportBeanDefinitionRegistrar
接口的類,以提供更高級的裝配能力。
??每種方式都有其應(yīng)用場景,根據(jù)具體的需求,我們可以選擇合適的方式來實現(xiàn)模塊裝配。比如在Spring Boot
中,我們?nèi)粘i_發(fā)可能會更多地使用基于Java
的配置和基于注解的組件掃描來實現(xiàn)模塊裝配。
2.1 @Import注解簡單使用
??@Import
是一個強大的注解,它為我們提供了一個快速、方便的方式,使我們可以將需要的類或者配置類直接裝配到Spring IOC
容器中。這個注解在模塊裝配的上下文中特別有用。
我們先來看一下簡單的應(yīng)用,后面再詳細介紹
全部代碼如下:
Book.java
package com.example.demo.bean;
public class Book {
private String name;
public Book() {
this.name = "Imported Book";
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Book{" +
"name='" + name + '\'' +
'}';
}
}
LibraryConfig.java
package com.example.demo.configuration;
import com.example.demo.bean.Book;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@Import(Book.class)
public class LibraryConfig {
}
??使用 @Import
注解來導(dǎo)入一個普通的類(即一個沒有使用 @Component
或者 @Service
之類的注解標(biāo)記的類),Spring
會為該類創(chuàng)建一個 Bean
,并且這個 Bean
的名字默認(rèn)就是這個類的全限定類名。
主程序:
package com.example.demo;
import com.example.demo.bean.Book;
import com.example.demo.configuration.LibraryConfig;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class DemoApplication {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(LibraryConfig.class);
Book book = context.getBean(Book.class);
System.out.println(book);
}
}
運行結(jié)果如下:
3. @Import模塊裝配的四種方式
3.1 @Import注解的功能介紹
??在Spring
中,有時候我們需要將某個類(可能是一個普通類,可能是一個配置類等等)導(dǎo)入到我們的應(yīng)用程序中。Spring
提供了四種主要的方式來完成這個任務(wù),后面我們會分別解釋。
@Import
注解可以有以下幾種使用方式:
-
導(dǎo)入普通類:可以將普通類(沒有被
@Component
或者@Service
等注解標(biāo)注的類)導(dǎo)入到Spring
的IOC
容器中,Spring
會為這個類創(chuàng)建一個Bean
,這個Bean
的名字默認(rèn)為類的全限定類名。 -
導(dǎo)入配置類:可以將一個或多個配置類(被
@Configuration
注解標(biāo)注的類)導(dǎo)入到Spring
的IOC
容器中,這樣我們就可以一次性地將這個配置類中定義的所有Bean
導(dǎo)入到Spring
的IOC
容器中。 -
使用ImportSelector接口:如果想動態(tài)地導(dǎo)入一些
Bean
到Spring
的IOC
容器中,那么可以實現(xiàn)ImportSelector
接口,然后在@Import
注解中引入ImportSelector
實現(xiàn)類,這樣Spring
就會將ImportSelector
實現(xiàn)類返回的類導(dǎo)入到Spring
的IOC
容器中。 -
使用ImportBeanDefinitionRegistrar接口:如果想在運行時動態(tài)地注冊一些
Bean
到Spring
的IOC
容器中,那么可以實現(xiàn)ImportBeanDefinitionRegistrar
接口,然后在@Import
注解中引入ImportBeanDefinitionRegistrar
實現(xiàn)類,這樣Spring
就會將ImportBeanDefinitionRegistrar
實現(xiàn)類注冊的Bean
導(dǎo)入到Spring
的IOC
容器中。
??@Import
注解主要用于手動裝配,它可以讓我們顯式地導(dǎo)入特定的類或者其他配置類到Spring
的IOC
容器中。特別是當(dāng)我們需要引入第三方庫中的類,或者我們想要顯式地控制哪些類被裝配進Spring
的IOC
容器時,@Import
注解會非常有用。它不僅可以直接導(dǎo)入普通的 Java
類并將其注冊為 Bean
,還可以導(dǎo)入實現(xiàn)了 ImportSelector
或 ImportBeanDefinitionRegistrar
接口的類。這兩個接口提供了更多的靈活性和控制力,使得我們可以在運行時動態(tài)地注冊 Bean
,這是通過 @Configuration + @Bean
注解組合無法做到的。
??例如,通過 ImportSelector
接口,可以在運行時決定需要導(dǎo)入哪些類。而通過 ImportBeanDefinitionRegistrar
接口,可以在運行時控制 Bean
的定義,包括 Bean
的名稱、作用域、構(gòu)造參數(shù)等等。
??雖然 @Configuration + @Bean
在許多情況下都足夠使用,但 @Import
注解由于其更大的靈活性和控制力,在處理更復(fù)雜的場景時,可能會是一個更好的選擇。
3.2 導(dǎo)入普通類與自定義注解的使用
我們第2
節(jié)的例子也是導(dǎo)入普通類,這里加一點難度,延伸到自定義注解的使用。
背景:圖書館模塊裝配
在這個例子中,我們將創(chuàng)建一個圖書館系統(tǒng),包括圖書館(Library
)類、圖書館管理員(Librarian
)類、圖書(Book
)類,還有書架(BookShelf
)類。我們的目標(biāo)是創(chuàng)建一個圖書館,并將所有組件裝配到一起。
首先,我們創(chuàng)建一個自定義@ImportLibrary
注解,通過此注解我們將把所有相關(guān)的類裝配到圖書館里面:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import({Librarian.class, Book.class, BookShelf.class})
public @interface ImportLibrary {
}
??這個@ImportLibrary
注解內(nèi)部實際上使用了@Import
注解。當(dāng)Spring
處理@Import
注解時,會將其參數(shù)指定的類添加到Spring
應(yīng)用上下文中。當(dāng)我們在Library
類上使用@ImportLibrary
注解時,Spring
會將Librarian.class
、Book.class
和BookShelf.class
這三個類添加到應(yīng)用上下文中。
??然后,我們創(chuàng)建圖書館管理員(Librarian
)、圖書(Book
)、書架(BookShelf
)這三個類:
Librarian.java
package com.example.demo.bean;
public class Librarian {
public void manage() {
System.out.println("The librarian is managing the library.");
}
}
Book.java
package com.example.demo.bean;
public class Book {
private String name;
// @ImportLibrary里面有@Import會自動裝配,會調(diào)用無參構(gòu)造,不寫會報錯
public Book() {
}
public Book(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
BookShelf.java
package com.example.demo.bean;
import java.util.List;
public class BookShelf {
private List<Book> books;
// @ImportLibrary里面有@Import會自動裝配,會調(diào)用無參構(gòu)造,不寫會報錯
public BookShelf() {
}
public BookShelf(List<Book> books) {
this.books = books;
}
public List<Book> getBooks() {
return books;
}
}
最后,我們創(chuàng)建一個圖書館(Library
)類,并在這個類上使用我們剛剛創(chuàng)建的@ImportLibrary
注解:
package com.example.demo.configuration;
import com.example.demo.annotations.ImportLibrary;
import com.example.demo.bean.Book;
import com.example.demo.bean.BookShelf;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Arrays;
@ImportLibrary
@Configuration
public class Library {
@Bean
public Book book1() {
return new Book("The Catcher in the Rye");
}
@Bean
public Book book2() {
return new Book("To Kill a Mockingbird");
}
@Bean
public BookShelf bookShelf(Book book1, Book book2) {
return new BookShelf(Arrays.asList(book1, book2));
}
}
然后我們可以創(chuàng)建一個啟動類并初始化IOC
容器,看看是否可以成功獲取到Librarian
類、BookShelf
類和Book
類的實例:
package com.example.demo;
import com.example.demo.bean.Book;
import com.example.demo.bean.BookShelf;
import com.example.demo.bean.Librarian;
import com.example.demo.configuration.Library;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class DemoApplication {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Library.class);
// 這行代碼供調(diào)試查看使用
String[] beanDefinitionNames = context.getBeanDefinitionNames();
Librarian librarian = context.getBean(Librarian.class);
BookShelf bookShelf = context.getBean("bookShelf", BookShelf.class);
Book book1 = (Book) context.getBean("book1");
Book book2 = (Book) context.getBean("book2");
librarian.manage();
bookShelf.getBooks().forEach(book -> System.out.println("Book: " + book.getName()));
}
}
這個例子中,我們通過@Import
注解一次性把Librarian
、Book
和BookShelf
這三個類導(dǎo)入到了Spring
的IOC
容器中,這就是模塊裝配的強大之處。
調(diào)試結(jié)果
當(dāng)我們使用 @Import
注解來導(dǎo)入一個普通的類(即一個沒有使用 @Component
或者 @Service
之類的注解標(biāo)記的類),Spring
會為該類創(chuàng)建一個 Bean
,并且這個 Bean
的名字默認(rèn)就是這個類的全限定類名。
運行結(jié)果:
3.3 導(dǎo)入配置類的策略
這里使用Spring
的 @Import
注解導(dǎo)入配置類,我們將創(chuàng)建一個BookConfig
類和LibraryConfig
類,然后在主應(yīng)用類中獲取Book
實例。
全部代碼如下:
創(chuàng)建一個配置類BookConfig
,用于創(chuàng)建和配置Book
實例:
package com.example.demo.configuration;
import com.example.demo.bean.Book;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class BookConfig {
@Bean
public Book book() {
Book book = new Book();
book.setName("Imported Book");
return book;
}
}
在這里,我們定義了一個Book
類:
package com.example.demo.bean;
public class Book {
private String name;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
創(chuàng)建一個配置類LibraryConfig
,使用@Import
注解來導(dǎo)入BookConfig
類:
package com.example.demo.configuration;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@Import(BookConfig.class)
public class LibraryConfig {
}
主程序:
package com.example.demo;
import com.example.demo.bean.Book;
import com.example.demo.configuration.LibraryConfig;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class DemoApplication {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(LibraryConfig.class);
Book book = context.getBean(Book.class);
System.out.println(book.getName());
}
}
運行結(jié)果:
在這個例子中,當(dāng)Spring
容器啟動時,它會通過@Import
注解將BookConfig
類導(dǎo)入到Spring
上下文中,并創(chuàng)建一個Bean
。然后我們可以在主程序中通過context.getBean(Book.class)
獲取到Book
的實例,并打印出書名。
3.4 使用ImportSelector進行選擇性裝配
如果我們想動態(tài)地選擇要導(dǎo)入的類,我們可以使用一個ImportSelector
實現(xiàn)。
全部代碼如下:
定義一個 Book
類:
package com.example.demo.bean;
public class Book {
private String name = "java從入門到精通";
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
創(chuàng)建圖書館管理員Librarian
類
package com.example.demo.bean;
public class Librarian {
public void manage() {
System.out.println("The librarian is managing the library.");
}
}
定義一個 BookImportSelector
,實現(xiàn) ImportSelector
接口:
package com.example.demo.configuration;
import com.example.demo.bean.Librarian;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
public class BookImportSelector implements ImportSelector {
/**
* 這里示范2種方式,一種是拿到class文件后getName,一種是直接寫全限定類名
* @param importingClassMetadata
* @return
*/
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[] { "com.example.demo.bean.Book", Librarian.class.getName() };
}
}
??ImportSelector
接口可以在運行時動態(tài)地選擇需要導(dǎo)入的類。實現(xiàn)該接口的類需要實現(xiàn)selectImports
方法,這個方法返回一個字符串?dāng)?shù)組,數(shù)組中的每個字符串代表需要導(dǎo)入的類的全類名,我們可以直接在這里將 Book
類和 Librarian
類加入到了 Spring
容器中。
使用Class.getName()
方法獲取全限定類名的方式,比直接硬編碼類的全名為字符串更推薦,原因如下:
-
避免錯誤:如果類名或包名有所改動,硬編碼的字符串可能不會跟隨變動,這可能導(dǎo)致錯誤。而使用
Class.getName()
方法,則會隨類的改動自動更新,避免此類錯誤。 -
代碼清晰:使用
Class.getName()
能讓讀代碼的人更清楚地知道你是要引用哪一個類。 - 增強代碼的可讀性和可維護性:使用類的字節(jié)碼獲取全限定類名,使得代碼閱讀者可以清晰地知道這是什么類,增加了代碼的可讀性。同時,也方便了代碼的維護,因為在修改類名或者包名時,不需要手動去修改硬編碼的類名。
定義一個配置類 LibraryConfig
,使用 @Import
注解導(dǎo)入 BookImportSelector
:
package com.example.demo.configuration;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@Import(BookImportSelector.class)
public class LibraryConfig {
}
創(chuàng)建一個主應(yīng)用類,從 Spring
的AnnotationConfigApplicationContext
中獲取 Book
的實例:
package com.example.demo;
import com.example.demo.bean.Book;
import com.example.demo.bean.Librarian;
import com.example.demo.configuration.LibraryConfig;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class DemoApplication {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(LibraryConfig.class);
Book book = context.getBean(Book.class);
Librarian librarian = context.getBean(Librarian.class);
System.out.println(book.getName());
librarian.manage();
}
}
運行結(jié)果:
??在 Spring Boot
中,ImportSelector
被大量使用,尤其在自動配置(auto-configuration
)機制中起著關(guān)鍵作用。例如,AutoConfigurationImportSelector
類就是間接實現(xiàn)了 ImportSelector
,用于自動導(dǎo)入所有 Spring Boot
的自動配置類。
??我們通常會在Spring Boot
啟動類上使用 @SpringBootApplication
注解,實際上,@SpringBootApplication
注解中也包含了 @EnableAutoConfiguration
,@EnableAutoConfiguration
是一個復(fù)合注解,它的實現(xiàn)中導(dǎo)入了普通類 @Import(AutoConfigurationImportSelector.class)
,AutoConfigurationImportSelector
類間接實現(xiàn)了 ImportSelector
接口,用于自動導(dǎo)入所有 Spring Boot
的自動配置類。
如下圖:
3.5 使用ImportBeanDefinitionRegistrar進行動態(tài)裝配
??ImportBeanDefinitionRegistrar
接口的主要功能是在運行時動態(tài)的往Spring
容器中注冊Bean
,實現(xiàn)該接口的類需要重寫registerBeanDefinitions
方法,這個方法可以通過參數(shù)中的BeanDefinitionRegistry
接口向Spring
容器注冊新的類,給應(yīng)用提供了更大的靈活性。
全部代碼如下:
首先,定義一個 Book
類:
package com.example.demo.bean;
public class Book {
private String name = "java從入門到精通";
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
定義一個 BookRegistrar
類,實現(xiàn) ImportBeanDefinitionRegistrar
接口:
package com.example.demo.configuration;
import com.example.demo.bean.Book;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
public class BookRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(Book.class);
// 通過反射技術(shù)調(diào)用setter方法給name賦值,也可以在構(gòu)造器賦值name,name需要調(diào)用beanDefinitionBuilder.addConstructorArgValue("戰(zhàn)爭與和平");
beanDefinitionBuilder.addPropertyValue("name", "戰(zhàn)爭與和平");
BeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
beanDefinition.setScope(BeanDefinition.SCOPE_PROTOTYPE);
registry.registerBeanDefinition("myBook", beanDefinition);
}
}
下面來詳細解釋一下BookRegistrar
類里面的registerBeanDefinitions
方法和參數(shù)。
-
AnnotationMetadata importingClassMetadata: 這個參數(shù)表示當(dāng)前被
@Import
注解導(dǎo)入的類的所有注解信息,它包含了該類上所有注解的詳細信息,比如注解的名稱,注解的參數(shù)等等。 -
BeanDefinitionRegistry registry: 這個參數(shù)是
Spring
的Bean
定義注冊類,我們可以通過它往Spring
容器中注冊Bean
。在這里,我們使用它來注冊我們的Book Bean
。
??在方法registerBeanDefinitions
中,我們創(chuàng)建了一個BeanDefinition
,并將其注冊到Spring
的BeanDefinitionRegistry
中。
??代碼首先通過BeanDefinitionBuilder.genericBeanDefinition(Book.class)
創(chuàng)建一個BeanDefinitionBuilder
實例,這個實例用于構(gòu)建一個BeanDefinition
。我們使用addPropertyValue("name", "戰(zhàn)爭與和平")
為該BeanDefinition
添加一個name
屬性值。
??接著我們通過beanDefinitionBuilder.getBeanDefinition()
方法得到BeanDefinition
實例,并設(shè)置其作用域為原型作用域,這表示每次從Spring
容器中獲取該Bean
時,都會創(chuàng)建一個新的實例。
??最后,我們將這個BeanDefinition
以名字 "myBook"
注冊到BeanDefinitionRegistry
中。這樣,我們就可以在Spring
容器中通過名字 "myBook"
來獲取我們的Book
類的實例了。
接著定義一個配置類 LibraryConfig
,使用 @Import
注解導(dǎo)入 BookRegistrar
:
package com.example.demo.configuration;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@Import(BookRegistrar.class)
public class LibraryConfig {
}
創(chuàng)建一個主應(yīng)用類,從 Spring ApplicationContext
中獲取 Book
的實例:
package com.example.demo;
import com.example.demo.bean.Book;
import com.example.demo.configuration.LibraryConfig;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class DemoApplication {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(LibraryConfig.class);
Book book = context.getBean("myBook", Book.class);
System.out.println(book.getName());
}
}
運行結(jié)果:
??在這個例子中,我們使用 AnnotationConfigApplicationContext
初始化 Spring
容器并提供配置類。然后通過 context.getBean("book", Book.class)
從 Spring
容器中獲取名為 book
的實例。
??ImportBeanDefinitionRegistrar
接口提供了非常大的靈活性,我們可以根據(jù)自己的需求編寫任何需要的注冊邏輯。這對于構(gòu)建復(fù)雜的、高度定制的Spring應(yīng)用是非常有用的。文章來源:http://www.zghlxwxcb.cn/news/detail-539160.html
??Spring Boot
就廣泛地使用了ImportBeanDefinitionRegistrar
。例如,它的@EnableConfigurationProperties
注解就是通過使用一個ImportBeanDefinitionRegistrar
來將配置屬性綁定到Beans
上的,這就是ImportBeanDefinitionRegistrar
在實踐中的一個實際應(yīng)用的例子。文章來源地址http://www.zghlxwxcb.cn/news/detail-539160.html
歡迎一鍵三連~
有問題請留言,大家一起探討學(xué)習(xí)
----------------------Talk is cheap, show me the code-----------------------
到了這里,關(guān)于Spring高手之路8——Spring Bean模塊裝配的藝術(shù):@Import詳解的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!