本文共享自华为云社区《Spring高手之路8——Spring Bean模块安装的艺术:@Import详解》,作者: 砖业洋__。
本文将带你深入探索Spring结构的安装机制,以及它如何使你的代码更具模块化和灵活性。咱们首要介绍Spring手动安装的根底知识,然后进一步解析@Import注解在模块安装中的关键角色。文章包含从导入一般类、装备类,到运用ImportSelector和ImportBeanDefinitionRegistrar进行动态和选择性安装等多个层次,旨在协助读者全面理解和把握Spring的安装技能。
1. Spring手动安装根底
在Spring
中,手动安装通常是指经过XML
装备文件明确指定Bean
及其依靠,或许在代码中直接运用new
关键字创立目标并设定依靠联系。
但是,随着Spring 2.0
引进注解,以及Spring 3.0
全面支撑注解驱动开发,这个进程变得更加主动化。例如,经过运用@Component + @ComponentScan
,Spring
能够主动地找到并创立bean
,经过@Autowired
,Spring
能够主动地注入依靠。这种办法被称为 “主动安装”。
关于手动安装,最常见的场景或许是在不运用Spring
的上下文的单元测试或许简略的POJO
类中,经过new
关键字直接创立目标和设定依靠联系。比方下面这段代码:
public class Main {
public static void main(String[] args) {
ServiceA serviceA = new ServiceA();
ServiceB serviceB = new ServiceB(serviceA);
//...
}
}
在这个比方中,咱们显式地创立了ServiceA
和ServiceB
的目标,并将ServiceA
的目标作为依靠传递给了ServiceB
。这便是一个典型的手动安装的比方。
需求留意的是,手动安装的运用通常是有限的,因为它需求开发者显式地在代码中办理目标的创立和依靠联系,这在大型运用中或许会变得非常杂乱和难以办理。因而,Spring
的主动安装机制(例如@Autowired
注解,或许@Configuration
和@Bean
的运用)通常是更常见和推荐的办法。
2. Spring结构中的模块安装
模块安装便是将咱们的类或许组件注册到Spring
的IoC
(Inversion of Control
,操控反转)容器中,以便于Spring
能够办理这些类,并且在需求的时分能够为咱们主动地将它们注入到其他的组件中。
在Spring
结构中,有多种办法能够完结模块安装,包含:
-
依据Java的装备:经过运用
@Configuration
和@Bean
注解在Java
代码中界说的Bean
。这是一种声明式的办法,咱们能够明确地操控Bean
的创立进程,也能够运用@Value
和@PropertySource
等注解来处理装备属性。 -
依据XML的装备:
Spring
也支撑经过XML
装备文件界说Bean
,这种办法在前期的Spring
版别中更常见,但现在依据Java
的装备办法更为干流。 -
依据注解的组件扫描:经过运用
@Component
、@Service
、@Repository
、@Controller
等注解以及@ComponentScan
来主动检测和注册Bean
。这是一种隐式的办法,Spring
会主动扫描指定的包来查找带有这些注解的类,并将这些类注册为Bean
。 -
运用@Import:这是一种显式的办法,能够经过它直接注册类到
IOC
容器中,无需这些类带有@Component
或其他特别注解。咱们能够运用它来注册一般的类,或许注册完结了ImportSelector
或ImportBeanDefinitionRegistrar
接口的类,以供给更高级的安装能力。
每种办法都有其运用场景,依据具体的需求,咱们能够选择适宜的办法来完结模块安装。比方在Spring Boot
中,咱们日常开发或许会更多地运用依据Java
的装备和依据注解的组件扫描来完结模块安装。
2.1 @Import注解简略运用
@Import
是一个强壮的注解,它为咱们供给了一个快速、方便的办法,使咱们能够将需求的类或许装备类直接安装到Spring IOC
容器中。这个注解在模块安装的上下文中特别有用。
咱们先来看一下简略的运用,后边再具体介绍
悉数代码如下:
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
注解来导入一个一般的类(即一个没有运用 @Component
或许 @Service
之类的注解符号的类),Spring
会为该类创立一个 Bean
,并且这个 Bean
的姓名默许便是这个类的全限制类名。
主程序:
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);
}
}
运转成果如下:
3. @Import模块安装的四种办法
3.1 @Import注解的功用介绍
在Spring
中,有时分咱们需求将某个类(或许是一个一般类,或许是一个装备类等等)导入到咱们的运用程序中。Spring
供给了四种首要的办法来完结这个任务,后边咱们会分别解释。
@Import
注解能够有以下几种运用办法:
-
导入一般类:能够将一般类(没有被
@Component
或许@Service
等注解标示的类)导入到Spring
的IOC
容器中,Spring
会为这个类创立一个Bean
,这个Bean
的姓名默许为类的全限制类名。 -
导入装备类:能够将一个或多个装备类(被
@Configuration
注解标示的类)导入到Spring
的IOC
容器中,这样咱们就能够一次性地将这个装备类中界说的一切Bean
导入到Spring
的IOC
容器中。 -
运用ImportSelector接口:假如想动态地导入一些
Bean
到Spring
的IOC
容器中,那么能够完结ImportSelector
接口,然后在@Import
注解中引进ImportSelector
完结类,这样Spring
就会将ImportSelector
完结类回来的类导入到Spring
的IOC
容器中。 -
运用ImportBeanDefinitionRegistrar接口:假如想在运转时动态地注册一些
Bean
到Spring
的IOC
容器中,那么能够完结ImportBeanDefinitionRegistrar
接口,然后在@Import
注解中引进ImportBeanDefinitionRegistrar
完结类,这样Spring
就会将ImportBeanDefinitionRegistrar
完结类注册的Bean
导入到Spring
的IOC
容器中。
@Import
注解首要用于手动安装,它能够让咱们显式地导入特定的类或许其他装备类到Spring
的IOC
容器中。特别是当咱们需求引进第三方库中的类,或许咱们想要显式地操控哪些类被安装进Spring
的IOC
容器时,@Import
注解会非常有用。它不仅能够直接导入一般的 Java
类并将其注册为 Bean
,还能够导入完结了 ImportSelector
或 ImportBeanDefinitionRegistrar
接口的类。这两个接口供给了更多的灵活性和操控力,使得咱们能够在运转时动态地注册 Bean
,这是经过 @Configuration + @Bean
注解组合无法做到的。
例如,经过 ImportSelector
接口,能够在运转时决议需求导入哪些类。而经过 ImportBeanDefinitionRegistrar
接口,能够在运转时操控 Bean
的界说,包含 Bean
的称号、效果域、结构参数等等。
尽管 @Configuration + @Bean
在许多情况下都足够运用,但 @Import
注解由于其更大的灵活性和操控力,在处理更杂乱的场景时,或许会是一个更好的选择。
3.2 导入一般类与自界说注解的运用
咱们第2
节的比方也是导入一般类,这儿加一点难度,延伸到自界说注解的运用。
布景:图书馆模块安装
在这个比方中,咱们将创立一个图书馆系统,包含图书馆(Library
)类、图书馆办理员(Librarian
)类、图书(Book
)类,还有书架(BookShelf
)类。咱们的目标是创立一个图书馆,并将一切组件安装到一同。
首要,咱们创立一个自界说@ImportLibrary
注解,经过此注解咱们将把一切相关的类安装到图书馆里边:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import({Librarian.class, Book.class, BookShelf.class})
public @interface ImportLibrary {
}
这个@ImportLibrary
注解内部实际上运用了@Import
注解。当Spring
处理@Import
注解时,会将其参数指定的类增加到Spring
运用上下文中。当咱们在Library
类上运用@ImportLibrary
注解时,Spring
会将Librarian.class
、Book.class
和BookShelf.class
这三个类增加到运用上下文中。
然后,咱们创立图书馆办理员(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会主动安装,会调用无参结构,不写会报错
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会主动安装,会调用无参结构,不写会报错
public BookShelf() {
}
public BookShelf(List<Book> books) {
this.books = books;
}
public List<Book> getBooks() {
return books;
}
}
最终,咱们创立一个图书馆(Library
)类,并在这个类上运用咱们刚刚创立的@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));
}
}
然后咱们能够创立一个发动类并初始化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);
// 这行代码供调试查看运用
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
这三个类导入到了Spring
的IOC
容器中,这便是模块安装的强壮之处。
调试成果
当咱们运用 @Import
注解来导入一个一般的类(即一个没有运用 @Component
或许 @Service
之类的注解符号的类),Spring
会为该类创立一个 Bean
,并且这个 Bean
的姓名默许便是这个类的全限制类名。
运转成果:
3.3 导入装备类的战略
这儿运用Spring
的 @Import
注解导入装备类,咱们将创立一个BookConfig
类和LibraryConfig
类,然后在主运用类中获取Book
实例。
悉数代码如下:
创立一个装备类BookConfig
,用于创立和装备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;
}
}
创立一个装备类LibraryConfig
,运用@Import
注解来导入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());
}
}
运转成果:
在这个比方中,当Spring
容器发动时,它会经过@Import
注解将BookConfig
类导入到Spring
上下文中,并创立一个Bean
。然后咱们能够在主程序中经过context.getBean(Book.class)
获取到Book
的实例,并打印出书名。
3.4 运用ImportSelector进行选择性安装
假如咱们想动态地选择要导入的类,咱们能够运用一个ImportSelector
完结。
悉数代码如下:
界说一个 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;
}
}
创立图书馆办理员Librarian
类
package com.example.demo.bean;
public class Librarian {
public void manage() {
System.out.println("The librarian is managing the library.");
}
}
界说一个 BookImportSelector
,完结 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
接口能够在运转时动态地选择需求导入的类。完结该接口的类需求完结selectImports
办法,这个办法回来一个字符串数组,数组中的每个字符串代表需求导入的类的全类名,咱们能够直接在这儿将 Book
类和 Librarian
类加入到了 Spring
容器中。
运用Class.getName()
办法获取全限制类名的办法,比直接硬编码类的全名为字符串更推荐,原因如下:
-
防止过错:假如类名或包名有所改动,硬编码的字符串或许不会跟随变化,这或许导致过错。而运用
Class.getName()
办法,则会随类的改动主动更新,防止此类过错。 -
代码明晰:运用
Class.getName()
能让读代码的人更清楚地知道你是要引用哪一个类。 - 增强代码的可读性和可维护性:运用类的字节码获取全限制类名,使得代码阅读者能够明晰地知道这是什么类,增加了代码的可读性。一起,也方便了代码的维护,因为在修改类名或许包名时,不需求手动去修改硬编码的类名。
界说一个装备类 LibraryConfig
,运用 @Import
注解导入 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 {
}
创立一个主运用类,从 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();
}
}
运转成果:
在 Spring Boot
中,ImportSelector
被大量运用,尤其在主动装备(auto-configuration
)机制中起着关键效果。例如,AutoConfigurationImportSelector
类便是间接完结了 ImportSelector
,用于主动导入一切 Spring Boot
的主动装备类。
咱们通常会在Spring Boot
发动类上运用 @SpringBootApplication
注解,实际上,@SpringBootApplication
注解中也包含了 @EnableAutoConfiguration
,@EnableAutoConfiguration
是一个复合注解,它的完结中导入了一般类 @Import(AutoConfigurationImportSelector.class)
,AutoConfigurationImportSelector
类间接完结了 ImportSelector
接口,用于主动导入一切 Spring Boot
的主动装备类。
如下图:
3.5 运用ImportBeanDefinitionRegistrar进行动态安装
ImportBeanDefinitionRegistrar
接口的首要功用是在运转时动态的往Spring
容器中注册Bean
,完结该接口的类需求重写registerBeanDefinitions
办法,这个办法能够经过参数中的BeanDefinitionRegistry
接口向Spring
容器注册新的类,给运用供给了更大的灵活性。
悉数代码如下:
首要,界说一个 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
类,完结 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);
// 经过反射技能调用setter办法给name赋值,也能够在结构器赋值name,name需求调用beanDefinitionBuilder.addConstructorArgValue("战争与和平");
beanDefinitionBuilder.addPropertyValue("name", "战争与和平");
BeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
beanDefinition.setScope(BeanDefinition.SCOPE_PROTOTYPE);
registry.registerBeanDefinition("myBook", beanDefinition);
}
}
下面来具体解释一下BookRegistrar
类里边的registerBeanDefinitions
办法和参数。
-
AnnotationMetadata importingClassMetadata: 这个参数表明当前被
@Import
注解导入的类的一切注解信息,它包含了该类上一切注解的具体信息,比方注解的称号,注解的参数等等。 -
BeanDefinitionRegistry registry: 这个参数是
Spring
的Bean
界说注册类,咱们能够经过它往Spring
容器中注册Bean
。在这儿,咱们运用它来注册咱们的Book Bean
。
在办法registerBeanDefinitions
中,咱们创立了一个BeanDefinition
,并将其注册到Spring
的BeanDefinitionRegistry
中。
代码首要经过BeanDefinitionBuilder.genericBeanDefinition(Book.class)
创立一个BeanDefinitionBuilder
实例,这个实例用于构建一个BeanDefinition
。咱们运用addPropertyValue("name", "战争与和平")
为该BeanDefinition
增加一个name
属性值。
接着咱们经过beanDefinitionBuilder.getBeanDefinition()
办法得到BeanDefinition
实例,并设置其效果域为原型效果域,这表明每次从Spring
容器中获取该Bean
时,都会创立一个新的实例。
最终,咱们将这个BeanDefinition
以姓名 "myBook"
注册到BeanDefinitionRegistry
中。这样,咱们就能够在Spring
容器中经过姓名 "myBook"
来获取咱们的Book
类的实例了。
接着界说一个装备类 LibraryConfig
,运用 @Import
注解导入 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 {
}
创立一个主运用类,从 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());
}
}
运转成果:
在这个比方中,咱们运用 AnnotationConfigApplicationContext
初始化 Spring
容器并供给装备类。然后经过 context.getBean("book", Book.class)
从 Spring
容器中获取名为 book
的实例。
ImportBeanDefinitionRegistrar
接口供给了非常大的灵活性,咱们能够依据自己的需求编写任何需求的注册逻辑。这关于构建杂乱的、高度定制的Spring运用是非常有用的。
Spring Boot
就广泛地运用了ImportBeanDefinitionRegistrar
。例如,它的@EnableConfigurationProperties
注解便是经过运用一个ImportBeanDefinitionRegistrar
来将装备属性绑定到Beans
上的,这便是ImportBeanDefinitionRegistrar
在实践中的一个实际运用的比方。
点击关注,第一时间了解华为云新鲜技能~