为什么要用@Import
我们定义bean对象,一般使用@Component,@Service,然后配合@ComponentScan使用
如果引入的对象,不再扫描的包内,那么@Component,@Service定义就无效。
所以我们引入@Import来将类引入ioc管理。
@Import定义
例子
@Import({CachingConfigurationSelector.class})
public @interface EnableCaching {
boolean proxyTargetClass() default false;
AdviceMode mode() default AdviceMode.PROXY;
int order() default 2147483647;
}
我们可以看到这里使用了一个CachingConfigurationSelector.class。
首先看下定义:
/**
* 表示一个或多个组件类引入-通常为@Configuration类。
* 提供功能等效于<import/>在Spring XML元素。
* 允许引入class类型:
* 1、@Configuration注解类
* 2、ImportSelector实现类
* 3、ImportBeanDefinitionRegistrar实现类
*
* 通过@Bean注解申明的类应该使用 @Autowired注入
*如果XML或其它非@Configuration bean定义资源需要引入,使用@ImportResource注解来替代
*
* @author Chris Beams
* @author Juergen Hoeller
* @since 3.0
* @see Configuration
* @see ImportSelector
* @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();
}
使用场景
@Import注解在4.2之前只支持导入配置类(需要加@Configuartion注解的是配置类),在4.2,@Import注解支持导入普通的java类(什么注解都诶呦),并将其声明成一个bean。
-
普通类【4.2以后】
定义普通类
public class Cat {
public void print(){
System.out.println("你是只猫");
}
}
public class Dog {
}
运行类
/*把用到的资源导入到当前容器中*/
@Import({Dog.class, Cat.class})
public class App {
public static void main(String[] args) throws Exception {
ConfigurableApplicationContext context = SpringApplication.run(App.class, args);
context.getBean(Cat.class).print();
System.out.println(context.getBean(Dog.class));
context.close();
}
}
运行结果
你是只猫
normal.Dog@3980e5
上面个写法的变种
增加一个管理类
public class MyConfig {
@Bean
public Dog getDog() {
return new Dog();
}
@Bean
public Cat getCat() {
return new Cat();
}
}
@Import(MyConfig.class)
public class App {
}
思考,如果不用import
方式一:修改注解,其他不变
@ComponentScan
public class App {
}
@Component
public class Cat {
}
@Component
public class Dog {
}
-
@Configuration注解的类
用法,4.2以后已经不需要Configuration标记,使用import的时候,除非需要支持自动扫描@ComponentScan
@Configuration
public class MyConfig {
@Bean
public Dog getDog() {
return new Dog();
}
@Bean
public Cat getCat() {
return new Cat();
}
}
@Import(MyConfig.class)
public class App {
}
-
实现接口:ImportSelector
定义对象StudentBean .java
public class StudentBean {
private int id;
private String name;
...
}
定义bean .AppConfig
public class AppConfig {
@Bean
public StudentBean studentBean() {
StudentBean studentBean = new StudentBean();
studentBean.setId(19);
studentBean.setName("admin1");
return studentBean;
}
}
定义引入Selector
public class SpringStudySelector implements ImportSelector{
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
importingClassMetadata.getAnnotationTypes().forEach(System.out::println);
return new String[]{AppConfig.class.getName()};
}
}
定义引入接口
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.TYPE)
@Import(SpringStudySelector.class)
public @interface EnableSpringStudy {
}
调用App
@EnableSpringStudy
public class App implements CommandLineRunner {
@Autowired
StudentBean studentBean;
@Override
public void run(String... args) throws Exception {
System.out.println(studentBean.getName());
}
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
-
实现接口:ImportBeanDefinitionRegistrar
定义类
public class Security {
}
定义bean注册类
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//手动注入 Security类的实例
BeanDefinitionBuilder beanDef_security = BeanDefinitionBuilder.rootBeanDefinition(Security.class);
registry.registerBeanDefinition("security", beanDef_security.getBeanDefinition());
}
}
调用
@Import(MyImportBeanDefinitionRegistrar.class)
public class App {
public static void main(String[] args) throws Exception {
ConfigurableApplicationContext context = SpringApplication.run(App.class, args);
System.out.println(context.getBean(Security.class));
}
}
其中上一步,可进行变种使用Enablexxx
定义EnableSecurity
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.TYPE)
@Import(MyImportBeanDefinitionRegistrar.class)
public @interface EnableSecurity {
}
调用,仅仅修改注解
@EnableSecurity
public class App {
public static void main(String[] args) throws Exception {
ConfigurableApplicationContext context = SpringApplication.run(App.class, args);
System.out.println(context.getBean(Security.class));
}
}
更优雅的Spring boot自动注入测试
使用CommandLineRunner
定义对象类
@Configuration
public class AppConfig {
// 定义了两个返回Book类型的Bean
public class Book {
public String title;
}
public class Person {
public String name;
public Book book;
}
@Bean
public Book book01() {
Book book = new Book();
book.title = "石头记";
return book;
}
@Bean
public Book book02() {
Book book = new Book();
book.title = "红楼梦";
return book;
}
@Bean
public Person person01(@Qualifier("book01") Book book) {
Person person = new Person();
person.name = "曹夢阮";
person.book = book;
return person;
}
@Bean
public Person person02(@Qualifier("book02") Book book) {
Person person = new Person();
person.name = "曹雪芹";
person.book = book;
return person;
}
}
调用
@ComponentScan
public class Demo02Application implements CommandLineRunner {
@Autowired
private AppConfig.Book book02;
@Autowired
private AppConfig.Person person02;
@Override
public void run(String... args) throws Exception {
System.out.println(book02.title);
System.out.println( person02.name);
System.out.println( person02.book.title);
}
public static void main(String[] args) {
SpringApplication.run(Demo02Application.class, args);
}
}
import实现原理
主要查看ConfigurationClassParser类
解析入口
// Process any @PropertySource annotations
// Process any @ComponentScan annotations
// Process any @Import annotations
processImports(configClass, sourceClass, getImports(sourceClass), true);
// Process any @ImportResource annotations
// Process individual @Bean methods
// Process default methods on interfaces
// Process superclass, if any
获取含有@Import注解的类
private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
Set<SourceClass> imports = new LinkedHashSet<>();
Set<SourceClass> visited = new LinkedHashSet<>();
collectImports(sourceClass, imports, visited);
return imports;
}
private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)
throws IOException {
if (visited.add(sourceClass)) {
//获取注解,如果名称为Import.class,则执行
for (SourceClass annotation : sourceClass.getAnnotations()) {
String annName = annotation.getMetadata().getClassName();
if (!annName.equals(Import.class.getName())) {
collectImports(annotation, imports, visited);
}
}
imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
}
}
解析注解中的类型
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, boolean checkForCircularImports) {
this.importStack.push(configClass);
try {
for (SourceClass candidate : importCandidates) {
//ImportSelector逻辑,实例化对象,并返回对象名,最终再次解析,使用bean解析
if (candidate.isAssignable(ImportSelector.class)) {
// Candidate class is an ImportSelector -> delegate to it to determine imports
Class<?> candidateClass = candidate.loadClass();
ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
ParserStrategyUtils.invokeAwareMethods(
selector, this.environment, this.resourceLoader, this.registry);
if (selector instanceof DeferredImportSelector) {
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
}
else {
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
processImports(configClass, currentSourceClass, importSourceClasses, false);
}
}
//实现ImportBeanDefinitionRegistrar注册解析,这里仅仅加入注册,后续处理
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
// Candidate class is an ImportBeanDefinitionRegistrar ->
// delegate to it to register additional bean definitions
Class<?> candidateClass = candidate.loadClass();
ImportBeanDefinitionRegistrar registrar =
BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
ParserStrategyUtils.invokeAwareMethods(
registrar, this.environment, this.resourceLoader, this.registry);
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}
else {
// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
// process it as an @Configuration class
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
processConfigurationClass(candidate.asConfigClass(configClass));
}
}
}
finally {
this.importStack.pop();
}
}
参考:
Spring Boot 自动配置之@Enable* 与@Import注解
SpringBoot @import的使用
Spring @Import注解 —— 导入资源
@Component 和,@Bean和@ImportResource的区别
Spring Boot: @Bean 和 @Qualifier 注解的使用
深入理解Spring的ImportSelector接口
SpringBoot @Import 详解