前言
上一篇文章介绍了SpringMvc的RequestMappingHandlerMapping,自定义了Controller和RequestMapping。
这里再介绍一下HandlerAdapter和HttpMessageConverter,并通过自定义注解来实现RequestBody和ResponseBody。HttpMessageConverter最常见的应用就是json的decode和encode。
RequestBody和ResponseBody
上一篇文章介绍了RequestMappingHandlerMapping在DispatcherServlet的作用。
RequestMappingHandlerMapping扫描了RequestMapping注释的HttpRequest对应的处理方法,并通过实现HandlerMapping的接口代理对应的方法。
而HandlerAdapter则是封装了HandlerMapping的方法,并围绕HandlerMapping实现一些嵌入操作。
RequestMappingHandlerAdapter是其中一个典型的例子,这个类包含HandlerMethodArgumentResolver,HandlerMethodReturnValueHandler的一些实现类来处理RequestMapping的参数和返回值。
private HandlerMethodArgumentResolverComposite argumentResolvers;
private HandlerMethodReturnValueHandlerComposite returnValueHandlers;
RequestResponseBodyMethodProcessor是HandlerAdapter的内部一个重要的类,这个类同时实现了HandlerMethodArgumentResolver,HandlerMethodReturnValueHandler。
其中HandlerMethodArgumentResolver接口有两个方法。
public interface HandlerMethodArgumentResolver {
boolean supportsParameter(MethodParameter parameter);
Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception;
}
HandlerMethodReturnValueHandler接口同样也有两个方法。
public interface HandlerMethodReturnValueHandler {
boolean supportsReturnType(MethodParameter returnType);
void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;
}
所以从RequestResponseBodyMethodProcessor实现的方法,就可以看出来这个类,会处理被@RequestBody注解的参数,和@ResponseBody注解的返回值。
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(RequestBody.class);
}
@Override
public boolean supportsReturnType(MethodParameter returnType) {
return (AnnotationUtils.findAnnotation(returnType.getContainingClass(), ResponseBody.class) != null ||
returnType.getMethodAnnotation(ResponseBody.class) != null);
}
接下来就介绍一下自定义ResponseBody和RequestBody的使用方法。
自定义ResponseBody和RequestBody
- 自定义注解,因为都是附加的注解,就不需要再加上@Component的注解了。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyResponseBody {
}
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRequestBody {
}
- 定义数据结构,简便起见,这里只decode和encode特定的类。RequestData是@MyRequestBody修饰的类,ResponseData是@MyResponseBody修饰的类。
public static class RequestData {
private Map<String, String> data;
public Map<String, String> getData() {
return data;
}
public void setData(Map<String, String> data) {
this.data = data;
}
public String toString() {
return "{\"data\":" + data + "}";
}
}
public static class ResponseData {
private Map<String, String> data;
public Map<String, String> getData() {
return data;
}
public void setData(Map<String, String> data) {
this.data = data;
}
}
- 定义controller
@Controller
public static class MyController {
@RequestMapping
@MyResponseBody
public ResponseData index(@MyRequestBody RequestData requestData) {
System.out.println(requestData);
ResponseData responseData = new ResponseData();
responseData.setData(requestData.getData());
return responseData;
}
}
- 注入HandlerAdapter,并加入了两个MyResolver,只不过一个是setCustomArgumentResolvers,另一个是setCustomReturnValueHandlers。
MyResolver都加入了DataMessageConvert,这个实现了HttpMessageConverter,稍后会介绍到。
@Configuration
public static class MyWebMvcConfigurationSupport extends WebMvcConfigurationSupport {
@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
RequestMappingHandlerAdapter requestMappingHandlerAdapter = super.requestMappingHandlerAdapter();
List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
converters.add(new DataMessageConvert());
List<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList<HandlerMethodArgumentResolver>();
argumentResolvers.add(new MyResolver(converters));
requestMappingHandlerAdapter.setCustomArgumentResolvers(argumentResolvers);
List<HandlerMethodReturnValueHandler> returnValueHandlers = new ArrayList<HandlerMethodReturnValueHandler>();
returnValueHandlers.add(new MyResolver(converters));
requestMappingHandlerAdapter.setCustomReturnValueHandlers(returnValueHandlers);
return requestMappingHandlerAdapter;
}
}
- MyResolver继承了AbstractMessageConverterMethodProcessor,并自定义了supportsParameter和supportsReturnType来加载自定义的注解。resolveArgument和handleReturnValue是沿用RequestResponseBodyMethodProcessor的方法,来调用HttpMessageConverter的处理方法。
public static class MyResolver extends AbstractMessageConverterMethodProcessor {
public MyResolver(List<HttpMessageConverter<?>> converters) {
super(converters);
}
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(MyRequestBody.class);
}
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
Object arg = readWithMessageConverters(webRequest, parameter, parameter.getGenericParameterType());
String name = Conventions.getVariableNameForParameter(parameter);
WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
if (arg != null) {
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
}
}
mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
return arg;
}
public boolean supportsReturnType(MethodParameter returnType) {
return returnType.getMethodAnnotation(MyResponseBody.class) != null;
}
public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
mavContainer.setRequestHandled(true);
writeWithMessageConverters(returnValue, returnType, webRequest);
}
}
-
HttpMessageConverter是处理RequestMapping的注释的方法的参数和返回值的接口类。自定义HttpMessageConverter,继承了AbstractGenericHttpMessageConverter来实现一些公用的方法。
实现了canRead方法,只解码RequestData这个类,同样实现了canWrite了方法,只编码ResponseData这个类。
简便起见这里只编码和解码Map<String, String>,方法也很简单,key和value直接用','链接,不同的entry之间用';'连接。
public static class DataMessageConvert extends AbstractGenericHttpMessageConverter<Object> {
@Override
public boolean canRead(Type type, Class<?> contextClass, MediaType mediaType) {
return ((Class) type).isAssignableFrom(RequestData.class);
}
@Override
public boolean canWrite(Type type, Class<?> clazz, MediaType mediaType) {
return ((Class) type).isAssignableFrom(ResponseData.class);
}
public List<MediaType> getSupportedMediaTypes() {
return Collections.singletonList(MediaType.ALL);
}
protected boolean supports(Class<?> clazz) {
return clazz.isAssignableFrom(Map.class);
}
public Object readInternal(Class<?> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
return readMap(inputMessage);
}
private Object readMap(HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
Charset cs = Charset.forName("UTF-8");
StringBuilder stringBuilder = new StringBuilder();
InputStream inputStream = inputMessage.getBody();
byte[] b = new byte[1024];
int length;
while ((length = inputStream.read(b)) != -1) {
ByteBuffer byteBuffer = ByteBuffer.allocate(length);
byteBuffer.put(b, 0, length);
byteBuffer.flip();
stringBuilder.append(cs.decode(byteBuffer).array());
}
String[] list = stringBuilder.toString().split(";");
Map<String, String> map = new HashMap<String, String>(list.length);
for (String entry : list) {
String[] keyValue = entry.split(",");
map.put(keyValue[0], keyValue[1]);
}
RequestData requestData = new RequestData();
requestData.setData(map);
return requestData;
}
public void writeInternal(Object o, Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
StringBuilder stringBuilder = new StringBuilder();
Map<String, String> map = ((ResponseData) o).getData();
for (Map.Entry<String, String> entry : map.entrySet()) {
stringBuilder.append(entry.getKey()).append(",").append(entry.getValue()).append(";");
}
stringBuilder.deleteCharAt(stringBuilder.length() - 1);
outputMessage.getBody().write(stringBuilder.toString().getBytes());
}
public Object read(Type type, Class<?> contextClass, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
return readMap(inputMessage);
}
}
- 程序入口,跟上一篇文章类似。
@Configuration
public class CustomizeResponseBodyTest {
public static void main(String[] args) throws ServletException, IOException {
MockServletContext mockServletContext = new MockServletContext();
MockServletConfig mockServletConfig = new MockServletConfig(mockServletContext);
AnnotationConfigWebApplicationContext annotationConfigWebApplicationContext = new AnnotationConfigWebApplicationContext();
annotationConfigWebApplicationContext.setServletConfig(mockServletConfig);
annotationConfigWebApplicationContext.register(CustomizeResponseBodyTest.class);
DispatcherServlet dispatcherServlet = new DispatcherServlet(annotationConfigWebApplicationContext);
dispatcherServlet.init(mockServletConfig);
MockHttpServletResponse response = new MockHttpServletResponse();
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
request.addHeader("Accept", "application/json");
request.setContent(("result,hello world;date," + Calendar.getInstance().getTimeInMillis()).getBytes());
dispatcherServlet.service(request, response);
System.out.println(new String(response.getContentAsByteArray()));
}
...
}
结语
这里主要介绍了HandlerAdapter和HttpMessageConverter。HandlerAdapter封装了HandlerMapping的方法,而HttpMessageConverter则是转换Request和Response的内容的接口,比如json的encode和decode。接下来会介绍更多关于Mvc的内容。