@Import注解源码
前言
平常在使用SpringBoot
的过程中,经常会使用到@EnableXXX
的注解,而随之一起的还有一个@Import
注解,这次就专门来看@Import
的源码
正文
先摘抄一部分它的英文注释吧:
/**
* // 导入一个或多个组件类,代表性的就是Configuration类型的类
* Indicates one or more <em>component classes</em> to import — typically
* {@link Configuration @Configuration} classes.
*
* // 提供与spring的xml中的import标签相同的功能。可以导入配置类,普通类,ImportSelector或ImportBeanDefinitionRegistrar接口的实现类
* <p>Provides functionality equivalent to the {@code <import/>} element in Spring XML.
* Allows for importing {@code @Configuration} classes, {@link ImportSelector} and
* {@link ImportBeanDefinitionRegistrar} implementations, as well as regular component
* classes (as of 4.2; analogous to {@link AnnotationConfigApplicationContext#register}).
*
* <p>{@code @Bean} definitions declared in imported {@code @Configuration} classes should be
* accessed by using {@link org.springframework.beans.factory.annotation.Autowired @Autowired}
* injection. Either the bean itself can be autowired, or the configuration class instance
* declaring the bean can be autowired. The latter approach allows for explicit, IDE-friendly
* navigation between {@code @Configuration} class methods.
*
* <p>May be declared at the class level or as a meta-annotation.
*
* <p>If XML or other non-{@code @Configuration} bean definition resources need to be
* imported, use the {@link ImportResource @ImportResource} annotation instead.
*
*/
大概意思就是:
- 可以导入一个类或多个类至容器中,和xml中的import标签功能一样
- 可导入普通类,Configuration类,ImportSelector或ImportBeanDefinitionRegistrar接口的实现类
- 导入配置文件,该配置文件可被其他的bean注入,也可以在配置中注入其他的bean。
例子
- 新建一个
springboot
项目 - 在启动类上层建立同层级的包。例如,启动类
test.java
在a.b.c.d
下面,那么在c
包下建立e
包,e
和d
处于同一级目录。
为什么要有第二点,因为springboot
会扫描启动类同级目录下的文件,如果在同一级目录,直接使用@Componment就行了。所以个人觉得@Import
就是导入与项目不是同一级目录的类。
导入普通类
在e包下新建两个类,TestImportA和TestImportB:
public class TestImportA {
public void test(){
System.out.println("TestImportB run");
}
}
public class TestImportB {
public void test(){
System.out.println("TestImportB run");
}
}
然后在启动类上面添加:
@SpringBootApplication
@Import({TestImportA.class, TestImportB.class})
public class SpringbootOneApplication {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(SpringbootOneApplication.class, args);
TestImportB testImportB = run.getBean(TestImportB.class);
testImportB.test();
}
}
这样,不会被扫描到的TestImportA.class, TestImportB.class就可以通过@Import注解扫描到,并放进容器了。
导入配置类
在e包下新建配置类TestImportConfig
@Configuration
public class TestImportConfig {
@Bean
public TestImportA testImportA() {
return new TestImportA();
}
@Bean
public TestImportB testImportB() {
return new TestImportB();
}
}
然后在启动类上面添加:
@SpringBootApplication
@Import(TestImportConfig.class)
public class SpringbootOneApplication {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(SpringbootOneApplication.class, args);
TestImportB testImportB = run.getBean(TestImportB.class);
testImportB.test();
}
}
效果和第一种是一样的。
导入ImportSelector的实现类
- 新建一个注解类TestImport
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(ImportSelectorImpl.class) // 注意建立ImportSelectorImpl类后加上这部分注解
public @interface TestImport {
String value() default "value";
boolean flag() default false;
}
说一下为什么要新建一个注解类。
ImportSelector的接口String[] selectImports(AnnotationMetadata importingClassMetadata)
,该接口接收了一个AnnotationMetadata,
这个类里面就包含了@Import注解所在的地方(类,接口,枚举)的注解信息,这样我们就可以在启动配置的时候获取一些需要的信息了。最典型的就是@EnableXXX注解,
在@EnableXXX中使用@Import导入ImportSelector实现类,这样就可以获取@EnableXXX注解的一些信息了。
- 新建一个ImportSelectorImpl来实现ImportSelector接口
public class ImportSelectorImpl implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
// 获取注解集合
MergedAnnotations annotations = importingClassMetadata.getAnnotations();
// 获取TestImport注解信息
MergedAnnotation<TestImport> testImportMergedAnnotation = annotations.get(TestImport.class);
// 打印
System.out.println("===="+testImportMergedAnnotation.getString("value"));
return new String[]{"a.b.c.e.TestImportA","a.b.c.e.TestImportB"};
}
}
- 在启动类上改用@TestImport注解
@SpringBootApplication
@TestImport
public class SpringbootOneApplication {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(SpringbootOneApplication.class, args);
TestImportB testImportB = run.getBean(TestImportB.class);
testImportB.test();
}
}
导入ImportBeanDefinitionRegistrar的实现类
这个用法和ImportSelector的用法是一样的,这里就不全展示了,直接在TestImport注解上换成@Import(ImportBeanDefinitionRegistrarImpl.class)就行了。
- 新建ImportBeanDefinitionRegistrar实现类
public class ImportBeanDefinitionRegistrarImpl implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 获取注解集合
MergedAnnotations annotations = importingClassMetadata.getAnnotations();
// 获取TestImport注解信息
MergedAnnotation<TestImport> testImportMergedAnnotation = annotations.get(TestImport.class);
// 打印value属性值
System.out.println("===="+testImportMergedAnnotation.getString("value"));
// 注册信息
registry.registerBeanDefinition("testImportB",new RootBeanDefinition(TestImportB.class));
}
}
最后
还记得最近的面试中,被问到了如何向spring容器中注册bean,当时我的回答是:
- @Component,@Service,@Controller,@Repository这四个注解,后三个底层都是用的@Component。
- @Configuration,在配置类中使用@Bean注册
- BeanFactoryAware, ApplicationContextAware,实现这两个接口,然后手动向容器中注册。
这次就可以另外加上一个@Import,ImportBeanDefinitionRegistrar以及ImportSelector了。