Skip to content

Latest commit

 

History

History
1792 lines (1535 loc) · 79.4 KB

springboot-source-note.md

File metadata and controls

1792 lines (1535 loc) · 79.4 KB

说明

Author: haitaoss

源码阅读仓库: spring-boot

参考资料和需要掌握的知识:

SpringBoot 是什么?

/** * SpringBoot 是什么? * 1. SpringBoot 就是一个 创建、配置、刷新IOC容器 的工厂,最终的目的就是得到一个IOC容器 * 2. 预留了很多接口,用于配置IOC容器,或者在容器的某个生命周期被回调,从而实现很多功能,比如 自动读取属性文件。 * 特殊的接口: * - BootstrapRegistryInitializer:用来初始化 DefaultBootstrapContext * - SpringApplicationRunListener :这是功能最多的接口,可用于 配置 Environment、配置IOC容器、接收IOC容器refresh结束回调,接收SpringBoot启动完成回调 * - ApplicationContextFactory:生成 Environment、ApplicationContext * - ApplicationContextInitializer:初始化IOC容器 * - ApplicationRunner、CommandLineRunner:在IOC容器refresh完成后会回调该接口的方法 * * 3. SpringBoot的自动配置,其实就是@Import一个类,这个类的逻辑就是读取配置文件得到自动注入的类有哪些,然后导入到IOC容器中 * */

使用@SpringBootApplication会发生什么

// 就是一个 @Configuration@SpringBootConfiguration// 会 @Import 导入类@EnableAutoConfiguration/** * TypeExcludeFilter 是一个大管家,我们可以往容器中注册 TypeExcludeFilter 类型的bean,扩展排除规则 * * AutoConfigurationExcludeFilter 是用来排除 * 标注了 @AutoConfiguration 配置类 * 或者 * META-INF/spring.factories key为 `EnableAutoConfiguration.class.getName()` 的类 * 或者 * META-INF/spring/`AutoConfiguration.class.getName()`.imports 文件中记录的类 * * 注:因为这些类会被 @EnableAutoConfiguration 所解析 * */@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication {}

spring-boot.jar!/META-INF/spring.factories

spring-boot-autoconfigure.jar!/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

注:现在SpringBoot已经改了,建议将自动注入类写在 imports 文件中。

// 这个只是注册 BasePackages.class 到容器中,没啥用@AutoConfigurationPackage/** * 会读取这两个文件拿到 自动注入的类 是啥: * - 读取 META-INF/spring.factories 文件 key是 `AutoConfiguration.class.getName()` * - 读取 META-INF/spring/`AutoConfiguration.class.getName()`.imports 文件的内容 * */@Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration { StringENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; Class<?>[] exclude() default {}; String[] excludeName() default {}; }
/** * 1. 会读取这两个文件拿到 自动注入的类 是啥: * - 读取 META-INF/spring.factories 文件 key是 `AutoConfiguration.class.getName()` * - 读取 META-INF/spring/`AutoConfiguration.class.getName()`.imports 文件的内容 * * 2. 排除的类可以通过下面三种方式配置: * 2.1 注解的 excludeName 属性的值 * 2.2 注解的 exclude 属性的值 * 2.3 属性 spring.autoconfigure.exclude 的值 * * 3. 会读取 META-INF/spring.factories 中key是 `AutoConfigurationImportFilter.class.getName()` * 作为 filter ,用来过滤掉 configurations * * 4. 会读取 META-INF/spring.factories 中key是 `AutoConfigurationImportListener.class.getName()` * 实例化后,发布事件(其实就是回调方法) * * 注:实例化后支持这些接口的注入 BeanClassLoaderAware、BeanFactoryAware、EnvironmentAware、ResourceLoaderAware *  * 5. 对 List<configClassName> 进行排序 * 5.1 先按照类名字符串进行排序 * 5.2 在按照 Order 接口的值进行排序 * 5.3 最后按照 @AutoConfigureBefore @AutoConfigureAfter  * */
// @AutoConfigureBefore @AutoConfigureAfter 的排序原理privatevoiddoSortByAfterAnnotation(...) { // 第一个排序的元素if (current == null) { current = toSort.remove(0); } processing.add(current); /** * 获取 current 应该放在 Xx类 的后面,对这些先排序,他们有序了,current 再插入就能保证 current 有序了 * * 比如 会得到 A 和 Other * @AutoConfigureAfter(A.class) * class Current{} * * @AutoConfigureBefore(Current.class) * class Other{} * */for (Stringafter : classes.getClassesRequestedAfter(current)) { // 若出现循环就报错checkForCycles(processing, current, after); // after 还没排序过,那就递归对其进行排序if (!sorted.contains(after) && toSort.contains(after)) { // 递归进行排序doSortByAfterAnnotation(classes, toSort, sorted, processing, after); } } // 移出标记processing.remove(current); // 直接插入,因为 sorted 已经插入了应该放在 current 之前的元素,所以这里直接放入就能保证 current 是有序的sorted.add(current); }

SpringBoot 生命周期

/** * SpringBoot 生命周期 * * ConfigurableApplicationContext context = SpringApplication.run(Main.class, args); * {@link SpringApplication#run(String...)} * * 1. 实例化一个 DefaultBootstrapContext * bootstrapContext 的声明周期范围是 IOC容器refresh之前 * DefaultBootstrapContext bootstrapContext = createBootstrapContext(); * * Tips:会回调 BootstrapRegistryInitializer#initialize 对其初始化 * * 2. 构造出 SpringApplicationRunListeners ,它是 SpringApplicationRunListener 的注册器 * SpringApplicationRunListeners listeners = getRunListeners(args); * * 3. 回调 SpringApplicationRunListener#starting 方法,可以在这个方法装饰 bootstrapContext * listeners.starting(bootstrapContext, this.mainApplicationClass); * * Tips:其实就是回调 listeners 中注册的 SpringApplicationRunListener 方法 * * 4. 将方法参数装饰一下 * ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); * * 5. 构造出 ConfigurableEnvironment * ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments); * * Tips: * 1. 可以使用 ApplicationContextFactory 来生成 ConfigurableEnvironment * 2. 回调 SpringApplicationRunListener#environmentPrepared 配置 ConfigurableEnvironment * Tips:application.yml 或者 SpringCloud 的 bootstrap.yml 就是通过这里的回调实现的 * * 3. 修改属性的访问顺序为: 命令行参数 -> 系统属性 -> 环境变量 ... -> 默认属性(默认是空的) * * 6. 构造出 Banner 并打印出banner的内容 * Banner printedBanner = printBanner(environment); * * Tips: * 会尝试从 environment 获取属性指定的文件:spring.banner.image.location、spring.banner.location * 或者是 banner.[jpg | gif | png | txt]对应的文件 * 作为要输出的内容 * * 7. 会根据classpath是否有某些类 推断出应该创建什么类型的IOC容器 * ConfigurableApplicationContext context = createApplicationContext(); * * 8. 配置 IOC 容器 * prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner); * * Tips: * 1. 给IOC容器设置上 environment * 2. 给IOC容器设置上 ResourceLoader、ConversionService * 3. 回调 ApplicationContextInitializer、SpringApplicationRunListener、bootstrapContext 方法 * 3.1 初始化IOC容器 ApplicationContextInitializer#initialize * 3.2 配置IOC容器 SpringApplicationRunListener#contextPrepared * 3.3 完成DefaultBootstrapContext的生命周期 DefaultBootstrapContext#close * 3.4 IOC容器配置好了 SpringApplicationRunListener#contextLoaded * Tips:将 listeners 中的 ApplicationListener 扩展给 context * * 4. 添加单例bean:springApplicationArguments、springBootBanner * 5. 设置两个属性 allowCircularReferences、allowBeanDefinitionOverriding * 6. 添加两个BeanFactoryPostProcessor: * LazyInitializationBeanFactoryPostProcessor:这个是用来判断是否需要给补全信息的 `beanDefinition.setLazyInit(true);` * PropertySourceOrderingBeanFactoryPostProcessor:这个是用来将名叫 defaultProperties 的 PropertySource 移到 最后面的,也就是属性访问的优先级最低 * 7. 将启动类添加到IOC容器中,说白了就是注册配置类 * * 9. 刷新IOC容器 * refreshContext(context); // 其实就是执行 applicationContext.refresh(); * afterRefresh(context, applicationArguments); // 预留的模板方法,空实现 * * 10. 回调 SpringApplicationRunListener#started 方法 * listeners.started(context, timeTakenToStartup); * * Tips:会回调 {@link ApplicationContextFactory#create(WebApplicationType)} 方法构造出 context * * 11. 回调 容器中类型是 ApplicationRunner、CommandLineRunner 的bean的方法 * callRunners(context, applicationArguments); * * Tips: * ApplicationRunner 优先于 CommandLineRunner 执行,然后同种类型会按照 Ordered 或者 @Order 或者 @Priority 升序排序 ,值越小越先执行 * * 12. 回调 SpringApplicationRunListener#ready 方法(SpringBoot启动耗时) * listeners.ready(context, timeTakenToReady); * * 13. 返回IOC容器 * return context; * * Tips:BootstrapRegistryInitializer、SpringApplicationRunListener、ApplicationContextFactory * 按照 类全名作为key 读取 META-INF/spring.factories 得到的 * */

@EnableConfigurationProperties

/** * 属性绑定 * * 使用 @EnableConfigurationProperties 会发生什么? * * 1. 会导入 @Import(EnableConfigurationPropertiesRegistrar.class) * * 2. EnableConfigurationPropertiesRegistrar 的回调方法的逻辑 * 2.1 会注册这几个bean * ConfigurationPropertiesBindingPostProcessor * ConfigurationPropertiesBinder.Factory * ConfigurationPropertiesBinder * BoundConfigurationProperties * * 2.2 会拿到 @EnableConfigurationProperties(value = {A.class}) 的注解值,构造出 BeanDefinition 然后注册到 BeanFactory 中 * 注:A 必须标注 @ConfigurationProperties 否则会检验失败,直接报错 * * Tips:spring-boot-autoconfigure.jar 的 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports * 写了 ConfigurationPropertiesAutoConfiguration,也就是默认情况下属性注入的功能是开启的 * @AutoConfiguration * @EnableConfigurationProperties * public class ConfigurationPropertiesAutoConfiguration {} * * * ConfigurationPropertiesBindingPostProcessor 会在bean初始化化前阶段生效 * {@link ConfigurationPropertiesBindingPostProcessor#postProcessBeforeInitialization(Object, String)} * 拦截有 @ConfigurationProperties 的bean 对其进行属性注入(得有set方法才行) * */

@Conditional 注解

注解作用分类
@ConditionalOnClassClassLoader能加载到类,才是匹配Class Conditions
@ConditionalOnMissingClassClassLoader不能加载到类,才是匹配Class Conditions
@ConditionalOnBeanBeanFactory注册的bean,存在 bean的类型 或者 bean上有注解 或者 bean的name 一致,才是匹配Bean Conditions
@ConditionalOnMissingBeanBeanFactory注册的bean,存在 **bean的类型 或者 bean上有注解 或者 bean的name 一致 ** 的就是不匹配Bean Conditions
ConditionalOnSingleCandidateBeanFactory注册的bean,存在 bean的类型 或者 bean上有注解 或者 bean的name 一致的个数只有一个 或者 有多个bean但是只有一个有@Primary 是匹配Bean Conditions
@ConditionalOnPropertyEnvironment中 属性存在 且 属性值一致 就是匹配Property Conditions
@ConditionalOnAvailableEndpoint会根据属性值决定是匹配Property Conditions
@ConditionalOnResourcecontext.getResourceLoader() 能加载到资源 才是匹配Resource Conditions
@ConditionalOnWebApplication其实就是根据 ClassLoader 中是否存在某个类,紧接着在判断 Environment 是某个类型 或者 ResourceLoader 是某个类型 就是匹配Web Application Conditions
@ConditionalOnNotWebApplication同上的反逻辑Web Application Conditions
@ConditionalOnWarDeploymentapplicationContext.getServletContext() != null 就是匹配。
注:看懂 SpringBoot 是如何启动web容器的就知道了。war包的方式是在创建IOC容器时就设置了 ServletContext ,所以在注册bean的时候,可以用这个属性来判断是否是 war 包运行的。嵌入式Web容器是在实例化 DispatcherServlet 是才设置 ServletContext
Web Application Conditions
@ConditionalOnExpression就是执行SPEL表达式,结果是啥就是啥SpEL Expression Conditions

类图和看源码思路

/** * Spring在扫描bean、注册扫描到的ConfigClass、注册@Bean的方法时会回调这个方法 * {@link Condition#matches(ConditionContext, AnnotatedTypeMetadata)} * * 而 SpringBootCondition 重写了 matches 方法 * {@link SpringBootCondition#matches(ConditionContext, AnnotatedTypeMetadata)} * 其执行逻辑是执行其抽象方法 * {@link SpringBootCondition#getMatchOutcome(ConditionContext, AnnotatedTypeMetadata)} *  * 所以说只需要看 抽象方法的实现逻辑,就能知道 SpringBootCondition具体子类的逻辑了,比如 * {@link OnBeanCondition#getMatchOutcome(ConditionContext, AnnotatedTypeMetadata)} * */
/** * 获取自动配置类时,会对自动配置类进行过滤掉 * {@link AutoConfigurationImportSelector#getAutoConfigurationEntry(AnnotationMetadata)} * * 具体有哪些过滤类时读取 META-INF/spring.factories 中key是 `AutoConfigurationImportFilter.class.getName()` * 作为 filter ,用来过滤掉 configurations * getConfigurationClassFilter().filter(configurations); * * 而 AutoConfigurationImportFilter 唯一的实现类是 FilteringSpringBootCondition,具体实现逻辑是 * {@link FilteringSpringBootCondition#match(String[], AutoConfigurationMetadata)} * {@link FilteringSpringBootCondition#getOutcomes(String[], AutoConfigurationMetadata)} * * 注:所以需要过滤自动配置类,我们可以继承 FilteringSpringBootCondition 然后实现 getOutcomes 方法即可 , * 最后将类全名写到 spring.factories 中就可以了。  * */

Conditional-0

@ConditionalOnAvailableEndpoint

/** * * @ConditionalOnAvailableEndpoint(endpoint = EnvironmentEndpoint.class) * * 1. ConditionalOnAvailableEndpoint 其实就是一个 @Conditional 注解,会用来判断是否将这个bean注册到BeanFactory中 * * 2. 用来判断 Endpoint 是否启用(为true就是启用), * 2.1 从environment读取属性 management.endpoint.`endpointId.toLowerCaseString()`.enabled 的值 (没设置就往下查找) * 2.2 从environment读取属性 management.endpoints.enabled-by-default 的值(没设置就往下查找) * 2.3 注解的值 @Endpoint.enableByDefault() , 这个值默认是true * * 3. 若启用,在进行 include满足 + 不满足exclude 的判断 * management.endpoints.[web|jmx|cloud-foundry].exposure.include * management.endpoints.[web|jmx|cloud-foundry].exposure.exclude * * 注:分别的默认值是 { JMX("*") , WEB("health") , CLOUD_FOUNDRY("*") } * * 详细的代码看 {@link OnAvailableEndpointCondition#getMatchOutcome(ConditionContext, AnnotatedTypeMetadata)} * * 自动注入的端点会使用这个注解来决定 @ConditionalOnAvailableEndpoint 是否注入,比如 EnvironmentEndpointAutoConfiguration * */

application.yml的加载

代码太狠了,只能看懂整体流程,没有细琢磨,反正能实现自定义扩展就挺好的。

/** * {@link SpringApplication#run(String...)} * {@link SpringApplication#prepareEnvironment(SpringApplicationRunListeners, DefaultBootstrapContext, ApplicationArguments)} * {@link EventPublishingRunListener#environmentPrepared(ConfigurableBootstrapContext, ConfigurableEnvironment)} * {@link EnvironmentPostProcessorApplicationListener#onApplicationEvent(ApplicationEvent)} * {@link EnvironmentPostProcessorApplicationListener#onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent)} * 读取 META-INF/spring.factories 文件 key 为 EnvironmentPostProcessor 的值,实例化然后回调方法 * {@link EnvironmentPostProcessor#postProcessEnvironment(ConfigurableEnvironment, SpringApplication)} * {@link ConfigDataEnvironmentPostProcessor#postProcessEnvironment(ConfigurableEnvironment, SpringApplication)} * {@link ConfigDataEnvironment#processAndApply()} * 1. 读取这三个属性值,作为要查找的属性文件 * spring.config.import * spring.config.additional-location * spring.config.location * 这个存在默认值:classpath:/;classpath:/config/;file:./;file:./config/;file:./config/`*`/ * 就是因为是这个默认值,所以SpringBoot项目可以在 项目路径下写 application.yml 就能生效 * * 2. 如果写的是路径 默认会拼接上 application.[yaml|yml|properties|xml] 读取属性文件成为 PropertySource * 注:application 是默认值,可以设置属性 spring.config.name 修改这个值 * * 3. 拿到 spring.profiles.include(多个值会合并) 和 spring.profiles.active(按照优先级只会取第一个) 的值作为的 profile 的值 * * 4. 遍历第二步的值 拼接上 profile 作为文件名,读取文件成为 PropertySource * * 5. 将 第二步和第四步的 PropertySource 扩展到 IOC容器的 Environment 中 * * */
/** * * application.yml 的解析主要涉及到这几个类。可以 META-INF/spring.factories 中设置的key的值,从而实现扩展属性文件的读取 * * org.springframework.boot.context.config.ConfigDataLocationResolver * 举例: StandardConfigDataLocationResolver * 解析的是路径,那就拼接上 application 作为文件名。解析的是文件那就不需要做处理 * 文件允许啥扩展名和怎么解析是依赖于 PropertySourceLoader * 注:application 是默认值,可以设置属性 spring.config.name 修改这个值 * * org.springframework.boot.env.PropertySourceLoader * 这是用来判断支持那些文件扩展名 和 解析属性文件成 PropertiesSource * * org.springframework.boot.context.config.ConfigDataLoader * 是用来处理 ConfigDataLocationResolver 的解析结果 * * */

示例代码

publicclassMain { publicstaticvoidmain(String[] args) throwsException { // System.setProperty("spring.profiles.active", "dev");System.setProperty("spring.profiles.include", "ext1,ext2"); // 这种方式增加的属性文件是一定生效的System.setProperty("spring.config.import", "classpath:/config/1.properties"); System.setProperty("spring.config.additional-location", "classpath:/ext/application-haitao.yml"); System.setProperty("spring.config.location", String.join(",", Arrays.asList( "optional:classpath:/;optional:classpath:/config/", "optional:file:./;optional:file:./config/;optional:file:./config/*/"/*,"optional:classpath:/haitao.properties"*/) // 注:使用 "optional:" 表示允许文件不存在 ) ); /** * 不能统配多个文件,只能统配到某个目录 * System.setProperty("spring.config.additional-location", "classpath:/ext/`*`.yml"); // 不支持 * System.setProperty("spring.config.additional-location", "classpath:/`*`/ext/"); // 支持,会找这个目录下名叫 application 的文件 * */ConfigurableApplicationContextcontext = SpringApplication.run(cn.haitaoss.Main.class, newString[]{"haitao"}); ConfigurableEnvironmentenvironment = context.getEnvironment(); System.out.println(environment.getProperty("dev")); System.out.println(environment.getProperty("bootstrap")); } }

自动装配举例

Tomcat 自动装配原理

看这个

# SpringBoot 启动的时候会读取这个文件,缓存 Key-Value 对 spring-boot-autoconfigure-2.6.2.jar!/META-INF/spring.factories # 主要是配置了这个value org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration

ServletWebServerFactoryAutoConfiguration 会导入 BeanPostProcessor

@Configuration(proxyBeanMethods = false) @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) @ConditionalOnClass(ServletRequest.class) @ConditionalOnWebApplication(type = Type.SERVLET) @EnableConfigurationProperties(ServerProperties.class) @Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class, // 这个是关键,注册一个 BeanPostProcessor ServletWebServerFactoryConfiguration.EmbeddedTomcat.class, // 这些是不同的 内嵌web容器ServletWebServerFactoryConfiguration.EmbeddedJetty.class, ServletWebServerFactoryConfiguration.EmbeddedUndertow.class }) publicclassServletWebServerFactoryAutoConfiguration { @Bean// 这个bean 也是挺有用的publicServletWebServerFactoryCustomizerservletWebServerFactoryCustomizer(ServerPropertiesserverProperties, ObjectProvider<WebListenerRegistrar> webListenerRegistrars, ObjectProvider<CookieSameSiteSupplier> cookieSameSiteSuppliers) { returnnewServletWebServerFactoryCustomizer(serverProperties, webListenerRegistrars.orderedStream().collect(Collectors.toList()), cookieSameSiteSuppliers.orderedStream().collect(Collectors.toList())); } // 默认注入这个 WebServerFactoryCustomizer 类型的bean,后置处理器会获取这个bean 来将配置文件配置的属性 设置到 内嵌web容器中 @Bean@ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat") publicTomcatServletWebServerFactoryCustomizertomcatServletWebServerFactoryCustomizer( ServerPropertiesserverProperties) { returnnewTomcatServletWebServerFactoryCustomizer(serverProperties); } }

导入的 WebServerFactoryCustomizerBeanPostProcessor

publicclassWebServerFactoryCustomizerBeanPostProcessorimplementsBeanPostProcessor, BeanFactoryAware { @OverridepublicObjectpostProcessBeforeInitialization(Objectbean, StringbeanName) throwsBeansException { if (beaninstanceofWebServerFactory) { // 只处理 WebServerFactory 类型。其实就是将我们配置的属性 设置到这个对象中postProcessBeforeInitialization((WebServerFactory) bean); } returnbean; } @SuppressWarnings("unchecked") privatevoidpostProcessBeforeInitialization(WebServerFactorywebServerFactory) { // getCustomizers() ---> 其实就是从容器中获取 WebServerFactoryCustomizer 类型的bean,这个bean是在自动装配的时候默认会注入的LambdaSafe.callbacks(WebServerFactoryCustomizer.class, getCustomizers(), webServerFactory) .withLogger(WebServerFactoryCustomizerBeanPostProcessor.class) .invoke((customizer) -> customizer.customize(webServerFactory)); } }

SpringMVC 自动装配

spring-boot-starter-web 自动注入了什么

  1. 在 SpringBoot 项目引入 spring-boot-starter-web
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
  1. 最终会引入 spring-boot-autoconfigure.jar

  2. spring-boot-autoconfigure 里面的 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件是自动配置的关键

    注:老版本是读的 /META-INF/spring.factories

  3. 配置类说明

    DispatcherServletAutoConfiguration:

    • 这个就是 SpringMVC 咯

    WebMvcAutoConfiguration:

    • 这个就是配置SpringMVC 的一些组件:HandlerInterceptor、ViewResolver等等

    • 里面主要是自动注入了默认bean,而且包含了 @EnableWebMvc 注入的bean在里面

    • 所以我感觉 @EnableWebMvc 和 自定义 WebMvcConfigurationSupport 子类注入到容器中 属实有点多余

@Configuration(proxyBeanMethods = false) @ConditionalOnWebApplication(type = Type.SERVLET) @ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class }) @ConditionalOnMissingBean(WebMvcConfigurationSupport.class) // look at@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10) @AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class }) publicclassWebMvcAutoConfiguration {}
  1. @EnableWebMvc 做了什么

    导入了 DelegatingWebMvcConfiguration ,这个其实就是 WebMvcConfigurationSupport 的子类,所以但我们使用 @EnableWebMvc 会让SpringBoot 默认的 SpringMVC 失效

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented@Import(DelegatingWebMvcConfiguration.class) public @interface EnableWebMvc { }
  1. DelegatingWebMvcConfiguration
// 继承 WebMvcConfigurationSupport 重写一些可配置的方法// 关键bean是 WebMvcConfigurationSupport 这里面定义注入的publicclassDelegatingWebMvcConfigurationextendsWebMvcConfigurationSupport {}

DelegatingWebMvcConfiguration: 会从容器中得到 List<WebMvcConfigurer> configurers

比如加工 interceptor

image-20220911135307077

破坏 SpringMVC 的自动装配

其实没必要破坏 SpringBoot 默认注入,因为默认注入就包含了下面的内容了。要想自定义也可以通过往容器中注入bean的方式,取消掉默认注入的bean

原理:SpringMVC 自动注入的类 WebMvcAutoConfiguration 注入条件是@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)

image-20220911114731273

第一种方式:

@EnableWebMvc// 其实就是注入了 DelegatingWebMvcConfigurationpublicclassConfigClass{}
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented@Import(DelegatingWebMvcConfiguration.class) public @interface EnableWebMvc { }

第二种方式(别使用这种方式,局限太多了):

@ComponentpublicclassWebConfigextendsWebMvcConfigurationSupport {}

第三种方式:

@ComponentpublicclassWebConfigextendsDelegatingWebMvcConfiguration {}

注册 HandlerInterceptor 的方式

继承 WebMvcConfigurationSupport 的方式

缺点非常明显:不能动态的设置 addInterceptors

就是你往容器中添加

@ComponentclassMyWebMvcConfigurer1implementsWebMvcConfigurer { @OverrideprotectedvoidaddInterceptors(InterceptorRegistryregistry) { registry.addInterceptor(newMyHandlerInterceptor()) .addPathPatterns("/**"); } }

是无法添加成功的

@ComponentclassWebConfigextendsWebMvcConfigurationSupport { @OverrideprotectedvoidaddInterceptors(InterceptorRegistryregistry) { registry.addInterceptor(newMyHandlerInterceptor()) .addPathPatterns("/**"); } }

要想在 WebConfig 的基础上又增加 addInterceptors。只能这样子,套娃处理

@ComponentclassWebConfigSonextendsWebConfig { @OverrideprotectedvoidaddInterceptors(InterceptorRegistryregistry) { // 注册父类的super.addInterceptors(registry); // 在注册新加的registry.addInterceptor(newMyHandlerInterceptor()) .addPathPatterns("/**"); } }

注意:要保证 WebConfigSon 先注册, WebConfig 后注册,才能生效。具体原因如下:

首先,拦截器是通过注册requestMappingHandlerMapping 这个bean 实现的

// 这是伪代码publicclassWebMvcConfigurationSupport { @BeanpublicRequestMappingHandlerMappingrequestMappingHandlerMapping() { // 执行方法,注册InterceptoraddInterceptors(registry); } }

因为 WebConfigSon、WebConfig 都继承了WebMvcConfigurationSupport。而 WebMvcConfigurationSupport 里面有@Bean 标注的方法。

当 Spring 在解析配置类时,如果发现该配置类有父类,且父类中含有@Bean方法,会把父类作为配置类进行解析。为了保证父类只会被解析一次会通过缓存来记录,记录的信息会包含该子类配置类是谁。

org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass(org.springframework.context.annotation.ConfigurationClass, org.springframework.context.annotation.ConfigurationClassParser.SourceClass, java.util.function.Predicate)

所以 WebConfigSon、WebConfig 先加载的才会有 requestMappingHandlerMapping bean。

当执行requestMappingHandlerMapping() 方式创建bean时,此时的 this 就是[WebConfigSon|WebConfig ] 中的一个,要想保证this 是WebConfigSon 那么就需要 WebConfigSon 比 WebConfig先被加载

如何确保第一个加载?

  1. 修改启动类,多添加一个配置类
@SpringBootApplicationpublicclassSpringbootApplicationimplementsCommandLineRunner { publicstaticvoidmain(String[] args) { SpringApplication.run(newClass[]{SpringbootApplication.class, MyConfig3.class}, args); } }
  1. 编写 MyConfig3
@Configuration@Import(WebMvcConfigurationSupport2.class) @Order(-1) // 这个很关键,保证优先加载、注册这个配置的信息publicclassMyConfig3 { // @Bean 注入方式,不会解析父类的方法中是否存在 @Bean 标注的方法 } classWebMvcConfigurationSupport2extendsWebMvcConfigurationSupport { @OverrideprotectedvoidaddInterceptors(InterceptorRegistryregistry) { System.out.println("WebMvcConfigurationSupport2---->"); super.addInterceptors(registry); }
// 方式二@ComponentScan@Order(-1) publicclassMyConfig3 {} @ComponentclassWebMvcConfigurationSupport2extendsWebMvcConfigurationSupport { @OverrideprotectedvoidaddInterceptors(InterceptorRegistryregistry) { System.out.println("WebMvcConfigurationSupport2---->"); super.addInterceptors(registry); } }

默认注入、@EnableWebMvc 和 继承DelegatingWebMvcConfiguration 的方式

这三种方式本质上就是往容器中注入 DelegatingWebMvcConfiguration bean。DelegatingWebMvcConfiguration 核心源码分析:

publicclassDelegatingWebMvcConfigurationextendsWebMvcConfigurationSupport { privatefinalWebMvcConfigurerCompositeconfigurers = newWebMvcConfigurerComposite(); // 关键点在这,会从容器中获取 WebMvcConfigurer@Autowired(required = false) publicvoidsetConfigurers(List<WebMvcConfigurer> configurers) { if (!CollectionUtils.isEmpty(configurers)) { this.configurers.addWebMvcConfigurers(configurers); } } // 设置默认属性时,会使用所有的 WebMvcConfigurer 进行加工@OverrideprotectedvoidaddInterceptors(InterceptorRegistryregistry) { this.configurers.addInterceptors(registry); } }

方式一:直接往容器中注入 WebMvcConfigurer 实现类即可

@ComponentclassMyWebMvcConfigurer1implementsWebMvcConfigurer { @OverridepublicvoidaddInterceptors(InterceptorRegistryregistry) { registry.addInterceptor(newMyHandlerInterceptor()) .addPathPatterns("/**"); } }

方式二:继承DelegatingWebMvcConfiguration 重写对应的方法

@ComponentpublicclassWebConfigurerextendsDelegatingWebMvcConfiguration { @OverrideprotectedvoidaddInterceptors(InterceptorRegistryregistry) { registry.addInterceptor(newMyHandlerInterceptor()) .addPathPatterns("/**"); registry.addInterceptor(newMyHandlerInterceptor()) .addPathPatterns("/**"); // 这一步很关键,不执行会导致 方式一 失效。super.addInterceptors(registry); } }

注册 Web 原生组件

方式一:@ServletComponentScan

Servlet 3.0 提供了以下 3 个注解:

  • @WebServlet:用于声明一个 Servlet;
  • @WebFilter:用于声明一个 Filter;
  • @WebListener:用于声明一个 Listener。

SrpingBoot 用于扫描 Serlvet 的注解:

  • @ServletComponentScan ,该注解可以扫描标记 @WebServlet、@WebFilter 和 @WebListener 三个注解的组件类,并将它们注册到容器中。
@ServletComponentScan@ComponentpublicclassWebConfig {} @WebListener// MyListener 必须是public修饰的且有无参构造器publicclassMyListenerimplementsServletContextListener { @OverridepublicvoidcontextInitialized(ServletContextEventsce) { System.out.println("cn.haitaoss.springboot.config.MyListener.contextInitialized..."); } } @WebFilterclassMyFilterimplementsFilter { @Overridepublicvoidinit(FilterConfigfilterConfig) throwsServletException { System.out.println("cn.haitaoss.springboot.config.MyFilter.init..."); } @OverridepublicvoiddoFilter(ServletRequestrequest, ServletResponseresponse, FilterChainchain) throwsIOException, ServletException { System.out.println("cn.haitaoss.springboot.config.MyFilter.doFilter..."); chain.doFilter(request, response); } } @WebServlet(urlPatterns = "/haitao") classMyServletextendsHttpServlet { @Overrideprotectedvoidservice(HttpServletRequestreq, HttpServletResponseresp) throwsServletException, IOException { System.out.println("cn.haitaoss.springboot.config.MyServlet.service..."); } }

方式二:使用 RegistrationBean 注册

使用 RegistrationBean 来注册原生 Web 组件

RegistrationBean 是个抽象类,负责将组件注册到 Servlet 容器中,Spring 提供了三个它的实现类,分别用来注册 Servlet、Filter 和 Listener。

  • ServletRegistrationBean:Servlet 的注册类
  • FilterRegistrationBean:Filter 的注册类
  • ServletListenerRegistrationBean:Listener 的注册类

我们可以在配置类中,使用 @Bean 注解将 ServletRegistrationBean、FilterRegistrationBean 和 ServletListenerRegistrationBean 添加 Spring 容器中,并通过它们将我们自定义的 Servlet、Filter 和 Listener 组件注册到容器中使用。

@ConfigurationpublicclassWebConfig1 { publicWebConfig1() { System.out.println("WebConfig1--->"); } /** * 注册 servlet * @return */@BeanpublicServletRegistrationBeanservletRegistrationBean() { MyServletmyServlet = newMyServlet(); returnnewServletRegistrationBean(myServlet, "/"); } /** * 注册过滤器 * @return */@BeanpublicFilterRegistrationBeanfilterRegistrationBean() { FilterRegistrationBeanfilterRegistrationBean = newFilterRegistrationBean(newMyFilter()); //注册该过滤器需要过滤的 url// filterRegistrationBean.setUrlPatterns(Arrays.asList("/index"));returnfilterRegistrationBean; } /** * 注册监听器 * @return */@BeanpublicServletListenerRegistrationBeanservletListenerRegistrationBean() { returnnewServletListenerRegistrationBean(newMyListener()); } } classMyListenerimplementsServletContextListener { @OverridepublicvoidcontextInitialized(ServletContextEventsce) { System.out.println("MyListener 监听到 ServletContext 初始化"); } @OverridepublicvoidcontextDestroyed(ServletContextEventsce) { System.out.println("MyListener 监听到 ServletContext 销毁"); } } classMyFilterimplementsFilter { @Overridepublicvoidinit(FilterConfigfilterConfig) throwsServletException { System.out.println("cn.haitaoss.springboot.config.MyFilter.init..."); } @OverridepublicvoiddoFilter(ServletRequestrequest, ServletResponseresponse, FilterChainchain) throwsIOException, ServletException { System.out.println("cn.haitaoss.springboot.config.MyFilter.doFilter..."); chain.doFilter(request, response); System.out.println("cn.haitaoss.springboot.config.MyFilter.doFilter...after"); } } classMyServletextendsHttpServlet { @Overrideprotectedvoidservice(HttpServletRequestreq, HttpServletResponseresp) throwsServletException, IOException { System.out.println("cn.haitaoss.springboot.config.MyServlet.service..."); } }

RegistrationBean 注册原理

很简单,web容器启动的时候会从IOC容器中获取 ServletContextInitializer 类型的bean,注册到web容器中

/**  * web容器启动时会遍历执行,完成初始化操作 {@link org.springframework.boot.web.servlet.ServletContextInitializer#onStartup(javax.servlet.ServletContext)} * 执行这子类重写的方法 {@link org.springframework.boot.web.servlet.RegistrationBean#onStartup(javax.servlet.ServletContext)} * 执行子类重写的方法: * {@link org.springframework.boot.web.servlet.ServletListenerRegistrationBean#register(java.lang.String, javax.servlet.ServletContext)} * {@link org.springframework.boot.web.servlet.AbstractFilterRegistrationBean#addRegistration(java.lang.String, javax.servlet.ServletContext)} * {@link org.springframework.boot.web.servlet.ServletRegistrationBean#addRegistration(java.lang.String, javax.servlet.ServletContext)} *  * */

image-20220911214222072

@ServletComponentScan 原理

/** * 1. 指定扫描包路径 @ServletComponentScan("package") * 2. 该注解会注册这个类型的bean {@link ServletComponentRegisteringPostProcessor} * 3. {@link ServletComponentRegisteringPostProcessor} 实现了 BeanFactoryPostProcessor,所以在 BeanFactory 初始化阶段,会回调方法 * 4. 回调方法 {@link ServletComponentRegisteringPostProcessor#postProcessBeanFactory(ConfigurableListableBeanFactory)} * 5. 创建扫描器(指定includeFilter {@link ServletComponentRegisteringPostProcessor#createComponentProvider()} ), * 6. 开始扫描包 {@link ServletComponentRegisteringPostProcessor#scanPackage(ClassPathScanningCandidateComponentProvider, String)} * 7. 只会扫描标注了 @WebListener、@WebFilter、@WebServlet() 的类 {@link ClassPathScanningCandidateComponentProvider#findCandidateComponents(String)} * 8. 处理扫描到的类,封装成 ServletComponentWebListenerRegistrar、FilterRegistrationBean、ServletRegistrationBean 的 BeanDefinition 注册到IOC容器中 * 遍历 ServletComponentHandler 处理(就是判断@WebListener、@WebFilter、@WebServlet()) {@link ServletComponentHandler#handle(AnnotatedBeanDefinition, BeanDefinitionRegistry)} * 使用对应的处理器进行处理。{@link ServletComponentHandler#doHandle(Map, AnnotatedBeanDefinition, BeanDefinitionRegistry)} * 处理的结果就是封装成对应的 RegistrationBean ,然后注册到 BeanDefinitionMap 中 * * 注: * 1. @ServletComponentScan 只在嵌入式web应用才会生效,{@link ServletComponentRegisteringPostProcessor#isRunningInEmbeddedWebServer()} * 2. 对 @WebListener 的解析比较特殊 * - 解析方法 {@link WebListenerHandler#doHandle(Map, AnnotatedBeanDefinition, BeanDefinitionRegistry)} * - 解析完注册的bean 是 {@link WebListenerHandler.ServletComponentWebListenerRegistrar} * - 类型 {@link WebListenerRegistrar} 是在创建 {@link WebServerFactory} 的时候通过BeanPostProcessor {@link WebServerFactoryCustomizerBeanPostProcessor} 使用 * {@link WebServerFactoryCustomizer} 加工 WebServerFactory的,会把 @WebListener 的全类名注册到 AbstractServletWebServerFactory 的 webListenerClassNames 属性中 * - tomcat在启动的时候,是直接拿到全类名 反射调用实例方法创建的 @WebListener 标注的类。而 @WebFilter、@WebServlet() 是从IOC容器中获取的, * 所以 我们在使用嵌入式web应用的时候 是不支持 @WebListener 标注的类使用自动装配、bean后置处理器等功能的 * */

@WebListener 有点特殊

/** * * @WebListener 解析过程,可以从两个角度看: * 1. 非嵌入式web容器如何解析的 * -> Servlet3.0 规范 本身就支持 @WebListener、@WebFilter、@WebServlet 来注册组件,注册逻辑很简单 反射调用无参构造器 * 2. 嵌入web容器如何解析的??? * * 注:@ServletComponentScan,如果是非嵌入式web容器,这个注解是不会生效的。 * 具体判断逻辑在这里:{@link ServletComponentRegisteringPostProcessor#postProcessBeanFactory(ConfigurableListableBeanFactory)} * * 通过 @ServletComponentScan 这种方式解析 @WebListener 也是直接通过反射创建的,所以不能进行属性注入等操作。 * 但是 解析 @WebFilter、@WebServlet 实例化bean是通过IOC容器创建的支持属性注入 * {@link org.springframework.boot.web.servlet.WebListenerHandler#doHandle(java.util.Map, org.springframework.beans.factory.annotation.AnnotatedBeanDefinition, org.springframework.beans.factory.support.BeanDefinitionRegistry)} * 该方法会注册这个类型的bean {@link WebListenerHandler.ServletComponentWebListenerRegistrar} * * 而 这个是通过 beanPostProcessor 回调方法 实现的 * {@link ServletWebServerFactoryAutoConfiguration} * 有@Bean 注册 {@link ServletWebServerFactoryAutoConfiguration#servletWebServerFactoryCustomizer(ServerProperties, ObjectProvider, ObjectProvider)} * * 有@Import 导入一个 BeanPostProcessorsRegistrar {@link ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar} * 这个会注册 BeanPostProcessor {@link WebServerFactoryCustomizerBeanPostProcessor} * 这个 BeanPostProcessor 会回调IOC容器中类型 WebServerFactoryCustomizer 的bean * * 也就是会调用这个 * {@link ServletWebServerFactoryCustomizer#customize(ConfigurableServletWebServerFactory)} * 这里会 添加 listenerClassName 到 ServerFactory的 webListenerClassNames 属性中。也就是记录 Listener 的全类名 * * 然后在准备context的时候会获取 * {@link TomcatServletWebServerFactory#configureContext(Context, ServletContextInitializer[])} * 添加 webListenerClassNames 到 TomcatEmbeddedContext 的 applicationListeners 属性中 {@link StandardContext#addApplicationListener(String)} * * 在tomcat 启动时,会读取属性 反射创建实例,回调 Listener 的初始化方法 * {@link StandardContext#listenerStart()} * * */

Spring WebFlux 自动装配

前置知识:Spring WebFlux 源码分析

spring-boot-autoconfigure.jar!/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports的部分内容

org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration
/** * * HttpHandlerAutoConfiguration * 注册 HttpHandler,其依赖 WebHandler * * WebFluxAutoConfiguration * 最主要是有这个 @Import({ EnableWebFluxConfiguration.class }) * 而 EnableWebFluxConfiguration 会注册 DispatcherHandler ,其是 WebHandler 类型的。DispatcherHandler 和 DispatcherServlet 的作用是类似的。 * * ReactiveWebServerApplicationContext 的 onRefresh 方法 * {@link ReactiveWebServerApplicationContext#onRefresh()} * {@link ReactiveWebServerApplicationContext#createWebServer()} * 1. 获取容器中 HttpHandler 类型的bean * 2. 使用工厂方法创建一个 WebServer,会需要 HttpHandler 作为参数 * * 比如 Tomcat : * // 使用 httpHandler 构造出 TomcatHttpHandlerAdapter,其实就是 Servlet * TomcatHttpHandlerAdapter servlet = new TomcatHttpHandlerAdapter(httpHandler); * // 将 TomcatHttpHandlerAdapter 注册到 ServletContext 中 * servletContext.addServlet(servletName, servlet); *  * 大致的调用链路:客户端发送请求 -> TomcatServer -> TomcatHttpHandlerAdapter -> HttpHandler -> WebHandler  * */

Spring Security 自动装配

前置知识:Spring Security 源码分析

spring-boot-autoconfigure.jar!/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports的部分内容

org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration

SecurityAutoConfiguration

/** * SecurityAutoConfiguration * 1. 条件注解 @ConditionalOnClass(DefaultAuthenticationEventPublisher.class) 该个类是 spring-security-core-5.7.6.jar 中定义的, * 也就是 classpath 中存在 spring-security 的依赖自动配置才会生效。 * * 2. 通过 @EnableConfigurationProperties(SecurityProperties.class) 将属性绑定到 SecurityProperties * * 3. 通过 @Import({ SpringBootWebSecurityConfiguration.class, SecurityDataConfiguration.class }) 注册两个bean * 3.1 SpringBootWebSecurityConfiguration 的作用 * - 满足 @ConditionalOnDefaultWebSecurity 就注册一个 SecurityFilterChain 到 BeanFactory 中 * - 注册 FilterRegistrationBean<ErrorPageSecurityFilter> 到 BeanFactory 中, 该 Filter 优先于 FilterChainProxy 执行,对于错误转发类型可以在这里快速返回,省的执行很多没必要的 Security Filter * - 使用 @EnableWebSecurity。这是非常关键的,也就是启用了 Spring Security 的各种组件 * * 3.2 SecurityDataConfiguration 的作用 * - 注册 SecurityEvaluationContextExtension 到 BeanFactory 中 * * 4. 若 BeanFactory 中不存在 AuthenticationEventPublisher 类型的bean,就注册默认值 DefaultAuthenticationEventPublisher 到 BeanFactory 中 * */
@AutoConfiguration@ConditionalOnClass(DefaultAuthenticationEventPublisher.class) @EnableConfigurationProperties(SecurityProperties.class) @Import({ SpringBootWebSecurityConfiguration.class, SecurityDataConfiguration.class }) publicclassSecurityAutoConfiguration { @Bean@ConditionalOnMissingBean(AuthenticationEventPublisher.class) publicDefaultAuthenticationEventPublisherauthenticationEventPublisher(ApplicationEventPublisherpublisher) { returnnewDefaultAuthenticationEventPublisher(publisher); } }

UserDetailsServiceAutoConfiguration

一大堆条件,最终目的是注册默认的 UserDetailsService 类型的bean

@AutoConfiguration@ConditionalOnClass(AuthenticationManager.class) @ConditionalOnBean(ObjectPostProcessor.class) // 都不存在才算是匹配@ConditionalOnMissingBean( value = { AuthenticationManager.class, AuthenticationProvider.class, UserDetailsService.class, AuthenticationManagerResolver.class }, type = { "org.springframework.security.oauth2.jwt.JwtDecoder", "org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector", "org.springframework.security.oauth2.client.registration.ClientRegistrationRepository", "org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository" }) publicclassUserDetailsServiceAutoConfiguration { @Bean@LazypublicInMemoryUserDetailsManagerinMemoryUserDetailsManager(SecurityPropertiesproperties, ObjectProvider<PasswordEncoder> passwordEncoder) { SecurityProperties.Useruser = properties.getUser(); List<String> roles = user.getRoles(); // InMemoryUserDetailsManager 是 UserDetailsService 类型的returnnewInMemoryUserDetailsManager( User.withUsername(user.getName()).password(getOrDeducePassword(user, passwordEncoder.getIfAvailable())) .roles(StringUtils.toStringArray(roles)).build()); } }

SecurityFilterAutoConfiguration

注册 DelegatingFilterProxy 到Web容器中作为Filter,就是通过这个 Filter 让 Spring Security 集成到 Web应用中。

@AutoConfiguration(after = SecurityAutoConfiguration.class) @ConditionalOnWebApplication(type = Type.SERVLET) @EnableConfigurationProperties(SecurityProperties.class) @ConditionalOnClass({ AbstractSecurityWebApplicationInitializer.class, SessionCreationPolicy.class }) publicclassSecurityFilterAutoConfiguration { @Bean@ConditionalOnBean(name = DEFAULT_FILTER_NAME) publicDelegatingFilterProxyRegistrationBeansecurityFilterChainRegistration( SecurityPropertiessecurityProperties) { /** * DelegatingFilterProxyRegistrationBean 它会注册 DelegatingFilterProxy 到 Web容器中作为Filter * 而 DelegatingFilterProxy 的逻辑是委托给 getBean(DEFAULT_FILTER_NAME) 返回的bean执行 *  * Tips:但使用@EnableWebSecurity 会注册名为 DEFAULT_FILTER_NAME 的bean到容器中  * */DelegatingFilterProxyRegistrationBeanregistration = newDelegatingFilterProxyRegistrationBean( DEFAULT_FILTER_NAME); registration.setOrder(securityProperties.getFilter().getOrder()); registration.setDispatcherTypes(getDispatcherTypes(securityProperties)); returnregistration; } }

OAuth2ClientAutoConfiguration

/** * OAuth2ClientAutoConfiguration * * 通过 @Import({ OAuth2ClientRegistrationRepositoryConfiguration.class, OAuth2WebSecurityConfiguration.class }) 注册两个bean * 1. OAuth2ClientRegistrationRepositoryConfiguration 的作用 * - 设置了 spring.security.oauth2.client.registration.** 属性信息就根据属性值构造出 InMemoryClientRegistrationRepository * 并注册到 BeanFactory 中 * * 2. OAuth2WebSecurityConfiguration * - BeanFactory 中不存在就注册 OAuth2AuthorizedClientService * - BeanFactory 中不存在就注册 OAuth2AuthorizedClientRepository * - BeanFactory 中不存在就注册 SecurityFilterChain * */

OAuth2ResourceServerAutoConfiguration

其实就是注册 OpaqueToken 和 JwtToken 的解析工具

@AutoConfiguration(before = { SecurityAutoConfiguration.class, UserDetailsServiceAutoConfiguration.class }) @EnableConfigurationProperties(OAuth2ResourceServerProperties.class) @ConditionalOnClass(BearerTokenAuthenticationToken.class) @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) @Import({ Oauth2ResourceServerConfiguration.JwtConfiguration.class, Oauth2ResourceServerConfiguration.OpaqueTokenConfiguration.class }) publicclassOAuth2ResourceServerAutoConfiguration { }

@ConditionalOnDefaultWebSecurity

ClassLoader 能加载到 SecurityFilterChain、HttpSecurity 两个类 且 BeanFactory 中不存在 WebSecurityConfigurerAdapter、SecurityFilterChain 两个bean 条件成立

@Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented@Conditional(DefaultWebSecurityCondition.class) public @interface ConditionalOnDefaultWebSecurity { }
classDefaultWebSecurityConditionextendsAllNestedConditions { DefaultWebSecurityCondition() { super(ConfigurationPhase.REGISTER_BEAN); } @ConditionalOnClass({ SecurityFilterChain.class, HttpSecurity.class }) staticclassClasses { } @ConditionalOnMissingBean({ org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter.class, SecurityFilterChain.class }) @SuppressWarnings("deprecation") staticclassBeans { } }

Web容器启动流程

Web容器的创建是在 onRefresh() 里面执行的,所以比单例bean的初始化 还要早,

如果有需求:当web容器启动的时候优先创建某些bean,可以通过下面的demo实现

@Component@DependsOn({"orderService"}) publicclassA { /**启动web容器之前,会从IOC容器中获取并创建 ServletContextInitializer 类型的bean,而 servletListenerRegistrationBean 实现了 ServletContextInitializer 接口,所以 ServletContextInitializer 会在web容器初始化阶段 被创建,又因为 servletListenerRegistrationBean 属于 A 这个配置类的,所以创建 servletListenerRegistrationBean 的时候会先 创建 A ,而 A 上面有 @DependsOn({"orderService"})所以会先创建 orderService*/@BeanpublicServletListenerRegistrationBeanservletListenerRegistrationBean() { ServletListenerRegistrationBean<ServletContextListener> listenerRegistrationBean = newServletListenerRegistrationBean<>(); ServletContextListenerservletContextListener = newServletContextListener() { @OverridepublicvoidcontextInitialized(ServletContextEventsce) { log.warn("servletListenerRegistrationBean执行顺序。。。。。。"); } }; listenerRegistrationBean.setListener(servletContextListener); listenerRegistrationBean.setOrder(2); returnlistenerRegistrationBean; } }

SpringBoot 嵌入式Tomcat启动流程

/** * 1. 启动Tomcat服务器 * {@link org.apache.catalina.startup.Tomcat#start()} * {@link LifecycleBase#start()} * {@link StandardServer#startInternal()} * {@link LifecycleBase#start()} * {@link StandardService#startInternal()} * {@link LifecycleBase#start()} * {@link StandardEngine#startInternal()} * {@link ContainerBase#startInternal()} * {@link LifecycleBase#start()} * {@link StandardHost#startInternal()} * {@link ContainerBase#startInternal()} * {@link LifecycleBase#start()} * {@link StandardContext#startInternal()} * * 看着很牛逼,但是还没有悟。StandardServer -> StandardService -> StandardEngine -> StandardHost -> StandardContext * 2. {@link StandardContext#startInternal()} 里面做了什么 * 2.1 容器初始化器回调 {@link ServletContainerInitializer#onStartup(Set, ServletContext)} ,可以在回调方法内 注册三大组件。 * SpringBoot 嵌入式Tomcat,就是在这里注册 {@link ServletContextInitializer} 类型的bean的 * * 2.2 启动监听器 {@link StandardContext#listenerStart() } * - 有 findApplicationListeners,那就反射创建存到 result 集合中(这个就是通过 @WebListener 注册才会有) * - 遍历 result 集合,按照类型 分开存到两个集合中: eventListeners、lifecycleListeners * - 获取通过 {@link ServletContainerInitializer#onStartup(Set, ServletContext)} 注册的Listener 按照类型添加到两个集合中: eventListeners、lifecycleListeners * - 设置一个属性值 `context.setNewServletContextListenerAllowed(false);` 。这个属性是为了防止在注册 lifecycleListener * - 回调 lifecycleListeners 集合里面的 `contextInitialized` 方法 * 注:不支持在contextInitialized 添加 lifecycleListener {@link ApplicationContext#addListener(EventListener)} * 注:可以看到通过 @WebListener 注册的监听器优先级比 {@link ServletContainerInitializer#onStartup(Set, ServletContext)} 注册的监听器先执行 * * 2.3 启动过滤器 {@link StandardContext#filterStart()} * - 回调初始化方法 {@link ApplicationFilterConfig#initFilter()} * * 2.4 加载并初始化所有 设置 `load on startup` 的servlet * - {@link StandardContext#loadOnStartup(Container[])} * - {@link StandardWrapper#loadServlet()} 如果servlet没有创建就创建 * - {@link Servlet#init(ServletConfig)} 回调servlet的初始化方法 * * 2.5 启动tomcat后台线程 {@link ContainerBase#threadStart()} * */

SpringBoot 独立Web应用启动流程

依赖 Servlet3.0

SpringBoot War 运行原理

spring-web 中有一个类 SpringServletContainerInitializer 该类实现了 ServletContainerInitializer 接口,所以tomcat 启动的时候会进行回调 SpringServletContainerInitializer#OnStartup

SpringServletContainerInitializer#OnStartup 会处理所有 WebApplicationInitializer 的类,然后回调

WebApplicationInitializer#onStartup

SpringBoot 提供SpringBootServletInitializer 抽象类 `` 实现该接口WebApplicationInitializer

所以我们 在SpringBoot 的启动类 实现 SpringBootServletInitializer 就可实现,在tomcat启动的时候 回调我们的启动类,来启动tomcat容器

注:SpringServletContainerInitializer 是通过 SPI 机制,告诉的tomcat,所以tomcat启动时才会知道有这个类

@SpringBootApplicationpublicclassWebApplicationextendsSpringBootServletInitializer{ @OverrideprotectedSpringApplicationBuildconfigure(SpringApplicationBuildbuild){ // 设置配置类,不写也没事 默认会判断当前类(WebApplication) 是否有@Configuration 注解,有就作为sourcereturnbuild.sources(WebApplication.class); } }
/** * * Servlet3.0 支持注解开发。 * 1. 提供一个接口 {@link javax.servlet.ServletContainerInitializer#onStartup(java.util.Set, javax.servlet.ServletContext)} * web容器启动时会回调该方法,第一个参数就是要处理的类型 第二个参数就是web容器上下文 * 2. 提供一个注解 @HandlesTypes(WebApplicationInitializer.class) * 就是 onStartup 第一个入参 会传入什么类型的类 * * SpringBoot 独立web项目启动流程: * * 1. 当web容器启动时 会获取ClassLoader 里面所有 ServletContainerInitializer 类型的实例,回调 onStartup 方法 {@link javax.servlet.ServletContainerInitializer#onStartup(java.util.Set, javax.servlet.ServletContext)} * 而依赖 spring-web-5.3.14.jar 中 有这个类 {@link SpringServletContainerInitializer} 实现ServletContainerInitializer接口, * 所以web容器启动时会回调这个方法 {@link SpringServletContainerInitializer#onStartup(Set, ServletContext)} * * 2. {@link SpringServletContainerInitializer#onStartup(Set, ServletContext)} 的逻辑 * 2.1 判断 类不是接口 且 不是抽象类 且 是WebApplicationInitializer的子类 --> 存到集合中 * 2.2 使用 {@link AnnotationAwareOrderComparator} 对集合进行升序排序。可通过这三种方式设置排序值:Ordered接口、@Order、@Priority * 2.3 遍历集合,回调 {@link WebApplicationInitializer#onStartup(ServletContext)} * * 3. SpringBoot 提供一个抽象类 SpringBootServletInitializer 实现了 WebApplicationInitializer 接口,所以我们继承抽象类,这样 web容器启动的时候就会执行, * 然后就能完成 IOC 容器的初始化。 * 所以会回调这个方法 {@link SpringBootServletInitializer#onStartup(ServletContext)} * * 3.1 spring容器的配置类是啥? * 1. 调用模板方法 {@link SpringBootServletInitializer#configure(SpringApplicationBuilder)}。子类可重写这个方法,往里面设置 sources * 2. getAllSources 为空,会判断当前类是否有 @Configuration 注解,有就把当前类设置为 primarySources * 3. getAllSources 为空 ,直接报错 * * 3.2 创建 springboot 实例 * {@link SpringBootServletInitializer#createSpringApplicationBuilder()} 实例化的关键点就是会加载 META-INF/spring.factories , * 解析key-value对作为 SpringBoot 实例的属性:bootstrapRegistryInitializers、initializers、listeners * * 3.3 给 SpringBoot 实例添加一个 initializer ,这个就是区别嵌入式web容器 和 独立web项目的关键初始化器 * `builder.initializers(new ServletContextApplicationContextInitializer(servletContext));` * * 4. 启动 SpringBoot {@link SpringApplication#run(String...)} * * 5. 准备context {@link SpringApplication#prepareContext(DefaultBootstrapContext, ConfigurableApplicationContext, ConfigurableEnvironment, SpringApplicationRunListeners, ApplicationArguments, Banner)} * 5.1 初始化IOC 容器 {@link SpringApplication#applyInitializers(ConfigurableApplicationContext)} * 会回调这个方法设置属性 {@link ServletContextApplicationContextInitializer#initialize(ConfigurableWebApplicationContext)} * 这里属性就是后面判断是 嵌入式web容器 还是 独立web项目的关键 * 5.2 关键点就是吧 sources 注册到 IOC 容器中 {@link SpringApplication#load(ApplicationContext, Object[])} * * 6. 刷新IOC 容器(这就是spring的流程了) {@link AbstractApplicationContext#refresh()} * * 7. web容器会在这里启动 {@link ServletWebServerApplicationContext#onRefresh()} * * 8. 创建web服务 {@link ServletWebServerApplicationContext#createWebServer()} * 8.1 servletContext == null ,获取 getWebServerFactory 创建web容器 * 8.2 servletContext != null 就注册三大组件到web容器中 * `getSelfInitializer().onStartup(servletContext);` * `getSelfInitializer()` 就是获取IOC 容器中 ServletContextInitializer 类型的bean,排序,回调方法注册组件 * * 注:servletContext 是通过 {@link ServletContextApplicationContextInitializer} 设置的 * 9. 完成bean工厂的初始化 {@link AbstractApplicationContext#finishBeanFactoryInitialization(ConfigurableListableBeanFactory)} * 把配置类注册到ioc 容器中 * {@link SpringApplication#prepareContext(DefaultBootstrapContext, ConfigurableApplicationContext, ConfigurableEnvironment, SpringApplicationRunListeners, ApplicationArguments, Banner)} * */

SpringBoot Test

https://docs.spring.io/spring-boot/docs/2.7.9/reference/html/features.html#features.testing

Spring Boot提供了一个@SpringBootTest注解,当您需要Spring Boot特性时可以使用。注解的工作原理是通过SpringApplication创建测试中使用的ApplicationContext。除了@SpringBootTest之外,还提供了许多其他注解来测试应用程序的更具体的功能,比如(@JdbcTest、@WebMvcTest)。

如果没有指定类,搜索算法从包含测试的包开始,直到找到一个用 @SpringBootApplication@SpringBootConfiguration 标注的类。只要您以合理的方式构造代码,通常就可以找到主配置

如果您使用的是JUnit 4,请不要忘记还将 @RunWith(SpringRunner.class) 添加到测试中,否则注释将被忽略。如果您使用的是JUnit 5,则不需要添加与 @SpringBootTest 等效的 @ExtendWith(SpringExtension.class) ,其他 @…Test 注释也不需要,因为已经使用它进行了注释。

@RunWith(SpringRunner.class) @SpringBootTestpublicclassMain { }

比如:

@BootstrapWith(SpringBootTestContextBootstrapper.class) @ExtendWith(SpringExtension.class) public @interface SpringBootTest {} @BootstrapWith(JdbcTestContextBootstrapper.class) @ExtendWith(SpringExtension.class) public @interface JdbcTest {}

默认情况下, @SpringBootTest 不会启动服务器。您可以使用 @SpringBootTestwebEnvironment 属性来进一步细化您的测试运行方式

@SpringBootTest( // 配置类classes = Main.class, // 指定环境。因为默认是不会使用 WebApplicationContextwebEnvironment = SpringBootTest.WebEnvironment.MOCK, // webEnvironment = SpringBootTest.WebEnvironment.NONE,// 配置属性properties = "spring.profiles.active=haitao" ) publicclassMain { @AutowiredApplicationContextapplicationContext; @Testpublicvoidtest(){ System.out.println(applicationContext.getClass()); System.out.println(Arrays.toString(applicationContext.getBeanDefinitionNames())); } }

默认情况下会将标注了 @SpringBootApplication 的类用作测试的配置类。

因此,不要在应用程序的主类中添加特定特定功能的配置这非常重要。假设您正在使用Spring Batch,并且依赖于它的自动配置,您可以如下定义

@SpringBootApplicationpublicclassMyApplication { } @Configuration(proxyBeanMethods = false) @EnableBatchProcessingpublicclassMyBatchConfiguration { // 这个得和 MyApplication 在同包或者子包下才行 }

好处是,下面的测试并不会注册 MyBatchConfiguration 从而可以实现简单的测试

具体的 @XxTest 会扫描那些bean,可以看官网。

比如 @WebMvcTest

@WebMvcTestpublicclassMain { @AutowiredApplicationContextapplicationContext; @Testpublicvoidtest(){ System.out.println(applicationContext.getClass()); System.out.println(Arrays.toString(applicationContext.getBeanDefinitionNames())); } }

Actuator

Spring Boot Actuator 使用文档说明:https://docs.spring.io/spring-boot/docs/2.7.9/reference/html/actuator.html#actuator.endpoints

Spring Boot Actuator Web API 快速预览:https://docs.spring.io/spring-boot/docs/2.7.9/actuator-api/htmlsingle/

Spring Boot 包括许多附加功能,可帮助您在将应用程序推向生产环境时监视管理应用程序。您可以选择使用HTTP端点JMX来管理和监视应用程序。审计、运行状况和指标收集也可以自动应用于应用程序。

spring-boot-actuator 模块提供了 Spring Boot 的所有生产就绪功能。要想启动这些功能,建议是添加 spring-boot-starter-actuator 的依赖项。原理就是其 spring-boot-actuator-autoconfigure.jar!/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中声明了自动配置类,所以启动SpringBoot就会生效

<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> </dependencies>

简单翻译一下官方文档的一些内容,具体有啥用,看[下面的源码分析](#Endpoint 自动注入原理分析)就知道了

默认情况下,启用除 shutdown 以外的所有端点。要配置端点的启用,请使用其 management.endpoint.<id>.enabled 属性。以下示例启用 shutdown 端点:

management.endpoint.shutdown.enabled=true

如果您希望端点启用是选择加入而不是选择退出,请将 management.endpoints.enabled-by-default 属性设置为 false ,并使用单个端点 enabled 属性重新选择加入。下面的示例启用 info 端点并禁用所有其他端点

management.endpoints.enabled-by-default=false management.endpoint.info.enabled=true

禁用的 Endpoint 将从应用程序上下文中完全删除。如果只想更改公开 Endpoint 的技术,请改用 includeexclude 属性。

默认值

PropertyDefault
management.endpoints.jmx.exposure.exclude
management.endpoints.jmx.exposure.include*
management.endpoints.web.exposure.exclude
management.endpoints.web.exposure.includehealth

* 可用于选择所有端点。例如,若要通过HTTP公开除 envbeans 端点之外的所有内容,请使用以下属性

* 在YAML中有特殊的含义,因此如果要包括(或排除)所有端点,请务必加上引号。

management.endpoints.web.exposure.include=* management.endpoints.web.exposue.exclude=env,beans
OperationHTTP method
@ReadOperationGET
@WriteOperationPOST
@DeleteOperationDELETE

示例代码

示例代码

常用的属性key

# 是否启用发现页,指的是 访问 /actuator 是否显示可访问 endpoint 的信息management.endpoints.web.discovery.enabled=true # 配置端点web访问的前缀路径management.endpoints.web.basePath=/actuator # 配置web的方式,可以访问那些端点management.endpoints.web.exposure.include=* # 替换路径,将 end 映射成 newEnvmanagement.endpoints.web.pathMapping.env=newEnv # 设置某个端点的缓存management.endpoint.env.cache.time-to-live=1 # health 端点的配置management.endpoint.health.show-details=always management.endpoint.health.show-components=always management.health.defaults.enabled=true # 给 health 端点 设置 custom 分组,指定这个分组可以看到的 HealthIndicator 是那些management.endpoint.health.group.custom.include=haitao,db # 给 health 端点 设置 custom 分组,指定这个分组附加访问路径management.endpoint.health.group.custom.additionalPath=server:/health-custom

Endpoint 自动注入原理分析

spring-boot-actuator-autoconfigure.jar!/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 的部分内容

org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration org.springframework.boot.actuate.autoconfigure.env.EnvironmentEndpointAutoConfiguration

EndpointAutoConfiguration

/** * * {@link EndpointAutoConfiguration} * 1. 添加 ParameterValueMapper bean * 用来转换参数值的,在反射调用 @ReadOperation、@WriteOperation、@DeleteOperation 标注的方法的构造方法参数时 * 会使用这个来映射参数的值,生成参数列表。{@link ReflectiveOperationInvoker#invoke(InvocationContext)} * * 2. 添加 CachingOperationInvokerAdvisor bean * @ReadOperation、@WriteOperation、@DeleteOperation 的方法执行是通过 {@link OperationInvoker#invoke(InvocationContext)} * CachingOperationInvokerAdvisor 是用来对 OperationInvoker 进行装饰的,目的就是拦截方法的执行 * 存在缓存就返回缓存结果。 * * 注:可以通过 management.endpoint.`endpointId.toLowerCaseString()`.cache.time-to-live 设置缓存的时间 **/

WebEndpointAutoConfiguration

/** * {@link WebEndpointAutoConfiguration} * 1. PathMapper * 默认是返回这个实现类 MappingWebEndpointPathMapper,会使用Map映射属性的值 management.endpoints.web.pathMapping.endpointId = newEndpointId * PathMapper 会在下面的三个 EndpointDiscoverer 被用到,其作用是为了修改 endpoint 要映射的url * * 2. WebEndpointDiscoverer * * 关键是有这几个属性: * - ParameterValueMapper 反射回调方法时,会使用这个来对参数进行装换 * - PathMapper 用来替换 endpoint 暴露的url * - org.springframework.boot.actuate.endpoint.invoke.OperationInvokerAdvisor 用来对调用@ReadOperation、@WriteOperation、@DeleteOperation方法的装饰类进行增强 * - EndpointFilter<ExposableWebEndpoint> 过滤哪些 endpoint 应该暴露 * * 是用来 找到标注了 @Endpoint 的bean 映射成 EndpointBean , 然后找到标注了 @EndpointExtension 的bean 映射成 ExtensionBean * 将 extensionBean 关联给 EndpointBean,会执行 EndpointFilter<ExposableWebEndpoint> 判断是否应该暴露。然后会解析 EndpointBean * 中的 @ReadOperation、@WriteOperation、@DeleteOperation 注解的方法装饰成 Operation 对象 * 将 Method 装饰成 DiscoveredOperationMethod * 将 target、DiscoveredOperationMethod、parameterValueMapper 装饰成 OperationInvoker * 使用 OperationInvokerAdvisor 对 OperationInvoker 进行装饰 * 最后 DiscoveredOperationMethod + OperationInvokerAdvisor 构造出 Operation * 最后会将 EndpointBean 装饰成 ExposableEndpoint 再返回  * * 3. ControllerEndpointDiscoverer * 关键是有这几个属性: * - PathMapper 用来替换 endpoint 暴露的url * - EndpointFilter<ExposableControllerEndpoint> 过滤哪些 endpoint 应该暴露 * * 是用来找到标注了 @ControllerEndpoint、@RestControllerEndpoint 的bean 映射成 EndpointBean * 会执行 EndpointFilter<ExposableControllerEndpoint> 判断是否应该暴露 * 最后会将 EndpointBean 装饰成 ExposableEndpoint 再返回 * * 4. ServletEndpointDiscoverer * 关键是有这几个属性: * - PathMapper 用来替换 endpoint 暴露的url * - EndpointFilter<ExposableServletEndpoint> 过滤哪些 endpoint 应该暴露 * * 是用来找到标注了 @ServletEndpoint 的bean 映射成 EndpointBean * 会执行 EndpointFilter<ExposableServletEndpoint> 判断是否应该暴露 * 最后会将 EndpointBean 装饰成 ExposableEndpoint 再返回 * * 5. PathMappedEndpoints 是记录所有的 EndpointsSupplier 的,也就是聚合 WebEndpointDiscoverer、ControllerEndpointDiscoverer、ServletEndpointDiscoverer * **/

ManagementContextAutoConfiguration

@AutoConfiguration@EnableConfigurationProperties({WebEndpointProperties.class, ManagementServerProperties.class}) publicclassManagementContextAutoConfiguration { @Configuration(proxyBeanMethods = false) @EnableManagementContext(ManagementContextType.SAME) staticclassEnableSameManagementContextConfiguration {} } /** * Tips:所以 @Import(ManagementContextConfigurationImportSelector.class) 就会被解析 * * 而 ManagementContextConfigurationImportSelector 的作用就是 * 读取 META-INF/spring/org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration.imports 的内容 * 作为配置类导入到 BeanFactory 中 * */@Import(ManagementContextConfigurationImportSelector.class) @interface EnableManagementContext {}

META-INF/spring/org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration.imports部分内容

org.springframework.boot.actuate.autoconfigure.endpoint.web.ServletEndpointManagementContextConfiguration org.springframework.boot.actuate.autoconfigure.endpoint.web.servlet.WebMvcEndpointManagementContextConfiguration

ServletEndpointManagementContextConfiguration

/** * {@link ServletEndpointManagementContextConfiguration} * 会注册 ServletEndpointRegistrar bean * 其作用是将 servletEndpointsSupplier.getEndpoints() 映射成 Servlet ,然后使用 ServletContext 进行注册 * * */

WebMvcEndpointManagementContextConfiguration

/** * {@link WebMvcEndpointManagementContextConfiguration} * 会注册三个bean,这三个bean其实就是 HandlerMapping: * - WebMvcEndpointHandlerMapping * 1. 拿到 @Endpoint、@ServletEndpoint、@ControllerEndpoint、@RestControllerEndpoint 的bean记录起来,目的是为了访问 /actuator 时能看到所有端点的信息 * 2. 处理有 @Endpoint 的bean,并会解析bean里面的 @ReadOperation 、@WriteOperation 、@DeleteOperation 方法,注册 Path和Method 的映射关系 * * - ControllerEndpointHandlerMapping * 1. 处理 @ControllerEndpoint @RestControllerEndpoint 的bean,处理里面的 @RequestMapping , 注册 Path和Method 的映射关系 * * - AdditionalHealthEndpointPathsWebMvcHandlerMapping * 若 group 设置了 additionalPath,那就注册映射关系 * 比如设置了属性:management.endpoint.health.group.custom.additionalPath=server:/health-custom *  * 注:health 端点逻辑没细看,大致思路是用到这几个配置类 * {@link HealthEndpointAutoConfiguration} * {@link HealthEndpointConfiguration#healthEndpointGroups(ApplicationContext, HealthEndpointProperties)} * {@link AutoConfiguredHealthEndpointGroups#AutoConfiguredHealthEndpointGroups(ApplicationContext, HealthEndpointProperties)} * * */

EnvironmentEndpointAutoConfiguration

@ConditionalOnAvailableEndpoint的原理

@AutoConfiguration@ConditionalOnAvailableEndpoint(endpoint = EnvironmentEndpoint.class) @EnableConfigurationProperties(EnvironmentEndpointProperties.class) publicclassEnvironmentEndpointAutoConfiguration { @Bean@ConditionalOnMissingBeanpublicEnvironmentEndpointenvironmentEndpoint() { returnendpoint; } @Bean@ConditionalOnMissingBean@ConditionalOnBean(EnvironmentEndpoint.class) @ConditionalOnAvailableEndpoint(exposure = { EndpointExposure.WEB, EndpointExposure.CLOUD_FOUNDRY }) publicEnvironmentEndpointWebExtensionenvironmentEndpointWebExtension() { returnnewEnvironmentEndpointWebExtension(environmentEndpoint); } }

Endpoint相关类图

ExposableEndpoint 是用来映射 @Endpoint 的

Endpoint

EndpointFilter 是用来过滤 ExposableEndpoint 是否应该暴露的

EndpointFilter

EndpointsSupplier 会使用 EndpointFilter 来过滤那些 ExposableEndpoint 应该返回

EndpointsSupplier

close