经常在springBoot项目中使用@EnableXX就能够开启某个功能,那么它是怎么实现的呢?
比如我定义了一个监控接口,设计和一些实现如下:
public interface MonitorService {
void monitor();
}
public class DefaultMonitorService implements MonitorService{
@Override
public void monitor() {
System.out.println("defaultMonitorService show :" + "current cpu used 10%");
}
}
public class MartinMonitorService implements MonitorService {
public void monitor() {
System.out.println("martinMonitorService show :" + "current cpu used 10%");
}
}
大部分情况希望就使用默认的监控服务,然而在有些时候,可能会使用其他的监控服务,甚至直接关闭该功能,如果实现该的功能呢?可以像下面这样做:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(MonitorImportSelector.class)
public @interface EnableMonitorService {
Class<?> value() default DefaultMonitorService.class;
}
public class MonitorImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
AnnotationAttributes annotationAttributes = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(EnableMonitorService.class.getName()));
//在这里可以拿到所有注解的信息,可以根据不同注解的和注解的属性来返回不同的class,
// 从而达到开启不同功能的目的
Class<?> value = (Class<?>) annotationAttributes.get("value");
return new String[]{value.getName()};
}
}
@RestController
public class MonitorController implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@GetMapping(value = "testMonitorService")
public String test() {
MonitorService monitorService;
try {
monitorService = applicationContext.getBean(MonitorService.class);
} catch (BeansException e) {
// 如果获取不到具体的监控服务,则说明该监控服务暂停服务了
return "monitor service has stop";
}
String monitorInfo = monitorService.monitor();
return monitorInfo;
}
}
EnableMonitorService相当于一个开关,同时也具备选择器的功能,当开启这个服务的时候,可以指定value去选择哪个服务实现来提供服务。EnableMonitorService上有一个@Import(MonitorImportSelector.class),这主要是利用了spring的特性,通过MonitorImportSelector获取EnableMonitorService注解上的信息,来把对应的服务注入到spring容器中,从而达到目的。MonitorController可以看作监控模块向外部暴露的服务。
下面根据业务需求,在启动类上使用该开关。
//@EnableMonitorService(value = MartinMonitorService.class)
@EnableMonitorService
@SpringBootApplication
public class JavaApplication {
public static void main(String[] args) {
SpringApplication.run(JavaApplication.class, args);
}
}
如果不想启用监控服务,那么就不要在JavaApplication上使用@EnableMonitorService,此时访问http://localhost:8080/testMon...返回:monitor service has stop
想要启动且使用默认的监控服务,此时使用@EnableMonitorService就行,访问上面的测试url返回:defaultMonitorService show :current cpu used 10%
最后想要启用MartinMonitorService,甚至自定义一个service,那么就需要在@EnableMonitorService注解中的value配置MartinMonitorService,访问上面的测试url返回:martinMonitorService show :current cpu used 10%
可以看到@EnableXX关键是利用了spring的@Import功能。下面看下@Import的注释:
/**
(不完全的)
允许导入@Configuration类、ImportSelector和importbeandefinitionregistry实现,以及常规组件类
*/
@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中的value可以直接指定一个@Configuration,或者使用ImportSelector,ImportBeanDefinitionRegistrar等接口的具体实现来加载需要导入的bean。最后也可以当做一个普通的类直接当作bean。
首先来看下,@Import是在何时被解析用到的,spring托管一个bean,可能是在bean扫描阶段把所有的bean信息收集到,当然也可能像FactoryBean在依赖注入的时候,再去收集bean信息进行托管。对于@Import不管是采用哪种方式,应该都是能够在扫描阶段收集到所有bean信息的。下面看下收集bean信息的源码:
protected final SourceClass doProcessConfigurationClass(
ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
throws IOException {
// Process any @Import annotations
processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
}
getImports()方法的主要作用是根据当前的sourceClass,判断该sourceClass上是否存在@Import,如果有则解析出其中的value作为候选者等待解析。
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
boolean checkForCircularImports) {
// 一般情况下SourceClass是不存在 @Import,所以直接返回了
if (importCandidates.isEmpty()) {
return;
}
for (SourceClass candidate : importCandidates) {
if (candidate.isAssignable(ImportSelector.class)) {
// Candidate class is an ImportSelector -> delegate to it to determine imports
Class<?> candidateClass = candidate.loadClass();
// 实例化该ImportSelector
ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
this.environment, this.resourceLoader, this.registry);
Predicate<String> selectorFilter = selector.getExclusionFilter();
if (selectorFilter != null) {
exclusionFilter = exclusionFilter.or(selectorFilter);
}
if (selector instanceof DeferredImportSelector) {
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
} else {
// 调用selectImports()返回这些类型信息
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
}
} 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 =
ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
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), exclusionFilter);
}
}
}
这段源码的主要作用是,处理每一个importCandidates,也就是我们配置在@Import中的value信息。
对于每一个candidate,如果是ImportSelector的实现类,那么先实例化该类型的对象,然后调用ImportSelector的selectImports返回所有需要被spring托管的bean class信息。注意这里递归的调用了processImports(),这是因为被导入的类,可能还存在ImportSelector或者ImportBeanDefinitionRegistrar的实现类。
如果candidate是一个ImportBeanDefinitionRegistrar,那么把该bean实例化后,注册到importBeanDefinitionRegistrars中,后续遍历该注册器,导入bean信息。
最后candidate不管是配置类,还是普通类,都当作一个ConfigurationClass来处理,并放在configurationClasses一个map中,ConfigurationClass的bean信息,会在所有bean信息解析后,单独解析ConfigurationClass。
ImportSelector:
public interface ImportSelector {
// 根据导入的@Configuration类的AnnotationMetadata选择并返回应该导入的类的名称
String[] selectImports(AnnotationMetadata importingClassMetadata);
}
通过注释也能够看得出,ImportSelector就是一个选择器得作用,就像前面的@EnableMonitorService配置的value来选择加载对应的bean信息。因此常常配合注解
作为一种选择器。当开发出一个框架,然后可以根据不同的应用场景,选择对应的实现,可以采用。
ImportBeanDefinitionRegistrar:
public interface ImportBeanDefinitionRegistrar {
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
BeanNameGenerator importBeanNameGenerator) {
registerBeanDefinitions(importingClassMetadata, registry);
}
// 根据导入的@Configuration类的给定注释元数据,根据需要注册bean定义。
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
}
}
不再提供选择器的功能,而是根据注解上的信息,对当前支持的功能进行适当配置,然后根据配置信息来适当注入对应的bean。比如像@EnableDubbo中的DubboConfigConfigurationRegistrar,就是根据配置来注入相关bean。此时并没有扮演一个选择器的角色(选择哪个dubbo实现)。
总结:ImportSelector和ImportBeanDefinitionRegistrar都可以实现@EnableXX的功能,前者侧重使用者可以根据不用的情况,策略选择对应的功能实现。后者是在特定的功能实现里进行配置剪裁。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。