Springboot的web自动配置

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配置类。


EnableWebMvcConfiguration.png

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数据。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,186评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,858评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,620评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,888评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,009评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,149评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,204评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,956评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,385评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,698评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,863评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,544评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,185评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,899评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,141评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,684评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,750评论 2 351

推荐阅读更多精彩内容