Spring Boot 2.0深度实践之核心技术篇【第六章 Web MVC REST应用 】

6-1 Web MVC REST应用和REST介绍

  • Rest简介

  • WebMVC REST支持

  • REST内容协商

  • CORS

在这里插入图片描述
在这里插入图片描述

架构约束

  • 统一接口(Uniform interface)
  • C/S架构(Client-Server)
  • 无状态(Stateless)
    用户状态集中在客户端而不是服务端(服务端没有客户的会话状态),Http协议本来就是无状态的(和服务端没有关联)。
  • 可缓存(Cacheable)
  • 分层系统(Layered System)
  • 按需代码(Code On damand)//客户端的脚本

统一接口( Uniform interface)

  • 资源识别 ( Identification of resources )
    • URI( Uniform Resource ldentifier )
  • 资源操作 ( Manipulation of resources through representations )
    • HTTP verbs : GET、 PUT、 POST 、DELETE
  • 自描述消息 ( Self-descriptive messages )
    • Content-Type
    • MIME-Type
    • Media Type : application/javascript、 text/html
  • 超媒体( HATEOAS )
    • Hypermedia As The Engine Of Application State

6-2 Web MVC REST 支持

注解驱动

  • 定义:@Controller 、@RestController
  • 映射: @RequestMapping、@*Mapping
  • 请求:@RequestParam、@RequestHeader、@CookieValue
  • 响应:@ResponseBody、ResponseEntity
  • 拦截: @RestControllerAdvice
  • 跨域:@CrossOrigin

定义

在这里插入图片描述

映射

在这里插入图片描述

请求

在这里插入图片描述

响应

在这里插入图片描述

拦截

在这里插入图片描述

跨域

在这里插入图片描述

6-3 REST 内容协商

核心组件

  • 生产媒体类型: @RequestMapping#produces
  • HTTP消息转换器: HttpMessageConverter
  • REST置器: WebMvcConfigurer
  • 内容协商管理器: ContentNegotiationManager
  • 媒体类型: MediaType
  • 消费媒体类型: @ RequestMapping#consumes
  • 处理方法参数解析器: HandlerMethodArgumentResolver
  • 处理方法返回值解析器: HandlerMethodReturnValueHandler
  • 生产媒体类型: @ RequestMapping#producesHTTP
  • Http消息转换器 : HttpMessageConverter
  • REST配置器 : WebMvcConfigurer

6-4 Web MVC REST 处理流程

在这里插入图片描述

6-5 Web MVC REST 处理流程源码分析

6-6 Web MVC REST 内容协商处理流程

重点类:

  • HandlerMethodArgumentResolver.java
  • HandlerMethodReturnValueHandler.java

Spring Web MVC REST内容协商处理流程

在这里插入图片描述

6-7 Web MVC REST 内容协商处理流程源码分析

REST执行流程

DispatcherServlet.getHandler()

DispatcherServlet.getHandlerAdapter()

AbstractHandlerMethodAdapter.handler()

RequestMappingHandlerAdapter.handleInternal()

RequestMappingHandlerAdapter.invokeHandlerMethod()

ServletInvocableHandlerMethod.invokeAndHandle()

InvocableHandlerMethod.invokeForRequest()

InvocableHandlerMethod.getMethodArgumentValues()

HandlerMethodArgumentResolverComposite.getArgumentResolver()

HandlerMethodReturnValueHandlerComposite.selectHandler()

RequestResponseBodyMethodProcessor.handleReturnValue()

AbstractMessageConverterMethodProcessor.writeWithMessageConverters()

AbstractMessageConverterMethodProcessor.getAcceptableMediaTypes()

ContentNegotiationManager.resolveMediaTypes()

AbstractGenericHttpMessageConverter.write()
//Jaxb2CollectionHttpMessageConverter.write()

AbstractJackson2HttpMessageConverter.writeInternal()
//JsonGenerator generator = this.objectMapper.getFactory().createGenerator(outputMessage.getBody(), encoding);

6-8 理解媒体类型

经过ContentNegotiat ionManagerContentNegotiationStrategy解析请求中的媒体类型,比如: Accept请求头

  • 如果成功解析,返回合法MediaType列表
  • 否则,返回单元素*/*媒体类型列表MediaType.All

ContentNegotiationManager.resolverMediaTypes()

理解可生成的媒体类型

返回@Controller HandlerMethod @RequestMapping.produces()属性所指定的MediaType列表:

  • 如果@RequestMapping. produces()在,返回指定MediaType列表
  • 否则,返回已注册的HttpMessageConverter列表中支持的MediaType列表
    AbstractMessageConverterMethodProcessor.getProducibleMediaTypes()

理解 @RequestMapping#consumes

过滤请求媒体类型(Content-Type的值)
用于@Controller HandlerMethod匹配:

  • 如果请求头Content-Type媒体类型兼容@RequestMapping.consumes()属性,执行该HandlerMethod
  • 否则HandlerMethod不会被调用

理解 @RequestMapping#produces

用于获取可生成的MediaType列表

  • 如果该列表与请求的媒体类型兼容,执行第一 个兼容HttpMessageConverter的实现,默认@RequestMapping#produces内容到响应头Content -Type
  • 否则,抛出HttpMediaTypeNotAcceptableException , HTTP Status Code:415
@RestController
public class IndexController {

    @PostMapping(value = "/echo/person",
            consumes = "application/json;charset=UTF-8",
            produces = "application/json;charset=GBK")
    public Person showPerson(@RequestBody Person person) {
        return person;
    }
}

在这里插入图片描述

@RestController
public class IndexController {

    @PostMapping(value = "/echo/person",
            consumes = "application/json;charset=UTF-8",
            produces = "application/json;charset=UTF-8")
    public Person showPerson(@RequestBody Person person) {
        return person;
    }
}

在这里插入图片描述

6-10 扩展 REST 内容协商-反序列化部分

自定义 HttpMessageConverter

需求

  • 实现 Content-Type 为 text/properties媒体类型的 HttpMessageConverter

实现步骤

  • 实现 HttpMessageConverter- PropertiesHttpMessageConverter
  • 配置 PropertiesHttpMessageConverterWebMvcConfigurer#extendMessageConverters

PropertiesHttpMessageConverter.java

/**
 * {@link Properties} {@link HttpMessageConverter} 实现
 * */
public class PropertiesHttpMessageConverter  extends AbstractGenericHttpMessageConverter<Properties> {

    public PropertiesHttpMessageConverter() {
        // 设置支持的 MediaType
        super( new MediaType("text", "properties"));
    }

    /**
     * 序列化
     * */
    @Override
    protected void writeInternal(Properties properties, Type type, HttpOutputMessage httpOutputMessage) throws IOException, HttpMessageNotWritableException {

    }

    /**
     * 反序列化
     * */
    @Override
    protected Properties readInternal(Class<? extends Properties> aClass, HttpInputMessage httpInputMessage) throws IOException, HttpMessageNotReadableException {

        //获取请求头
        HttpHeaders httpHeaders = httpInputMessage.getHeaders();

        //获取媒体类型
        MediaType mediaType = httpHeaders.getContentType();

        //获取字符集
        Charset charset = mediaType.getCharset();

        charset = charset == null ? Charset.forName("UTF-8") : charset;

        // 字节流
        InputStream inputStream = httpInputMessage.getBody();

        InputStreamReader reader  = new InputStreamReader(inputStream,charset);

        Properties properties = new Properties();

        // 加载字符流成为 Properties 对象
        properties.load(reader);

        return properties;
    }

    @Override
    public Properties read(Type type, Class<?> aClass, HttpInputMessage httpInputMessage) throws IOException, HttpMessageNotReadableException {
        return readInternal(null,httpInputMessage);
    }
}

PropertiesWebMvcConfigurer.java

@Configuration
public class PropertiesWebMvcConfigurer implements WebMvcConfigurer {
    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(new PropertiesHttpMessageConverter());
    }
}
@PostMapping(value = "/echo/pro",
            consumes = "text/properties;charset=UTF-8")
    public String addPro(@RequestBody  Properties properties) {
        return "嘻嘻嘻";
    }

在这里插入图片描述
在这里插入图片描述

输出

在这里插入图片描述

6-11 扩展 REST 内容协商-序列化部分

首先示例当前会返回什么内容?

在这里插入图片描述

返回格式为json明显与预期不符合(预期应该是keyvalue的字符串)

重写序列化代码

PropertiesHttpMessageConverter.java
 /**
     * 序列化
     * */
    @Override
    protected void writeInternal(Properties properties, Type type, HttpOutputMessage httpOutputMessage) throws IOException, HttpMessageNotWritableException {

        //获取请求头
        HttpHeaders httpHeaders = httpOutputMessage.getHeaders();

        MediaType mediaType = httpHeaders.getContentType();

        Charset charset = mediaType.getCharset();

        charset = charset == null ? Charset.forName("UTF-8") : charset;

        OutputStream outputStream = httpOutputMessage.getBody();

        OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream,charset);

        // Properties 写入到字符输出流
        properties.store(outputStreamWriter,"From PropertiesHttpMessageConverter");

    }

再运行示例

在这里插入图片描述

结果不变

解决方式

  • 1 修改Accpet的值

在这里插入图片描述

  • 2 修改自定义MessageConverter的优先级
PropertiesWebMvcConfigurer.java
@Configuration
public class PropertiesWebMvcConfigurer implements WebMvcConfigurer {
    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(0,new PropertiesHttpMessageConverter());
    }
}
核心代码
RequestResponseBodyMethodProcessor.handleReturnValue()
AbstractMessageConverterMethodProcessor.writeWithMessageConverters()
AbstractMessageConverterMethodProcessor.getProducibleMediaTypes()

6-12 自定义 Resolver 实现

自定义 HandlerMethodArgumentResolver

需求

  • 不依赖 @RequestBody, 实现 Properties格式请求内容,解析为 Properties对象的方法参数
  • 复用 PropertiesHttpMessageConverter

实现步骤

  • 实现 HandlerMethodArgumentResolver- PropertiesHandlerMethodArgumentResolver
  • 配置 PropertiesHandlerMethodArgumentResolverWebMvcConfigurer#addArgumentResolvers
  • RequestMappingHandlerAdapter#setArgumentResolvers

PropertiesHandlerMethodArgumentResolver.java

public class PropertiesHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
    @Override
    public boolean supportsParameter(MethodParameter methodParameter) {
        return Properties.class.equals(methodParameter.getParameterType());
    }

    @Override
    public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {

        ServletWebRequest servletWebRequest = (ServletWebRequest)nativeWebRequest;

        HttpServletRequest request = servletWebRequest.getRequest();

        String contentType = request.getHeader("Content-Type");

        MediaType mediaType = MediaType.parseMediaType(contentType);

        Charset charset = mediaType.getCharset();

        charset = charset == null ? Charset.forName("UTF-8") : charset;

        //请求输入字节流
        InputStream inputStream = request.getInputStream();

        InputStreamReader inputStreamReader = new InputStreamReader(inputStream,charset);

        Properties properties = new Properties();

        properties.load(inputStream);

        return properties;
    }
}

PropertiesWebMvcConfigurer.java

@Configuration
public class PropertiesWebMvcConfigurer implements WebMvcConfigurer {
    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(0,new PropertiesHttpMessageConverter());
    }

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        if(resolvers.isEmpty()){
            resolvers.add(new PropertiesHandlerMethodArgumentResolver());
        }else{

            resolvers.add(0,new PropertiesHandlerMethodArgumentResolver());
        }
    }
}

PropertiesRestWebController.java

@RestController
public class PropertiesRestWebController {

    @PostMapping(value = "/echo/pro",
            consumes = "text/properties;charset=UTF-8")
    public Properties addPro(Properties properties) {
        return properties;
    }
}

测试

在这里插入图片描述

  • 测试结果
java.lang.IllegalArgumentException: argument type mismatch
java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to java.util.Properties

PropertiesWebMvcConfigurer.java

@Autowired
    public RequestMappingHandlerAdapter requestMappingHandlerAdapter;

    @PostConstruct
    public void init(){
        List<HandlerMethodArgumentResolver> argumentResolvers = requestMappingHandlerAdapter.getArgumentResolvers();

        List<HandlerMethodArgumentResolver> argumentResolverList = new ArrayList<>(argumentResolvers.size() +1);

        argumentResolverList.add(0, new PropertiesHandlerMethodArgumentResolver());

        argumentResolverList.addAll(argumentResolvers);

        requestMappingHandlerAdapter.setArgumentResolvers(argumentResolverList);
    }
    /*@Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {

        //添加自定义HandlerMethodArgumentResolver优先级低于内建HandlerMethodArgumentResolver
        if(resolvers.isEmpty()){
            resolvers.add(new PropertiesHandlerMethodArgumentResolver());
        }else{

            resolvers.add(0,new PropertiesHandlerMethodArgumentResolver());
        }
    }*/

6-13 自定义 Handler 实现

自定义 HandlerMethodReturnValueHandler

需求
  • 不依赖 @ResponseBody,实现 Properties类型方法返回值,转化为 Properties格式内容响应内容
  • 复用 PropertiesHttpMessageConverter
实现步骤
  • 实现 HandlerMethodReturnValueHandler- PropertiesHandlerMethodReturnValueHandler
  • 配置 PropertiesHandlerMethodReturnValueHandlerWebMvcConfigurer#addReturnValueHandlers
  • RequestMappingHandlerAdapter#setReturnValueHandlers
PropertiesHandlerMethodReturnValueHandler.java
public class PropertiesHandlerMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
    @Override
    public boolean supportsReturnType(MethodParameter returnType) {

        return Properties.class.equals(returnType.getMethod().getReturnType());

    }

    @Override
    public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

        //强制转换
        Properties properties = (Properties)returnValue;

        //
        PropertiesHttpMessageConverter converter = new PropertiesHttpMessageConverter();

        ServletWebRequest servletWebRequest = (ServletWebRequest)webRequest;

        HttpServletRequest httpServletRequest = servletWebRequest.getRequest();

        String contentType = httpServletRequest.getHeader("Content-Type");

        MediaType mediaType = MediaType.parseMediaType(contentType);

        HttpServletResponse httpServletResponse = servletWebRequest.getResponse();

        HttpOutputMessage httpOutputMessage = new ServletServerHttpResponse(httpServletResponse);

        converter.write(properties, mediaType,httpOutputMessage);

        mavContainer.setRequestHandled(true);
    }
}

PropertiesWebMvcConfigurer.java
@Autowired
    public RequestMappingHandlerAdapter requestMappingHandlerAdapter;

    @PostConstruct
    public void init(){
        List<HandlerMethodArgumentResolver> argumentResolvers = requestMappingHandlerAdapter.getArgumentResolvers();

        List<HandlerMethodArgumentResolver> argumentResolverList = new ArrayList<>(argumentResolvers.size() +1);

        argumentResolverList.add(0, new PropertiesHandlerMethodArgumentResolver());

        argumentResolverList.addAll(argumentResolvers);

        requestMappingHandlerAdapter.setArgumentResolvers(argumentResolverList);

        List<HandlerMethodReturnValueHandler> returnValueHandlers = requestMappingHandlerAdapter.getReturnValueHandlers();

        List<HandlerMethodReturnValueHandler> returnValueHandlerList = new ArrayList<>(returnValueHandlers.size() + 1);

        returnValueHandlerList.add(0,new PropertiesHandlerMethodReturnValueHandler());

        returnValueHandlerList.addAll(returnValueHandlers);

        requestMappingHandlerAdapter.setReturnValueHandlers(returnValueHandlerList);
    }

6-14 REST 内容协商CORS

Cross-Orgin Resource Sharing(CORS)
  • 注解驱动@CrossOrgin
  • 代码驱动 WebMvcConfigurer#addCorsMappings
  • Filter组件:CrosFilter
    在这里插入图片描述
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值