6-1 Web MVC REST应用和REST介绍
架构约束
-
统一接口(
Uniform interface
) -
C/S
架构(Client-Server
) -
无状态(
用户状态集中在客户端而不是服务端(服务端没有客户的会话状态),Http协议本来就是无状态的(和服务端没有关联)。Stateless
) -
可缓存(
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 内容协商处理流程
重点类:
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 ionManager
的ContentNegotiationStrategy
解析请求中的媒体类型,比如: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
- 配置
PropertiesHttpMessageConverter
到WebMvcConfigurer#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
明显与预期不符合(预期应该是key
和value
的字符串)
重写序列化代码
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");
}
再运行示例
结果不变
解决方式
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
- 配置
PropertiesHandlerMethodArgumentResolver
到WebMvcConfigurer#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
配置PropertiesHandlerMethodReturnValueHandler
到WebMvcConfigurer#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