持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情
一、背景
springboot的出现无疑事我们java开发人员的一个福音,它不仅帮我们集成了各类的装配插件,同时也让我们在各个插件的配置文件变的简单很多。创建一个springboot不用任何的配置,我们就可以让项目启动起来,比如端口号,web服务器等都是springboot自动帮我们进行配置的,我们需要修改的时候,只需要在默认的application.properties中进行相关配置即可,比如修改端口号:server.port=8090,那么还有一些其他配置该如何进行配置呢?我们可以看下官网给的配置信息清单。
1-1、springboot的配置信息
首先打开spring官网,然后打开springboot的最新稳定版配置信息,如下: 1、打开官网
2、查看最新版本文档
3、打开配置文件信息页
4、官网提供了相关配置信息
5、搜索我们熟知的端口号
1-2、通过启动类逐步了解自动配置
1-2-1、springboot的@SpringBootApplication
之前的文章提到过,springboot启动类有一个注释@SpringBootApplication,标注了这个注解就代表这个类为springboot的主配置类,SpringBoot需要运行这个类的main方法来启动SpringBoot应用;我们进入这个注解
可以看到这个文件中有很一些其他的注解,这些注解各代表含义如下:
@Target({ElementType.TYPE}):
设置当前注解可以标记在哪,经常用到的如下:
TYPE:标记在类上面
FIELD:标记在属性字段上面
METHOD:标记在方法方面
CONSTRUCTOR:标记在构造函数
PARAMETER:标记在方法参数上面\
@Retention(RetentionPolicy.RUNTIME):当注解标注的类编译以什么方式保留
RUNTIME:会被jvm加载
CLASS :只保留原class文件,不会保留注解,就是在编译之后注解不会保留,通过反射获得注解的方式,是无法获取到的类对象的
SOURCE:
@Documented :java doc 会生成注解信息
@Inherited :是否会被继承
@SpringBootConfiguration :标注在某个类上,表示这是一个Spring Boot的配置类;进入这个类如下
其中
@Confifiguration:配置类上来标注这个注解;就是讲当前标注的类最为配置类。
@Indexed,它可以为 Spring 的模式注解添加索引,以提升应用启动性能。
@EnableAutoConfifiguration:开启自动配置功能;
以前我们需要配置的东西,Spring Boot帮我们自动配置;@EnableAutoConfifiguration告诉SpringBoot开启自动配置,会帮我们自动去加载自动配置类
@ComponentScan : 扫描包 相当于在spring.xml 配置中context:component-scan 但是并没有指定basepackage,如果没有指定,spring底层会自动扫描当前配置类所在的包,如我这边会自动扫描com.jony包下的所有包
excludeFilters:相当于之前spring文章中说的context:exclude-filter
TypeExcludeFilter:springboot对外提供的扩展类, 可以供我们去按照我们的方式进行排除,这个类我们可以进行进行实现,并重写里面的match方法,实现我们自己需要排除的相关操作。
通过以上操作,就可以将MyController排除
AutoConfigurationExcludeFilter 排除所有配置类并且是自动配置类中里面的其中一个
1-2-2、@EnableAutoConfiguration 自动配置类注解
这个注解里面,最主要的就是@EnableAutoConfiguration,这么直白的名字,一看就知道它要开启自动配置,我们进去@EnableAutoConfiguration的源码。
再次进入@AutoConfigurationPackage
再次进入Registrar
进入register方法,如下:
debug模式启动,可以看到packageNames=com.jony
通过以上我们可以得知@AutoConfigurationPackage将当前配置类所在包保存在BasePackages的Bean中。供Spring内部使用
1-2-2-1、AutoConfigurationImportSelector
1、在EnableAutoConfiguration注解类中,通过@Import导入了AutoConfigurationImportSelector,如下:
2、可以看到其实现了DeferredImportSelector,在之前spring文章中提到可以通过ImportSelector,导入数组类的完整限定名进行bean的注入。
而这个地方的DeferredImportSelector是一个延迟的加载模式
3、再次进入DeferredImportSelector,可以看到其继承了ImportSelector,并且定了一个新的Group接口,同时有一个getImportGroup方法。
4、AutoConfigurationImportSelector注解类中就会查看是否实现了DeferredImportSelector中的getImportGroup方法,如果没有实现就会调用ImportSelect中的selectImports,如果有则需要返回一个自定义的实现了DeferredImportSelector中group接口的类,如下:
5、如果实现了DeferredImportSelector中的getImportGroup方法,就会同步调用Group接口里的process()方法(用于获得相关配置信息),以及通过Group接口里的selectImports()方法(用于对配置进行整理过滤)。
5-1、DeferredImportSelector中的子接口Group
5-2、AutoConfigurationImportSelector中实现的process和selectImports()
6、在process方法中核心调用了getAutoConfigurationEntry方法,然后在这个方法中调用了getCandidateConfigurations,加载了所有自动配置类(后缀都是以AutoConguration结尾)使用springboot2.6.7版本目前有133个,添加debug可以看到如下:
7、在getCandidateConfigurations中通过调用SpringFactoriesLoader.loadFactoryNames方法来获取所有自动配置文件
7-1、通过调用loadFactoryNames中的factoryType.getName(),获得到了完整限定名
7-2、同时调用了loadSpringFactories,用来读取所有存放配置类的文件,首先会从缓存中进行读取,如果缓存中没有则读取相关jar包中的配置文件,路径为:
META-INF/spring.factories
,如下:
7-3、通过上图可以看到,通过classLoader去类路径查找相关的配置文件,实际上就是jar包中的BOOT-INF/lib中的包去查找
7-4、如下可以看到通过while挨个读取本地仓库中的jar包中的META-INF/spring.factories
7-5、以springboot为例,可以看到jar包下,相关信息,通过读取spring.factories,就可以读取到key/val的值,这块value为list
7-5-1、最终实际读取了以下三个jar包中的spring.factories
spring-boot
spring-autoconfigure
spring-beans
7-6、最终可以看到一共读取了19个key
7-7、需要注意的是,这19个key并不是全部都是自动配置类,会通过org.springframework.boot.autoconfigure.EnableAutoConfiguration进行过滤,这样就得到133个value,只有这个key下的value才是自动配置类
7-8、之后又根据pom中依赖的启动器,进行再次过滤,因为目前只依赖了springmvc,因此最终过滤出来,最终剩下25个,如下
8、通过以上的流程最终流程为
1-3、自动配置类的解析
上面介绍了自动配置类的读取加载过程,那一个自动配置类的原理到底是怎么样的呢?我们用HttpEncodingAutoConfiguration这个自动配置来来进行解析了解。
/*
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.web.servlet;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.filter.OrderedCharacterEncodingFilter;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.boot.web.servlet.server.Encoding;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.web.filter.CharacterEncodingFilter;
/**
* {@link EnableAutoConfiguration Auto-configuration} for configuring the encoding to use
* in web applications.
*
* @author Stephane Nicoll
* @author Brian Clozel
* @since 2.0.0
*/
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(ServerProperties.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass(CharacterEncodingFilter.class)
@ConditionalOnProperty(prefix = "server.servlet.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {
private final Encoding properties;
public HttpEncodingAutoConfiguration(ServerProperties properties) {
this.properties = properties.getServlet().getEncoding();
}
@Bean
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(Encoding.Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(Encoding.Type.RESPONSE));
return filter;
}
@Bean
public LocaleCharsetMappingsCustomizer localeCharsetMappingsCustomizer() {
return new LocaleCharsetMappingsCustomizer(this.properties);
}
static class LocaleCharsetMappingsCustomizer
implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>, Ordered {
private final Encoding properties;
LocaleCharsetMappingsCustomizer(Encoding properties) {
this.properties = properties;
}
@Override
public void customize(ConfigurableServletWebServerFactory factory) {
if (this.properties.getMapping() != null) {
factory.setLocaleCharsetMappings(this.properties.getMapping());
}
}
@Override
public int getOrder() {
return 0;
}
}
}
1-3-1、@Configuration(proxyBeanMethods = false)注解
标记了@Configuration Spring底层会给配置创建cglib动态代理。 作用:就是防止每次调用本类的Bean方法而重新创建对象,Bean是默认单例的。而设置@Configuration(proxyBeanMethods = false)就是讲bean设置为多例,每次new都会重新创建。
1-3-2、@EnableConfigurationProperties(ServerProperties.class)
启用可以在配置类设置的属性对应的类,使用配置类可以使用的一些属性,我们进入到ServerProperties可以看到
这样就可以使用server.port/server.address 等相关配置信息了。
1-3-2、@Conditional派生注解(Spring注解版原生的@Conditional作用)
1-3-2-1、相关注解作用
@xxxConditional根据当前不同的条件判断,决定这个配置类是否生效
如:
1、@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)指定当前环境必须是一个servlet的环境,否则就不起作用
2、@ConditionalOnClass(CharacterEncodingFilter.class)当前类路径或者jar包中必须包含CharacterEncodingFilter这个类,否则就不起作用。
3、@ConditionalOnProperty(prefix = "server.servlet.encoding", value = "enabled", matchIfMissing = true)指当配置server.servlet.encoding.enabled=true的时候才起作用,如果=false就不起作用,这时候如若需要让这个配置类起作用,就需要在application.properties中配置server.servlet.encoding.enabled=true
如下,在项目中进行了配置,然后启动的时候就会进入构造函数的断点
当设置=false的时候,就不会起作用了。请注意默认设置的是true
1-3-2-2、characterEncodingFilter方法
1、之前在springmvc的文章中有介绍,解决中文乱码是通过Filter操作的,在web.xml中添加CharacterEncodingFilter,通过设置编码集和forceEncoding=true即可完成编码设置。
而sringboot会先查看是否有CharacterEncodingFilter这个Bean,如果没有则走下面的方法内容。
2、编码集的设置
通过上图可以看到,实际上编码最终的设置为
properties.getServlet().getEncoding().getCharset().name()
properties为ServerProperties类,ServerProperties类设置的前缀为server,因此最终上面的代码在配置文件中的表现方式为:
server.servlet.encoding.charset
然后springboot底层机会拿到上面的编码设置到filter.setEncoding中,然后内嵌的tomcat拿到这个值就生效了。
同样可以通过设置server.servlet.encoding.force=true
设置request/response的编码集
1-3-2-3、Conditional派生其他注解
作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效;
注解 | 作用 |
---|---|
@Conditional扩展注解作用 | (判断是否满足当前指定条件) |
@ConditionalOnJava | 系统的java版本是否符合要求 |
@ConditionalOnBean | 容器中存在指定Bean; |
@ConditionalOnMissingBean | 容器中不存在指定Bean; |
@ConditionalOnExpression | 满足SpEL表达式指定 |
@ConditionalOnClass | 系统中有指定的类 |
@ConditionalOnMissingClass | 系统中没有指定的类 |
@ConditionalOnSingleCandidate | 容器中只有一个指定的Bean,或者这个Bean是首选Bean |
@ConditionalOnProperty | 系统中指定的属性是否有指定的值 |
@ConditionalOnResource | 类路径下是否存在指定资源文件 |
@ConditionalOnWebApplication | 当前是web环境 |
@ConditionalOnNotWebApplication | 当前不是web环境 |
@ConditionalOnJndi | JNDI存在指定项 |
实际上上面说道从133个配置类过滤到了25个,就是通过Conditional进行过滤的。
1-4、通过在application.properties设置debug=true,查看加载的自动配置类及未加载的自动配置了
1-4-1、加载的自动配置类
1-4-2、未加载的自动配置类
1-4-3、可以在官网进行查看这些自动配置类的作用
下面这些即为相关的自动配置类