WebMvc 底层配置说明
WebMvcConfigurer
WebMvcConfigurer 是 SpringBoot 接入 SpringMvc 框架后,提供给用户自定义Web功能的接口,用户可以通过重写里面的方法来修改或增加Mvc中没有配置好的功能。
以下为 WebMvcConfigurer 接口的各接口的作用
SpringBoot 在 WebMvc 上提供了三种配置模式,分别是《全自动》、《手自一体》、《全手动》三种模式。
全自动:直接编写控制器逻辑,用户不需要知道MVC都配了什么设置,直接开始写业务即可。
手身一体:保留自动配置的效果同时,允许手动设置部分功能,定义Mvc底层组件
使用 @Configuration + 配置 WebMvcConfigurer + 配置 WebMvcRegistrations
注意:手自一体不要在 @Configuration 组件中标记 @EnableWebMvc
全手动:禁用自动配置交果,全手动设置
使用 @Configuration + 配置 WebMvcConfigurer,并标记 @EnableWebMvc
为什么在容器中放一个 WebMvcConfigurer 就能生效?
1.WebMvcAutoConfiguration 是一个自动配置类,它里面有一个 EnableWebMvcConfiguration
2.EnableWebMvcConfiguration继承了 DelegatingWebMvcConfiguration,这两个都生效
3.DelegatingWebMvcConfiguration利用 DI 把容器中所有WebMvcConfigurer 都注入进来
4.别人调用 DelegatingWebMvcConfiguration 的方法配置底层规则,而它调用所有 WebMvcConfigurer 的配置底层方法
路径匹配
Spring5.3 之后加入了更多的请求路径匹配的实现策略;
以前只支持 AntPathMatcher 策略, 现在提供了 PathPatternParser 策略。并且可以让我们指定到底使用那种策略。
可以通过配置项{spring.mvc.pathmatch.matching-strategy=path_pattern_parser}来更改路径匹配规则
Ant 风格的路径模式语法具有以下规则:
- *:表示任意数量的字符
- ?:表示任意一个字符。
- **:表示任意数量的目录。
- {}:表示一个命名的模式占位符。
- []:表示字符集合,例如[a-z]表示小写字母。
例如:
- *.html 匹配任意名称,扩展名为.html的文件。
- /folder1/*/*.java 匹配在folder1目录下的任意两级目录下的.java文件。
- /folder2/**/*.jsp 匹配在folder2目录下任意目录深度的.jsp文件。
- /{type}/{id}.html 匹配任意文件名为{id}.html,在任意命名的{type}目录下的文件。
注意:Ant 风格的路径模式语法中的特殊字符需要转义,如:
- 要匹配文件路径中的星号,则需要转义为\\*。
- 要匹配文件路径中的问号,则需要转义为\\?。
内容协商
即一套系统适配多端数据格式的返回,当我希望以json格式输出时,系统输出json,当我希望输出xml时,则以xml方式输出
boot默认引入了jackson的json包,所以默认是使用json进行数据返回的。
1.我们可以引入多一个支持xml的包,来实现同时支持xml的数据返回。
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
2.我们在需要返回的数据中,加入注解 @JacksonXmlRootElement
3.默认来说,内容协商会使用 accept 请求头作为识别,当accept 请求头为 application/json 时,则会返回 json 数据。当请求头为 application/xml 时,则会返回 xml 数据。
4.我们可以通过修改配置,开启基于请求参数的内容协商功能。
# 开启基于请求参数的内容协商功能。 默认参数名:format。 默认此功能不开启
spring.mvc.contentnegotiation.favor-parameter=true
# 指定内容协商时使⽤的参数名。默认是 format
spring.mvc.contentnegotiation.parameter-name=type
自定义内容协商前置-HttpMassageConverter的工作原理 (了解)
我们先来了解一下 HttpMassageConverter 是如何对对象数据进行转换的。
- 1.我们的对象输出,来自注解 @ResponseBody (或使用了 @RestController),而 @ResponseBody 由 HttpMessageConverter 处理
- (请求处理阶段,这里不讨论处理请求的问题,只讨论如何把对象转为返回内容的问题)
- 2.请求会先进入 DispatcherServlet.class 的 doDispatch() 进行处理
- 3.doDispatch() 会找到一个 HandlerAdapter 适配器进行处理整个请求
- 4.RequestMappingHandlerAdapter 来执行,调用 invokeHandlerMethod() 切面方法
- 5.会准备两个东西,HandlerMethodArgumentResolver (参数解析器,确定⽬标⽅法每个参数值),和 HandlerMethodReturnValueHandler (返回值处理器,确定⽬标⽅法的返回值改怎么处理)
- 6.invokeAndHandle() 真正执行调用 Controller 中的方法
- (返回对象处理阶段)
- 7.得到返回值,开始找到合适的返回值处理器 HandlerMethodReturnValueHandler
- 8.会通过检查是否支持注解来判断返回值处理器是否支持,最终找到 RequestResponseBodyMethodProcessor 能处理
- 9.RequestResponseBodyMethodProcessor 调用 writeWithMessageConverters , 利用 MessageConverter 把返回值写出去
- 10.HttpMessageConverter 会先进⾏内容协商, 遍历所有 MessageConverter 看谁能支持这种内容数据
- 11.最终因为要 json 所以 MappingJackson2HttpMessageConverter ⽀持写出json
- 12.jackson⽤ ObjectMapper 把对象写出去
SpringBoot 中默认会提供6种 MessageConverter 该初始化存在 WebMvcAutoConfigurationAdapter -> EnableWebMvcConfiguration -> DelegatingWebMvcConfiguration -> WebMvcConfigurationSupport,如下:
自定义内容协商-yaml
如果我们希望转为json和xml以外的格式,如yaml或properties的格式,此时我们就需要动到 HttpMessageConverter 组件。通过定制组件来实现一切其它内容协商。
编写增加 WebMvcConfigurer 提供的 configureMessageConverters 底层,修改底层的 MessageConverter
1.增加对yaml的支持,引入yaml支持包
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
</dependency>
2.增加请求头中的accept识别配置
spring.mvc.contentnegotiation.media-types
注意,media-types 是一个 Map<String, MediaType> 类型,所以在配置中应该需要定义一个名称
spring.mvc.contentnegotiation.media-types.yaml=application/yaml
3.编写自定义内容转换器,需要实现 HttpMessageConverter 或 继承 AbstractHttpMessageConverter<可转换类型>
@Configuration
public class MyMessageConverter extends AbstractHttpMessageConverter<Object> {
private final ObjectMapper objectMapper;
public MyMessageConverter(ObjectMapper objectMapper) {
// 告诉父类,我们这个类是支持什么类型的消息
super(new MediaType("application","yaml", StandardCharsets.UTF_8));
// 创建一个 Yaml 消息处理工厂,并设置关闭了某些功能(如本例中的删除分页“---”标签)
YAMLFactory yamlFactory = new YAMLFactory().disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER);
this.objectMapper = new ObjectMapper(yamlFactory);
}
// 判断该数据类型是否支持使用这个来转换
@Override
protected boolean supports(Class<?> clazz) {
return true;
}
// 读入数据时如何转化为对象
@Override
protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
return null;
}
// 如何把对象转换为输出数据
@Override
protected void writeInternal(Object o, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
// 把接收到的 对象 使用 objectMapper 来写出消息转换(objectMapper 在上面已经定义为使用 yamlFactory 处理)
try(OutputStream os = outputMessage.getBody()) {
objectMapper.writeValue(os, o);
}
// try() 是使用 Java 新特性的自动释放流的try方法
}
}
思考:是不是可以通过实现功能,来创建自定义加密数据传输功能?
4.把自定义编写的消息处理器,加入到SpringBoot的消息处理器队列中去
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters){
MyMessageConverter myMessageConverter = new MyMessageConverter();
converters.add(myMessageConverter);
}
注意:实现WebMvcConfigurer接口
addResourceHandler
addResourceHandler 是负责处理程序中静态资源的方法,它定义了静态资源如何处理。
private void addResourceHandler(ResourceHandlerRegistry registry, String pattern, Consumer<ResourceHandlerRegistration> customizer) {
if (!registry.hasMappingForPattern(pattern)) {
ResourceHandlerRegistration registration = registry.addResourceHandler(new String[]{pattern});
customizer.accept(registration);
// 静态资源的缓存机制,设置是缓存间隔多少秒(默认为0)
registration.setCachePeriod(this.getSeconds(this.resourceProperties.getCache().getPeriod()));
// 静态资源的缓存机制,缓存控间,默认无
registration.setCacheControl(this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl());
// 静态资源的缓存机制,是否使用lastModified头,默认为false
registration.setUseLastModified(this.resourceProperties.getCache().isUseLastModified());
this.customizeResourceHandlerRegistration(registration);
}
}
关于 lastModified: 浏览器首次获取资源时,会向服务器索取资源的最后修改时间,当下次再需要资源时,浏览器会先向服务器索取资源的最后修改时间,如果时间一致则不重新获取资源,否则再一次向服务器请求获取最新的静态资源。
静态资源映射
静态资源映射规则在 WebMvcAutoConfiguration 中进行了定义:
1. /webjars/** 的所有路径 资源都在 classpath:/META-INF/resources/webjars/
2./**的所有路径 资源都在classpath:/META-INF/resources/、classpath:/resources/、classpath:/static/、classpath:/public/
错误处理机制
SpringBoot的错误处理自动配置都在 ErrorMvcAutoConfiguration 中,它的作用主要是以下两大类
- SpringBoot 会自适应处理错误,响应错误页面和响应JSON错误内容的数据(下面有具体的处理机制)
- SpringMVC 的错误处理机制依然保留,MVC处理不了的,才会交给Boot进行处理
具体的处理机制如下图:
主要功能实现代码:
// 负责处出页面端的错误页生成
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE) // text/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());
// 页面端,会通过 resolveErrorView 来创建一个错误页面,这个错误页面由MVC在 resource 文件夹中查找特定的页面,看下面的代码
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
// 如果用户没有提供特定名称的错误页面代码,则mvc自己新建一个新的作为错误页输出
return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
}
// 负责生成JSON端的错误信息生成
@RequestMapping
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);
}
resolveErrorView 方法代码如下:
@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) {
// MVC 会通过查找 resource 文件夹下的 error 文件夹,并串合查找对应的精确错误码作为页面
// 如 resources/error/404.html 这样的文件
// 如果 error/404.html 中没有找到,就会找 resources/ 下有没有404.html 文件
// 如果上面的都没有,就会找模糊码 如 resources/error/4xx(5xx).html 文件
// 如果没有,就会在 /resources 下载 4xx, 5xx .html 文件
// 如果以上都没有,就会由Boot 实时生成错误页面(ErrorAutoConfigurer中的 render 方法)。
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) {
// Ignore
}
}
return null;
}
规则:
- 解析一个错误页
- 如果发生了500、404、503、403 这些错误
- 如果有模板引擎,默认在 classpath:/templates/error/精确码.html
- 如果没有模板引擎,在静态资源文件夹下找 精确码.html
- 如果匹配不到精确码.html这些精确的错误页,就去找5xx.html, 4xx.html模糊匹配
- 如果有模板引擎,默认在 classpath:/templates/error/5xx.html
- 如果没有模板引擎,在静态资源文件夹下找 5xx.html
- 如果发生了500、404、503、403 这些错误
- 如果模板引擎路径templates下有 error.html页面,就直接渲染
使用自定义错误json响应
- 前后分离使用json通讯,可以使用
@ControllerAdvice + @ExceptionHandler进行统一异常处理
使用服务端页面渲染
- 不可预知的一些,HTTP码表示的服务器或客户端错误
- 给classpath:/templates/error/下面,放常用精确的错误码页面。500.html, 404.html
- 给classpath:/templates/error/下面,放通用模糊匹配的错误码页面。 5xx.html, 4xx.html
- 发生业务错误
- 核心业务,每一种错误,都应该代码控制,跳转到自己定制的错误页。
- 通用业务, classpath:/templates/error.html页面,显示错误信息。
- 其中ModelAndView中的Model对象会提供相应的错误数据对象信息。
Tomcat服务器启动原理(了解)
- SpringBoot 默认嵌入Tomcat作为Servlet容器。
- 自动配置类是ServletWebServerFactoryAutoConfiguration , EmbeddedWebServerFactoryCustomizerAutoConfiguration
- 自动配置类开始分析功能。`xxxxAutoConfiguration`
-
public class ServletWebServerFactoryAutoConfiguration {}- ServletWebServerFactoryAutoConfiguration 自动配置了嵌入式容器场景
- 绑定了ServerProperties配置类,所有和服务器有关的配置 server
- ServletWebServerFactoryAutoConfiguration 导入了 嵌入式的三大服务器 Tomcat、Jetty、Undertow
- 导入 Tomcat、Jetty、Undertow 都有条件注解。系统中有这个类才行(也就是导了包)
- 默认 Tomcat配置生效。给容器中放 TomcatServletWebServerFactory
- 都给容器中 ServletWebServerFactory放了一个 web服务器工厂(造web服务器的)
- web服务器工厂 都有一个功能, getWebServer获取web服务器
- TomcatServletWebServerFactory 创建了 tomcat。
- ServletWebServerFactory 什么时候会创建 webServer出来。
- ServletWebServerApplicationContextioc容器,启动的时候会调用创建web服务器
- Spring容器刷新(启动)的时候,会预留一个时机,刷新子容器。onRefresh()
- 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。
修改server下的相关配置就可以修改服务器参数
通过给容器中放一个ServletWebServerFactory,来禁用掉SpringBoot默认放的服务器工厂(Spring会检测容器中是否已存在ServletWebServerFactory),实现自定义嵌入任意服务器。
@EnableWebMvc 原理前缀,WebMvcAutoConfiguration 的配置原理
在说 @EnableWebMvc 为什么能禁用所有配置的原理之前,我们要分析一下 WebMvcAutoConfiguration 的配置过程,
- WebMvcAutoConfiguration web场景的自动配置类
- 支持RESTful的filter:HiddenHttpMethodFilter
- 支持非POST请求,请求体携带数据:FormContentFilter
- 导入 EnableWebMvcConfiguration
- RequestMappingHandlerAdapter
- WelcomePageHandlerMapping: 欢迎页功能支持(模板引擎目录、静态资源目录放index.html),项目访问/ 就默认展示这个页面.
- RequestMappingHandlerMapping:找每个请求由谁处理的映射关系
- ExceptionHandlerExceptionResolver:默认的异常解析器
- LocaleResolver:国际化解析器
- ThemeResolver:主题解析器
- FlashMapManager:临时数据共享
- FormattingConversionService: 数据格式化 、类型转化
- Validator: 数据校验JSR303提供的数据校验功能
- WebBindingInitializer:请求参数的封装与绑定
- ContentNegotiationManager:内容协商管理器
- WebMvcAutoConfigurationAdapter配置生效,它是一个WebMvcConfigurer,定义mvc底层组件
- 定义好 WebMvcConfigurer 底层组件默认功能;所有功能详见列表
- 视图解析器: InternalResourceViewResolver
- 视图解析器: BeanNameViewResolver,视图名(controller方法的返回值字符串)就是组件名
- 内容协商解析器: ContentNegotiatingViewResolver
- 请求上下文过滤器: RequestContextFilter: 任意位置直接获取当前请求
- 静态资源链规则
- ProblemDetailsExceptionHandler:错误详情
- SpringMVC内部场景异常被它捕获:新特性,后面会讲
- 定义了MVC默认的底层行为: WebMvcConfigurer
其中,WebMvcAutoConfiguration 必然会被 SpringBoot 自动配置调用,同时,WebMvcAutoConfiguration 中有一个条件载入 @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
也就是说,如果要导入 WebMvcAutoConfiguration 条件是容器中不存在 WebMvcConfigurationSupport,否则 WebMvcAutoConfiguration 不会进行加载。
ProblemDetailsExceptionHandler 新特性
ProblemDetailsExceptionHandler 是SpringBoot 的一个新加入的官方提供的错误处理预置,它实际是一个 @ControllerAdvice
它提供了SpringBoot自身的错误的 ExceptionHandler 处理,它提供了一种新的 Content-Type 数据类型“application/problem+json”
@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
})
不过,ProblemDetailsExceptionHandler 默认是不开启的,因为它在代码中指定了条件载入
@Configuration(proxyBeanMethods = false)
@ConditionalOnBooleanProperty("spring.mvc.problemdetails.enabled")
static class ProblemDetailsErrorHandlingConfiguration {
@Bean
@ConditionalOnMissingBean(ResponseEntityExceptionHandler.class)
@Order(0)
ProblemDetailsExceptionHandler problemDetailsExceptionHandler() {
return new ProblemDetailsExceptionHandler();
}
}
也就是说,要开启,需要在配置文件中设置“spring.mvc.problemdetails.enabled=true”才行。
WebMvcConfigurer 具体功能
| 提供⽅法 | 核⼼参数 | 功能 | 默认 |
| addFormatters | FormatterRegistry | 格式化器:⽀持属性 上@NumberFormat 和@DatetimeFormat 的数据类型转换 | GenericConversionS ervice |
| getValidator | ⽆ | 数据校验:校验 Controller 上使⽤
@Valid标注的参数合 法性。需要导⼊ starter-validator |
⽆ |
| addInterceptors | InterceptorRegistry | 拦截器:拦截收到的 所有请求 | ⽆ |
| configureContentNe | ContentNegotiation | 内容协商:⽀持多种 | ⽀持 json |
| gotiation | Configurer | 数据格式返回。需要 | |
| 配合⽀持这种类型的 | |||
| HttpMessageConver | |||
| ter | |||
| configureMessageConverters | List<HttpMessageConverter<?>> | 消息转换器:标注@ResponseBody的返回值会利⽤MessageConverter直接写出去 | 8 个,⽀持byte,string,multipart,resource,json |
| addViewControllers | ViewControllerRegistry | 视图映射:直接将请求路径与物理视图映射。⽤于⽆ java 业务逻辑的直接视图⻚渲染 | ⽆ |
| <mvc:view-controller> | |||
| configureViewResolvers | ViewResolverRegistry | 视图解析器:逻辑视图转为物理视图 | ViewResolverComposite |
| addResourceHandlers | ResourceHandlerRegistry | 静态资源处理:静态
资源路径映射、缓存控制 |
ResourceHandlerRegistry |
| configureDefaultSer vletHandling | DefaultServletHandl erConfigurer | 默认 Servlet:可以 覆盖 Tomcat 的 DefaultServlet。让 DispatcherServlet拦 截/ | ⽆ |
| configurePathMatch | PathMatchConfigur er | 路径匹配:⾃定义 URL 路径匹配。可以
⾃动为所有路径加上 指定前缀,⽐如 /api |
⽆ |
| configureAsyncSupp ort | AsyncSupportConfig urer | 异步⽀持: | TaskExecutionAuto Configuration |
| addCorsMappings | CorsRegistry | 跨域: | ⽆ |
| addArgumentResolv ers | List<HandlerMethod ArgumentResolver> | 参数解析器: | mvc 默认提供 |
| addReturnValueHan dlers | List<HandlerMethod ReturnValueHandler
> |
返回值解析器: | mvc 默认提供 |
| configureHandlerEx ceptionResolvers | List<HandlerExcepti onResolver> | 异常处理器: | 默认 3 个 ExceptionHandlerEx ceptionResolver ResponseStatusExc eptionResolver DefaultHandlerExce ptionResolver |
| getMessageCodesR esolver | ⽆ | 消息码解析器:国际 化使⽤ | ⽆ |
@EnableWebMvc 的禁用配置原理
我们知道,只要在项目中加入 @EnableWebMvc 后,SpringBoot 就会禁用所有的默认配置项,那么它是如何因这个注解,就会处理掉所有默认配置呢?
从上面的 WebMvcAutoConfiguration 的配置原理可知,WebMvcAutoConfiguration 的引入前提是容器中不存在 WebMvcConfigurationSupport 对象
我们看一下 @EnableWebMvc 注解的代码
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}
从代码可知,@EnableWebMvc 注解的代码中包含了 引入 DelegatingWebMvcConfiguration 类
从 DelegatingWebMvcConfiguration 类得知
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {}
DelegatingWebMvcConfiguration 正是 WebMvcConfigurationSupport 的字类,因此,当我们引入了 @EnableWebMvc 时,容器中就存在了 WebMvcConfigurationSupport 对象,自然 WebMvcAutoConfiguration 就不会被加载
而正好,DelegatingWebMvcConfiguration 在重写了 WebMvcConfigurationSupport 类方法后,代码中没有做任何的配置动作。
最终使得所有预设配置都没有载入。
函数式Web请求(不常用)
相对使用 @Controller + @RequestMapping 的方式来定义Web请求,在SpringMVC 5.2之后,推出了一种新的Web请求方式,它解决了传统请求的耦合性。
即,在Controller类中,定义一个请求,需要在方法上加上 @GetMapping 注解来定义请求 URI 路径,这在一定程度上说明,路由定义和业务方法耦合在一起了,函数式请求就是希望解耦路由定义和业务。
通过路由单独定义,业务方法单独定义,拆分的方式解耦。
举例:假如某个web请求方法使用RESTful提供5种处理方式:
GET /user/1 获取1号用户
GET /users 获取所有用户
POST /user 请求体携带JSON,新增一个用户
PUT /user/1 请求体携带JSON,修改1号用户
DELETE /user/1 删除1号用户
我们可能需要在Controller中定义5个同样的方法。使用5个定义路由的注解,而函数式请求,则是在一个方法中,把5个路由路径在一个类中全给定义了,业务方法再另外在另一个类中定义。
函数式请求提供以下四种核心类
- RouterFunction : 用于定义路由路径的方法
- RequestPredicate : 用于定义请求的各项参数,如“接收何种数据类型”、“取出请求头”等
- ServerRequest : 客户端请求带来的请求体对象
- ServerResponse : 向客户端写出的响应体对象
实现步骤
1.创建一个容器组件(可以是Conponent也可以是Configuration)
2.实现 routerFunction 方法,并注册为Bean
@Bean
public RouterFunction<ServerResponse> routerFunction ()
从方法中我们得知,我们需要返回一个 RouterFunction<ServerResponse> 类型的对象。
3.我们使用 静态类 RouterFunctions 来定义一个router 路由路径
RouterFunctions.route().GET(业务方法)
RouterFunctions.route().GET(路径,业务方法)
RouterFunctions.route().GET(请求参数,业务方法)
RouterFunctions.route().GET(路径,请求参数,业务方法)
注意:还能定义POST等方法
4.创建一个专用于处理业务的类,里面把所有请求需要处理的业务添加上去,它实际上就是配合 业务方法(即HandlerFunction),而 HandlerFunction 实际上是一个函数式接口,所以我们的业务方法需要符合 HandlerFunction 的函数方法格式,因此,我们先参考 HandlerFunction 的函数接口方法格式
public interface HandlerFunction<T extends ServerResponse> {
/**
* Handle the given request.
* @param request the request to handle
* @return the response
*/
T handle(ServerRequest request) throws Exception;
// 可知,函数式方法,是泛型T,而T需要是ServerResponse的子类
}
所以我们就在业务方法中创建符合该函数式方法的格式
@Component
public class UserBizService {
public ServerResponse getUsers(ServerRequest serverRequest){
// 可以获取到路径接收到的 {id} 数据
String id = serverRequest.pathVariable("id");
// 处理查询业务
return ServerResponse.ok().body(id);
}
public ServerResponse saveUser(ServerRequest serverRequest) throws ServletException, IOException {
// 通过 serverRequest 获取到接收的数据
User body = serverRequest.body(User.class);
// 处理数据写入
return ServerResponse.ok().body(body);
}
}
5.在3中创建的路由类中,定义接收到的请求的处理方法,可通过DI注入业务类,并调用相应的处理方法
@Bean
public RouterFunction<ServerResponse> routerFunction(){
return RouterFunctions.route()
// 使用GET方法定义路径,接收所有类型的数据,使用userBizService::getUsers来处理接收到的请求
.GET("/user/{id}", RequestPredicates.accept(MediaType.ALL),userBizService::getUsers)
// 使用 POST方法定义路径,接收来自 JSON 类型的数据,使用 userBizService::saveUser 来处理请求
.POST("/user",RequestPredicates.contentType(MediaType.APPLICATION_JSON),userBizService::saveUser)
.build();
// ... 添加其它的方法
}
通过以上的方法实例,我们可以得知,就是把路由和请求规则写到一个类中,并把处理业务的代码写到另一个类中,以这种方式来解耦,但当前的业务依然使用传统的方式来实理请求处理,因为这种方法虽然得到了解耦,但是处理方法貌似变得十分麻烦,不常用。
.pdf-Adobe-Acrobat-Pro.jpg)




.pdf-Adobe-Acrobat-Pro-1.jpg)




共有 0 条评论