Pre
四种处理方式
在使用 Spring Boot 时,@Import 也是一个非常常见的注解,可以用来动态创建 Bean。
在 @Import 注解的属性中可以设置需要引入的类名,例如 @AutoConfigurationPackage 注解上的 @Import(AutoConfigurationPackages.Registrar.class)。根据该类的不同类型,Spring 容器针对 @Import 注解有以下四种处理方式:
如果该类实现了 ImportSelector 接口,Spring 容器就会实例化该类,并且调用其 selectImports 方法;
如果该类实现了 DeferredImportSelector 接口,则 Spring 容器也会实例化该类并调用其 selectImports方法。DeferredImportSelector 继承了 ImportSelector,区别在于 DeferredImportSelector 实例的 selectImports 方法调用时机晚于 ImportSelector 的实例,要等到 @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