19Springboot对web的自动配置
springboot对于webmvc的自动配置类:WebMvcAutoConfiguration
@Configuration
// 条件注解 在GenericWebApplicationContext中生效
@ConditionalOnWebApplication(type = Type.SERVLET)
// 条件注解 存在以下class时生效
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
// 条件注解 不存在WebMvcConfigurationSupport class生效
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
// 必须在下面的类配置完成后,才会进行自动配置
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {}
Conditional注解的原理
springboot中大量使用了@Conditional注解,因此理解Conditional注解是读懂springboot自动装配的前提。
@Conditional中的条件成立时,配置类或者bean对象才会生效
public @interface Conditional {
// 指定进行条件判断的类(普通的实现了接口的类)
Class<? extends Condition>[] value();
}
示例:
// 只要满足MyAppConfigCondition中的验证逻辑时,这个配置类才会生效
@Conditional(MyAppConfigCondition.class)
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {}
public class MyAppConfigCondition implements Condition {
// 验证逻辑
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
if (context.getBeanFactory().getBeanDefinition("appConfig")!=null) {
// 配置appConfig这个配置类的bd 就通过
return true;
}
if (metadata.getClass().isInterface()) {
// 注解的类是接口 不通过
return false;
}
return true;
}
}
context:它在spring是ConditionEvaluator.ConditionContextImpl类型的
private static class ConditionContextImpl implements ConditionContext {
// 注册器
private final BeanDefinitionRegistry registry;
// bean工厂
private final ConfigurableListableBeanFactory beanFactory;
// 环境
private final Environment environment;
// 资源加载器
private final ResourceLoader resourceLoader;
// 类加载器
private final ClassLoader classLoader;
}
metadata:它在spring中会传入当前类的所有的注解信息:当前类上的注解集合、注解和注解的值、类名、类方法的元数据集合、成员内部类、接口等信息。
因此使用Condition接口可以完成多方面的校验。
在spring的源码中的体现,Spring中对与扫描出来bd会判断是否存在同级的Conditional注解,如果存在回去调用Conditional引入类的matches方法。
// ConfigurationClassParser#processConfigurationClass full or lite
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
return;
}
// 省略下面的代码
}
public boolean shouldSkip(AnnotatedTypeMetadata metadata, ConfigurationPhase phase) {
// 不存在注解,或者不存在condition注解,就直接返回false
if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
return false;
}
if (phase == null) {
// 是否存在configuration注解
if (metadata instanceof AnnotationMetadata &&
ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
// 递归,向容器中注入一个配置类
return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
}
// 不存在configuration,代表向容器中注入一个普通的bean对象
return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
}
// 取出condition中的class
List<Condition> conditions = new ArrayList<>();
for (String[] conditionClasses : getConditionClasses(metadata)) {
for (String conditionClass : conditionClasses) {
Condition condition = getCondition(conditionClass, this.context.getClassLoader());
conditions.add(condition);
}
}
// 对condition进行排序
AnnotationAwareOrderComparator.sort(conditions);
for (Condition condition : conditions) {
ConfigurationPhase requiredPhase = null;
if (condition instanceof ConfigurationCondition) {
requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
}
if ((requiredPhase == null || requiredPhase == phase)
&& !condition.matches(this.context, metadata)) {// 调用matches方法
return true;
}
}
return false;
}
上面的仅仅只是在扫描db后面的判断,spring中还有很多地方会去检查这个bean对象是否需要跳过,具体的验证时机如下:
- ClassPathScanningCandidateComponentProvider#scanCandidateComponents
- isCandidateComponent bean扫描的时候检查
- ConfigurationClassParser#processConfigurationClass bd扫描完成后的验证
- ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForConfigurationClass 处理import导入的类、importSelector 导入的类、配置类的@Bean、扫描类的@Bean验证
Springboot对于Conditional注解进行了扩充
@Conditional扩展注解 | 作用(判断是否满足当前指定条件) |
---|---|
@ConditionalOnJava | 系统的java版本是否符合要求 |
@ConditionalOnBean | 容器中存在指定Bean |
@ConditionalOnMissingBean | 容器中不存在指定Bean |
@ConditionalOnExpression | 满足SpEL表达式指定 |
@ConditionalOnClass | 系统中有指定的类 |
@ConditionalOnMissingClass | 系统中没有指定的类 |
@ConditionalOnSingleCandidate | 容器中只有一个指定的Bean,或者这个Bean是首选Bean |
@ConditionalOnProperty | 系统中指定的属性是否有指定的值 |
@ConditionalOnResource | 类路径下是否存在指定资源文件 |
@ConditionalOnWebApplication | 当前是web环境 |
@ConditionalOnNotWebApplication | 当前不是web环境 |
@ConditionalOnJndi | JNDI存在指定项 |
WebMvcAutoConfiguration前置的配置类
1、DispatcherServletAutoConfiguration
在DispatcherServletAutoConfiguration生效之前回去配置servlet容器(ServletWebServerFactoryAutoConfiguration),就是根据依赖的不同构建不同类型的容器:Tomcat、Jetty或Undertow。
(1)配置前端控制器DispatcherServlet:DispatcherServletConfiguration#dispatcherServlet
(2)配置文件上传解析器MultipartResolver:DispatcherServletConfiguration#multipartResolver
(3)注册前端控制器DispatcherServletRegistrationBean:DispatcherServletRegistrationConfiguration#dispatcherServletRegistration (就是将上面的两个组件组装起来,并设置前端控制器的初始化等)
@Bean("dispatcherServletRegistration")
public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet) {
DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
this.webMvcProperties.getServlet().getPath());
// 设置servlet名称
registration.setName("dispatcherServlet");
// 设置什么时候初始化servlet 默认是第一次发起请求时初始化
registration.setLoadOnStartup(this.webMvcProperties.getServlet().getLoadOnStartup());
if (this.multipartConfig != null) {
// 设置文件上传设置
registration.setMultipartConfig(this.multipartConfig);
}
return registration;
}
2、TaskExecutionAutoConfiguration
构建一个线程池
@Lazy
@Bean(name = { "applicationTaskExecutor","taskExecutor" })
@ConditionalOnMissingBean(Executor.class)
// builder 是TaskExecutionProperties配置类,内部设置了线程池的参数
public ThreadPoolTaskExecutor applicationTaskExecutor(TaskExecutorBuilder builder) {
// 使用下面的taskExecutorBuilder bean对象build一个线程池
return builder.build();
}
@Bean
@ConditionalOnMissingBean
public TaskExecutorBuilder taskExecutorBuilder() {
// 读取TaskExecutionProperties的配置参数,构建TaskExecutorBuilder
TaskExecutionProperties.Pool pool = this.properties.getPool();
TaskExecutorBuilder builder = new TaskExecutorBuilder();
builder = builder.queueCapacity(pool.getQueueCapacity());
builder = builder.corePoolSize(pool.getCoreSize());
builder = builder.maxPoolSize(pool.getMaxSize());
builder = builder.allowCoreThreadTimeOut(pool.isAllowCoreThreadTimeout());
builder = builder.keepAlive(pool.getKeepAlive());
builder = builder.threadNamePrefix(this.properties.getThreadNamePrefix());
builder = builder.customizers(this.taskExecutorCustomizers);
builder = builder.taskDecorator(this.taskDecorator.getIfUnique());
return builder;
}
3、ValidationAutoConfiguration
ValidationAutoConfiguration向spring容器中注册了两个类,默认的验证器对象defaultValidator和方法验证的后置处理器methodValidationPostProcessor。
ValidationAutoConfiguration自动配置类只有检测到存在classpath:META-INF/services/javax.validation.spi.ValidationProvider
文件并且存在ExecutableValidator类时才会进行自动配置。因为如果存在这两个东西,表示引入了Validation Provider jar包。
@Configuration
@ConditionalOnClass(ExecutableValidator.class)
@ConditionalOnResource(resources = "classpath:META-INF/services/javax.validation.spi.ValidationProvider")
@Import(PrimaryDefaultValidatorPostProcessor.class)
public class ValidationAutoConfiguration {
// defaultValidator是默认的Validator bean
// 作用:提供验证逻辑,比如基于JSR-303注解的bean属性和方法参数的验证
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
// 如果容器中存在自己写的Validator类型的bean时,这里就会不定义这个bean了
@ConditionalOnMissingBean(Validator.class)
public static LocalValidatorFactoryBean defaultValidator() {
LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
MessageInterpolatorFactory interpolatorFactory = new MessageInterpolatorFactory();
factoryBean.setMessageInterpolator(interpolatorFactory.getObject());
return factoryBean;
}
// MethodValidationPostProcessor它是一个BeanPostProcessor,这个bean对象的后置处理器会检测所有带有注解@Validated的bean定义,使用这里的validator验证器对bean中的方法进行验证
@Bean
// 在容器中不存在该类型的bean时才定义
@ConditionalOnMissingBean
public static MethodValidationPostProcessor methodValidationPostProcessor(Environment environment, @Lazy Validator validator) {
MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
boolean proxyTargetClass = environment.getProperty("spring.aop.proxy-target-class", Boolean.class, true);
processor.setProxyTargetClass(proxyTargetClass);
processor.setValidator(validator);
return processor;
}
}
WebMvcAutoConfiguration
在前端控制器和servlet容器初始化等基本组件初始化完成之后,接下来就是初始化springMVC中的各种组件。
1、添加两个过滤器OrderedHiddenHttpMethodFilter和OrderedFormContentFilter
- OrderedHiddenHttpMethodFilter这个filter会将PUT、DELETE、PATCH的请求转为HttpMethodRequestWrapper
- OrderedFormContentFilter解析表单数据,将请求转为FormContentRequestWrapper
2、web的配置类:WebMvcAutoConfigurationAdapter
- 配置了默认的视图解析器:defaultViewResolver -> prefix suffix
- beanName视图解析器:beanNameViewResolver
- 配置类favicon的HandlerMapping -> **/favicon.ico
- 配置区域的视图解析器:localeResolver 检查是否配置了区域信息,没有配置就默认返回AcceptHeaderLocaleResolver,配置了返回FixedLocaleResolver
3、配置requestContextFilter过滤器
4、webMvc的配置类:EnableWebMvcConfiguration
- RequestMappingHandlerMapping
- RequestMappingHandlerAdapter
- WelcomePageHandlerMapping
- FormattingConversionService
- Validator
- 配置静态资源的路径
- 更多的组件已经在WebMvcConfigurationSupport配置类中配置了
EnableWebMvcConfiguration继承了DelegatingWebMvcConfiguration配置类,DelegatingWebMvcConfiguration又继承了WebMvcConfigurationSupport配置类。
WebMvcAutoConfiguration自动配置类是@EnableWebMvc注解的超集。@EnableWebMvc只是导入了DelegatingWebMvcConfiguration这个配置,而WebMvcAutoConfiguration配置类从Servlet容器到SprigMVC的各种组件都配置完整了。
静态资源的配置
WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter#addResourceHandlers对静态资源路径进行了配置。
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) {
return;
}
Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
if (!registry.hasMappingForPattern("/webjars/**")) {
customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/")
.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
}
// staticPathPattern: /**
String staticPathPattern = this.mvcProperties.getStaticPathPattern();
// 静态资源文件夹映射
if (!registry.hasMappingForPattern(staticPathPattern)) {
customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
.addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
}
}
Springboot中对于静态资源的定义:ResourceProperties#staticLocations
@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
public class ResourceProperties {
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/",
"classpath:/resources/", "classpath:/static/", "classpath:/public/" };
private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
}
Springboot返回json数据
在单独使用springMVC的时候我们需要配置一个json的转换器来响应json数据给浏览,但是在springboot中,它会自动给我们配置json的转换器。
spring-boot-starter-web的pom文件中包含了json的依赖,并且默认使用的是fasterxml.jackson
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
</dependency>
那么在自动构建处理器适配器时,因为存在jackson相关的class,那么就会添加很多的消息转换器。
org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport
static {
// 判断是否存在一些jar包 使用哪一种技术
ClassLoader classLoader = WebMvcConfigurationSupport.class.getClassLoader();
romePresent = ClassUtils.isPresent("com.rometools.rome.feed.WireFeed", classLoader);
jaxb2Present = ClassUtils.isPresent("javax.xml.bind.Binder", classLoader);
jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) &&
ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
jackson2SmilePresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", classLoader);
jackson2CborPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.cbor.CBORFactory", classLoader);
gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);
jsonbPresent = ClassUtils.isPresent("javax.json.bind.Jsonb", classLoader);
}
// 添加默认的消息转换器
protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
stringHttpMessageConverter.setWriteAcceptCharset(false); // see SPR-7316
messageConverters.add(new ByteArrayHttpMessageConverter());
messageConverters.add(stringHttpMessageConverter);
messageConverters.add(new ResourceHttpMessageConverter());
messageConverters.add(new ResourceRegionHttpMessageConverter());
try {
messageConverters.add(new SourceHttpMessageConverter<>());
}catch (Throwable ex) {
// Ignore when no TransformerFactory implementation is available...
}
messageConverters.add(new AllEncompassingFormHttpMessageConverter());
// Jackson转换器
if (jackson2Present) {
Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.json();
if (this.applicationContext != null) {
builder.applicationContext(this.applicationContext);
}
messageConverters.add(new MappingJackson2HttpMessageConverter(builder.build()));
}
// 更多参考源码
}
因此springboot可以不用配置消息转换器就可以响应json数据。