您的位置:首页 > 编程语言 > Java开发

Spring Boot - 自动装配中的不可忽视的@Import

2021-05-22 08:07 603 查看

文章目录

  • @EnableAutoConfiguration
  • @Import源码
  • 最佳实践 @EnableXXX
  • 源码

  • Pre

    Spring Boot - 自动配置实现原理

    四种处理方式

    在使用 Spring Boot 时,@Import 也是一个非常常见的注解,可以用来动态创建 Bean。

    在 @Import 注解的属性中可以设置需要引入的类名,例如 @AutoConfigurationPackage 注解上的 @Import(AutoConfigurationPackages.Registrar.class)。根据该类的不同类型,Spring 容器针对 @Import 注解有以下四种处理方式:

    • 如果该类实现了 ImportSelector 接口,Spring 容器就会实例化该类,并且调用其 selectImports 方法;
    • 如果该类实现了 DeferredImportSelector 接口,则 Spring 容器也会实例化该类并调用其 selectImports方法。DeferredImportSelector 继承了 ImportSelector,区别在于 DeferredImportSelector 实例的 selectImports 方法调用时机晚于 ImportSelec 20000 tor 的实例,要等到 @Configuration 注解中相关的业务全部都处理完了才会调用;
    • 如果该类实现了 ImportBeanDefinitionRegistrar 接口,Spring 容器就会实例化该类,并且调用其 registerBeanDefinitions 方法;
    • 如果该类没有实现上述三种接口中的任何一个,Spring 容器就会直接实例化该类。

    我们来搞一搞@Import吧

    从@SpringBootApplication注解说起

    我们都知道 启动一个SpringBoot应用 无须各种的配置文件,无须各种繁杂的pom依赖,一个main方法,就能run起来了。

    与其他框架整合也相当方便,使用EnableXXXXX注解就可以完成整合

    那SpringBoot是如何实现自动配置的????

    @SpringBootApplication组合注解说明

    @SpringBootApplication: Spring Boot应用标注在某个类上说明这个类是SpringBoot的主配置类,SpringBoot需要运行这个类的main方法来启动SpringBoot应用

    @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited

    注解说明:

    • @Target(ElementType.TYPE) 设置当前注解可以标记在哪
    • @Retention(RetentionPolicy.RUNTIME) 当注解标注的类编译以什么方式保留。 RetentionPolicy.RUNTIME 会被jvm加载
    • @Documented java doc 会生成注解信息
    • @Inherited 是否会被继承

    更详细的请参考我以前写的一篇博文: Java-Java5.0注解解读

    @SpringBootConfiguration

    Spring Boot的配置类 , 标注在某个类上,表示这是一个Spring Boot的配置类

    @EnableAutoConfiguration

    开启自动配置功能 , @EnableAutoConfiguration告诉SpringBoot开启自动配置,会自动去加载自动配置类

    @ComponentScan

    相当于在spring.xml 配置中

    <context:comonent-scan>
    但是并没有指定basepackage,如果没有指定spring底层会自动扫描当前配置类所有在的包

    @EnableAutoConfiguration

    SpringBootApplication注解中最重要的一个注解就是 @EnableAutoConfiguration.

    @AutoConfigurationPackage

    将当前配置类所在包保存在BasePackages的Bean中。供Spring内部使用

    使用了@Import注解 保存扫描路径, 注册

    那看下 org.springframework.boot.autoconfigure.AutoConfigurationPackages.Registrar

    @Import(AutoConfigurationImportSelector.class)

    关键点!

    可以看到,在@EnableAutoConfiguration注解内也使用到了@Import注解来完成导入配置的功能

    那都用它,我们来搞搞@Import吧

    @Import源码


    @Import表示要导入的一个或多个@Configuration类

    我们来看下value方法源码中的注释: Configuration,ImportSelector,ImportBeanDefinitionRegistrar 或者是一个普通的组件

    那分别来看下如何使用吧

    POM 核心 有个context就够了

    <dependencies>
    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.18</version>
    </dependency>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.6.RELEASE</version>
    </dependency>
    </dependencies>

    @Import 普通组件

    @Import({ 要导入的组件 } )

    【模拟第三方框架提供的】

    package com.artisan.configuration;
    
    import com.artisan.beans.Artisan;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    /**
    * @author 小工匠
    * @version 1.0
    * @description:  自定义的配置类  类比第三方的配置类
    * @date 2021/5/22 8:50
    * @mark: show me the code , change the world
    */
    
    @Configuration
    public class ArtisanConfig {
    
    @Bean
    public Artisan artisan() {
    Artisan artisan = new Artisan();
    artisan.setName("小工匠");
    artisan.setAge(18);
    return artisan;
    }
    }

    【模拟Spring自己的】

    /**
    * 系统当前加载的配置类
    */
    
    @Configuration
    @Import({ArtisanConfig.class})
    // @Import(ArtisanImportSelector.class)
    //@Import(ArtisanRegistrar.class)
    //@EnableArtisan
    public class AppConfig {
    }

    将AppConfig.java添加注解 @Import({ArtisanConfig.class}) , 将第三方的配置类导入到Bean容器中 , 本质上就是导入 一个Configuration配置类组件

    【测试类】

    public class Main {
    
    public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    
    Artisan artisan =  ctx.getBean(Artisan.class);
    System.out.println(artisan);
    }
    }

    【测试结果】

    @Import 实现了ImportSelector接口的组件 (类的全限定类名)

    【ImportSelector接口 返回全限定名】

    package com.artisan.impt;
    
    import org.springframework.context.annotation.ImportSelector;
    import org.springframework.core.type.AnnotationMetadata;
    
    /**
    * @author 小工匠
    * @version 1.0
    * @description: TODO
    * @date 2021/5/22 9:25
    * @mark: show me the code , change the world
    */
    public class ArtisanImportSelector implements ImportSelector {
    
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
    return new String[]{"com.artisan.beans.Artisan"};
    }
    }

    【AppConfig - @Import(ArtisanImportSelector.class)】

    /**
    * 系统当前加载的配置类
    */
    
    @Configuration
    //@Import({ArtisanConfig.class})
    @Import(ArtisanImportSelector.class)
    //@Import(ArtisanRegistrar.class)
    //@EnableArtisan
    public class AppConfig {
    }

    测试

    ArtisanSelector返回的类的全限定类名,即为导入到容器中的组件全类名

    @Import 实现了ImportBeanDefinitionRegistrar接口的组件

    【ImportBeanDefinitionRegistrar 接口】

    package com.artisan.impt;
    
    import com.artisan.beans.Artisan;
    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;
    
    /**
    * @author 小工匠
    * @version 1.0
    * @description: TODO
    * @date 2021/5/22 9:28
    * @mark: show me the code , change the world
    */
    public class ArtisanRegistrar implements ImportBeanDefinitionRegistrar {
    
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(Artisan.class);
    builder.setScope(BeanDefinition.SCOPE_SINGLETON);
    builder.addPropertyValue("name", "小工匠Registrar");
    builder.addPropertyValue("age", "18");
    registry.registerBeanDefinition("artisan", builder.getBeanDefinition());
    }
    }

    【AppConfig - @Import(ArtisanRegistrar.class) 】

    /**
    * 系统当前加载的配置类
    */
    
    @Configuration
    //@Import({ArtisanConfig.class})
    // @Import(ArtisanImportSelector.class)
    @Import(ArtisanRegistrar.class)
    //@EnableArtisan
    public class AppConfig {
    }

    【测试】

    ImportBeanDefinitionRegistrar类似于ImportSelector用法,只不过这种用法能自定义化注册,往容器内注入一个BeanDefinition,然后BeanDeiniton在容器内转为一个实例bean。

    最佳实践 @EnableXXX

    【自定义注解 @EnableArtisan】

    package com.artisan.annotation;
    
    import com.artisan.configuration.ArtisanConfig;
    import com.artisan.impt.ArtisanImportSelector;
    import com.artisan.impt.ArtisanRegistrar;
    import org.springframework.context.annotation.Import;
    
    import java.lang.annotation.*;
    
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    
    // @Import({ArtisanConfig.class})   @Import({ArtisanImportSelector.class})  都可以
    @Import({ArtisanRegistrar.class})
    public @interface EnableArtisan {
    
    }

    将AppConfig.java添加@EnableArtisan

    【测试结果】

    源码

    https://github.com/yangshangwei/boot2/tree/master/spring_maven

    内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
    标签: