首页 > 其他分享 >SpringBoot自动配置原理

SpringBoot自动配置原理

时间:2023-05-08 10:37:00浏览次数:34  
标签:return SpringBoot 配置 properties 自动 原理 加载 public String

SpringBoot自动配置原理

一、什么是Spring Boot的自动配置?

Spring Boot的最大的特点就是简化了各种xml配置内容,还记得曾经使用SSM框架时我们在spring-mybatis.xml配置了多少内容吗?数据源、连接池、会话工厂、事务管理···,而现在Spring Boot告诉你这些都不需要了,一切交给它的自动配置吧!

所以现在能大概明白什么是Spring Boot的自动配置了吗?简单来说就是用注解来对一些常规的配置做默认配置,简化xml配置内容,使你的项目能够快速运行。

是否对Spring Boot自动配置的原理感到好奇呢?下面我们将浅析Spring Boot的自动配置原理。

二、三大注解

在启动类中可以看到@SpringBootApplication注解,它是SpringBoot的核心注解,也是一个组合注解。我们进入这个注解可以看到里面又包含了其它很多注解,但其中@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan三个注解尤为重要。

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
	...
}
  • @SpringBootConfiguration:继承自Configuration,支持JavaConfig的方式进行配置。
  • @EnableAutoConfiguration:本文重点讲解,主要用于开启自动配置。
  • @ComponentScan:自动扫描组件,可以指定扫描路径,Spring会把指定路径下带有指定注解的类注册到IOC容器中。

三、@EnableAutoConfiguration

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

3.1 @AutoConfigurationPackage

我们先进入@AutoConfigurationPackage,发现它里面依然引用了@Import注解,继续进入AutoConfigurationPackages.Registrar.class,找到registerBeanDefinitions的方法。

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

}
	static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

		@Override
		public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
			register(registry, new PackageImport(metadata).getPackageName());
		}

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

	}

可以看到这个方法包含两个参数metadataregistry。下面简单介绍下这两个参数。

  • metadata:用来获取启动类的信息,获取该类所在的包。
  • registry:用于bean注册。

可以大概知道这个方法是用于注册bean的定义的。

因此@AutoConfigurationPackage这个注解的作用是将添加该注解的类所在的包作为自动配置package进行管理。而该注解包含在@SpringBootApplication注解里面,所以SpringBoot应用启动时会将启动类所在的包作为自动配置的package。

3.2 @Import({AutoConfigurationImportSelector.class})

AutoConfigurationImportSelector.class进入查看源码,

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
		ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {

它实现了DeferredImportSelector接口,DeferredImportSelector又继承了ImportSelector

public interface ImportSelector {

	String[] selectImports(AnnotationMetadata importingClassMetadata);
	
	@Nullable
	default Predicate<String> getExclusionFilter() {
		return null;
	}

}

可以看到所有的配置信息通过getCandidateConfigurations(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader())得到,并最终由一个列表保存。我们继续查看getCandidateConfigurations()方法。

3.2.1 selectImports

AutoConfigurationImportSelector中实现的selectImports

selectImports

public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
				.loadMetadata(this.beanClassLoader);
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
				annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}

加载元数据的配置用到了AutoConfigurationMetadataLoader类提供的loadMetaData方法,该方法会默认加载类路径下 META-INF/springautoconfigure-metadata.properties 内的配置。

final class AutoConfigurationMetadataLoader {

    //默认加载元数据的路径
	protected static final String PATH = "META-INF/spring-autoconfigure-metadata.properties";

	private AutoConfigurationMetadataLoader() {
	}

    //默认调用改方法,传入默认 PATH
	static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
		return loadMetadata(classLoader, PATH);
	}

	static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
		try {
			//获取数据存储 FEnumeration 中
			Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path)
					: ClassLoader.getSystemResources(path);
			Properties properties = new Properties();
			while (urls.hasMoreElements()) {
				//遍历 Enumeration 中的 URL,加载其中的属性, 存储到 Properties 中
				properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));
			}
			return loadMetadata(properties);
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);
		}
	}

    //创建 AutoConfigurat ionMe tadata 的实现类 PropertiesAutoConf igurat ionMetadat
	static AutoConfigurationMetadata loadMetadata(Properties properties) {
		return new PropertiesAutoConfigurationMetadata(properties);
	}

	// AutoConfigurationMetadata 的内部实现类
	private static class PropertiesAutoConfigurationMetadata implements AutoConfigurationMetadata {

		private final Properties properties;

		PropertiesAutoConfigurationMetadata(Properties properties) {
			this.properties = properties;
		}

		@Override
		public boolean wasProcessed(String className) {
			return this.properties.containsKey(className);
		}

		@Override
		public Integer getInteger(String className, String key) {
			return getInteger(className, key, null);
		}

		@Override
		public Integer getInteger(String className, String key, Integer defaultValue) {
			String value = get(className, key);
			return (value != null) ? Integer.valueOf(value) : defaultValue;
		}

		@Override
		public Set<String> getSet(String className, String key) {
			return getSet(className, key, null);
		}

		@Override
		public Set<String> getSet(String className, String key, Set<String> defaultValue) {
			String value = get(className, key);
			return (value != null) ? StringUtils.commaDelimitedListToSet(value) : defaultValue;
		}

		@Override
		public String get(String className, String key) {
			return get(className, key, null);
		}

		@Override
		public String get(String className, String key, String defaultValue) {
			String value = this.properties.getProperty(className + "." + key);
			return (value != null) ? value : defaultValue;
		}

	}

}

在上面的代码中AutoConfigurationMetadataLoader 调用 ladMetadaClassLoadar cassLoaden)方法,会获取默认变量 PATH 指定的文件,然后加载并存储于 Enumeration 数据结构中。随后从变量 PATH 指定的文件中获取其中配置的属性存储 Poperties 内,最终调用在该类内部实现的 AutoConfigurationMetadata 的子类的构造方法。

spring-autoconfigure-metadata.properties 文件内的配置格式如下。

自动配置类的全限定名.注解名称=值

如果 spnaotningre-etadata properties 文件内有多个值,就用英文逗号分隔,例如:

org. springframework . boot . autoconfigure . data. jdbc . IdbcRepositoriesAutoConfiguration . ConditionalOnClass=org. springframework. data. jdbc . repos itory. config.JdbcConfigurat ion, org. springframework. jdbc . core . namedpar am .NamedParameterJdbcOperations
...

为什么要加载此元数据呢?加载元数据主要是为了后续过滤自动配置使用。Spring Boot 使用-Annlation 的处理器来收集自动加载的条件,这些条件可以在元数据文件中进行配置。SpingBoot 会将收集好的 @Confguration 进行一 次过滤,进而剔除不满足条件的配置类。

3.2.2 getAutoConfigurationEntry

selectImports中调用了getAutoConfigurationEntry获得自动配置的实体,下面来看下getAutoConfigurationEntry

getAutoConfigurationEntry

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
			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 = filter(configurations, autoConfigurationMetadata);
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}

getAutoConfigurationEntry()方法内部调用 getCandidateConfigurations()方法,获取候选的配置

3.2.3 getCandidateConfigurations

getCandidateConfigurations

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

继续进入loadFactoryNames()方法,可以发现一个获取资源的可疑地址:FACTORIES_RESOURCE_LOCATION

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

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

		try {
			Enumeration<URL> urls = (classLoader != null ?
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); //获取所有资源 
			result = new LinkedMultiValueMap<>();
			while (urls.hasMoreElements()) {// 判断有没有更多的元素 
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
				// 所有的资源封装到Properties配置类
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryTypeName = ((String) entry.getKey()).trim();
					for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
						result.add(factoryTypeName, factoryImplementationName.trim());
					}
				}
			}
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}

再进入FACTORIES_RESOURCE_LOCATION,发现值为:META-INF/spring.factories

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

原来Spring启动的时候会扫描所有jar下的META-INF/spring.factories,将其包装成Properties对象,然后再从Properties对象中获取key值为:EnableAutoConfiguration的数据添加到容器中。

回到之前的getCandidateConfigurations(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader())方法,注意第一个参数,是一个方法,我们进入这个方法,发现返回的是EnableAutoConfiguration.class。可以得知第一个参数的作用就是用来确定key值的。

	protected Class<?> getSpringFactoriesLoaderFactoryClass() {
		return EnableAutoConfiguration.class;
	}

通过此方法,就可以获取到所有的spring.factories中的所有自动配置类,实现SpringBoot的自动装配。但是实现自动装配后,不一定能生效,需要满足@ConditionalOn***的条件。

@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
注解 作用
@ConditionalOnProperty application.properties 或 application.yml中是否有满足条件的配置
@ConditionalOnBean Bean 已经存在应用上下文时才会加载
@ConditionalOnMissingBean Bean 不存在应用上下文时才会加载
@ConditionalOnClass 某个类存在于 classpath 中才加载
@ConditionalOnMissingClass 某个类不存在于 classpath 中才加载
@ConditionalOnExpression 当条件为true时才加载
@ConditionalOnSingleCandidate 只有指定类已存在于 BeanFactory 中,并且可以确定单个
@ConditionalOnResource 加载的 bean 依赖指定资源存在于 classpath
@ConditionalOnJndi 只有指定的资源通过 JNDI 加载后才加载 bean
@ConditionalOnJava 只有运行指定版本的 Java 才会加载 Bean
@ConditionalOnWebApplication 只有运行在 web 应用里才会加载这个 bean
@ConditionalOnNotWebApplication 只有运行在非 web 应用里才会加载这个 bean
@ConditionalOnCloudPlatform 只有运行在指定的云平台上才加载指定的 bean,CloudPlatform 是 org.springframework.boot.cloud 下一个 enum 类型的类

四、总结

springboot 所有的自动配置都是在启动的时候扫描并加载:spring.factories所有的自动配置类都在这里面,但是不一定生效。要判断条件是否成立,只要导入了对应的start,就有对应的启动器了,有了启动器,我们自动装配就会生效,然后就配置成功!
image

标签:return,SpringBoot,配置,properties,自动,原理,加载,public,String
From: https://www.cnblogs.com/leepandar/p/17380937.html

相关文章

  • SpringBoot运行流程
    SpringBoot运行流程一、准备阶段我们先看一下这个SpringApplication的构造方法中做了什么事情,为run方法准备了那些事情通常在一个springboot的应用中,会看到下面一段代码作为应用的入口@SpringBootApplicationpublicclassApplication{publicstaticvoidmain(String......
  • SpringBoot版本接口
    SpringBoot版本接口前言为什么接口会出现多个版本一般来说,RestfulAPI接口是提供给其它模块,系统或是其他公司使用,不能随意频繁的变更。然而,需求和业务不断变化,接口和参数也会发生相应的变化。如果直接对原来的接口进行修改,势必会影响线其他系统的正常运行。这就必须对api接口......
  • SpringBoot参数校验
    SpringBoot参数校验为什么需要参数校验在日常的接口开发中,为了防止非法参数对业务造成影响,经常需要对接口的参数进行校验,例如登录的时候需要校验用户名和密码是否为空,添加用户的时候校验用户邮箱地址、手机号码格式是否正确。靠代码对接口参数一个个校验的话就太繁琐了,代码可读......
  • SpringBoot统一异常处理
    SpringBoot统一异常处理概述Spring在3.2版本增加了一个注解@ControllerAdvice,可以与@ExceptionHandler、@InitBinder、@ModelAttribute等注解注解配套使用。简单的说,该注解可以把异常处理器应用到所有控制器,而不是单个控制器。借助该注解,我们可以实现:在独立的某个地方,比如单独......
  • SpringBoot添加日志
    SpringBoot添加日志前言SpringBoot使用ApacheCommons日志记录进行所有内部日志记录。SpringBoot的默认配置支持使用JavaUtilLogging,Log4j2和Logback。使用这些,可以配置控制台日志记录以及文件日志记录。如果使用的是SpringBootStarters,Logback将为日志记录提供良好的支......
  • Java开发、SpringBoot开发(狂神说Java)
    目录JavaSpringBoot开发学习(狂神说Java)SpringBoot概述微服务SpringBoot程序安装测试配置文件原理自动配置主启动类yaml语法给属性赋值的几种方式JR303校验多环境配置及配置文件位置SpringBootWeb开发理论静态资源首页模板引擎Thymeleaf语法MVC配置原理,扩展SpringMVC视图解析视......
  • Node.js躬行记(28)——Cypress自动化测试实践
    最近在研究如何提升项目质量,提炼了许多个用于自测的测试用例,但是每次修改后,都手工测试,成本太高,于是就想到了自动化测试。在一年前已将Cypress集成到管理后台的项目中,不过没有投入到实践中。今天在实践时发现,版本已经到了12.X,当时集成的版本是8.X。一、准备在......
  • SpringBoot访问外部接口
    SpringBoot访问外部接口原生的Http请求@RequestMapping("/doPostGetJson")publicStringdoPostGetJson()throwsParseException{//此处将要发送的数据转换为json格式字符串StringjsonText="{id:1}";JSONObjectjson=(JSONObject)JSONObject.parse(jsonTe......
  • Win10打开IE自动跳转至Edge解决办法
    WIN+R输入inetcpl.cpl弹出Internet属性对话窗口点击上面菜单中的【高级】选项滑动右侧滚动条,找到【浏览】项下面的【启用第三方浏览器拓展*】并取消勾选双击IE浏览器图标测试是否生效......
  • java基于springboot+vue非前后端分离的学生成绩管理系统、学生信息管理系统,附源码+数
    1、项目介绍java基于springboot+vue非前后端分离的学生成绩管理系统、学生信息管理系统。本文首先介绍了学生成绩管理的技术发展背景与发展现状,然后遵循软件常规开发流程,首先针对系统选取适用的语言和开发平台,根据需求分析制定模块并设计数据库结构,再根据系统总体功能模块的设计......