经常在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的功能,前者侧重使用者可以根据不用的情况,策略选择对应的功能实现。后者是在特定的功能实现里进行配置剪裁。


Martin
5 声望0 粉丝

后端