SpringBoot3 Web开发

news2024/11/18 7:24:37

注:SpringBoot的Web开发能力,由SpringMVC提供。

0. WebMvcAutoConfiguration原理

1. 生效条件

@AutoConfiguration(after = { DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
		ValidationAutoConfiguration.class }) //在这些自动配置之后
@ConditionalOnWebApplication(type = Type.SERVLET) //如果是web应用就生效,类型SERVLET、REACTIVE 响应式web
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class) //容器中没有这个Bean,才生效。默认就是没有
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)//优先级
@ImportRuntimeHints(WebResourcesRuntimeHints.class)
public class WebMvcAutoConfiguration { 
    
}

用于配置和初始化与 Web MVC(模型-视图-控制器)相关的组件。下面逐一解释其中的注解和代码块:

1、 @AutoConfiguration:

    • @AutoConfiguration 是 Spring Boot 自动配置的标志性注解之一,表示这是一个自动配置类。

2、 after 属性:

    • @AutoConfiguration(after = { ... }) 指定了在加载这个自动配置类之前应该先加载的其他自动配置类。
    • 在这里,WebMvcAutoConfigurationDispatcherServletAutoConfigurationTaskExecutionAutoConfigurationValidationAutoConfiguration 之后加载。

3、@ConditionalOnWebApplication(type = Type.SERVLET):

    • 使用 @ConditionalOnWebApplication 注解,表示条件仅在 Web 应用程序(具体是 Servlet 类型的应用程序)存在时才生效。
    • 这确保了这段配置仅在构建 Servlet 类型的 Web 应用时生效。

4、 @ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class }):

    • 使用 @ConditionalOnClass 注解,表示条件仅在类路径下存在指定的类时才生效。
    • 这里指定了必须存在 Servlet.classDispatcherServlet.classWebMvcConfigurer.class 才会启用这段配置。

5、 @ConditionalOnMissingBean(WebMvcConfigurationSupport.class):

    • 使用 @ConditionalOnMissingBean 注解,表示条件仅在容器中不存在 WebMvcConfigurationSupport 类型的 Bean 时才生效。
    • 这确保了这段配置在容器中没有用户自定义的 WebMvcConfigurationSupport Bean 时才会生效。

6、 @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10):

    • 使用 @AutoConfigureOrder 注解,表示设置自动配置的优先级。在这里,设置为比默认优先级更高的值。

7、@ImportRuntimeHints(WebResourcesRuntimeHints.class):

    • 使用 @ImportRuntimeHints 注解,引入了运行时提示,其中包含了 WebResourcesRuntimeHints 类。
    • 运行时提示用于指示应用程序在运行时的某些行为,这里引入的提示可能与 Web 资源的运行时行为有关。

总体来说,这个自动配置类 WebMvcAutoConfiguration 主要负责在满足一系列条件的情况下配置 Spring MVC 相关的组件,确保这些配置仅在特定的 Web 应用环境下生效。

2. 效果

WebMvcAutoConfiguration的类结构时候会发现就是整体只有三大部分:

两个filter (用于数据请求) + WebMvcConfigurer类(接口) (用于实现各种默认组件行为)+ EnableWebMvcConfiguration类

(配置一些额外功能可以看作是上一个接口功能的补充,比如:欢迎页,静态资源前缀)

image.png

1、放了两个Filter:

    1. HiddenHttpMethodFilter;页面表单提交Rest请求(GET、POST、PUT、DELETE)
    2. FormContentFilter: 表单内容Filter,GET(数据放URL后面)、POST(数据放请求体)请求可以携带数据,PUT、DELETE 的请求体数据会被忽略

2、给容器中放了WebMvcConfigurer组件;给SpringMVC添加各种定制功能

    1. 所有的功能最终会和配置文件进行绑定
    2. WebMvcProperties: spring.mvc配置文件
    3. WebProperties: spring.web配置文件
@Configuration(proxyBeanMethods = false)
@Import(EnableWebMvcConfiguration.class) //额外导入了其他配置
@EnableConfigurationProperties({ WebMvcProperties.class, WebProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware{

}

3. WebMvcConfigurer接口

提供了配置SpringMVC底层的所有组件入口

img

4. 静态资源规则源码

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    if (!this.resourceProperties.isAddMappings()) {
        logger.debug("Default resource handling disabled");
        return;
    }
    //1、
    addResourceHandler(registry, this.mvcProperties.getWebjarsPathPattern(),
            "classpath:/META-INF/resources/webjars/");
    addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
        registration.addResourceLocations(this.resourceProperties.getStaticLocations());
        if (this.servletContext != null) {
            ServletContextResource resource = new ServletContextResource(this.servletContext, SERVLET_LOCATION);
            registration.addResourceLocations(resource);
        }
    });
}

1、规则一:访问: /webjars/**路径就去 classpath:/META-INF/resources/webjars/下找资源.

    • 含义: 当访问路径以 /webjars/ 开头时,会去 classpath:/META-INF/resources/webjars/ 目录下寻找相应的资源。
    • 实现: Maven 中导入了 WebJars 的依赖,这些依赖通常包含前端库和框架,通过访问 /webjars/** 路径来获取这些静态资源。

2、规则二:访问: /**路径就去 静态资源默认的四个位置找资源

    • 含义: 当访问任意路径时,会去默认的静态资源位置查找资源。这些位置包括:
      • classpath:/META-INF/resources/
      • classpath:/resources/
      • classpath:/static/
      • classpath:/public/
    • 实现: 通过 ResourceHandlerRegistry 将这些路径配置为静态资源位置。

image.png

3、规则三:静态资源默认都有缓存规则的设置

  • 含义: 静态资源默认有缓存规则的设置,主要通过配置文件中的 spring.web.resources.cache 进行配置。
  • 缓存周期(cachePeriod): 配置静态资源的缓存周期,即多久不用就要向服务器请求新的资源,默认没有缓存周期,单位为秒。
  • HTTP 缓存控制(cacheControl): 配置 HTTP 缓存控制的规则,包括缓存的最大时间、是否需要校验缓存等。https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Caching
  • 使用最后一次修改时间(useLastModified): 配置是否使用静态资源的最后一次修改时间来判断是否需要重新获取资源。配合HTTP Cache规则

如果浏览器访问了一个静态资源 index.js,如果服务这个资源没有发生变化,下次访问的时候就可以直接让浏览器用自己缓存中的东西,而不用给服务器发请求。

registration.setCachePeriod(getSeconds(this.resourceProperties.getCache().getPeriod()));
registration.setCacheControl(this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl());
registration.setUseLastModified(this.resourceProperties.getCache().isUseLastModified());

5. EnableWebMvcConfiguration 源码

//SpringBoot 给容器中放 WebMvcConfigurationSupport 组件。
//我们如果自己放了 WebMvcConfigurationSupport 组件,Boot的WebMvcAutoConfiguration都会失效。
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(WebProperties.class)
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {
    
}
  1. HandlerMapping: 根据请求路径 /a 找那个handler能处理请求

    • 含义: 在 Web MVC 中,HandlerMapping 的作用是根据请求路径找到能够处理请求的处理器(handler)。
    • WelcomePageHandlerMapping:
      • a. 作用: 处理欢迎页的映射。当访问 //index.html 等路径时,会寻找静态资源位置下的 index.html 页面。
      • ⅰ. 所有路径下的请求: /** 路径下的所有请求,都会在默认的四个静态资源路径下查找,包括欢迎页。
      • ⅱ. 默认访问 index.html: 如果静态资源位置下有 index.html 页面,项目启动时默认访问这个页面。

6. 为什么容器中放一个WebMvcConfigurer就能配置底层行为

  1. WebMvcAutoConfiguration 是一个自动配置类,它里面有一个 EnableWebMvcConfiguration
  2. EnableWebMvcConfiguration继承与 DelegatingWebMvcConfiguration,这两个都生效
  3. DelegatingWebMvcConfiguration利用 DI 把容器中 所有 WebMvcConfigurer 注入进来
  4. 别人调用 DelegatingWebMvcConfiguration 的方法配置底层规则,而它调用所有 WebMvcConfigurer的配置底层方法。DelegatingWebMvcConfiguration是代理,别人调用它,它调用容器中所有WebMvcConfigurer的底层配置方法。

7. WebMvcConfigurationSupport

提供了很多的默认设置。

判断系统中是否有相应的类:如果有,就加入相应的HttpMessageConverter

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);

1. Web场景

1. 自动配置

1、整合web场景

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

2、引入了 autoconfigure功能

3、@EnableAutoConfiguration注解使用@Import(AutoConfigurationImportSelector.class)批量导入组件

4、加载 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中配置的所有组件

5、所有自动配置类如下

org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration
org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration
====以下是响应式web场景和现在的没关系======
org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration
org.springframework.boot.autoconfigure.web.reactive.ReactiveMultipartAutoConfiguration
org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration
org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration
org.springframework.boot.autoconfigure.web.reactive.WebSessionIdResolverAutoConfiguration
org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration
org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorAutoConfiguration
org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration
================以上没关系=================
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration

6、绑定了配置文件的一堆配置项

  • 1、SpringMVC的所有配置 spring.mvc
  • 2、Web场景通用配置 spring.web
  • 3、文件上传配置 spring.servlet.multipart
  • 4、服务器的配置 server: 比如:编码方式

2. 默认效果

默认配置:

  1. 包含了 ContentNegotiatingViewResolverBeanNameViewResolver 组件,方便视图解析
  2. 默认的静态资源处理机制: 静态资源放在 static 文件夹下即可直接访问
  3. 自动注册Converter,GenericConverter,Formatter组件,适配常见数据类型转换格式化需求。将配置文件的参数进行数据类型转化绑定bean的参数。
  4. 支持 HttpMessageConverters,可以方便返回json等数据类型。string或对象转为返回给前端json类型数据。
  5. 注册 MessageCodesResolver,方便国际化及错误消息处理
  6. 支持 静态 index.html。自动执行静态文件夹下的html文件。
  7. 自动使用ConfigurableWebBindingInitializer,实现消息处理数据绑定类型转化数据校验等功能。controller前端传来的值,用javabean来绑定。

重要:

  • 如果想保持 boot mvc 的默认配置,并且自定义更多的 mvc 配置,如:interceptors , formatters, view controllers 等。可以使用*@Configuration**注解添加一个* WebMvcConfigurer 类型的配置类,并不要标注 @EnableWebMvc
  • 如果想保持 boot mvc 的默认配置,但要自定义核心组件实例,比如:RequestMappingHandlerMapping, RequestMappingHandlerAdapter, 或ExceptionHandlerExceptionResolver,给容器中放一个WebMvcRegistrations 组件即可
  • 如果想全面接管 Spring MVC,@Configuration 标注一个配置类,并加上***@EnableWebMvc注解,实现WebMvcConfigurer*** 接口

2. 静态资源

1. 默认规则

1. 静态资源映射

静态资源映射规则在 WebMvcAutoConfiguration 中进行了定义:

  1. /webjars/** 的所有路径 资源都在classpath:/META-INF/resources/webjars/

  2. /** 的所有路径 资源都在 classpath:/META-INF/resources/classpath:/resources/classpath:/static/classpath:/public/

  3. 所有静态资源都定义了缓存规则。【浏览器访问过一次,就会缓存一段时间】,但此功能参数无默认值

    1. period: 缓存间隔。 默认 0S;
    2. cacheControl:缓存控制。 默认无;
    3. useLastModified:是否使用lastModified头。 默认 false;

2. 静态资源缓存

如前面所述

  1. 所有静态资源都定义了缓存规则。【浏览器访问过一次,就会缓存一段时间】,但此功能参数无默认值

    1. period: 缓存间隔。 默认 0S;
    2. cacheControl:缓存控制。 默认无;
    3. useLastModified:是否使用lastModified头。 默认 false;

3. 欢迎页

欢迎页规则在 WebMvcAutoConfiguration 中进行了定义:

  1. 静态资源目录下找 index.html
  2. 没有就在 templates下找index模板页

4. Favicon

  1. 在静态资源目录下找 favicon.ico

5. 缓存实验

server.port=9000

#1、spring.web:
# 1.配置国际化的区域信息
# 2.静态资源策略(开启、处理链、缓存)

#开启静态资源映射规则
spring.web.resources.add-mappings=true

#设置缓存
#spring.web.resources.cache.period=3600
##缓存详细合并项控制,覆盖period配置:
## 浏览器第一次请求服务器,服务器告诉浏览器此资源缓存7200秒,7200秒以内的所有此资源访问不用发给服务器请求,7200秒以后发请求给服务器
spring.web.resources.cache.cachecontrol.max-age=7200
#使用资源 last-modified 时间,来对比服务器和浏览器的资源是否相同没有变化。相同返回 304
spring.web.resources.cache.use-last-modified=true

2. 自定义静态资源规则

自定义静态资源路径、自定义缓存规则

1. 配置方式

spring.mvc: 静态资源访问前缀路径

spring.web

  • 静态资源目录
  • 静态资源缓存策略
#1、spring.web:
# 1.配置国际化的区域信息
# 2.静态资源策略(开启、处理链、缓存)

#开启静态资源映射规则
spring.web.resources.add-mappings=true

#设置缓存
spring.web.resources.cache.period=3600
##缓存详细合并项控制,覆盖period配置:
## 浏览器第一次请求服务器,服务器告诉浏览器此资源缓存7200秒,7200秒以内的所有此资源访问不用发给服务器请求,7200秒以后发请求给服务器
spring.web.resources.cache.cachecontrol.max-age=7200
## 共享缓存
spring.web.resources.cache.cachecontrol.cache-public=true
#使用资源 last-modified 时间,来对比服务器和浏览器的资源是否相同没有变化。相同返回 304
spring.web.resources.cache.use-last-modified=true

#自定义静态资源文件夹位置
spring.web.resources.static-locations=classpath:/a/,classpath:/b/,classpath:/static/

#2、 spring.mvc
## 2.1. 自定义webjars路径前缀
spring.mvc.webjars-path-pattern=/wj/**
## 2.2. 静态资源访问路径前缀
spring.mvc.static-path-pattern=/static/**

2. 代码方式

  • 容器中只要有一个 WebMvcConfigurer 组件。配置的底层行为都会生效
  • @EnableWebMvc //禁用boot的默认配置
@Configuration //这是一个配置类
public class MyConfig implements WebMvcConfigurer {


    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        //保留以前规则
        //自己写新的规则。
        registry.addResourceHandler("/static/**")
                .addResourceLocations("classpath:/a/","classpath:/b/")
                .setCacheControl(CacheControl.maxAge(1180, TimeUnit.SECONDS));
    }
}
@Configuration //这是一个配置类,给容器中放一个 WebMvcConfigurer 组件,就能自定义底层
public class MyConfig  /*implements WebMvcConfigurer*/ {

    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer() {
            @Override
            public void addResourceHandlers(ResourceHandlerRegistry registry) {
                registry.addResourceHandler("/static/**")
                        .addResourceLocations("classpath:/a/", "classpath:/b/")
                        .setCacheControl(CacheControl.maxAge(1180, TimeUnit.SECONDS));
            }
        };
    }

}

3. 路径匹配

Spring5.3 之后加入了更多的请求路径匹配的实现策略;

以前只支持 AntPathMatcher 策略, 现在提供了 PathPatternParser 策略。并且可以让我们指定到底使用那种策略。

1. Ant风格路径用法

Ant 风格的路径模式语法具有以下规则:

  • *:表示任意数量的字符。
  • ?:表示任意一个字符
  • **:表示任意数量的目录
  • {}:表示一个命名的模式占位符
  • []:表示字符集合,例如[a-z]表示小写字母。

例如:

  • *.html 匹配任意名称,扩展名为.html的文件。
  • /folder1//.java 匹配在folder1目录下的任意两级目录下的.java文件。
  • /folder2/**/*.jsp 匹配在folder2目录下任意目录深度的.jsp文件。
  • /{type}/{id}.html 匹配任意文件名为{id}.html,在任意命名的{type}目录下的文件。

注意:Ant 风格的路径模式语法中的特殊字符需要转义,如:

  • 要匹配文件路径中的星号,则需要转义为\\*
  • 要匹配文件路径中的问号,则需要转义为\\?

2. 模式切换

AntPathMatcher 与 PathPatternParser
  • PathPatternParser 在 jmh 基准测试下,有 6~8 倍吞吐量提升,降低 30%~40%空间分配率
  • PathPatternParser 兼容 AntPathMatcher语法,并支持更多类型的路径模式
  • PathPatternParser "**" 多段匹配的支持仅允许在模式末尾使用
    @GetMapping("/a*/b?/{p1:[a-f]+}")
    public String hello(HttpServletRequest request, 
                        @PathVariable("p1") String path) {

        log.info("路径变量p1: {}", path);
        //获取请求路径
        String uri = request.getRequestURI();
        return uri;
    }

总结:

  • 使用默认的路径匹配规则,是由 PathPatternParser 提供的
  • 如果路径中间需要有 **,替换成ant风格路径
# 改变路径匹配策略:
# ant_path_matcher 老版策略;
# path_pattern_parser 新版策略;
spring.mvc.pathmatch.matching-strategy=ant_path_matcher

4. 内容协商

一套系统适配多端数据返回

img

1. 多端内容适配

1. 默认规则

  1. SpringBoot 多端内容适配

    1. 基于请求头内容协商:(默认开启)
      1. 客户端向服务端发送请求,携带HTTP标准的Accept请求头
        1. Accept: application/jsontext/xmltext/yaml
        2. 服务端根据客户端请求头期望的数据类型进行动态返回
    1. 基于请求参数内容协商:(需要开启)
      1. 发送请求 GET /projects/spring-boot?format=json
      2. 匹配到 @GetMapping(“/projects/spring-boot”)
      3. 根据参数协商,优先返回 json 类型数据【需要开启参数匹配设置
      4. 发送请求 GET /projects/spring-boot?format=xml,优先返回 xml 类型数据

image.png

2. 效果演示

请求同一个接口,可以返回json和xml不同格式数据

1、引入支持写出xml内容依赖

<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
</dependency>

2、标注注解

@JacksonXmlRootElement  // 可以写出为xml文档
@Data
public class Person {
    private Long id;
    private String userName;
    private String email;
    private Integer age;
}

3、开启基于请求参数的内容协商

# 开启基于请求参数的内容协商功能。 默认参数名:format。 默认此功能不开启
spring.mvc.contentnegotiation.favor-parameter=true
# 指定内容协商时使用的参数名。默认是 format
spring.mvc.contentnegotiation.parameter-name=type

4、效果

img

img

3. 配置协商规则与支持类型

  1. 修改内容协商方式
#使用参数进行内容协商
spring.mvc.contentnegotiation.favor-parameter=true  
#自定义参数名,默认为format
spring.mvc.contentnegotiation.parameter-name=myparam 
  1. 大多数 MediaType 都是开箱即用的。也可以自定义内容类型,如:
spring.mvc.contentnegotiation.media-types.yaml=text/yaml

2. 自定义内容返回

1. 增加yaml返回支持

导入依赖

<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-yaml</artifactId>
</dependency>

把对象写出成YAML

    public static void main(String[] args) throws JsonProcessingException {
        Person person = new Person();
        person.setId(1L);
        person.setUserName("张三");
        person.setEmail("aaa@qq.com");
        person.setAge(18);

        YAMLFactory factory = new YAMLFactory().disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER);
        ObjectMapper mapper = new ObjectMapper(factory);

        String s = mapper.writeValueAsString(person);
        System.out.println(s);
    }

编写配置

#新增一种媒体类型
spring.mvc.contentnegotiation.media-types.yaml=text/yaml

增加HttpMessageConverter组件,专门负责把对象写出为yaml格式

    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer() {
            @Override //配置一个能把对象转为yaml的messageConverter
            public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
                converters.add(new MyYamlHttpMessageConverter());
            }
        };
    }

2. 思考:如何增加其他

  • 配置媒体类型支持:

    • spring.mvc.contentnegotiation.media-types.yaml=text/yaml
  • 编写对应的HttpMessageConverter,要告诉Boot这个支持的媒体类型

    • 按照3的示例
  • 把MessageConverter组件加入到底层

    • 容器中放一个WebMvcConfigurer 组件,并配置底层的MessageConverter

3. HttpMessageConverter的示例写法

public class MyYamlHttpMessageConverter extends AbstractHttpMessageConverter<Object> {

    private ObjectMapper objectMapper = null; //把对象转成yaml

    public MyYamlHttpMessageConverter(){
        //告诉SpringBoot这个MessageConverter支持哪种媒体类型  //媒体类型
        super(new MediaType("text", "yaml", Charset.forName("UTF-8")));
        YAMLFactory factory = new YAMLFactory()
                .disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER);
        this.objectMapper = new ObjectMapper(factory);
    }

    @Override
    protected boolean supports(Class<?> clazz) {
        //只要是对象类型,不是基本类型
        return true;
    }

    @Override  //@RequestBody
    protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        return null;
    }

    @Override //@ResponseBody 把对象怎么写出去
    protected void writeInternal(Object methodReturnValue, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {

        //try-with写法,自动关流
        try(OutputStream os = outputMessage.getBody()){
            this.objectMapper.writeValue(os,methodReturnValue);
        }

    }
}

3. 内容协商原理-HttpMessageConverter

  • HttpMessageConverter 怎么工作?合适工作?
  • 定制 HttpMessageConverter 来实现多端内容协商
  • 编写WebMvcConfigurer提供的configureMessageConverters底层,修改底层的MessageConverter

1. @ResponseBodyHttpMessageConverter处理

标注了@ResponseBody的返回值 将会由支持它的 HttpMessageConverter写给浏览器

  1. 如果controller方法的返回值标注了 @ResponseBody 注解

    1. 请求进来先来到DispatcherServletdoDispatch()进行处理
    2. 找到一个 HandlerAdapter 适配器。利用适配器执行目标方法
    3. RequestMappingHandlerAdapter来执行,调用invokeHandlerMethod()来执行目标方法
    4. 目标方法执行之前,准备好两个东西
      1. HandlerMethodArgumentResolver:参数解析器,确定目标方法每个参数值
      2. HandlerMethodReturnValueHandler:返回值处理器,确定目标方法的返回值改怎么处理
    1. RequestMappingHandlerAdapter 里面的invokeAndHandle()真正执行目标方法
    2. 目标方法执行完成,会返回返回值对象
    3. 找到一个合适的返回值处理器 HandlerMethodReturnValueHandler
    4. 最终找到 RequestResponseBodyMethodProcessor能处理 标注了 @ResponseBody注解的方法
    5. RequestResponseBodyMethodProcessor 调用writeWithMessageConverters ,利用MessageConverter把返回值写出去

上面解释:@ResponseBodyHttpMessageConverter处理

  1. HttpMessageConverter先进行内容协商

    1. 遍历所有的MessageConverter看谁支持这种内容类型的数据
    2. 默认MessageConverter有以下
    3. img
    4. 最终因为要json所以MappingJackson2HttpMessageConverter支持写出json
    5. jackson用ObjectMapper把对象写出去

2. WebMvcAutoConfiguration提供几种默认HttpMessageConverters

  • EnableWebMvcConfiguration通过 addDefaultHttpMessageConverters添加了默认的MessageConverter;如下:

    • ByteArrayHttpMessageConverter: 支持字节数据读写
    • StringHttpMessageConverter: 支持字符串读写
    • ResourceHttpMessageConverter:支持资源读写
    • ResourceRegionHttpMessageConverter: 支持分区资源写出
    • AllEncompassingFormHttpMessageConverter:支持表单xml/json读写
    • MappingJackson2HttpMessageConverter: 支持请求响应体Json读写

默认8个:

img

系统提供默认的MessageConverter 功能有限,仅用于json或者普通返回数据。额外增加新的内容协商功能,必须增加新的HttpMessageConverter

5. 国际化

国际化的自动配置参照MessageSourceAutoConfiguration

实现步骤

  1. Spring Boot 在类路径根下查找messages资源绑定文件。文件名为:messages.properties

  2. 多语言可以定义多个消息文件,命名为messages_区域代码.properties。如:

    1. messages.properties:默认
    2. messages_zh_CN.properties:中文环境
    3. messages_en_US.properties:英语环境
  3. 程序中可以自动注入 MessageSource组件,获取国际化的配置项值

  4. 页面中可以使用表达式 #{}获取国际化的配置项值

    @Autowired  //国际化取消息用的组件
    MessageSource messageSource;
    @GetMapping("/haha")
    public String haha(HttpServletRequest request){

        Locale locale = request.getLocale();
        //利用代码的方式获取国际化配置文件中指定的配置项的值
        String login = messageSource.getMessage("login", null, locale);
        return login;
    }

6. 错误处理

1. 默认机制

错误处理的自动配置都在ErrorMvcAutoConfiguration中,两大核心机制:

    1. SpringBoot 会自适应处理错误,响应页面或JSON数据
    1. SpringMVC的错误处理机制依然保留,MVC处理不了,才会交给boot进行处理

在这里插入图片描述

  • 发生错误以后,转发给/error路径,SpringBoot在底层写好一个 BasicErrorController的组件,专门处理这个请求

image.png

	@RequestMapping(produces = MediaType.TEXT_HTML_VALUE) //返回HTML
	public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
		HttpStatus status = getStatus(request);
		Map<String, Object> model = Collections
			.unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
		response.setStatus(status.value());
		ModelAndView modelAndView = resolveErrorView(request, response, status, model);
		return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
	}

	@RequestMapping  //返回 ResponseEntity, JSON
	public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
		HttpStatus status = getStatus(request);
		if (status == HttpStatus.NO_CONTENT) {
			return new ResponseEntity<>(status);
		}
		Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
		return new ResponseEntity<>(body, status);
	}
  • 错误页面是这么解析到的
//1、解析错误的自定义视图地址
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
//2、如果解析不到错误页面的地址,默认的错误页就是 error
return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);

容器中专门有一个错误视图解析器

@Bean
@ConditionalOnBean(DispatcherServlet.class)
@ConditionalOnMissingBean(ErrorViewResolver.class)
DefaultErrorViewResolver conventionErrorViewResolver() {
    return new DefaultErrorViewResolver(this.applicationContext, this.resources);
}

SpringBoot解析自定义错误页的默认规则

	@Override
	public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
		ModelAndView modelAndView = resolve(String.valueOf(status.value()), model);
		if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
			modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
		}
		return modelAndView;
	}

	private ModelAndView resolve(String viewName, Map<String, Object> model) {
		String errorViewName = "error/" + viewName;
		TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName,
				this.applicationContext);
		if (provider != null) {
			return new ModelAndView(errorViewName, model);
		}
		return resolveResource(errorViewName, model);
	}

	private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
		for (String location : this.resources.getStaticLocations()) {
			try {
				Resource resource = this.applicationContext.getResource(location);
				resource = resource.createRelative(viewName + ".html");
				if (resource.exists()) {
					return new ModelAndView(new HtmlResourceView(resource), model);
				}
			}
			catch (Exception ex) {
			}
		}
		return null;
	}

容器中有一个默认的名为 error 的 view; 提供了默认白页功能

@Bean(name = "error")
@ConditionalOnMissingBean(name = "error")
public View defaultErrorView() {
    return this.defaultErrorView;
}

封装了JSON格式的错误信息

	@Bean
	@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
	public DefaultErrorAttributes errorAttributes() {
		return new DefaultErrorAttributes();
	}

规则:

1、解析一个错误页

    • 如果发生了500、404、503、403 这些错误
      1. 如果有模板引擎,默认在 classpath:/templates/error/**精确码.html**
      2. 如果没有模板引擎,在静态资源文件夹下找 **精确码.html**
    • 如果匹配不到精确码.html这些精确的错误页,就去找5xx.html4xx.html模糊匹配
      1. 如果有模板引擎,默认在 classpath:/templates/error/5xx.html
      2. 如果没有模板引擎,在静态资源文件夹下找 5xx.html

2、如果模板引擎路径templates下有 error.html页面,就直接渲染

错误页的解析规则:当发生错误的时候,如果spring mvc的错误出来机制处理不了,就会将错误转发到/error路径,由spring boot进行处理,spring boot在处理的时候,都是先根据状态码进行精确匹配,匹配不到就进行模糊匹配,精确匹配和模糊匹配的时候都是如果有模版引擎,就先去templates/error下找,如果没有模版引擎就去静态资源下找;如果精确匹配和模糊匹配都没找到,就直接渲染templates/error.html,如果templates/error.html不存在,就直接返回spring boot默认定义好的白页

2. 自定义错误响应

1. 自定义json响应

使用@ControllerAdvice + @ExceptionHandler 进行统一异常处理

@ExceptionHandler标识一个方法处理错误,默认只能处理这个类发生的指定错误
@ControllerAdvice集中处理所有@Controller类的错误

2. 自定义页面响应

根据boot的错误页面规则,自定义页面模板

3. 最佳实战

  • 前后分离

    • 后台发生的所有错误,@ControllerAdvice + @ExceptionHandler进行统一异常处理。
  • 服务端页面渲染

    • 不可预知的一些,HTTP码表示的服务器或客户端错误
      • classpath:/templates/error/下面,放常用精确的错误码页面。500.html404.html
      • classpath:/templates/error/下面,放通用模糊匹配的错误码页面。 5xx.html4xx.html
    • 发生业务错误
      • 核心业务,每一种错误,都应该代码控制,跳转到自己定制的错误页
      • 通用业务classpath:/templates/error.html页面,显示错误信息

页面,JSON,可用的Model数据如下

img

7. 嵌入式容器

Servlet容器:管理、运行Servlet组件(Servlet、Filter、Listener)的环境,一般指服务器

JavaWeb的三大组件:

  • Servlet:用来处理请求
  • Filter:过滤请求
  • Listener:监听请求

1. 自动配置原理

  • SpringBoot 默认嵌入Tomcat作为Servlet容器。
  • 自动配置类ServletWebServerFactoryAutoConfigurationEmbeddedWebServerFactoryCustomizerAutoConfiguration
  • 自动配置类开始分析功能。xxxxAutoConfiguration
@AutoConfiguration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
		ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
		ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
		ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {
    
}
  1. ServletWebServerFactoryAutoConfiguration 自动配置了嵌入式容器场景

  2. 绑定了ServerProperties配置类,所有和服务器有关的配置 server

  3. ServletWebServerFactoryAutoConfiguration 导入了 嵌入式的三大服务器 TomcatJettyUndertow

    1. 导入 TomcatJettyUndertow 都有条件注解。系统中有这个类才行(也就是导了包)
    2. 默认 Tomcat配置生效。给容器中放 TomcatServletWebServerFactory
    3. 都给容器中 ServletWebServerFactory放了一个 web服务器工厂(造web服务器的)
    4. web服务器工厂 都有一个功能,getWebServer获取web服务器
    5. TomcatServletWebServerFactory 创建了 tomcat。
  4. ServletWebServerFactory 什么时候会创建 webServer出来。

  5. ServletWebServerApplicationContextioc容器,启动的时候会调用创建web服务器

  6. Spring**容器刷新(启动)**的时候,会预留一个时机,刷新子容器。onRefresh()

  7. refresh() 容器刷新 十二大步的刷新子容器会调用 onRefresh()

	@Override
	protected void onRefresh() {
		super.onRefresh();
		try {
			createWebServer();
		}
		catch (Throwable ex) {
			throw new ApplicationContextException("Unable to start web server", ex);
		}
	}

Web场景的Spring容器启动,在onRefresh的时候,会调用创建web服务器的方法。

Web服务器的创建是通过WebServerFactory搞定的。容器中又会根据导了什么包条件注解,启动相关的 服务器配置,默认EmbeddedTomcat会给容器中放一个 TomcatServletWebServerFactory,导致项目启动,自动创建出Tomcat。

2. 自定义

img

切换服务器;

<properties>
    <servlet-api.version>3.1.0</servlet-api.version>
</properties>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <!-- Exclude the Tomcat dependency -->
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<!-- Use Jetty instead -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jetty</artifactId>
</dependency>

3. 最佳实践

用法:

  • 修改server下的相关配置就可以修改服务器参数
  • 通过给容器中放一个**ServletWebServerFactory,来禁用掉SpringBoot默认放的服务器工厂,实现自定义嵌入任意服务器**。

8. 全面接管SpringMVC

  • SpringBoot 默认配置好了 SpringMVC 的所有常用特性。

  • 如果我们需要全面接管SpringMVC的所有配置并禁用默认配置,仅需要编写一个WebMvcConfigurer配置类,并标注 @EnableWebMvc 即可

  • 全手动模式

    • @EnableWebMvc : 禁用默认配置
    • **WebMvcConfigurer**组件:定义MVC的底层行为

1. WebMvcAutoConfiguration 到底自动配置了哪些规则

SpringMVC自动配置场景给我们配置了如下所有默认行为

  1. WebMvcAutoConfigurationweb场景的自动配置类

    1. 支持RESTful的filter:HiddenHttpMethodFilter
    2. 支持非POST请求,请求体携带数据:FormContentFilter
    3. 导入**EnableWebMvcConfiguration**:
      1. RequestMappingHandlerAdapter
      2. WelcomePageHandlerMapping欢迎页功能支持(模板引擎目录、静态资源目录放index.html),项目访问/ 就默认展示这个页面.
      3. RequestMappingHandlerMapping:找每个请求由谁处理的映射关系
      4. ExceptionHandlerExceptionResolver:默认的异常解析器
      5. LocaleResolver:国际化解析器
      6. ThemeResolver:主题解析器
      7. FlashMapManager:临时数据共享
      8. FormattingConversionService: 数据格式化 、类型转化
      9. Validator: 数据校验JSR303提供的数据校验功能
      10. WebBindingInitializer:请求参数的封装与绑定
      11. ContentNegotiationManager:内容协商管理器
    1. **WebMvcAutoConfigurationAdapter**配置生效,它是一个WebMvcConfigurer,定义mvc底层组件
      1. 定义好 WebMvcConfigurer 底层组件默认功能;所有功能详见列表
      2. 视图解析器:InternalResourceViewResolver
      3. 视图解析器:BeanNameViewResolver,**视图名(controller方法的返回值字符串)**就是组件名
      4. 内容协商解析器:ContentNegotiatingViewResolver
      5. 请求上下文过滤器:RequestContextFilter: 任意位置直接获取当前请求
      6. 静态资源链规则
      7. ProblemDetailsExceptionHandler:错误详情
        1. SpringMVC内部场景异常被它捕获:
    1. 定义了MVC默认的底层行为: WebMvcConfigurer

2. @EnableWebMvc 禁用默认行为

  1. @EnableWebMvc给容器中导入 DelegatingWebMvcConfiguration组件,

​ 他是 WebMvcConfigurationSupport

  1. WebMvcAutoConfiguration有一个核心的条件注解, @ConditionalOnMissingBean(WebMvcConfigurationSupport.class),容器中没有WebMvcConfigurationSupportWebMvcAutoConfiguration才生效.
  2. @EnableWebMvc 导入 WebMvcConfigurationSupport 导致 WebMvcAutoConfiguration 失效。导致禁用了默认行为
  • @EnableWebMVC 禁用了 Mvc的自动配置
  • WebMvcConfigurer 定义SpringMVC底层组件的功能类

3. WebMvcConfigurer 功能

定义扩展SpringMVC底层功能

提供方法核心参数功能默认
addFormattersFormatterRegistry格式化器:支持属性上@NumberFormat和@DatetimeFormat的数据类型转换GenericConversionService
getValidator数据校验:校验 Controller 上使用@Valid标注的参数合法性。需要导入starter-validator
addInterceptorsInterceptorRegistry拦截器:拦截收到的所有请求
configureContentNegotiationContentNegotiationConfigurer内容协商:支持多种数据格式返回。需要配合支持这种类型的HttpMessageConverter支持 json
configureMessageConvertersList<HttpMessageConverter<?>>消息转换器:标注@ResponseBody的返回值会利用MessageConverter直接写出去8 个,支持byte,string,multipart,resource,json
addViewControllersViewControllerRegistry视图映射:直接将请求路径与物理视图映射。用于无 java 业务逻辑的直接视图页渲染无 mvc:view-controller
configureViewResolversViewResolverRegistry视图解析器:逻辑视图转为物理视图ViewResolverComposite
addResourceHandlersResourceHandlerRegistry静态资源处理:静态资源路径映射、缓存控制ResourceHandlerRegistry
configureDefaultServletHandlingDefaultServletHandlerConfigurer默认 Servlet:可以覆盖 Tomcat 的DefaultServlet。让DispatcherServlet拦截/
configurePathMatchPathMatchConfigurer路径匹配:自定义 URL 路径匹配。可以自动为所有路径加上指定前缀,比如 /api
configureAsyncSupportAsyncSupportConfigurer异步支持TaskExecutionAutoConfiguration
addCorsMappingsCorsRegistry跨域
addArgumentResolversList参数解析器mvc 默认提供
addReturnValueHandlersList返回值解析器mvc 默认提供
configureHandlerExceptionResolversList异常处理器默认 3 个 ExceptionHandlerExceptionResolver ResponseStatusExceptionResolver DefaultHandlerExceptionResolver
getMessageCodesResolver消息码解析器:国际化使用

9. 最佳实践

SpringBoot 已经默认配置好了Web开发场景常用功能。我们直接使用即可。

三种方式

方式用法效果
全自动直接编写控制器逻辑全部使用自动配置默认效果
手自一体@Configuration + 配置**WebMvcConfigurer**+ 配置 WebMvcRegistrations不要标注 @**EnableWebMvc**保留自动配置效果 手动设置部分功能 定义MVC底层组件
全手动@Configuration + 配置**WebMvcConfigurer**标注 @**EnableWebMvc**禁用自动配置效果 全手动设置

总结:

给容器中写一个配置类@Configuration 实现 WebMvcConfigurer但是不要标注 @EnableWebMvc注解,实现手自一体的效果。

两种模式

1、前后分离模式@RestController 响应JSON数据

2、前后不分离模式:@Controller + Thymeleaf模板引擎

10. Web新特性

1. Problemdetails

RFC 7807: https://www.rfc-editor.org/rfc/rfc7807

错误信息返回新格式

原理

@Configuration(proxyBeanMethods = false)
//配置过一个属性 spring.mvc.problemdetails.enabled=true
@ConditionalOnProperty(prefix = "spring.mvc.problemdetails", name = "enabled", havingValue = "true")
static class ProblemDetailsErrorHandlingConfiguration {

    @Bean
    @ConditionalOnMissingBean(ResponseEntityExceptionHandler.class)
    ProblemDetailsExceptionHandler problemDetailsExceptionHandler() {
        return new ProblemDetailsExceptionHandler();
    }

}
  1. ProblemDetailsExceptionHandler 是一个 @ControllerAdvice集中处理系统异常
  2. 处理以下异常。如果系统出现以下异常,会被SpringBoot支持以 RFC 7807规范方式返回错误数据
	@ExceptionHandler({
			HttpRequestMethodNotSupportedException.class, //请求方式不支持
			HttpMediaTypeNotSupportedException.class,
			HttpMediaTypeNotAcceptableException.class,
			MissingPathVariableException.class,
			MissingServletRequestParameterException.class,
			MissingServletRequestPartException.class,
			ServletRequestBindingException.class,
			MethodArgumentNotValidException.class,
			NoHandlerFoundException.class,
			AsyncRequestTimeoutException.class,
			ErrorResponseException.class,
			ConversionNotSupportedException.class,
			TypeMismatchException.class,
			HttpMessageNotReadableException.class,
			HttpMessageNotWritableException.class,
			BindException.class
		})

效果:

默认响应错误的json。状态码 405

{
    "timestamp": "2023-04-18T11:13:05.515+00:00",
    "status": 405,
    "error": "Method Not Allowed",
    "trace": "org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'POST' is not supported\r\n\tat org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping.handleNoMatch(RequestMappingInfoHandlerMapping.java:265)\r\n\tat org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.lookupHandlerMethod(AbstractHandlerMethodMapping.java:441)\r\n\tat org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.getHandlerInternal(AbstractHandlerMethodMapping.java:382)\r\n\tat org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping.getHandlerInternal(RequestMappingInfoHandlerMapping.java:126)\r\n\tat org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping.getHandlerInternal(RequestMappingInfoHandlerMapping.java:68)\r\n\tat org.springframework.web.servlet.handler.AbstractHandlerMapping.getHandler(AbstractHandlerMapping.java:505)\r\n\tat org.springframework.web.servlet.DispatcherServlet.getHandler(DispatcherServlet.java:1275)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1057)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:974)\r\n\tat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1011)\r\n\tat org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914)\r\n\tat jakarta.servlet.http.HttpServlet.service(HttpServlet.java:563)\r\n\tat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885)\r\n\tat jakarta.servlet.http.HttpServlet.service(HttpServlet.java:631)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:205)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)\r\n\tat org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)\r\n\tat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)\r\n\tat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)\r\n\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)\r\n\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:166)\r\n\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90)\r\n\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:493)\r\n\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115)\r\n\tat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93)\r\n\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)\r\n\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:341)\r\n\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:390)\r\n\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)\r\n\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:894)\r\n\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1741)\r\n\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)\r\n\tat org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)\r\n\tat org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)\r\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)\r\n\tat java.base/java.lang.Thread.run(Thread.java:833)\r\n",
    "message": "Method 'POST' is not supported.",
    "path": "/list"
}

开启ProblemDetails返回, 使用新的MediaType

Content-Type: application/problem+json+ 额外扩展返回

img

{
    "type": "about:blank",
    "title": "Method Not Allowed",
    "status": 405,
    "detail": "Method 'POST' is not supported.",
    "instance": "/list"
}

2. 函数式Web

SpringMVC 5.2 以后 允许我们使用函数式的方式,定义Web的请求处理流程

函数式接口

Web请求处理的方式:

  1. @Controller + @RequestMapping耦合式路由业务耦合)
  2. 函数式Web:分离式(路由、业务分离)

1. 场景

场景:User RESTful - CRUD

  • GET /user/1 获取1号用户
  • GET /users 获取所有用户
  • POST /user 请求体携带JSON,新增一个用户
  • PUT /user/1 请求体携带JSON,修改1号用户
  • DELETE /user/1 删除1号用户

2. 核心类

  • RouterFunction
  • RequestPredicate
  • ServerRequest
  • ServerResponse

image.png

3. 示例

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.servlet.function.RequestPredicate;
import org.springframework.web.servlet.function.RouterFunction;
import org.springframework.web.servlet.function.ServerResponse;

import static org.springframework.web.servlet.function.RequestPredicates.accept;
import static org.springframework.web.servlet.function.RouterFunctions.route;

@Configuration(proxyBeanMethods = false)
public class MyRoutingConfiguration {

    private static final RequestPredicate ACCEPT_JSON = accept(MediaType.APPLICATION_JSON);

    @Bean
    public RouterFunction<ServerResponse> routerFunction(MyUserHandler userHandler) {
        return route()
                .GET("/{user}", ACCEPT_JSON, userHandler::getUser)
                .GET("/{user}/customers", ACCEPT_JSON, userHandler::getUserCustomers)
                .DELETE("/{user}", ACCEPT_JSON, userHandler::deleteUser)
                .build();
    }

}
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.function.ServerRequest;
import org.springframework.web.servlet.function.ServerResponse;

@Component
public class MyUserHandler {

    public ServerResponse getUser(ServerRequest request) {
        ...
        return ServerResponse.ok().build();
    }

    public ServerResponse getUserCustomers(ServerRequest request) {
        ...
        return ServerResponse.ok().build();
    }

    public ServerResponse deleteUser(ServerRequest request) {
        ...
        return ServerResponse.ok().build();
    }

}
b.servlet.function.RequestPredicates.accept;
import static org.springframework.web.servlet.function.RouterFunctions.route;

@Configuration(proxyBeanMethods = false)
public class MyRoutingConfiguration {

    private static final RequestPredicate ACCEPT_JSON = accept(MediaType.APPLICATION_JSON);

    @Bean
    public RouterFunction<ServerResponse> routerFunction(MyUserHandler userHandler) {
        return route()
                .GET("/{user}", ACCEPT_JSON, userHandler::getUser)
                .GET("/{user}/customers", ACCEPT_JSON, userHandler::getUserCustomers)
                .DELETE("/{user}", ACCEPT_JSON, userHandler::deleteUser)
                .build();
    }

}
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.function.ServerRequest;
import org.springframework.web.servlet.function.ServerResponse;

@Component
public class MyUserHandler {

    public ServerResponse getUser(ServerRequest request) {
        ...
        return ServerResponse.ok().build();
    }

    public ServerResponse getUserCustomers(ServerRequest request) {
        ...
        return ServerResponse.ok().build();
    }

    public ServerResponse deleteUser(ServerRequest request) {
        ...
        return ServerResponse.ok().build();
    }

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1338529.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

PAT 乙级 1030 完美数列

解题思路:这道题首先需要排序&#xff0c;然后双循环找出最大的那个&#xff0c;但一定要注意优化&#xff0c;比如我已经刚好找到临界&#xff0c;或者之后的不用遍历了没意义了&#xff0c;要赶紧跳出循环&#xff0c;否则会超时&#xff0c;另外其实这题还有个范围的坑过&am…

测试服务器带宽(ubuntu)

apt install python3 python3-pippip3 install speedtest-clispeestest-cli

【三维生成】稀疏重建、Image-to-3D方法(汇总)

系列文章目录 总结一下近5年的三维生成算法&#xff0c;持续更新 文章目录 系列文章目录一、LRM&#xff1a;单图像的大模型重建&#xff08;2023&#xff09;摘要1.前言2.Method3.实验 二、SSDNeRF&#xff1a;单阶段Diffusion NeRF的三维生成和重建&#xff08;ICCV 2023&am…

策略模式(组件协作)

策略模式&#xff08;组件协作&#xff09; 链接&#xff1a;策略模式实例代码 注解 目的 正常情况下&#xff0c;一个类/对象中会包含其所有可能会使用的内外方法&#xff0c;但是一般情况下&#xff0c;这些常使用的类都是由不同的父类继承、组合得来的&#xff0c;来实现…

Upload上传图片,回显图片,编辑图片,限制图片,不显示上传图标,图片放大功能

效果图&#xff1a; 新增、编辑时&#xff1a;限制上传四张&#xff0c;当超过四张隐藏上传图标 图片放大 &#xff1a;效果图 详情&#xff1a;回显时不显示上传图标 页面&#xff1a;template 部分 图片在前端存储&#xff0c;提交时一并给后端 :file-list"repairPlan…

Biotech - 小分子化合物、蛋白质、核酸的不同数据表征方式

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/135247719 小分子化合物、蛋白质、核酸之间的关联是生命科学研究的一个重要领域&#xff0c;涉及到细胞功能的调控、信号传导的机制、疾病的发生和…

html table+css实现可编辑表格

要实现可编辑的 HTML 表格&#xff0c;你可以使用 JavaScript 和 HTML5 的 contenteditable 属性。 <!DOCTYPE html> <html> <head><style>table {border-collapse: collapse;width: 100%;}th, td {border: 1px solid black;padding: 8px;text-align:…

模型树实操

很多时候&#xff0c;数据都是有层级和分类的&#xff0c;使用laravel的Dcat框架&#xff0c;可以快速搭建一个结构清晰、且可以鼠标拖拽排序的后台&#xff1b;先上例子更直观&#xff1a; 这里是Dcat的模型树使用文档&#xff0c;戳一下&#xff1b; 重点注意事项有&#x…

如何给已分区的磁盘增加空间?详细教程分享!

用户案例&#xff1a;​给已分区的磁盘增加空间 “怎样给已分区的磁盘增加空间呢&#xff1f;我的电脑硬盘分成了C、D、E、F这四个区&#xff0c;每个区都是114G。但现在我觉得C盘的114G空间有些太小了&#xff0c;所以想把其他分区多余的空间给C盘。压缩完分区后&#xff0…

一款能封堵牙本质小管的牙膏,帮你告别牙齿敏感

冬天来临&#xff0c;牙齿敏感成为了很多人的困扰。吃冷饮、涮火锅都让人倍感不适&#xff0c;这时候一款有效的牙膏就显得格外重要。最近我发现了一款非常不错的清九野小红盾牙膏&#xff0c;它能够有效缓解牙齿敏感问题。 造成“敏感牙”的原因有很多&#xff0c;但根本原因还…

Vue3-26-路由-useRouter 和 useRoute 两个API 的作用介绍

先来说说两个API 的作用 useRouter() : 返回的是项目中的 路由实例的对象 可以通过这个实例对象进行路由的逻辑跳转 useRoute() : 返回的是当前的 路由对象&#xff0c; 可以在当前路由对象中获取到路由名称、路由参数、路由路径等玩完整的路由信息。 写个案例看一下具体是什么…

利用Milvus Cloud和LangChain构建机器人:一种引人入胜且通俗易懂的方法

一、引言 机器人已经深入我们的日常生活&#xff0c;从家庭服务到工业生产&#xff0c;再到医疗和运输等领域。然而&#xff0c;这些机器人往往需要复杂的算法和数据处理技术才能有效地执行任务。在这个过程中&#xff0c;人工智能&#xff08;AI&#xff09;和机器学习&#…

CTFshow-pwn入门-栈溢出pwn39-pwn40

pwn39 首先我们还是先将二级制文件托到虚拟机里面查看文件的保护信息。 chmod x pwn checksec pwn文件依然是只开启了栈不可执行&#xff0c;canary和pie都没开。并且该文件是32位的&#xff0c;那我们就托到ida32中反编译一下吧。 int __cdecl main(int argc, const char **…

k8s的二进制部署: 源码包部署-----node节点部署

服务器IP软件包k8s--master0120.0.0.61kube-aplserver&#xff0c;kube-controer-manager&#xff0c;kube-scheduler&#xff0c;etcdk8s--master0220.0.0.62kube-controer-manager&#xff0c;kube-schedulernode节点0120.0.0.62kubelet&#xff0c;kube-proxy&#xff0c;et…

Duboo-入门到学废【上篇】

目录 1&#x1f95e;.什么是duboo 2&#x1f32d;.架构图 3.&#x1f37f;快速入门 4.&#x1f9c7;浅浅理解 1.什么是duboo&#x1f936;&#x1f936;&#x1f936; Dubbo是一个由阿里巴巴开发的基于Java的开源RPC框架。它提供了高性能、透明化的远程方法调用&#xff0…

模型的线性化与离散化方法

模型的线性化与离散化方法 一、线性化——泰勒展开 对于非线性系统&#xff1a; 将一个非线性系统转换为线性系统&#xff0c;泰勒展开公式&#xff1a; 由于我们 线性化 的需求&#xff0c;只取前两项即可&#xff1a; 二、离散化——欧拉法

python13

前言&#xff1a;相信看到这篇文章的小伙伴都或多或少有一些编程基础&#xff0c;懂得一些linux的基本命令了吧&#xff0c;本篇文章将带领大家服务器如何部署一个使用django框架开发的一个网站进行云服务器端的部署。 文章使用到的的工具 Python&#xff1a;一种编程语言&…

Git 分布式版本控制系统(序章1)

第一章 Git 分布式版本控制系统 为什么学Git? 某些企业面试需要掌握Git&#xff0c;同时&#xff0c;也方便管理自己的Qt项目。 一、Git 客户端下载&#xff08;Windows&#xff09; 下载地址 https://gitee.com/all-about-git#git-%E5%A4%A7%E5%85%A8 二、Git 的特点 分支…

网站服务器被入侵,如何排查,该如何预防入侵呢?

在我们日常使用服务器的过程中&#xff0c;当公司的网站服务器被黑客入侵时&#xff0c;导致整个网站以及业务系统瘫痪&#xff0c;将会给企业带来无法估量的损失。作为服务器的维护人员应当在第一时间做好安全响应&#xff0c;对入侵问题做到及时处理&#xff0c;以最快的时间…

DRF从入门到精通五(路由组件、认证组件)

文章目录 一、路由组件REST framework提供了两个routeraction装饰器 二、认证组件 一、路由组件 对于视图集ViewSetMixin&#xff0c;我们除了可以自己手动指明请求方式与动作action之间的对应关系外&#xff0c;还可以使用Routers来帮助我们快速实现路由信息。 REST framework…