四时宝库

程序员的知识宝库

讲讲 springboot @EnableAutoConfiguration 怎么实现的

近期由于有些面试将平时零散的知识进行梳理下并做记录,欢迎大家一起交流

简介

SpringBoot一个最核心思想是:约定大于配置,这种虽然降低了配置的灵活性

但是却简化了应用的搭建与开发,因为这种约定从实现角度看就是SpringBoot提供了大量的默认配置参数本次我们来聊聊 @EnableAutoConfiguration的作用。

@EnableAutoConfiguration是启用Spring的自动装配的关键注解

@EnableAutoConfiguration 注解实现原理

通过源码会发现该注解会@Import(AutoConfigurationImportSelector.class),AutoConfigurationImportSelector这个类是实现自动装配的关键

根据@Import装配类的特性,并且 AutoConfigurationImportSelector 实现自 ImportSelector 类,所以知道它可以根据 AutoConfigurationImportSelector 的实现在 selectImports 方法返回的数组(类的全类名)去自动装配bean到Spring IOC容器中。此注解相当于xml配置中 import 标签

@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
   String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
   Class<?>[] exclude() default {};
   String[] excludeName() default {};

}

AutoConfigurationImportSelector 分析

进入AutoConfigurationImportSelector类通过读取META-INF/spring.factories文件中key为EnableAutoConfiguration完成目标类加载,以mybatis为例应该为

org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),getBeanClassLoader()); 
    Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you " + "are using a custom packaging, make sure that file is correct.");
    return configurations;
}
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
    return EnableAutoConfiguration.class;
}

至此默认配置已经加载到spring中,而用户只需要根据实际情况修改对应的组件的配置参数,以mybatis为例其参数均有@EnableConfigurationProperties({MybatisProperties.class}) 提供

@Import 注解何时被解析

而@Import 注解被容器解析时才会真正调用实现了ImportSelector接口的类,这个过程正是在refresh方法的invokeBeanFactoryPostProcessors中完成

public void refresh() throws BeansException, IllegalStateException {
     Object var1 = this.startupShutdownMonitor;
     synchronized(this.startupShutdownMonitor) {
         this.prepareRefresh();
         ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
         this.prepareBeanFactory(beanFactory);

         try {
              this.postProcessBeanFactory(beanFactory);
              this.invokeBeanFactoryPostProcessors(beanFactory);
              this.registerBeanPostProcessors(beanFactory);
              ....省略部分代码有兴趣的可以直接查看源码

    }
}

继续分析invokeBeanFactoryPostProcessors该方法主要通过beanfactory来加载一些处理器类,通过PostProcessorRegistrationDelegate工具类调用invokeBeanFactoryPostProcessors

protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
    PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, this.getBeanFactoryPostProcessors());
    if (beanFactory.getTempClassLoader() == null && beanFactory.containsBean("loadTimeWeaver")) {
        beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
        beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
    }
}

进入invokeBeanFactoryPostProcessors发现该方法将会加载实现了BeanDefinitionRegistryPostProcessor注册处理器的实现类其中处理import注解的处理器为ConfigurationClassPostProcessor,此处理器会处理四个注解:@PropertySource, @ComponentScan, @Import, @ImportResource

接下来分析下ConfigurationClassPostProcessorprocessConfigBeanDefinitions方法其中ConfigurationClassParser类将会完成上述四个注解的解析工作

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
     ....省略部分代码

     ConfigurationClassParser parser = new ConfigurationClassParser(this.metadataReaderFactory, this.problemReporter, this.environment, this.resourceLoader, this.componentScanBeanNameGenerator, registry);
     Set<BeanDefinitionHolder> candidates = new LinkedHashSet(configCandidates);
     HashSet alreadyParsed = new HashSet(configCandidates.size());

     do {
         parser.parse(candidates);
         parser.validate();
         ....省略部分代码
        }
 }

最后我们看下ConfigurationClassParser类中doProcessConfigurationClass方法,代码注释中已经明确指明了每一个注解的解析逻辑,这里不再展开分析

protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter) throws IOException { 
  if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
    // Recursively process any member (nested) classes first 
    processMemberClasses(configClass, sourceClass, filter);
  }
  // Process any @PropertySource annotations
  for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
    sourceClass.getMetadata(), PropertySources.class,org.springframework.context.annotation.PropertySource.class)) {
    if (this.environment instanceof ConfigurableEnvironment) {
      processPropertySource(propertySource);
    }
    else {
      logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +"]. Reason: Environment must implement ConfigurableEnvironment");
    }
	}
  // Process any @ComponentScan annotation
  Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
  if (!componentScans.isEmpty() &&!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
    for (AnnotationAttributes componentScan : componentScans) {
      // The config class is annotated with @ComponentScan -> perform the scan immediately
      Set<BeanDefinitionHolder> scannedBeanDefinitions =this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
      // Check the set of scanned definitions for any further config classes and parse recursively if needed
      for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
        BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
        if (bdCand == null) {
          bdCand = holder.getBeanDefinition();
        }
        if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
          parse(bdCand.getBeanClassName(), holder.getBeanName());
				}
      }
    }
	}

  // Process any @Import annotations
  processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
  // Process any @ImportResource annotations
  AnnotationAttributes importResource =AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
	if (importResource != null) { 
    String[] resources = importResource.getStringArray("locations");
		Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
		for (String resource : resources) {
      String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
      configClass.addImportedResource(resolvedResource, readerClass);
    }
  }

	// Process individual @Bean methods
  Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
  for (MethodMetadata methodMetadata : beanMethods) {
    configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
  }
  // Process default methods on interfaces
  processInterfaces(configClass, sourceClass);
  // Process superclass, if any
  if (sourceClass.getMetadata().hasSuperClass()) {
    String superclass = sourceClass.getMetadata().getSuperClassName();
    if (superclass != null && !superclass.startsWith("java") &&!this.knownSuperclasses.containsKey(superclass)) {
      this.knownSuperclasses.put(superclass, configClass);
      // Superclass found, return its annotation metadata and recurs
      return sourceClass.getSuperClass();
    }
  }
  // No superclass -> processing is complete
	return null;
}

总结

EnableAutoConfiguration与starter配合完美的实现了自动装配,大大提升了搭建与开发的效率

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言
    友情链接