
Spring所拥有的强大功能之一就是可以集成各种开源软件。但随着互联网的高速发展,各种框架层出不穷,这就对系统架构的灵活性、扩展性、可伸缩性、高可用性都提出了新的要求。随着项目的发展,Spring慢慢地集成了更多的开源软件,引入大量配置文件,这会导致程序出错率高、运行效率低下的问题。为了解决这些状况,Spring Boot应运而生。但Spring Boot本身并不提供Spring的核心功能,而是作为Spring的脚手架框架,以达到快速构建项目、预置三方配置、开箱即用的目的。
地址:.2.2.RELEASE
不同版本之间的Spring Boot源代码的顶层目录结构会有所变化,但并不影响其核心功能。v2.2.2.RELEASE版本由以下子模块构成。
该模块包含了Spring Boot所有的核心功能。
下图为了更清晰地表达Spring Boot各项目之间的关系,我们基于依赖的传递性,省略了部分依赖关系。比如,Spring Boot Starters不仅依赖了Spring Boot Autoconfigure项目,还依赖了Spring Boot和Spring,而Spring Boot Autoconfigure项目又依赖了Spring Boot,Spring Boot又依赖了Spring相关项目。因此在图中就省略了Spring Boot Starters和底层依赖的关联。
pring Boot Parent是Spring Boot及图中依赖Spring Boot项目的Parent项目,同样为了结构清晰,图中不显示相关关联。
从上图中我们可以清晰地看到Spring Boot几乎完全基于Spring,同时提供了Spring Boot和Spring Boot Autoconfigure两个核心的模块,而其他相关功能又都是基于这两个核心模块展开的。
Spring Boot最核心的功能就是自动配置,功能的实现都是基于“约定优于配置”的原则。那么Spring Boot是如何约定,又是如何实现自动配置功能的呢?
使用Spring Boot时,我们只需引入对应的Starters,Spring Boot启动时便会自动加载相关依赖,配置相应的初始化参数,以最快捷、简单的形式对第三方软件进行集成,这便是Spring Boot的自动配置功能。我们先从整体上看一下Spring Boot实现该运作机制涉及的核心部分,如下图所示。
上图描述了Spring Boot自动配置功能运作过程中涉及的几个核心功能及其相互之间的关系包括@EnableAutoConfiguration、spring.factories、各组件对应的AutoConfiguration类、@Conditional注解以及各种Starters。
可以用一句话来描述整个过程:Spring Boot通过@EnableAutoConfiguration注解开启自动配置,加载spring.factories中注册的各种AutoConfiguration类,当某个AutoConfiguration类满足其注解@Conditional指定的生效条件(Starters提供的依赖、配置或Spring容器中是否存在某个Bean等)时,实例化该AutoConfiguration类中定义的Bean(组件等),并注入Spring容器,就可以完成依赖框架的自动配置。
概念:
Spring Boot项目创建完成会默认生成一个*Application的入口类。在默认情况下,无论是通过IDEA还是通过官方创建基于Maven的Spring Boot项目,入口类的命名规则都是artifactId+Application。通过该类的main方法即可启动Spring Boot项目,代码如下:
@SpringBootApplication
public class SpringBootLearnApplication {public static void main(String[] args) {SpringApplication.run(SpringBootLearnApplication.class, args);}
}
这里的main方法并无特别之处,就是一个标准的Java应用的main方法,用于启动Spring Boot项目的入口。在默认情况下,把按照上述规则命名并包含main方法的类称为入口类。
在Spring Boot入口类(除单元测试外)中,唯一的一个注解就是@SpringBootApplication。它是Spring Boot项目的核心注解,用于开启自动配置,准确说是通过该注解内组合的@EnableAutoConfiguration开启了自动配置,@SpringBootApplication部分源代码如下:
package org.springframework.boot.autoconfigure;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.SpringBootConfiguration;
import org.t.TypeExcludeFilter;
import t.annotation.ComponentScan;
import t.annotation.Configuration;
import t.annotation.FilterType;
import t.annotation.ComponentScan.Filter;
import annotation.AliasFor;
@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 {//排除指定自动配置类@AliasFor(annotation = EnableAutoConfiguration.class)Class<?>[] exclude() default {};//排除指定自动配置类名@AliasFor(annotation = EnableAutoConfiguration.class)String[] excludeName() default {};// 指定扫描的基础包,激活注解组件的初始化@AliasFor(annotation = ComponentScan.class,attribute = "basePackages")String[] scanBasePackages() default {};//指定扫描的类,用于初始化@AliasFor(annotation = ComponentScan.class,attribute = "basePackageClasses")Class<?>[] scanBasePackageClasses() default {};//指定是否代理@Bean方法以强制执行bean的生命周期行为@AliasFor(annotation = Configuration.class)boolean proxyBeanMethods() default true;
}
通过源代码可以看出,该注解提供了以下成员属性(注解中的成员变量以方法的形式体现)。
@ComponentScan主要就是定义扫描的路径从中找出标识了需要装配的类自动装配到spring的bean容器中。@Controller,@Service,@Repository注解,查看其源码你会发现,他们中有一个共同的注解@Component。@ComponentScan注解默认就会装配标识了@Controller,@Service,@Repository,@Component注解的类到spring容器中。
用@Configuration注释类表明其主要目的是作为bean定义的源
@Configuration类允许通过调用同一类中的其他@Bean方法来定义bean之间的依赖关系。
@EnableAutoConfiguration是开启自动配置的注解,在创建的Spring Boot项目中并不能直接看到此注解,它是由组合注解@SpringBootApplication引入的。
在未使用Spring Boot的情况下,Bean的生命周期由Spring来管理,然而Spring无法自动配置@Configuration注解的类。而Spring Boot的核心功能之一就是根据约定自动管理该注解标注的类。用来实现该功能的组件之一便是@EnableAutoConfiguration注解。@EnableAutoConfiguration位于spring-boot-autoconfigure包内,当使用@SpringBootApplication注解时,@EnableAutoConfiguration注解会自动生效。
@EnableAutoConfiguration的主要功能是启动Spring应用程序上下文时进行自动配置,它会尝试猜测并配置项目可能需要的Bean。自动配置通常是基于项目classpath中引入的类和已定义的Bean来实现的。在此过程中,被自动配置的组件来自项目自身和项目依赖的jar包中。
举个例子:如果将tomcat-embedded.jar添加到classpath下,那么@EnableAutoConfiguration会认为你准备使用TomcatServletWebServerFactory类,并帮你初始化相关配置。与此同时,如果自定义了基于ServletWebServerFactory的Bean,那么@EnableAutoConfiguration将不会进行TomcatServletWebServerFactory类的初始化。这一系列的操作判断都由Spring Boot来完成。
@EnableAutoConfiguration注解的源码如下:
package org.springframework.boot.autoconfigure;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import t.annotation.Import;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {//用来覆盖配置开启/关闭自动配置的功能String ENABLED_OVERRIDE_PROPERTY = "ableautoconfiguration";//根据类(Class)排除指定的自动配置Class<?>[] exclude() default {};//根据类名排除指定的自动配置String[] excludeName() default {};
}
@EnableAutoConfiguration注解提供了一个常量和两个成员参数的定义:
//通过@SpringBootApplication排除DataSourceAutoConfiguration
@SpringBootApplication(exclude=DataSourceAutoConfiguration.class)
public class SpringBootLearnApplication {public static void main(String[] args) {SpringApplication.run(SpringBootLearnApplication.class, args);}
}
或
//通过@SpringBootApplication排除DataSourceAutoConfiguration
@SpringBootApplication(excludeName=DataSourceAutoConfiguration.class)
public class SpringBootLearnApplication {public static void main(String[] args) {SpringApplication.run(SpringBootLearnApplication.class, args);}
}
需要注意的是,被@EnableAutoConfiguration注解的类所在package还具有特定的意义,通常会被作为扫描注解@Entity的根路径。这也是在使用@SpringBootApplication注解时需要将被注解的类放在顶级package下的原因,如果放在较低层级,它所在package的同级或上级中的类就无法被扫描到。
@EnableAutoConfiguration的关键功能是通过@Import注解导入的ImportSelector来完成的。从源代码得知@Import(AutoConfigurationImportSelector.class)是@EnableAutoConfiguration注解的组成部分,也是自动配置功能的核心实现者。
@Import(AutoConfigurationImportSelector.class)又可以分为两部分:@Import和对应的ImportSelector。下面讲解@Import的基本使用方法和ImportSelector的实现类AutoConfigurationImportSelector。
@Import注解位于spring-context项目内,主要提供导入配置类的功能。为什么要专门讲解@Import的功能及使用呢?如果查看Spring Boot的源代码,我们会发现大量的EnableXXX类都使用了该注解。了解@Import注解的基本使用方法,能够帮助我们更好地进行源代码的阅读和理解。@Import的源码如下:
package t.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {Class<?>[] value();
}
@Import的作用和xml配置中标签的作用一样,我们可以通过@Import引入@Configuration注解的类,也可以导入实现了ImportSelector或ImportBeanDefinitionRegistrar的类,还可以通过@Import导入普通的POJO(将其注册成Spring Bean,导入POJO需要Spring 4.2以上版本)。
@Import的许多功能都需要借助接口ImportSelector来实现,ImportSelector决定可引入哪些@Configuration。ImportSelector接口源码如下:
package t.annotation;
import ype.AnnotationMetadata;
public interface ImportSelector {String[] selectImports(AnnotationMetadata var1);
}
ImportSelector接口只提供了一个参数为AnnotationMetadata的方法,返回的结果为一个字符串数组。其中参数AnnotationMetadata内包含了被@Import注解的类的注解信息。在selectImports方法内可根据具体实现决定返回哪些配置类的全限定名,将结果以字符串数组的形式返回。
如果实现了接口ImportSelector的类的同时又实现了以下4个Aware接口,那么Spring保证在调用ImportSelector之前会先调用Aware接口的方法。这4个接口为:EnvironmentAware、BeanFactoryAware、BeanClassLoaderAware和ResourceLoaderAware。
在AutoConfigurationImportSelector的源代码中就实现了这4个接口,部分源代码及Aware的全限定名代码如下:
package org.springframework.boot.autoconfigure;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanFactoryAware;
import t.EnvironmentAware;
import t.ResourceLoaderAware;
import t.annotation.DeferredImportSelector;
import Ordered;
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
...
}
在上面的源代码中,AutoConfigurationImportSelector并没有直接实现ImportSelector接口,而是实现了它的子接口DeferredImportSelector。DeferredImportSelector接口与ImportSelector的区别是,前者会在所有的@Configuration类加载完成之后再加载返回的配置类,而ImportSelector是在加载完@Configuration类之前先去加载返回的配置类。
DeferredImportSelector的加载顺序可以通过@Order注解或实现Ordered接口来指定。同时,DeferredImportSelector提供了新的方法getImportGroup()来跨DeferredImportSelector实现自定义Configuration的加载顺序。
下图展示了AutoConfigurationImportSelector的核心功能及流程,省略了外部通过@Import注解调用该类的部分:
当AutoConfigurationImportSelector被@Import注解引入之后,它的selectImports方法会被调用并执行其实现的自动装配逻辑。selectImports方法几乎涵盖了组件自动装配的所有处理逻辑。AutoConfigurationImportSelector的selectImports方法源代码如下:
public String[] selectImports(AnnotationMetadata annotationMetadata) {//检测自动配置功能是否开启,默认为开启if (!this.isEnabled(annotationMetadata)) {return NO_IMPORTS;} else {//加载自动配置的原信息,配置文件为类路径中META-INF目录下的//spring-autoconfigure-metadata.properties文件AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);//封装将被引用的自动配置信息AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = AutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);//返回符合条件的配置类的全限定名数组Configurations());}
}
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {if (!this.isEnabled(annotationMetadata)) {return EMPTY_ENTRY;} else {AnnotationAttributes attributes = Attributes(annotationMetadata);//通过SpringFactoriesLoader类提供的方法加载类路径中META-INF目录下的//spring.factories文件中针对EnableAutoConfiguration的注册配置类List<String> configurations = CandidateConfigurations(annotationMetadata, attributes);//对获得的注册配置类集合进行去重处理,防止多个项目引入同样的配置类configurations = veDuplicates(configurations);//获得注解中被exclude或excluseName所排除的类的集合Set<String> exclusions = Exclusions(annotationMetadata, attributes);//检查被排除类是否可实例化,是否被自动注册配置所使用,不符合条件,则抛出异常this.checkExcludedClasses(configurations, exclusions);//从自动配置类集合中取出被排除的类veAll(exclusions);//检查配置类的注解是否符合spring.factories文件中AutoConfigurationImportFilter指定的注解检查条件configurations = this.filter(configurations, autoConfigurationMetadata);//将筛选完成的配置类和排查的配置类构建为事件类,并传入监听器。//监听器的配置在于spring.factories文件中,通过//AutoConfigurationImportListener指定this.fireAutoConfigurationImportEvents(configurations, exclusions);return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);}
}
本节我们从整体层面了解了AutoConfigurationImportSelector的概况及操作流程,后文将对这些流程进行细化拆分,并通过阅读源代码来分析Spring Boot是如何实现自动加载功能的。
加载元数据配置主要是为后续操作提供数据支持。我们先来看加载相关源代码的具体实现,该功能的代码依旧在selectImports方法内。
public String[] selectImports(AnnotationMetadata annotationMetadata) {...AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);...
}
加载元数据的配置用到了AutoConfigurationMetadataLoader类提供的loadMetaData方法,该方法会默认加载类路径下META-INF/spring-autoconfigure-metadata.properties内的配置。
package org.springframework.boot.autoconfigure;
import java.io.IOException;
import java.URL;
import java.util.Enumeration;
import java.util.Properties;
import java.util.Set;
import io.UrlResource;
import io.support.PropertiesLoaderUtils;
import org.springframework.util.StringUtils;
final class AutoConfigurationMetadataLoader {
//默认加载元数据的路基protected static final String PATH = "META-INF/spring-autoconfigure-metadata.properties";private AutoConfigurationMetadataLoader() {}//默认调用该方法,传入默认PATHstatic AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {return loadMetadata(classLoader, "META-INF/spring-autoconfigure-metadata.properties");}static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {try {//获取数据存储于Enumeration中Enumeration<URL> urls = classLoader != null ? Resources(path) : SystemResources(path);Properties properties = new Properties();while(urls.hasMoreElements()) {//遍历Enumeration中的URL,加载其中的属性,存储到Properties中properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource((Element())));}return loadMetadata(properties);} catch (IOException var4) {throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", var4);}}//创建AutoConfigurationMetadata的实现类PropertiesAutoConfigurationMetadatastatic AutoConfigurationMetadata loadMetadata(Properties properties) {return new AutoConfigurationMetadataLoader.PropertiesAutoConfigurationMetadata(properties);}
//AutoConfigurationMetadata的内部实现类private static class PropertiesAutoConfigurationMetadata implements AutoConfigurationMetadata {.....}
}
在上面的代码中,AutoConfigurationMetadataLoader调用loadMetadata(ClassLoader classLoader)方法,会获取默认变量PATH指定的文件,然后加载并存储于Enumeration数据结构中。随后,从变量PATH指定的文件中获取其中配置的属性存储于Properties内,最终调用在该类内部实现的AutoConfigurationMetadata的子类的构造方法。
spring-autoconfigure-metadata.properties文件内的配置格式如下:
自动配置类的全限定名.注解名称=值
如果spring-autoconfigure-metadata.properties文件内有多个值,就用英文逗号分隔,例如:
...
org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration.ConditionalOnClass
=
org.springframework.fig.JdbcConfiguration,org.amedparam.NamedParameterJdbcOperations
...
为什么要加载此元数据呢?加载元数据主要是为了后续过滤自动配置使用。Spring Boot使用一个Annotation的处理器来收集自动加载的条件,这些条件可以在元数据文件中进行配置。Spring Boot会将收集好的@Configuration进行一次过滤,进而剔除不满足条件的配置类。在官方文档中已经明确指出,使用这种配置方式可以有效缩短Spring Boot的启动时间,减少@Configuration类的数量,从而减少初始化Bean的耗时。
加载自动配置组件是自动配置的核心组件之一,这些自动配置组件在类路径中META-INF目录下的spring.factories文件中进行注册。Spring Boot预置了一部分常用组件,如果我们需要创建自己的组件,可参考Spring Boot预置组件在自己的Starters中进行配置。通过Spring Core提供的SpringFactoriesLoader类可以读取spring.factories文件中注册的类。下面我们通过源代码来看一下如何在AutoConfigurationImportSelector类中通过getCandidateConfigurations方法来读取spring.factories文件中注册的类。
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {List<String> configurations = SpringFactoriesLoader.SpringFactoriesLoaderFactoryClass(), BeanClassLoader());Empty(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;
}
getCandidateConfigurations方法使用SpringFactoriesLoader类提供的loadFactoryNames方法来读取META-INF/spring.factories中的配置。如果程序未读取到任何配置内容,会抛出异常信息。而loadFactoryNames方法的第一个参数为getSpringFactoriesLoaderFactoryClass方法返回的EnableAutoConfiguration.class,也就是说loadFactoryNames只会读取配置文件中针对自动配置的注册类。
SpringFactoriesLoader类的loadFactoryNames方法相关代码如下:
public final class SpringFactoriesLoader {//加载文件的路径,可能存在多个public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";...//加载所有的META-INF/spring.factories文件,封装成Map,//并从中获取指定的类名的列表public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {String factoryTypeName = Name();return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, ptyList());}//加载所有的META-INF/spring.factories文件,封装成Map,//Key为接口的全类名,Value为对应配置值的List集合private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {MultiValueMap<String, String> result = ((classLoader);if (result != null) {return result;} else {try {Enumeration<URL> urls = classLoader != null ? Resources("META-INF/spring.factories") : SystemResources("META-INF/spring.factories");LinkedMultiValueMap result = new LinkedMultiValueMap();while(urls.hasMoreElements()) {URL url = (Element();UrlResource resource = new UrlResource(url);Properties properties = PropertiesLoaderUtils.loadProperties(resource);Iterator var6 = Set().iterator();while(var6.hasNext()) {Entry<?, ?> entry = (();String factoryTypeName = ((Key()).trim();String[] var9 = StringUtilsmaDelimitedListToStringArray((Value());int var10 = var9.length;for(int var11 = 0; var11 < var10; ++var11) {String factoryImplementationName = var9[var11];result.add(factoryTypeName, im());}}}cache.put(classLoader, result);return result;} catch (IOException var13) {throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);}}}...
}
简单描述以上加载的过程就是:SpringFactoriesLoader加载器加载指定ClassLoader下面的所有META-INF/spring.factories文件,并将文件解析内容存于Map<String,List<String>>内。然后,通过loadFactoryNames传递过来的class的名称从Map中获得该类的配置列表。结合下面spring.factories文件的内容格式,我们可以更加清晰地了解Map<String,List>中都存储了什么。
...
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfig-
uration,
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,
...
以上代码仅以EnableAutoConfiguration配置的部分内容为例,spring.factories文件的基本格式为自动配置类的全限定名=值,与3.6.1节中介绍的元数据的格式很相似,只不过缺少了“.注解名称”部分,如果包含多个值,用英文逗号分隔。我们继续以EnableAutoConfiguration的配置为例,Map<String,List<String>>内存储的对应数据就是key值为org.springframework.boot.autoconfigure.EnableAutoConfiguration,Value值为其等号后面以分号分割的各种AutoConfiguration类。
当然,spring.factories文件内还有其他的配置,比如用于监听的Listeners和用于过滤的Filters等。很显然,在加载自动配置组件时,此方法只用到了EnableAutoConfiguration对应的配置。因为程序默认加载的是ClassLoader下面的所有META-INF/spring.factories文件中的配置,所以难免在不同的jar包中出现重复的配置。我们可以在源代码中使用Set集合数据不可重复的特性进行去重操作。
protected final <T> List<T> removeDuplicates(List<T> list) {return new ArrayList(new LinkedHashSet(list));
}
在3.6.2节中我们获得了spring.factories文件中注册的自动加载组件,但如果在实际应用的过程中并不需要其中的某个或某些组件,可通过配置@EnableAutoConfiguration的注解属性exclude或excludeName进行有针对性的排除,当然也可以通过配置文件进行排除。先通过源代码看看如何获取排除组件的功能。
protected Set<String> getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) {//创建Set集合并把待排除的内容存于集合内,//LinkedHashSet具有不可重复性Set<String> excluded = new LinkedHashSet();excluded.addAll(this.asList(attributes, "exclude"));excluded.addAll(Arrays.StringArray("excludeName")));excluded.ExcludeAutoConfigurationsProperty());return excluded;
}
private List<String> getExcludeAutoConfigurationsProperty() {if (Environment() instanceof ConfigurableEnvironment) {Binder binder = (Environment());return (List)binder.bind("lude", String[].class).map(Arrays::asList).ptyList());} else {String[] excludes = (String[])Environment().getProperty("lude", String[].class);return excludes != null ? Arrays.asList(excludes) : ptyList();}
}
AutoConfigurationImportSelector中通过调用getExclusions方法来获取被排除类的集合。它会收集@EnableAutoConfiguration注解中配置的exclude属性值、excludeName属性值,并通过方法getExcludeAutoConfigurationsProperty获取在配置文件中key为lude的配置值。以排除自动配置DataSourceAutoConfiguration为例,配置文件中的配置形式如下:
lude=org.springframework.boot.autoconfigure.jdbc.DataSource-
AutoConfiguration
获取到被排除组件的集合之后,首先是对待排除类进行检查操作,代码如下:
private void checkExcludedClasses(List<String> configurations, Set<String> exclusions) {List<String> invalidExcludes = new ArrayList(exclusions.size());Iterator var4 = exclusions.iterator();//遍历并判断是否存在对应的配置类while(var4.hasNext()) {String exclusion = (();if (ClassUtils.isPresent(exclusion, Class().getClassLoader()) && !ains(exclusion)) {invalidExcludes.add(exclusion);}}//如果不为空,就进行处理if (!invalidExcludes.isEmpty()) {this.handleInvalidExcludes(invalidExcludes);}
}
//抛出指定异常
protected void handleInvalidExcludes(List<String> invalidExcludes) {StringBuilder message = new StringBuilder();Iterator var3 = invalidExcludes.iterator();while(var3.hasNext()) {String exclude = (();message.append("t- ").append(exclude).append(String.format("%n"));}throw new IllegalStateException(String.format("The following classes could not be excluded because they are not auto-configuration classes:%n%s", message));
}
以上代码中,checkExcludedClasses方法用来确保被排除的类存在于当前的ClassLoader中,并且包含在spring.factories注册的集合中。如果不满足以上条件,调用handleInvalidExcludes方法抛出异常。如果被排除类都符合条件,调用veAll(exclusions)方法从自动配置集合中移除被排除集合的类,至此完成初步的自动配置组件排除。
当完成初步的自动配置组件排除工作之后,AutoConfigurationImportSelector会结合在此之前获取的AutoConfigurationMetadata对象,对组件进行再次过滤。
private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) {long startTime = System.nanoTime();String[] candidates = StringArray(configurations);boolean[] skip = new boolean[candidates.length];boolean skipped = false;Iterator var8 = AutoConfigurationImportFilters().iterator();while(var8.hasNext()) {AutoConfigurationImportFilter filter = (();this.invokeAwareMethods(filter);boolean[] match = filter.match(candidates, autoConfigurationMetadata);for(int i = 0; i < match.length; ++i) {if (!match[i]) {skip[i] = true;candidates[i] = null;skipped = true;}}}if (!skipped) {return configurations;} else {List<String> result = new ArrayList(candidates.length);int numberFiltered;for(numberFiltered = 0; numberFiltered < candidates.length; ++numberFiltered) {if (!skip[numberFiltered]) {result.add(candidates[numberFiltered]);}}if (logger.isTraceEnabled()) {numberFiltered = configurations.size() - result.size();ace("Filtered " + numberFiltered + " auto configuration class in " + Millis(System.nanoTime() - startTime) + " ms");}return new ArrayList(result);}
}
下面,我们先来明确一下都有哪些数据参与了以上两种方法,然后再进行业务逻辑的梳理:
# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=
org.springframework.dition.OnBeanCondition,
org.springframework.dition.OnClassCondition,
org.springframework.dition.OnWebApplicationCondition
在spring-boot-autoconfigure中默认配置了3个筛选条件,OnBeanCondition、OnClassCondition和OnWebApplicationCondition,它们均实现了AutoConfigurationImportFilter接口。在明确了以上信息之后,该filter方法的过滤功能就很简单了。用一句话来概述就是:对自动配置组件列表进行再次过滤,过滤条件为该列表中自动配置类的注解得包含在OnBeanCondition、OnClassCondition和OnWebApplicationCondition中指定的注解,依次包含@ConditionalOnBean、@ConditionalOnClass和@ConditionalOnWebApplication。
那么在这个实现过程中,AutoConfigurationMetadata对应的元数据和AutoConfigurationImportFilter接口及其实现类是如何进行具体筛选的呢?我们先来看一下AutoConfigurationImportFilter接口相关类的结构及功能,如下图所示:
下面进行相关的源代码及步骤的分解。我们已经知道AutoConfigurationImportFilter接口可以在spring.factories中注册过滤器,用来过滤自动配置类,在实例化之前快速排除不需要的自动配置,代码如下:
package org.springframework.boot.autoconfigure;
@FunctionalInterface
public interface AutoConfigurationImportFilter {boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata);
}
match方法接收两个参数,一个是待过滤的自动配置类数组,另一个是自动配置的元数据信息。match返回的结果为匹配过滤后的结果布尔数组,数组的大小与String[ ] autoConfigurationClasses一致,如果需排除,设置对应值为false。上图已经显示AutoConfigurationImportFilter接口的match方法主要在其抽象子类中实现,而抽象子类FilteringSpringBootCondition在实现match方法的同时又定义了新的抽象方法getOutcomes,继承该抽象类的其他3个子类均实现了getOutcomes方法,代码如下:
package org.springframework.dition;
abstract class FilteringSpringBootCondition extends SpringBootCondition implements AutoConfigurationImportFilter, BeanFactoryAware, BeanClassLoaderAware {...public boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {...ConditionOutcome[] outcomes = Outcomes(autoConfigurationClasses, autoConfigurationMetadata);boolean[] match = new boolean[outcomes.length];for(int i = 0; i < outcomes.length; ++i) {match[i] = outcomes[i] == null || outcomes[i].isMatch();if (!match[i] && outcomes[i] != null) {this.logOutcome(autoConfigurationClasses[i], outcomes[i]);if (report != null) {dConditionEvaluation(autoConfigurationClasses[i], this, outcomes[i]);}}}return match;}//过滤核心功能,该方法由子类实现protected abstract ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata);...
}
通过上面的源码可以看出,match方法在抽象类FilteringSpringBootCondition中主要的功能就是调用getOutcomes方法,并将其返回的结果转换成布尔数组。而相关的过滤核心功能由子类实现的getOutcomes方法来实现。
下面以实现类OnClassCondition来具体说明执行过程。首先看一下入口方法getOutcomes的源代码:
package org.springframework.dition;
@Order(-2147483648)
class OnClassCondition extends FilteringSpringBootCondition {...protected final ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {//如果有多个处理器,采用后台线程处理if (Runtime().availableProcessors() > 1) {solveOutcomesThreaded(autoConfigurationClasses, autoConfigurationMetadata);} else {OnClassCondition.OutcomesResolver outcomesResolver = new OnClassCondition.StandardOutcomesResolver(autoConfigurationClasses, 0, autoConfigurationClasses.length, autoConfigurationMetadata, BeanClassLoader());solveOutcomes();}}...
}
Spring Boot当前版本对getOutcomes方法进行了性能优化,根据处理器的情况不同采用了不同的方式进行操作。如果使用多个处理器,采用后台线程处理(之前版本的实现方法)。否则,getOutcomes直接创建StandardOutcomesResolver来处理。在resolveOutcomesThreaded方法中主要采用了分半处理的方法来提升处理效率,而核心功能都是在内部类StandardOutcomesResolver的resolveOutcomes方法中实现。resolveOutcomesThreaded的分半处理实现代码如下:
private ConditionOutcome[] resolveOutcomesThreaded(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {int split = autoConfigurationClasses.length / 2;OnClassCondition.OutcomesResolver firstHalfResolver = ateOutcomesResolver(autoConfigurationClasses, 0, split, autoConfigurationMetadata);OnClassCondition.OutcomesResolver secondHalfResolver = new OnClassCondition.StandardOutcomesResolver(autoConfigurationClasses, split, autoConfigurationClasses.length, autoConfigurationMetadata, BeanClassLoader());ConditionOutcome[] secondHalf = solveOutcomes();ConditionOutcome[] firstHalf = solveOutcomes();ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];System.arraycopy(firstHalf, 0, outcomes, 0, firstHalf.length);System.arraycopy(secondHalf, 0, outcomes, split, secondHalf.length);return outcomes;
}
内部类StandardOutcomesResolver的源代码重点关注getOutcomes方法的实现,它实现了获取元数据中的指定配置,间接调用getOutcome(String className,ClassLoader classLoader)方法来判断该类是否符合条件,部分源代码如下:
@Order(-2147483648)
class OnClassCondition extends FilteringSpringBootCondition {
...
private final class StandardOutcomesResolver
implements OnClassCondition.OutcomesResolver {...private ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses, int start, int end, AutoConfigurationMetadata autoConfigurationMetadata) {ConditionOutcome[] outcomes = new ConditionOutcome[end - start];for(int i = start; i < end; ++i) {String autoConfigurationClass = autoConfigurationClasses[i];if (autoConfigurationClass != null) {String candidates = (autoConfigurationClass, "ConditionalOnClass");if (candidates != null) {outcomes[i - start] = Outcome(candidates);}}}return outcomes;}...//判断该类是否符合条件private ConditionOutcome getOutcome(String className, ClassLoader classLoader) {return ClassNameFilter.MISSING.matches(className, classLoader) ? Match(ConditionMessage.forCondition(ConditionalOnClass.class, new Object[0]).didNotFind("required class").items(Style.QUOTE, new Object[]{className})) : null;} }
}
在获取元数据指定配置的功能时用到了AutoConfigurationMetadata接口的get(String className,String key)方法,而该方法由类AutoConfigurationMetadataLoader来实现。该类在前面的章节已经提过了,它会加载META-INF/spring-autoconfigure-metadata.properties中的配置。
final class AutoConfigurationMetadataLoader {protected static final String PATH = "META-INF/spring-autoconfigure-metadata.properties";private AutoConfigurationMetadataLoader() {}static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {return loadMetadata(classLoader, "META-INF/spring-autoconfigure-metadata.properties");}...private static class PropertiesAutoConfigurationMetadata implements AutoConfigurationMetadata {...public String get(String className, String key, String defaultValue) {String value = Property(className + "." + key);return value != null ? value : defaultValue;}}
}
AutoConfigurationMetadataLoader的内部类PropertiesAutoConfigurationMetadata实现了AutoConfigurationMetadata接口的具体方法,其中包含我们用到的get(String className,String key)方法。根据get方法实现过程,我们不难发现,在getOutcomes方法中获取到的candidates,其实就是META-INF/spring-autoconfigure-metadata.properties文件中配置的key为自动加载注解类+“.”+“ConditionalOnClass”的字符串,而value为其获得的值。以数据源的自动配置为例,寻找到的对应元数据配置如下:
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration.ConditionalOnClass
=
javax.sql.DataSource,org.springframework.bedded.EmbeddedDatabaseType
key为自动加载组件org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,加上“.”,再加上当前过滤条件中指定的ConditionalOnClass。然后,根据此key获得的value值为javax.sql.DataSource,org.springframework.bedded.EmbeddedDatabaseType。当获取到对应的candidates值之后,最终会调用getOutcome(String className,ClassLoader classLoader)方法,并在其中使用枚举类ClassNameFilter.MISSING的matches方法来判断candidates值是否匹配。而枚举类ClassNameFilter位于OnClassCondition继承的抽象类FilteringSpringBootCondition中。
abstract class FilteringSpringBootCondition extends SpringBootCondition implements AutoConfigurationImportFilter, BeanFactoryAware, BeanClassLoaderAware {...protected static enum ClassNameFilter {PRESENT {public boolean matches(String className, ClassLoader classLoader) {return isPresent(className, classLoader);}},MISSING {public boolean matches(String className, ClassLoader classLoader) {return !isPresent(className, classLoader);}};private ClassNameFilter() {}abstract boolean matches(String className, ClassLoader classLoader);//通过类加载是否抛出异常来判断该类是否存在static boolean isPresent(String className, ClassLoader classLoader) {if (classLoader == null) {classLoader = DefaultClassLoader();}try {solve(className, classLoader);return true;} catch (Throwable var3) {return false;}}}
}
ClassNameFilter的匹配原则很简单,就是通过类加载器去加载指定的类。如果指定的类加载成功,即没有抛出异常,说明ClassNameFilter匹配成功。如果抛出异常,说明ClassNameFilter匹配失败。至此,整个过滤过程的核心部分已经完成了。我们再用一张简单的流程图来回顾整个过滤的过程:
在完成了以上步骤的过滤、筛选之后,我们最终获得了要进行自动配置的类的集合,在将该集合返回之前,在AutoConfigurationImportSelector类中完成的最后一步操作就是相关事件的封装和广播,相关代码如下:
private void fireAutoConfigurationImportEvents(List<String> configurations, Set<String> exclusions) {List<AutoConfigurationImportListener> listeners = AutoConfigurationImportListeners();if (!listeners.isEmpty()) {AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this, configurations, exclusions);Iterator var5 = listeners.iterator();while(var5.hasNext()) {AutoConfigurationImportListener listener = (();this.invokeAwareMethods(listener);AutoConfigurationImportEvent(event);}}
}
以上代码首先通过SpringFactoriesLoader类提供的loadFactories方法将spring.factories中配置的接口AutoConfigurationImportListener的实现类加载出来。然后,将筛选出的自动配置类集合和被排除的自动配置类集合封装成AutoConfigurationImportEvent事件对象,并传入该事件对象通过监听器提供的onAutoConfigurationImportEvent方法,最后进行事件广播。spring.factories中自动配置监听器相关配置代码如下。
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=
org.springframework.dition.ConditionEvaluationReportAuto-
ConfigurationImportListener
前面我们完成了自动配置类的读取和筛选,在这个过程中已经涉及了像@Conditional-OnClass这样的条件注解。打开每一个自动配置类,我们都会看到@Conditional或其衍生的条件注解。下面就先认识一下@Conditional注解。
@Conditional注解是由Spring 4.0版本引入的新特性,可根据是否满足指定的条件来决定是否进行Bean的实例化及装配,比如,设定当类路径下包含某个jar包的时候才会对注解的类进行实例化操作。总之,就是根据一些特定条件来控制Bean实例化的行为,@Conditional注解代码如下:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {Class<? extends Condition>[] value();
}
@Conditional注解唯一的元素属性是接口Condition的数组,只有在数组中指定的所有Condition的matches方法都返回true的情况下,被注解的类才会被加载。我们前面讲到的OnClassCondition类就是Condition的子类之一,相关代码如下:
package t.annotation;
import ype.AnnotatedTypeMetadata;
@FunctionalInterface
public interface Condition {boolean matches(ConditionContext var1, AnnotatedTypeMetadata var2);
}
matches方法的第一个参数为ConditionContext,可通过该接口提供的方法来获得Spring应用的上下文信息,ConditionContext接口定义如下:
package t.annotation;
import org.springframework.fig.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import nv.Environment;
import io.ResourceLoader;
import org.springframework.lang.Nullable;
public interface ConditionContext {//返回BeanDefinitionRegistry注册表,可以检查Bean的定义BeanDefinitionRegistry getRegistry();//返回ConfigurableListableBeanFactory,//可以检查Bean是否已经存在,进一步检查Bean属性@NullableConfigurableListableBeanFactory getBeanFactory();//返回Environment,可以获得当前应用环境变量,//检测当前环境是否存在Environment getEnvironment();//返回ResourceLoader,用于读取或检查所加载的资源ResourceLoader getResourceLoader();//返回ClassLoader,用于检查类是否存在@NullableClassLoader getClassLoader();
}
matches方法的第二个参数为AnnotatedTypeMetadata,该接口提供了访问特定类或方法的注解功能,并且不需要加载类,可以用来检查带有@Bean注解的方法上是否还有其他注解,AnnotatedTypeMetadata接口定义如下:
package ype;
import java.lang.annotation.Annotation;
import java.util.Map;
import java.util.function.Predicate;
import annotation.MergedAnnotation;
import annotation.MergedAnnotationCollectors;
import annotation.MergedAnnotationPredicates;
import annotation.MergedAnnotationSelectors;
import annotation.MergedAnnotations;
import annotation.MergedAnnotation.Adapt;
import org.springframework.lang.Nullable;
import org.springframework.util.MultiValueMap;
public interface AnnotatedTypeMetadata {MergedAnnotations getAnnotations();default boolean isAnnotated(String annotationName) {Annotations().isPresent(annotationName);}@Nullabledefault Map<String, Object> getAnnotationAttributes(String annotationName) {AnnotationAttributes(annotationName, false);}@Nullabledefault Map<String, Object> getAnnotationAttributes(String annotationName, boolean classValuesAsString) {MergedAnnotation<Annotation> annotation = Annotations().get(annotationName, (Predicate)null, MergedAnnotationSelectors.firstDirectlyDeclared());return !annotation.isPresent() ? null : annotation.asAnnotationAttributes(Adapt.values(classValuesAsString, true));}@Nullabledefault MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName) {AllAnnotationAttributes(annotationName, false);}@Nullabledefault MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName, boolean classValuesAsString) {Adapt[] adaptations = Adapt.values(classValuesAsString, true);return (Annotations().stream(annotationName).filter(MergedAnnotationPredicates.unique(MergedAnnotation::getMetaTypes)).map(MergedAnnotation::withNonMergedAttributes).MultiValueMap((map) -> {return map.isEmpty() ? null : map;}, adaptations));}
}
该接口的isAnnotated方法能够提供判断带有@Bean注解的方法上是否还有其他注解的功能。其他方法提供不同形式的获取@Bean注解的方法上其他注解的属性信息。
在Spring Boot的autoconfigure项目中提供了各类基于@Conditional注解的衍生注解,它们适用不同的场景并提供了不同的功能。以下相关注解均位于spring-boot-autoconfigure项目的org.springframework.dition包下。
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnWebApplicationCondition.class)
public @interface ConditionalOnWebApplication {// 所需的Web应用类型Type type() default Type.ANY;// 可选应用类型枚举enum Type {// 任何类型ANY,// 基于servlet的Web应用SERVLET,// 基于reactive的Web应用REACTIVE}
}
@ConditionalOnWebApplication注解的源代码中组合了@Conditional注解,并且指定了对应的Condition为OnWebApplicationCondition。OnWebApplicationCondition类的结构与前面讲到的OnClassCondition一样,都继承自SpringBootCondition并实现AutoConfigurationImportFilter接口。
关于实现AutoConfigurationImportFilter接口的match方法在前面已经讲解过,这里重点讲解关于继承SpringBootCondition和实现Condition接口的功能。下图展示了以OnWebApplicationCondition为例的衍生注解的关系结构,其中省略了之前章节讲过的Filter相关内容,重点描述了Condition的功能和方法。
现在我们已经学习了Condition接口的源码,那么抽象类SpringBootCondition是如何实现该方法的呢?相关源代码如下:
public abstract class SpringBootCondition implements Condition {...public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {...ConditionOutcome outcome = MatchOutcome(context, metadata);...return outcome.isMatch();...} public abstract ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata);...
}
在抽象类SpringBootCondition中实现了matches方法,而该方法中最核心的部分是通过调用新定义的抽象方法getMatchOutcome并交由子类来实现,在matches方法中根据子类返回的结果判断是否匹配。下面我们来看OnWebApplicationCondition的源代码是如何实现相关功能的:
@Order(-2147483628)
class OnWebApplicationCondition extends FilteringSpringBootCondition {...public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {boolean required = metadata.isAnnotated(Name());ConditionOutcome outcome = this.isWebApplication(context, metadata, required);if (required && !outcome.isMatch()) {ConditionMessage());} else {return !required && outcome.isMatch() ? ConditionMessage()) : ConditionOutcome.ConditionMessage());}}...
}
可以看出,是否匹配是由两个条件决定的:被注解的类或方法是否包含ConditionalOnWebApplication注解,是否为Web应用。
@Order(-2147483628)
class OnWebApplicationCondition extends FilteringSpringBootCondition {private static final String SERVLET_WEB_APPLICATION_CLASS = "org.t.support.GenericWebApplicationContext";...//推断Web应用是否匹配private ConditionOutcome isWebApplication(ConditionContext context, AnnotatedTypeMetadata metadata, boolean required) {switch(this.deduceType(metadata)) {case SERVLET://是否为servletreturn this.isServletWebApplication(context);case REACTIVE://是否为reactivereturn this.isReactiveWebApplication(context);default://其他return this.isAnyWebApplication(context, required);}}private ConditionOutcome isServletWebApplication(ConditionContext context) {Builder message = ConditionMessage.forCondition("", new Object[0]);//判断常量定义类是否存在if (!ClassNameFilter.isPresent("org.t.support.GenericWebApplicationContext", ClassLoader())) {Match(message.didNotFind("servlet web application classes").atAll());} else {//判断beanFactory是否存在if (BeanFactory() != null) {String[] scopes = BeanFactory().getRegisteredScopeNames();if (ainsElement(scopes, "session")) {return ConditionOutcome.match(message.foundExactly("'session' scope"));}}//判断Environment的类型是否为ConfigurableWebEnvironment类型if (Environment() instanceof ConfigurableWebEnvironment) {return ConditionOutcome.match(message.foundExactly("ConfigurableWebEnvironment"));} else {//判断是否为WebApplicationContext类型ResourceLoader() instanceof WebApplicationContext ? ConditionOutcome.match(message.foundExactly("WebApplicationContext")) : Match(message.because("not a servlet web application"));}}}...//从AnnotatedTypeMetadata中获取type值private Type deduceType(AnnotatedTypeMetadata metadata) {Map<String, Object> attributes = AnnotationAttributes(Name());return attributes != null ? (("type") : Type.ANY;}
}
首先在isWebApplication方法中进行Web应用类型的推断。这里使用AnnotatedTypeMetadata的getAnnotationAttributes方法获取所有关于@ConditionalOnWebApplication的注解属性。返回值为null说明未配置任何属性,默认为Type.ANY,如果配置属性,会获得type属性对应的值。如果返回值为Type.SERVLET,调用isServletWebApplication方法来进行判断。该方法的判断有以下条件:
在了解整个Spring Boot的运作原理之后,我们以Spring Boot内置的http编码功能为例,分析一下整个自动配置的过程。在常规的Web项目中该配置位于l,通过<filter>来进行配置。
<filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param>
</filter>
而在Spring Boot中通过内置的HttpEncodingAutoConfiguration来完成这一功能。下面我们具体分析一下该功能都涉及哪些配置和实现。根据前面讲的操作流程,我们先来看一下META-INF/spring.factories中对该自动配置的注册:
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=
...
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,
...
当完成注册之后,在加载的过程中会使用元数据的配置进行过滤,对应的配置内容在META-INF/spring-autoconfigure-metadata.properties文件中。org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration.ConditionalOnClass=org.springframework.web.filter.CharacterEncodingFilter,在过滤的过程中要判断自动配置类HttpEncodingAutoConfiguration是否被@ConditionalOnClass注解,源代码如下:
@Configuration
@EnableConfigurationProperties(HttpProperties.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass(CharacterEncodingFilter.class)
@ConditionalOnProperty(prefix = "ding", value = "enabled",matchIfMissing = true)
public class HttpEncodingAutoConfiguration {private final HttpProperties.Encoding properties;public HttpEncodingAutoConfiguration(HttpProperties properties) {this.properties = Encoding();}@Bean@ConditionalOnMissingBeanpublic CharacterEncodingFilter characterEncodingFilter() {CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();filter.setEncoding(Charset().name());filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST));filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE));return filter;}...}
很明显,它被@ConditionalOnClass注解,并且指定实例化的条件为类路径下必须有CharacterEncodingFilter存在。再看一下该类的其他注解:
@ConfigurationProperties(prefix = "spring.http")
public class HttpProperties {...public static class Encoding {public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;private Charset charset = DEFAULT_CHARSET;private Boolean force;private Boolean forceRequest;private Boolean forceResponse;private Map<Locale, Charset> mapping;...}
}
而在application.properties中,我们会进行如下对应配置:
ding.force=true
ding.charset=UTF-8
ding.force-request=true
...
本文发布于:2024-03-11 15:31:49,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/1710553789142186.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
| 留言与评论(共有 0 条评论) |