扩展 REST 内容协商
核心组件
组件名称 | 实现 | 说明 |
---|---|---|
内容协商管理器 | ContentNegotiationManager | ContentNegotiationStrategy 控制策略 |
媒体类型 | MediaType | HTTP 消息媒体类型,如 text/html |
消费媒体类型 | @RequestMapping#consumes | 请求头 Content-Type 媒体类型映射 |
生产媒体类型 | @RequestMapping#produces | 响应头 Content-Type 媒体类型映射 |
HTTP消息转换器 | HttpMessageConverter | HTTP 消息转换器,用于反序列化 HTTP 请求或序列化响应 |
Web MVC 配置器 | WebMvcConfigurer | 配置 REST 相关的组件 |
处理方法 | HandlerMethod | @RequestMapping 标注的方法 |
处理方法参数解析器 | HandlerMethodArgumentResolver | 用于 HTTP 请求中解析 HandlerMethod 参数内容 |
处理方法返回值解析器 | HandlerMethodReturnValueHandler | 用于 HandlerMethod 返回值解析为 HTTP 响应内容 |
自定义 HttpMessageConverter,用于反序列化 HTTP 请求或序列化响应
需求
实现Content-Type 为 text/properties 媒体类型的 HttpMessageConverter
实现步骤
- 实现 HttpMessageConverter - PropertiesHttpMessageConverter
- 配置 PropertiesHttpMessageConverter 到 WebMvcConfigurer#extendMessageConverters
编码实现
- 编写properties转换器类PropertiesHttpMessageConverter继承 AbstractGenericHttpMessageConverter
,实现writeInternal,readInternal和read方法,以及在构造参数中调用父类构造方法添加支持的MediaType
// 省略import
import java.io.*;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.util.Properties;
public class PropertiesHttpMessageConverter extends AbstractGenericHttpMessageConverter<Properties> {
public PropertiesHttpMessageConverter() {
// 设置支持的 MediaType
super(new MediaType("text", "properties"));
}
// 序列化response
@Override
protected void writeInternal(Properties properties, Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
// Properties -> String
// OutputStream -> Writer
HttpHeaders httpHeaders = outputMessage.getHeaders();
MediaType mediaType = httpHeaders.getContentType();
// 获取字符编码
Charset charset = mediaType.getCharset();
// 当 charset 不存在时,使用 UTF-8
charset = charset == null ? Charset.forName("UTF-8") : charset;
// 字节输出流
OutputStream outputStream = outputMessage.getBody();
// 字符输出流
Writer writer = new OutputStreamWriter(outputStream, charset);
// Properties 写入到字符输出流
properties.store(writer,"From PropertiesHttpMessageConverter");
}
// http请求反序列化
@Override
protected Properties readInternal(Class<? extends Properties> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
// 字符流 -> 字符编码
// 从 请求头 Content-Type 解析编码
HttpHeaders httpHeaders = inputMessage.getHeaders();
MediaType mediaType = httpHeaders.getContentType();
// 获取字符编码
Charset charset = mediaType.getCharset();
// 当 charset 不存在时,使用 UTF-8
charset = charset == null ? Charset.forName("UTF-8") : charset;
// 字节流
InputStream inputStream = inputMessage.getBody();
InputStreamReader reader = new InputStreamReader(inputStream, charset);
Properties properties = new Properties();
// 加载字符流成为 Properties 对象
properties.load(reader);
return properties;
}
@Override
public Properties read(Type type, Class<?> contextClass, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
return readInternal(null, inputMessage);
}
}
- 编写config配置类RestWebMvcConfigurer,实现WebMvcConfigurer接口,重写extendMessageConverters方法,将PropertiesHttpMessageConverter对象添加到集合首位,不然会被默认传出json串
@Configuration
public class RestWebMvcConfigurer implements WebMvcConfigurer {
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
// 不建议添加到 converters 的末尾
// converters.add(new PropertiesHttpMessageConverter());
converters.set(0, new PropertiesHttpMessageConverter()); // 添加到集合首位
}
}
- 编写Controller方法,接受参数和返回类型都是properties类型
package com.web.config;
@RestController
public class PropertiesRestController {
@PostMapping(value = "/add/props",
consumes = "text/properties;charset=UTF-8" // Content-Type 过滤媒体类型
)
public Properties addProperties(@RequestBody Properties properties) {
return properties;
}
}
- 启动类一定要扫描到config下的包
@SpringBootApplication(scanBasePackages = {
"com.web.controller",
"com.web.config"
})
public class SpringBootRestBootstrap {
public static void main(String[] args) {
SpringApplication.run(SpringBootRestBootstrap.class, args);
}
}
- 使用postman测试
在Headers中设置Content-Type和Accept为text/properties
自定义 HandlerMethodArgumentResolver
需求
- 不依赖 @RequestBody , 实现 Properties 格式请求内容,解析为 Properties 对象的方法参数
- 复用 PropertiesHttpMessageConverter
实现步骤
- 实现HandlerMethodArgumentResolver - PropertiesHandlerMethodArgumentResolver
配置 PropertiesHandlerMethodArgumentResolver 到 WebMvcConfigurer#addArgumentResolvers,因为优先级顺序,会出现问题- RequestMappingHandlerAdapter#setArgumentResolvers
代码实现
- 编写PropertiesHandlerMethodArgumentResolver类实现HandlerMethodArgumentResolver接口
public class PropertiesHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return Properties.class.equals(parameter.getParameterType());
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
// 复用 PropertiesHttpMessageConverter,见上面
PropertiesHttpMessageConverter converter = new PropertiesHttpMessageConverter();
ServletWebRequest servletWebRequest = (ServletWebRequest) webRequest;
// Servlet Request API
HttpServletRequest request = servletWebRequest.getRequest();
HttpInputMessage httpInputMessage = new ServletServerHttpRequest(request);
return converter.read(null, null, httpInputMessage);
}
}
- 配置 PropertiesHandlerMethodArgumentResolver 到 WebMvcConfigurer#addArgumentResolvers,此处不能简单的在addArgumentResolvers方法中resolvers参数中添加PropertiesHandlerMethodReturnValueHandler对象,因为添加自定义 HandlerMethodArgumentResolver,优先级低于内建 HandlerMethodArgumentResolver,唯有重新设定requestMappingHandlerAdapter对象中的argumentResolvers属性,自定义Handler对象亦如此
@Configuration
public class RestWebMvcConfigurer implements WebMvcConfigurer {
@Autowired
private RequestMappingHandlerAdapter requestMappingHandlerAdapter;
@PostConstruct
public void init() {
// 获取当前 RequestMappingHandlerAdapter 所有的 Resolver 对象
List<HandlerMethodArgumentResolver> resolvers = requestMappingHandlerAdapter.getArgumentResolvers();
List<HandlerMethodArgumentResolver> newResolvers = new ArrayList<>(resolvers.size() + 1);
// 添加 PropertiesHandlerMethodArgumentResolver 到集合首位
newResolvers.add(new PropertiesHandlerMethodArgumentResolver());
// 添加 已注册的 Resolver 对象集合
newResolvers.addAll(resolvers);
// 重新设置 Resolver 对象集合
requestMappingHandlerAdapter.setArgumentResolvers(newResolvers);
// 获取当前 HandlerMethodReturnValueHandler 所有的 Handler 对象
List<HandlerMethodReturnValueHandler> handlers = requestMappingHandlerAdapter.getReturnValueHandlers();
List<HandlerMethodReturnValueHandler> newHandlers = new ArrayList<>(handlers.size() + 1);
// 添加 PropertiesHandlerMethodReturnValueHandler 到集合首位
newHandlers.add(new PropertiesHandlerMethodReturnValueHandler());
// 添加 已注册的 Handler 对象集合
newHandlers.addAll(handlers);
// 重新设置 Handler 对象集合
requestMappingHandlerAdapter.setReturnValueHandlers(newHandlers);
}
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
// 添加 PropertiesHandlerMethodArgumentResolver 到集合首位
// 添加自定义 HandlerMethodArgumentResolver,优先级低于内建 HandlerMethodArgumentResolver
// if (resolvers.isEmpty()) {
// resolvers.add(new PropertiesHandlerMethodArgumentResolver());
// } else {
// resolvers.set(0, new PropertiesHandlerMethodArgumentResolver());
// }
}
}
- controller类中的handlerMethod方法不再需要添加@RequestBody注解
@RestController
public class PropertiesRestController {
@PostMapping(value = "/add/props",
consumes = "text/properties;charset=UTF-8" // Content-Type 过滤媒体类型
)
public Properties addProperties(
// @RequestBody
Properties properties) {
return properties;
}
}
自定义 HandlerMethodReturnValueHandler
需求
- 不依赖 @ResponseBody ,实现 Properties 类型方法返回值,转化为 Properties 格式内容响应内容
- 复用 PropertiesHttpMessageConverter
实现步骤
- 实现HandlerMethodReturnValueHandler - PropertiesHandlerMethodReturnValueHandler
配置 PropertiesHandlerMethodReturnValueHandler 到 WebMvcConfigurer#addReturnValueHandlers,因为优先级顺序,会出现问题- RequestMappingHandlerAdapter#setReturnValueHandlers
代码实现
- 编写类PropertiesHandlerMethodReturnValueHandler继承HandlerMethodReturnValueHandler接口
public class PropertiesHandlerMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
@Override
public boolean supportsReturnType(MethodParameter returnType) {
// 判断方法的返回类型,是否与 Properties 类型匹配
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
PropertiesHttpMessageConverter converter = new PropertiesHttpMessageConverter();
ServletWebRequest servletWebRequest = (ServletWebRequest) webRequest;
// Servlet Request API
HttpServletRequest request = servletWebRequest.getRequest();
String contentType = request.getHeader("Content-Type");
// 获取请求头 Content-Type 中的媒体类型
MediaType mediaType = MediaType.parseMediaType(contentType);
// 获取 Servlet Response 对象
HttpServletResponse response = servletWebRequest.getResponse();
HttpOutputMessage message = new ServletServerHttpResponse(response);
// 通过 PropertiesHttpMessageConverter 输出
converter.write(properties, mediaType, message);
// 告知 Spring Web MVC 当前请求已经处理完毕
mavContainer.setRequestHandled(true);
}
}
- 配置,同上Resolver
@Configuration
public class RestWebMvcConfigurer implements WebMvcConfigurer {
@Autowired
private RequestMappingHandlerAdapter requestMappingHandlerAdapter;
@PostConstruct
public void init() {
// 获取当前 RequestMappingHandlerAdapter 所有的 Resolver 对象
List<HandlerMethodArgumentResolver> resolvers = requestMappingHandlerAdapter.getArgumentResolvers();
List<HandlerMethodArgumentResolver> newResolvers = new ArrayList<>(resolvers.size() + 1);
// 添加 PropertiesHandlerMethodArgumentResolver 到集合首位
newResolvers.add(new PropertiesHandlerMethodArgumentResolver());
// 添加 已注册的 Resolver 对象集合
newResolvers.addAll(resolvers);
// 重新设置 Resolver 对象集合
requestMappingHandlerAdapter.setArgumentResolvers(newResolvers);
// 获取当前 HandlerMethodReturnValueHandler 所有的 Handler 对象
List<HandlerMethodReturnValueHandler> handlers = requestMappingHandlerAdapter.getReturnValueHandlers();
List<HandlerMethodReturnValueHandler> newHandlers = new ArrayList<>(handlers.size() + 1);
// 添加 PropertiesHandlerMethodReturnValueHandler 到集合首位
newHandlers.add(new PropertiesHandlerMethodReturnValueHandler());
// 添加 已注册的 Handler 对象集合
newHandlers.addAll(handlers);
// 重新设置 Handler 对象集合
requestMappingHandlerAdapter.setReturnValueHandlers(newHandlers);
}
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
// 添加 PropertiesHandlerMethodArgumentResolver 到集合首位
// 添加自定义 HandlerMethodArgumentResolver,优先级低于内建 HandlerMethodArgumentResolver
// if (resolvers.isEmpty()) {
// resolvers.add(new PropertiesHandlerMethodArgumentResolver());
// } else {
// resolvers.set(0, new PropertiesHandlerMethodArgumentResolver());
// }
}
}
- controller类中可以去除@RestController注解
//@RestController
@Controller
public class PropertiesRestController {
@PostMapping(value = "/add/props",
consumes = "text/properties;charset=UTF-8" // Content-Type 过滤媒体类型
)
public Properties addProperties(
// @RequestBody
Properties properties) {
return properties;
}
}