概述
文章目录
- 1. 背景介绍
- 2. @Import 详解
- 2.1 @Import 概述
- 2.2 . 利用 @Import 注入普通组件
- 2.3. 利用 ImportSelector 注入组件
- 2.4 利用 ImportBeanDefinitionRegistrar 注入组件
- 3. 扩展介绍
- 3.1 Spring 扩展原理实现
- 3.2 ImportBeanDefinitionRegistrar 原理分析
- 4. 总结
这是我Spring Frame 专栏的第六篇文章,在 Spring注解驱动开发(五):利用@Lazy实现懒加载 这篇文章中,我向你详细介绍了@Lazy注解的作用以及注意事项,如果你未读过那篇文章,但是对内容感兴趣的话,我建议你去读一下
1. 背景介绍
在 Spring 容器里,有三种常见的向 Ioc 容器中注入组件的方式:
- 利用
@ComponentScan+@Component
向容器中注入组件 (专栏第一篇文章讲述) - 利用
@Configuration+@Bean
向容器中注入组件 (专栏第二篇文章讲述) - 利用
@Import
向容器中注入组件
这三种方式是你使用Spring和阅读 Spring 各功能组件的代码必须要掌握的知识点,本篇文章我就会给你讲述 @Import 的用法
2. @Import 详解
这里我先定义几个自定义组件,以便后续内容的讲述.
这里我按照MVC的开发流程,定义了一套组件,代码如下所示:
- Person
public class Person {
private Integer id;
private String name;
private String sex;
public Person() {
}
public Person(Integer id, String name, String sex) {
this.id = id;
this.name = name;
this.sex = sex;
}
// 省略 getter,setter,toString 方法
- PersonDao
@Repository
public class PersonDao {
}
- PersonService
@Service
public class PersonService {
}
- PersonController
@Controller
public class PersonController {
}
- 定义配置类
CustomerConfig
:
@Configuration
public class CustomerConfig {
}
- 最后定义一个测试类:
public class IcoMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(CustomerConfig.class);
System.out.println("Ioc 容器初始化完成");
System.out.println("---------------------");
context.close();
}
}
好啦,至此已经准备好了测试环境,接下来进入文章的正题.
2.1 @Import 概述
按照惯例,我们先看一眼 @Import 注解的源码,并从它的注释了解一下它的作用
/**
* Indicates one or more <em>component classes</em> to import — typically
* {@link Configuration @Configuration} classes.
*
* .......
*
* @author Chris Beams
* @author Juergen Hoeller
* @since 3.0
* @see Configuration
* @see ImportSelector
* @see ImportBeanDefinitionRegistrar
* @see ImportResource
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
/**
* {@link Configuration @Configuration}, {@link ImportSelector},
* {@link ImportBeanDefinitionRegistrar}, or regular component classes to import.
*/
Class<?>[] value();
}
- 从整个类的注释我们可以看到,这个类就是向容器中放入候选Bean的,和@Configuration 类似
- 从其属性注释可以知道:@Import可以配合
Configuration、ImportSelector和ImportBeanDefinitionRegistrar
来使用,同时也可以把Import当成普通的bean来使用
好啦,从源码中我们了解了其作用,我们开始尝试使用不同的方式,利用@Import 向容器中注入组件.
2.2 . 利用 @Import 注入普通组件
第一种方式就是向容器中注入普通的组件,不借助其它类(Configuration、ImportSelector和ImportBeanDefinitionRegistrar
)
我们先来改造以下配置类,在它的上面标注 @Import 注解,并将Person的类信息设置为其属性值:
@Import(value = {Person.class})
@Configuration
public class CustomerConfig {
}
接下来我们修改一下测试类,打印当前容器中存在的BeanDefinition的名字以及是否可以获取对应的Person对象实例
public class IcoMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(CustomerConfig.class);
System.out.println("Ioc 容器初始化完成");
System.out.println("----------BeanDefinitionNames-----------");
String[] definitionNames = context.getBeanDefinitionNames();
for (String definitionName : definitionNames) {
System.out.println(definitionName);
}
System.out.println("----------Person对象-----------");
Person person = context.getBean(Person.class);
System.out.println("person===>"+person);
context.close();
}
}
从运行结果发现Person的实例被注入到了容器中,说明使用@Import注解导入组件时,容器中就会自动注册这个组件,并且在Spring Ioc容器中的默认名就是组件的全类名
⭐️ 这里我只是注入了一个组件,但是其源码写的是一个数组,你可以批量向容器中导入组件
2.3. 利用 ImportSelector 注入组件
上面我讲述了利用 @Import 可以向容器中注入普通的Bean,那么现在我向你讲述第二种方式:利用 ImportSelector
注入组件
我们还是先看一下ImportSelector 的源码,了解一下它的使用方式:
/**
* Interface to be implemented by types that determine which @{@link Configuration}
* class(es) should be imported based on a given selection criteria, usually one or
* more annotation attributes.
*
* ......
*
* @author Chris Beams
* @author Juergen Hoeller
* @since 3.1
* @see DeferredImportSelector
* @see Import
* @see ImportBeanDefinitionRegistrar
* @see Configuration
*/
public interface ImportSelector {
/**
* Select and return the names of which class(es) should be imported based on
* the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
* @return the class names, or an empty array if none
*/
String[] selectImports(AnnotationMetadata importingClassMetadata);
/**
* Return a predicate for excluding classes from the import candidates, to be
* transitively applied to all classes found through this selector's imports.
* <p>If this predicate returns {@code true} for a given fully-qualified
* class name, said class will not be considered as an imported configuration
* class, bypassing class file loading as well as metadata introspection.
* @return the filter predicate for fully-qualified candidate class names
* of transitively imported configuration classes, or {@code null} if none
* @since 5.2.4
*/
@Nullable
default Predicate<String> getExclusionFilter() {
return null;
}
}
从注释可以看到,这个类主要借助 selectImports()
方法,它有一个AnnotationMetadata
类型的参数(能够获取到当前标注@Import注解的类的所有注解信息),它的返回值是一个字符串数组,数组中的每一个元素都应该是想注入组件的全类名
概念很枯燥,接下来我就实定义一个 ImportSelector
实现类,向你展示一下它的用法:
- 我们先定义一个实现类
public class CustomerSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
// 1. 我们看一眼AnnotationMetadata能获取哪些注解
Set<String> annotationTypes = importingClassMetadata.getAnnotationTypes();
annotationTypes.forEach(System.out::println);
// ----------------- 此处你可以自定义一些判断逻辑,来对该类的注解信息进行过滤判断,来指定向导入哪些组件--------------
// 2. 返回 全类名字符串 数组 !!!! 切记不要返回NULL,否则Spring Ioc 容器会报错
return new String[]{"org.zhang.blog.ioc.domain.Person"};
}
}
在里面我先获取了注解信息,这里是让你理解以下 AnnotationMetadata 参数的作用,之后就是返回一个全类名字符串数组
⭐️ 切记不要返回 Null , 否则 Spring 会报错(如果你对错误感兴趣,可以自己 Debug 一下,由于篇幅原因,我就不带你看了)
- 修改我们的配置类,在 @Import 注解内只导入这个
ImportSelector
实现类
@Import(value = {CustomerSelector.class})
@Configuration
public class CustomerConfig {
}
- 执行我们上面定义好的测试类,查看结果:
public class IcoMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(CustomerConfig.class);
System.out.println("Ioc 容器初始化完成");
System.out.println("----------BeanDefinitionNames-----------");
String[] definitionNames = context.getBeanDefinitionNames();
for (String definitionName : definitionNames) {
System.out.println(definitionName);
}
System.out.println("----------Person对象-----------");
Person person = context.getBean(Person.class);
System.out.println("person===>"+person);
context.close();
}
}
从结果中可以看到:
- AnnotationMetadata 获取到了 @Import标注的类(CustomerConfig )上面的所有注解信息(
@Import,@Configuration
)- 利用 这个
CustomerSelector
成功的向容器中注入了 Person 类的实例
好啦,相信到这里你应该掌握 ImportSelector 的用法了
2.4 利用 ImportBeanDefinitionRegistrar 注入组件
在上面两部分,我向你介绍了 @Import 注解的两种使用方式,这里我向你介绍最后一种使用方式:自定义 ImportBeanDefinitionRegistrar
实现类
我们还是先看一下 ImportBeanDefinitionRegistrar 的源码,了解一下它的使用方式:
/**
* Interface to be implemented by types that register additional bean definitions when
* processing @{@link Configuration} classes. Useful when operating at the bean definition
* level (as opposed to {@code @Bean} method/instance level) is desired or necessary.
*
* ......
* @author Chris Beams
* @author Juergen Hoeller
* @since 3.1
* @see Import
* @see ImportSelector
* @see Configuration
*/
public interface ImportBeanDefinitionRegistrar {
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
BeanNameGenerator importBeanNameGenerator) {
registerBeanDefinitions(importingClassMetadata, registry);
}
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
}
}
从源码中可以看到,它有一个 registerBeanDefinitions() 方法,通过该方法,我们可以向Spring容器中注册 BeanDefinition (Bean的元信息,在创建组件的时候需要这些元信息),Spring官方在动态注册bean时,大部分使用的都是ImportBeanDefinitionRegistrar接口
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
boolean proxyTargetClass() default false;
boolean exposeProxy() default false;
}
上面代码开启 Spring AOP 功能时就利用了 ImportBeanDefinitionRegistrar
好啦,概念说完了,我们开始实操使用一下它,这里我们只要重写 registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry)
方法就好了,它的第一个参数我相信你已经不陌生了,第二个参数 BeanDefinitionRegistry
的作用和它的名字一样: BeanDefiniton 的注册类,将对应Bean元信息放入Ioc容器中,在之后创建组件实例时使用
- 我们先定义一个ImportBeanDefinitionRegistrar
public class CustomerImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 1. 这里你依然可以利用AnnotationMetadata和BeanDefinitionRegistry 中的 的信息定制化的注册组件
String[] beanDefinitionNames = registry.getBeanDefinitionNames();
System.out.println("-----------BeanDefinitionRegistry 中的 BeanDefinition 信息--------------");
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
System.out.println("----------注入Person 的 BeanDefinition------------");
RootBeanDefinition beanDefinition = new RootBeanDefinition(Person.class);
// 注册一个bean,并且指定bean的名称
registry.registerBeanDefinition("person", beanDefinition);
}
}
- 修改配置类,利用 @Import 导入
CustomerImportBeanDefinitionRegistrar
@Import(value = {CustomerImportBeanDefinitionRegistrar.class})
@Configuration
public class CustomerConfig {
/**
* 向容器中注入Person1
* @return
*/
public Person person1() {
System.out.println("person1 被加载");
return new Person(1, "jack", "female");
}
}
- 执行测试类:
public class IcoMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(CustomerConfig.class);
System.out.println("Ioc 容器初始化完成");
System.out.println("----------BeanDefinitionNames-----------");
String[] definitionNames = context.getBeanDefinitionNames();
for (String definitionName : definitionNames) {
System.out.println(definitionName);
}
System.out.println("----------Person对象-----------");
Person person = context.getBean(Person.class);
System.out.println("person===>"+person);
context.close();
}
}
在注册和未注册Person前后的Ioc容器中的 BeanDefinition 是不同的,这也侧面验证了这个Person的 BeanDefinition 是利用CustomerImportBeanDefinitionRegistrar
注入的
好啦,到这里我相信你已经可以基本使用 ImportBeanDefinitionRegistrar
向容器中注入 Bean 了
3. 扩展介绍
3.1 Spring 扩展原理实现
我个人认为,@Import 是 Spring Ioc 容器提供的最佳的扩展功能,这里我给你一些理由,在你以后使用SpringBoot以及Spring 的各种扩展功能时都会看到它的踪迹:
- ImportSelecto是Spring中导入外部配置的核心接口,在Spring Boot的自动化配置和
@EnableXXX
(功能性注解)都有它的存在 - ImportBeanDefinitionRegistrar 也是Spring 常用功能的注册Bean的不二之选,它可以独立的向Spring Ioc 容器中导入一些BeanDefinition信息
3.2 ImportBeanDefinitionRegistrar 原理分析
ImportBeanDefinitionRegistrar
接口的所有实现类都会被ConfigurationClassPostProcessor处理,ConfigurationClassPostProcessor实现了BeanFactoryPostProcessor接口,所以ImportBeanDefinitionRegistrar中动态注册的bean是优先于依赖其的bean初始化的,也能被aop、validator等机制处理
在此处,我就带你探究一下如何从源码中追溯到 ConfigurationClassPostProcessor
处理这些类信息:
-
我们现在自定义的接口实现类的
registerBeanDefinitions
打一个断点
-
Debug 模式运行我们的测试类,看一下对应的函数调用栈:
从调用栈明显可以看到是从ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry
开始处理BeanDefinitionRegistry
:
调用ConfigurationClassBeanDefinitionReader#loadBeanDefinitions
开始加载BeanDefinition
之后调用ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForConfigurationClass
开始加载所有配置类(此时会顺便处理@Import注解相关信息)
最后调用ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsFromRegistrars
开始遍历所有的ImportBeanDefinitionRegistrar
接口实现类
private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) {
registrars.forEach((registrar, metadata) ->
registrar.registerBeanDefinitions(metadata, this.registry, this.importBeanNameGenerator));
}
到这里,你应该能知道 ImportBeanDefinitionRegistrar
在何时起作用了
4. 总结
这篇文章,我主要向你介绍了:
- Spring Ioc 容器注入组件的三种常见方式
- @Import 的用法
- ImportBeanDefinitionRegistrar 的源码调用时机
最后,我希望你看完本篇文章后,能够在适当的时候使用 @Import 注解向容器中注入组件,也希望你指出我在文章中的错误点,希望我们一起进步,也希望你能给我的文章点个赞,原创不易!
最后
以上就是犹豫大侠为你收集整理的Spring注解驱动开发(六):利用@Import向容器中注入组件的全部内容,希望文章能够帮你解决Spring注解驱动开发(六):利用@Import向容器中注入组件所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复