平常在使用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. * */
大概意思就是:
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(); } }
效果和第一种是一样的。
@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注解的一些信息了。
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"}; } }
@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(); } }
这个用法和ImportSelector的用法是一样的,这里就不全展示了,直接在TestImport注解上换成@Import(ImportBeanDefinitionRegistrarImpl.class)就行了。
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,当时我的回答是:
这次就可以另外加上一个@Import,ImportBeanDefinitionRegistrar以及ImportSelector了。