0


Spring高手之路8——Spring Bean模块装配的艺术:@Import详解

文章目录

1. Spring手动装配基础

  在

Spring

中,手动装配通常是指通过

XML

配置文件明确指定

Bean

及其依赖,或者在代码中直接使用

new

关键字创建对象并设定依赖关系。

  然而,随着

Spring 2.0

引入注解,以及

Spring 3.0

全面支持注解驱动开发,这个过程变得更加自动化。例如,通过使用

@Component + @ComponentScan

Spring

可以自动地找到并创建

bean

,通过

@Autowired

Spring

可以自动地注入依赖。这种方式被称为 “自动装配”。

  对于手动装配,最常见的场景可能是在不使用

Spring

的上下文的单元测试或者简单的

POJO

类中,通过

new

关键字直接创建对象和设定依赖关系。比如下面这段代码:

publicclassMain{publicstaticvoidmain(String[] args){ServiceA serviceA =newServiceA();ServiceB serviceB =newServiceB(serviceA);//...}}

  在这个例子中,我们显式地创建了

ServiceA

ServiceB

的对象,并将

ServiceA

的对象作为依赖传递给了

ServiceB

。这就是一个典型的手动装配的例子。

  需要注意的是,手动装配的使用通常是有限的,因为它需要开发者显式地在代码中管理对象的创建和依赖关系,这在大型应用中可能会变得非常复杂和难以管理。因此,

Spring

的自动装配机制(例如

@Autowired

注解,或者

@Configuration

@Bean

的使用)通常是更常见和推荐的方式。


2. Spring框架中的模块装配

  模块装配就是将我们的类或者组件注册到

Spring

IoC

Inversion of Control

,控制反转)容器中,以便于

Spring

能够管理这些类,并且在需要的时候能够为我们自动地将它们注入到其他的组件中。

Spring

框架中,有多种方式可以实现模块装配,包括:

  1. 基于Java的配置:通过使用@Configuration@Bean注解在Java代码中定义的Bean。这是一种声明式的方式,我们可以明确地控制Bean的创建过程,也可以使用@Value@PropertySource等注解来处理配置属性。
  2. 基于XML的配置Spring也支持通过XML配置文件定义Bean,这种方式在早期的Spring版本中更常见,但现在基于Java的配置方式更为主流。
  3. 基于注解的组件扫描:通过使用@Component@Service@Repository@Controller等注解以及@ComponentScan来自动检测和注册Bean。这是一种隐式的方式,Spring会自动扫描指定的包来查找带有这些注解的类,并将这些类注册为Bean
  4. 使用@Import:这是一种显式的方式,可以通过它直接注册类到IOC容器中,无需这些类带有@Component或其他特殊注解。我们可以使用它来注册普通的类,或者注册实现了ImportSelectorImportBeanDefinitionRegistrar接口的类,以提供更高级的装配能力。

  每种方式都有其应用场景,根据具体的需求,我们可以选择合适的方式来实现模块装配。比如在

Spring Boot

中,我们日常开发可能会更多地使用基于

Java

的配置和基于注解的组件扫描来实现模块装配。

2.1 @Import注解简单使用

@Import

是一个强大的注解,它为我们提供了一个快速、方便的方式,使我们可以将需要的类或者配置类直接装配到

Spring IOC

容器中。这个注解在模块装配的上下文中特别有用。

我们先来看一下简单的应用,后面再详细介绍

全部代码如下:

Book.java

packagecom.example.demo.bean;publicclassBook{privateString name;publicBook(){this.name ="Imported Book";}publicStringgetName(){return name;}@OverridepublicStringtoString(){return"Book{"+"name='"+ name +'\''+'}';}}

LibraryConfig.java

packagecom.example.demo.configuration;importcom.example.demo.bean.Book;importorg.springframework.context.annotation.Configuration;importorg.springframework.context.annotation.Import;@Configuration@Import(Book.class)publicclassLibraryConfig{}

  使用

@Import

注解来导入一个普通的类(即一个没有使用

@Component

或者

@Service

之类的注解标记的类),

Spring

会为该类创建一个

Bean

,并且这个

Bean

的名字默认就是这个类的全限定类名。

主程序:

packagecom.example.demo;importcom.example.demo.bean.Book;importcom.example.demo.configuration.LibraryConfig;importorg.springframework.context.annotation.AnnotationConfigApplicationContext;publicclassDemoApplication{publicstaticvoidmain(String[] args){AnnotationConfigApplicationContext context =newAnnotationConfigApplicationContext(LibraryConfig.class);Book book = context.getBean(Book.class);System.out.println(book);}}

运行结果如下:

在这里插入图片描述


3. @Import模块装配的四种方式

3.1 @Import注解的功能介绍

  在

Spring

中,有时候我们需要将某个类(可能是一个普通类,可能是一个配置类等等)导入到我们的应用程序中。

Spring

提供了四种主要的方式来完成这个任务,后面我们会分别解释。

@Import

注解可以有以下几种使用方式:

  • 导入普通类:可以将普通类(没有被@Component或者@Service等注解标注的类)导入到SpringIOC容器中,Spring会为这个类创建一个Bean,这个Bean的名字默认为类的全限定类名。
  • 导入配置类:可以将一个或多个配置类(被@Configuration注解标注的类)导入到SpringIOC容器中,这样我们就可以一次性地将这个配置类中定义的所有Bean导入到SpringIOC容器中。
  • 使用ImportSelector接口:如果想动态地导入一些BeanSpringIOC容器中,那么可以实现ImportSelector接口,然后在@Import注解中引入ImportSelector实现类,这样Spring就会将ImportSelector实现类返回的类导入到SpringIOC容器中。
  • 使用ImportBeanDefinitionRegistrar接口:如果想在运行时动态地注册一些BeanSpringIOC容器中,那么可以实现ImportBeanDefinitionRegistrar接口,然后在@Import注解中引入ImportBeanDefinitionRegistrar实现类,这样Spring就会将ImportBeanDefinitionRegistrar实现类注册的Bean导入到SpringIOC容器中。
@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@interfaceImportLibrary{}

  这个

@ImportLibrary

注解内部实际上使用了

@Import

注解。当

Spring

处理

@Import

注解时,会将其参数指定的类添加到

Spring

应用上下文中。当我们在

Library

类上使用

@ImportLibrary

注解时,

Spring

会将

Librarian.class

Book.class

BookShelf.class

这三个类添加到应用上下文中。

  然后,我们创建图书馆管理员(

Librarian

)、图书(

Book

)、书架(

BookShelf

)这三个类:

Librarian.java

packagecom.example.demo.bean;publicclassLibrarian{publicvoidmanage(){System.out.println("The librarian is managing the library.");}}

Book.java

packagecom.example.demo.bean;publicclassBook{privateString name;// @ImportLibrary里面有@Import会自动装配,会调用无参构造,不写会报错publicBook(){}publicBook(String name){this.name = name;}publicStringgetName(){return name;}}

BookShelf.java

packagecom.example.demo.bean;importjava.util.List;publicclassBookShelf{privateList<Book> books;// @ImportLibrary里面有@Import会自动装配,会调用无参构造,不写会报错publicBookShelf(){}publicBookShelf(List<Book> books){this.books = books;}publicList<Book>getBooks(){return books;}}

最后,我们创建一个图书馆(

Library

)类,并在这个类上使用我们刚刚创建的

@ImportLibrary

注解:

packagecom.example.demo.configuration;importcom.example.demo.annotations.ImportLibrary;importcom.example.demo.bean.Book;importcom.example.demo.bean.BookShelf;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importjava.util.Arrays;@ImportLibrary@ConfigurationpublicclassLibrary{@BeanpublicBookbook1(){returnnewBook("The Catcher in the Rye");}@BeanpublicBookbook2(){returnnewBook("To Kill a Mockingbird");}@BeanpublicBookShelfbookShelf(Book book1,Book book2){returnnewBookShelf(Arrays.asList(book1, book2));}}

然后我们可以创建一个启动类并初始化

IOC

容器,看看是否可以成功获取到

Librarian

类、

BookShelf

类和

Book

类的实例:

packagecom.example.demo;importcom.example.demo.bean.Book;importcom.example.demo.bean.BookShelf;importcom.example.demo.bean.Librarian;importcom.example.demo.configuration.Library;importorg.springframework.context.annotation.AnnotationConfigApplicationContext;publicclassDemoApplication{publicstaticvoidmain(String[] args){AnnotationConfigApplicationContext context =newAnnotationConfigApplicationContext(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

实例:

packagecom.example.demo.configuration;importcom.example.demo.bean.Book;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;@ConfigurationpublicclassBookConfig{@BeanpublicBookbook(){Book book =newBook();
        book.setName("Imported Book");return book;}}

在这里,我们定义了一个

Book

类:

packagecom.example.demo.bean;publicclassBook{privateString name;publicvoidsetName(String name){this.name = name;}publicStringgetName(){return name;}}

创建一个配置类

LibraryConfig

,使用

@Import

注解来导入

BookConfig

类:

packagecom.example.demo.configuration;importorg.springframework.context.annotation.Configuration;importorg.springframework.context.annotation.Import;@Configuration@Import(BookConfig.class)publicclassLibraryConfig{}

主程序:

packagecom.example.demo;importcom.example.demo.bean.Book;importcom.example.demo.configuration.LibraryConfig;importorg.springframework.context.annotation.AnnotationConfigApplicationContext;publicclassDemoApplication{publicstaticvoidmain(String[] args){AnnotationConfigApplicationContext context =newAnnotationConfigApplicationContext(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

类:

packagecom.example.demo.bean;publicclassBook{privateString name ="java从入门到精通";publicvoidsetName(String name){this.name = name;}publicStringgetName(){return name;}}

创建图书馆管理员

Librarian

packagecom.example.demo.bean;publicclassLibrarian{publicvoidmanage(){System.out.println("The librarian is managing the library.");}}

定义一个

BookImportSelector

,实现

ImportSelector

接口:

packagecom.example.demo.configuration;importcom.example.demo.bean.Librarian;importorg.springframework.context.annotation.ImportSelector;importorg.springframework.core.type.AnnotationMetadata;publicclassBookImportSelectorimplementsImportSelector{/**
     * 这里示范2种方式,一种是拿到class文件后getName,一种是直接写全限定类名
     * @param importingClassMetadata
     * @return
     */@OverridepublicString[]selectImports(AnnotationMetadata importingClassMetadata){returnnewString[]{"com.example.demo.bean.Book",Librarian.class.getName()};}}
ImportSelector

接口可以在运行时动态地选择需要导入的类。实现该接口的类需要实现

selectImports

方法,这个方法返回一个字符串数组,数组中的每个字符串代表需要导入的类的全类名,我们可以直接在这里将

Book

类和

Librarian

类加入到了

Spring

容器中。

使用

Class.getName()

方法获取全限定类名的方式,比直接硬编码类的全名为字符串更推荐,原因如下:

  1. 避免错误:如果类名或包名有所改动,硬编码的字符串可能不会跟随变动,这可能导致错误。而使用Class.getName()方法,则会随类的改动自动更新,避免此类错误。
  2. 代码清晰:使用Class.getName()能让读代码的人更清楚地知道你是要引用哪一个类。
  3. 增强代码的可读性和可维护性:使用类的字节码获取全限定类名,使得代码阅读者可以清晰地知道这是什么类,增加了代码的可读性。同时,也方便了代码的维护,因为在修改类名或者包名时,不需要手动去修改硬编码的类名。

定义一个配置类

LibraryConfig

,使用

@Import

注解导入

BookImportSelector

packagecom.example.demo.configuration;importorg.springframework.context.annotation.Configuration;importorg.springframework.context.annotation.Import;@Configuration@Import(BookImportSelector.class)publicclassLibraryConfig{}

创建一个主应用类,从

Spring

AnnotationConfigApplicationContext

中获取

Book

的实例:

packagecom.example.demo;importcom.example.demo.bean.Book;importcom.example.demo.bean.Librarian;importcom.example.demo.configuration.LibraryConfig;importorg.springframework.context.annotation.AnnotationConfigApplicationContext;publicclassDemoApplication{publicstaticvoidmain(String[] args){AnnotationConfigApplicationContext context =newAnnotationConfigApplicationContext(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

类:

packagecom.example.demo.bean;publicclassBook{privateString name ="java从入门到精通";publicvoidsetName(String name){this.name = name;}publicStringgetName(){return name;}}

定义一个

BookRegistrar

类,实现

ImportBeanDefinitionRegistrar

接口:

packagecom.example.demo.configuration;importcom.example.demo.bean.Book;importorg.springframework.beans.factory.config.BeanDefinition;importorg.springframework.beans.factory.support.BeanDefinitionBuilder;importorg.springframework.beans.factory.support.BeanDefinitionRegistry;importorg.springframework.context.annotation.ImportBeanDefinitionRegistrar;importorg.springframework.core.type.AnnotationMetadata;publicclassBookRegistrarimplementsImportBeanDefinitionRegistrar{@OverridepublicvoidregisterBeanDefinitions(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: 这个参数是SpringBean定义注册类,我们可以通过它往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

packagecom.example.demo.configuration;importorg.springframework.context.annotation.Configuration;importorg.springframework.context.annotation.Import;@Configuration@Import(BookRegistrar.class)publicclassLibraryConfig{}

创建一个主应用类,从

Spring ApplicationContext

中获取

Book

的实例:

packagecom.example.demo;importcom.example.demo.bean.Book;importcom.example.demo.configuration.LibraryConfig;importorg.springframework.context.annotation.AnnotationConfigApplicationContext;publicclassDemoApplication{publicstaticvoidmain(String[] args){AnnotationConfigApplicationContext context =newAnnotationConfigApplicationContext(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

在实践中的一个实际应用的例子。


欢迎一键三连~

有问题请留言,大家一起探讨学习

----------------------Talk is cheap, show me the code-----------------------


本文转载自: https://blog.csdn.net/qq_34115899/article/details/131270467
版权归原作者 砖业洋__ 所有, 如有侵权,请联系我们删除。

“Spring高手之路8——Spring Bean模块装配的艺术:@Import详解”的评论:

还没有评论