本文基于 Spring 5.2.x
@Import
注解
@Import
是Spring
基于 Java 注解配置的主要组成部分。@Import
注解提供了@Bean
注解的功能,同时还有原来Spring
基于 xml 配置文件里的<import>
标签组织多个分散的xml文件的功能,当然在这里是组织多个分散的@Configuration
的类。
下面将分别说明@Import
注解的功能。
1. 引入其他的@Configuration
假设有如下接口和两个实现类:
package com.test interface ServiceInterface { void test(); } class ServiceA implements ServiceInterface { @Override public void test() { System.out.println("ServiceA"); } } class ServiceB implements ServiceInterface { @Override public void test() { System.out.println("ServiceB"); } }
两个@Configuration
,其中ConfigA``@Import``ConfigB
:
package com.test @Import(ConfigB.class) @Configuration class ConfigA { @Bean @ConditionalOnMissingBean public ServiceInterface getServiceA() { return new ServiceA(); } } @Configuration class ConfigB { @Bean @ConditionalOnMissingBean public ServiceInterface getServiceB() { return new ServiceB(); } }
通过ConfigA
创建AnnotationConfigApplicationContext
,获取ServiceInterface
,看是哪种实现:
public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigA.class); ServiceInterface bean = ctx.getBean(ServiceInterface.class); bean.test(); }
输出为:ServiceB
.证明@Import
的优先于本身的的类定义加载。
2. 直接初始化其他类的Bean
在Spring 4.2之后,@Import
可以直接指定实体类,加载这个类定义到context
中。 例如把上面代码中的ConfigA
的@Import
修改为@Import(ServiceB.class)
,就会生成ServiceB
的Bean
到容器上下文中,之后运行main
方法,输出为:ServiceB
.证明@Import
的优先于本身的的类定义加载.
3. 指定实现ImportSelector
(以及DefferredServiceImportSelector
)的类,用于个性化加载
指定实现ImportSelector
的类,通过AnnotationMetadata
里面的属性,动态加载类。AnnotationMetadata
是Import
注解所在的类属性(如果所在类是注解类,则延伸至应用这个注解类的非注解类为止)。
需要实现selectImports
方法,返回要加载的@Configuation
或者具体Bean
类的全限定名的String
数组。
package com.test; class ServiceImportSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { //可以是@Configuration注解修饰的类,也可以是具体的Bean类的全限定名称 return new String[]{"com.test.ConfigB"}; } } @Import(ServiceImportSelector.class) @Configuration class ConfigA { @Bean @ConditionalOnMissingBean public ServiceInterface getServiceA() { return new ServiceA(); } }
再次运行main
方法,输出:ServiceB
.证明@Import
的优先于本身的的类定义加载。 一般的,框架中如果基于AnnotationMetadata
的参数实现动态加载类,一般会写一个额外的Enable
注解,配合使用。例如:
package com.test; @Retention(RetentionPolicy.RUNTIME) @Documented @Target(ElementType.TYPE) @Import(ServiceImportSelector.class) @interface EnableService { String name(); } class ServiceImportSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { //这里的importingClassMetadata针对的是使用@EnableService的非注解类 //因为`AnnotationMetadata`是`Import`注解所在的类属性,如果所在类是注解类,则延伸至应用这个注解类的非注解类为止 Map<String , Object> map = importingClassMetadata.getAnnotationAttributes(EnableService.class.getName(), true); String name = (String) map.get("name"); if (Objects.equals(name, "B")) { return new String[]{"com.test.ConfigB"}; } return new String[0]; } }
之后,在ConfigA
中增加注解@EnableService(name = "B")
package com.test; @EnableService(name = "B") @Configuration class ConfigA { @Bean @ConditionalOnMissingBean public ServiceInterface getServiceA() { return new ServiceA(); } }
再次运行main
方法,输出:ServiceB
.
还可以实现DeferredImportSelector
接口,这样selectImports
返回的类就都是最后加载的,而不是像@Import
注解那样,先加载。 例如:
package com.test; class DefferredServiceImportSelector implements DeferredImportSelector { @Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { Map<String, Object> map = importingClassMetadata.getAnnotationAttributes(EnableService.class.getName(), true); String name = (String) map.get("name"); if (Objects.equals(name, "B")) { return new String[]{"com.test.ConfigB"}; } return new String[0]; } }
修改EnableService
注解:
@Retention(RetentionPolicy.RUNTIME) @Documented @Target(ElementType.TYPE) @Import(DefferredServiceImportSelector.class) @interface EnableService { String name(); }
这样ConfigA
就优先于DefferredServiceImportSelector
返回的ConfigB
加载,执行main
方法,输出:ServiceA
4. 指定实现ImportBeanDefinitionRegistrar
的类,用于个性化加载
与ImportSelector
用法与用途类似,但是如果我们想重定义Bean
,例如动态注入属性,改变Bean
的类型和Scope
等等,就需要通过指定实现ImportBeanDefinitionRegistrar
的类实现。例如:
定义ServiceC
package com.test; class ServiceC implements ServiceInterface { private final String name; ServiceC(String name) { this.name = name; } @Override public void test() { System.out.println(name); } }
定义ServiceImportBeanDefinitionRegistrar
动态注册ServiceC
,修改EnableService
package com.test; @Retention(RetentionPolicy.RUNTIME) @Documented @Target(ElementType.TYPE) @Import(ServiceImportBeanDefinitionRegistrar.class) @interface EnableService { String name(); } class ServiceImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { Map<String, Object> map = importingClassMetadata.getAnnotationAttributes(EnableService.class.getName(), true); String name = (String) map.get("name"); BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.rootBeanDefinition(ServiceC.class) //增加构造参数 .addConstructorArgValue(name); //注册Bean registry.registerBeanDefinition("serviceC", beanDefinitionBuilder.getBeanDefinition()); } }
并且根据后面的源代码解析可以知道,ImportBeanDefinitionRegistrar
在 @Bean
注解之后加载,所以要修改ConfigA
去掉其中被@ConditionalOnMissingBean
注解的Bean
,否则一定会生成ConfigA
的ServiceInterface
package com.test; @EnableService(name = "TestServiceC") @Configuration class ConfigA { // @Bean // @ConditionalOnMissingBean // public ServiceInterface getServiceA() { // return new ServiceA(); // } }
之后运行main
,输出:TestServiceC