SpringBoot源码解读系列三——引导注解

我们再来看下SpringBoot应用的启动类:

查看代码
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.HashMap;
import java.util.Map;


@SpringBootApplication
public class DemoApplication {

    @RequestMapping("/test")
    @ResponseBody
    public Map<String,String> test(){
        Map<String,String> map = new HashMap<>();
        map.put("key","Test");
        return map;
    }

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}

这里有一个核心的注解@SpringBootApplication,我们跟进去看一下,会看到这个是在SpringBoot1.2.0版本引进的,其中注释是这样的:

Indicates a configuration class that declares one or more @Bean methods and also triggers auto-configuration and component scanning. This is a convenience annotation that is equivalent to declaring @Configuration, @EnableAutoConfiguration and @ComponentScan.

翻译一下:标示一个配置类,声明一个或多个@Bean方法,并触发自动配置和组件扫描。这是一个便利的注解,相当于声明@Configuration, @EnableAutoConfiguration和@ComponentScan。这其实是一个注解的集合,源码看了下相当于集中了

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan

三个注解。下面分三个部分来介绍:

1、@SpringBootConfiguration注解

跟进源码看一下,这个是在SpingBoot 1.4.0引入进来的注解,其主要还是在于又引进了@Configuration注解,如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration 

再看下注释

Indicates that a class provides Spring Boot application @Configuration. Can be used as an alternative to the Spring's standard @Configuration annotation so that configuration can be found automatically (for example in tests).
Application should only ever include one @SpringBootConfiguration and most idiomatic Spring Boot applications will inherit it from @SpringBootApplication.

翻译:标识类提供Spring Boot应用程序@Configuration。可以作为Spring的标准@Configuration注释的替代品,这样可以自动找到配置(例如在测试中)。应用程序应该只包含一个@SpringBootConfiguration,大多数习惯用法的SpringBoot应用程序都会从@SpringBootApplication继承它。

这说明,这个来源于Spring原生的@Configuration,跟进去看确实是用到了Spring的Configuration注解,这个就不继续跟了,有兴趣可以详见后续的Spring源码解析。

其实,跟到这里,会发现这个注解的作用就是在于标识这个类是能够被注册到Spring容器中的,类似于声明为一个Spring Bean而已。

2、@EnableAutoConfiguration注解

这个注解是在SpringBoot1.0.0就被引入进来了,其父注解主要是@AutoConfigurationPackage,

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration 

同样,我们可以看下官网是怎么说的:

Enable auto-configuration of the Spring Application Context, attempting to guess and configure beans that you are likely to need. Auto-configuration classes are usually applied based on your classpath and what beans you have defined. For example, if you have tomcat-embedded.jar on your classpath you are likely to want a TomcatServletWebServerFactory (unless you have defined your own ServletWebServerFactory bean).
When using @SpringBootApplication, the auto-configuration of the context is automatically enabled and adding this annotation has therefore no additional effect.
Auto-configuration tries to be as intelligent as possible and will back-away as you define more of your own configuration. You can always manually exclude() any configuration that you never want to apply (use excludeName() if you don't have access to them). You can also exclude them via the spring.autoconfigure.exclude property. Auto-configuration is always applied after user-defined beans have been registered.
The package of the class that is annotated with @EnableAutoConfiguration, usually via @SpringBootApplication, has specific significance and is often used as a 'default'. For example, it will be used when scanning for @Entity classes. It is generally recommended that you place @EnableAutoConfiguration (if you're not using @SpringBootApplication) in a root package so that all sub-packages and classes can be searched.
Auto-configuration classes are regular Spring @Configuration beans. They are located using the SpringFactoriesLoader mechanism (keyed against this class). Generally auto-configuration beans are @Conditional beans (most often using @ConditionalOnClass and @ConditionalOnMissingBean annotations).

翻译:启用Spring Application Context的自动配置,尝试猜测和配置您可能需要的bean。自动配置类通常基于您的类路径和您定义的bean应用。例如,如果你的类路径中有tomcat-embed.jar,你可能需要一个TomcatServletWebServerFactory(除非你已经定义了自己的ServletWebServerFactory bean)。当使用@SpringBootApplication时,上下文的自动配置会被自动启用,因此添加这个注释不会有额外的效果。自动配置试图尽可能地智能,当您定义更多自己的配置时,自动配置就会失效。您总是可以手动排除()任何您不想应用的配置(如果您没有访问它们,则使用excludeName())。你也可以通过spring. autoconfiguration.exclude属性来排除它们。自动配置总是在注册了用户定义的bean之后应用。被@EnableAutoConfiguration注解的类的包,通常是通过@SpringBootApplication注释的,具有特定的意义,通常被用作“默认”。例如,它将在扫描@Entity类时使用。通常建议您将@EnableAutoConfiguration(如果您不使用@SpringBootApplication)放在根包中,以便可以搜索所有的子包和类。自动配置类是常规的Spring @Configuration bean。它们是使用springfactoryesloader机制来定位的(针对这个类)。通常自动配置bean是@条件bean(最常用的是@ConditionalOnClass和@ConditionalOnMissingBean注释)。

从这里我们可以总结出几点:

  • 开启自动配置功能
  • 以前使用Spring需要配置的信息,Spring Boot帮助自动配置;
  • @EnableAutoConfiguration通知SpringBoot开启自动配置功能,这样自动配置才能生效。

跟进去继续分解下,看一下@AutoConfigurationPackage

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage

我们看到是用到了AutoConfigurationPackages.Registrar.class,这个默认将主配置类(@SpringBootApplication)所在的包及其子包里面的所有组件扫描到Spring容器中。如下

查看代码

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

		@Override
		public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
             //默认将会扫描@SpringBootApplication标注的主配置类所在的包及其子包下所有组件
			register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
		}

		@Override
		public Set<Object> determineImports(AnnotationMetadata metadata) {
			return Collections.singleton(new PackageImports(metadata));
		}

	}

另外看一下@Import(AutoConfigurationImportSelector.class)

其实,EnableAutoConfigurationImportSelector: 导入哪些组件的选择器,将所有需要导入的组件以全类名的方式返回,这些组件就会被添加到容器中。

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
   if (!isEnabled(annotationMetadata)) {
      return NO_IMPORTS;
   }
   AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
   return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
查看代码

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
		configurations = removeDuplicates(configurations);
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		checkExcludedClasses(configurations, exclusions);
		configurations.removeAll(exclusions);
		configurations = getConfigurationClassFilter().filter(configurations);
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}

其中重点是这个

List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);

会给容器中注入众多的自动配置类(xxxAutoConfiguration),就是给容器中导入这个场景需要的所有组件,并配置好这些组件。我们跟进去看看:

查看代码

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;
	}

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
		ClassLoader classLoaderToUse = classLoader;
		if (classLoaderToUse == null) {
			classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
		}
		String factoryTypeName = factoryType.getName();
		return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
	}

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
		Map<String, List<String>> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}

		result = new HashMap<>();
		try {
			Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryTypeName = ((String) entry.getKey()).trim();
					String[] factoryImplementationNames =
							StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
					for (String factoryImplementationName : factoryImplementationNames) {
						result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
								.add(factoryImplementationName.trim());
					}
				}
			}

			// Replace all lists with unmodifiable lists containing unique elements
			result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
					.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
			cache.put(classLoader, result);
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
		return result;
	}

我们可以看到:SpringBoot启动的时候从类路径下的 META-INF/spring.factories中获取EnableAutoConfiguration指定的值,并将这些值作为自动配置类导入到容器中,自动配置类就会生效,最后完成自动配置工作。EnableAutoConfiguration默认在spring-boot-autoconfigure这个包中。注:关于自动配置又是一门学问了,另外开篇讲解

3、@ComponentScan注解

这个@ComponentScan是直接继承Spring的@ComponentScan注解的,主要是定义扫描的路径从中找出标识了需要装配的类自动装配到spring的bean容器中。

总结

本文详细分析了SpringBoot的启动类注解@SpringBootApplication,当然这还涉及到底层Spring的注解,有兴趣可以自行研究或者参考后续要推出的Spring5+的源码解析。

posted @ 2022-02-11 16:38  为了生活,加油  阅读(275)  评论(0编辑  收藏  举报