overview
RequestBodyAdvice
RequestBodyAdvice is an interface provided by spring mvc4.2, which allows the request body to be read and converted into an object, and takes the processing result object as the @ RequestBody parameter or the @ HttpEntity method parameter. It can be seen that its scope of action is:
Parameters marked with @ RequestBody
The parameter is HttpEntity
ResponseBodyAdvice
ResponseBodyAdvice is an interface provided by spring mvc4.1. It allows you to customize the returned data after executing @ ResponseBody, or use HttpMessageConverter to customize the Controller Method that returns @ ResponseEntity before writing to the body. It can be seen that its scope of action is:
Tag with @ ResponseBody annotation
Return @ ResponseEntity
Source code
These are two new interfaces added to spring 4.2
1,RequestBodyAdvice
public interface RequestBodyAdvice { boolean supports(MethodParameter var1, Type var2, Class<? extends HttpMessageConverter<?>> var3); HttpInputMessage beforeBodyRead(HttpInputMessage var1, MethodParameter var2, Type var3, Class<? extends HttpMessageConverter<?>> var4) throws IOException; Object afterBodyRead(Object var1, HttpInputMessage var2, MethodParameter var3, Type var4, Class<? extends HttpMessageConverter<?>> var5); @Nullable Object handleEmptyBody(@Nullable Object var1, HttpInputMessage var2, MethodParameter var3, Type var4, Class<? extends HttpMessageConverter<?>> var5); }
The readWithMessageConverters() method of AbstractMessageConverterMethodArgumentResolver calls the method of this interface.
@Nullable protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException { boolean noContentType = false; MediaType contentType; try { contentType = inputMessage.getHeaders().getContentType(); } catch (InvalidMediaTypeException var16) { throw new HttpMediaTypeNotSupportedException(var16.getMessage()); } if (contentType == null) { noContentType = true; contentType = MediaType.APPLICATION_OCTET_STREAM; } Class<?> contextClass = parameter.getContainingClass(); Class<T> targetClass = targetType instanceof Class ? (Class)targetType : null; if (targetClass == null) { ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter); targetClass = resolvableType.resolve(); } HttpMethod httpMethod = inputMessage instanceof HttpRequest ? ((HttpRequest)inputMessage).getMethod() : null; Object body = NO_VALUE; AbstractMessageConverterMethodArgumentResolver.EmptyBodyCheckingHttpInputMessage message; try { label94: { message = new AbstractMessageConverterMethodArgumentResolver.EmptyBodyCheckingHttpInputMessage(inputMessage); Iterator var11 = this.messageConverters.iterator(); HttpMessageConverter converter; Class converterType; GenericHttpMessageConverter genericConverter; while(true) { if (!var11.hasNext()) { break label94; } converter = (HttpMessageConverter)var11.next(); converterType = converter.getClass(); genericConverter = converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter)converter : null; if (genericConverter != null) { if (genericConverter.canRead(targetType, contextClass, contentType)) { break; } } else if (targetClass != null && converter.canRead(targetClass, contentType)) { break; } } if (message.hasBody()) { HttpInputMessage msgToUse = this.getAdvice().beforeBodyRead(message, parameter, targetType, converterType); body = genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) : converter.read(targetClass, msgToUse); body = this.getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType); } else { body = this.getAdvice().handleEmptyBody((Object)null, message, parameter, targetType, converterType); } } } catch (IOException var17) { throw new HttpMessageNotReadableException("I/O error while reading input message", var17, inputMessage); } if (body != NO_VALUE) { LogFormatUtils.traceDebug(this.logger, (traceOn) -> { String formatted = LogFormatUtils.formatValue(body, !traceOn); return "Read \"" + contentType + "\" to [" + formatted + "]"; }); return body; } else if (httpMethod != null && SUPPORTED_METHODS.contains(httpMethod) && (!noContentType || message.hasBody())) { throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes); } else { return null; } }
The HttpMessageConverter does some processing before and after processing the request body and when the body is empty.
It can be seen from this that when using these handlermethodargumentresolvers, we can perform pre-processing and post-processing on the request body
ResponseBodyAdvice
public interface ResponseBodyAdvice<T> { boolean supports(MethodParameter var1, Class<? extends HttpMessageConverter<?>> var2); @Nullable T beforeBodyWrite(@Nullable T var1, MethodParameter var2, MediaType var3, Class<? extends HttpMessageConverter<?>> var4, ServerHttpRequest var5, ServerHttpResponse var6); }
This can process the returned result of @ ResponseBody before outputting it to the response.
Through generics, specify the type of response body object that needs to be "intercepted". The implementation of this interface will be executed after the data returned by the Controller method is matched to the HttpMessageConverter and before the HttpMessageConverter is serialized. You can modify the response body uniformly by overriding beforeBodyWrite.
application
Use of RequestBodyAdvice
First, an implementation class is implemented RequestBodyAdvice,Annotate the class after@ControllerAdvice,Both of them are indispensable. For example, some request bodies have been encrypted and can be decrypted here. The core method is supports,The value returned by this method boolean Value, which determines whether to execute beforeBodyRead method. And our main logic is beforeBodyRead Method, decrypt the request body of the client. Note: no need to add@Component annotation
//@Component @ControllerAdvice public class RequestBodyDecrypt implements RequestBodyAdvice { @Reference private EquipmentService equipmentService; @Override public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) { return true; // Must be true to execute the beforeBodyRead and afterBodyRead methods } @Override public HttpInputMessage beforeBodyRead(HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) throws IOException { String equipmentNo = httpInputMessage.getHeaders().getFirst("equipmentNo"); // Get equipmentNo from the request header, and the getFirst() method gets the value according to the name of the request header String privateKey = null; List<EquipmentDTO> mapList = equipmentService.getByEquipmentNo(equipmentNo); if (mapList.size() > 0) { EquipmentDTO equipmentDTO = mapList.get(0); privateKey = equipmentDTO.getProdPriKey(); } // Extract data InputStream is = httpInputMessage.getBody(); // Get the request body from the HTTPInputMessage to get the byte input stream byte[] data = new byte[is.available()]; is.read(data); String dataStr = new String(data, StandardCharsets.UTF_8); JSONObject json = JSONObject.parseObject(dataStr); String decrypt = null; try { decrypt = RSAUtils.decryptByPrivateKey(json.getString("applyData"), privateKey); // Request body after decryption of private key } catch (Exception e) { throw new RuntimeException("data error"); } // Encapsulate the decrypted request body into HttpInputMessage and return return new DecodedHttpInputMessage(httpInputMessage.getHeaders(), new ByteArrayInputStream(decrypt.getBytes(StandardCharsets.UTF_8))); } @Override public Object afterBodyRead(Object body, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) { return body; } @Override public Object handleEmptyBody(Object body, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) { return body; } static class DecodedHttpInputMessage implements HttpInputMessage { HttpHeaders headers; InputStream body; public DecodedHttpInputMessage(HttpHeaders headers, InputStream body) { this.headers = headers; this.body = body; } @Override public InputStream getBody() throws IOException { return body; } @Override public HttpHeaders getHeaders() { return headers; } } }
decryptByPrivateKey:
public static String decryptByPrivateKey(String encryptData, String priKey) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, IOException { byte[] sourceBytes = Base64.getDecoder().decode(encryptData); byte[] keyBytes = Base64.getDecoder().decode(priKey); PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); Key priK = keyFactory.generatePrivate(pkcs8KeySpec); Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); cipher.init(Cipher.DECRYPT_MODE, priK); int inputLen = sourceBytes.length; ByteArrayOutputStream out = new ByteArrayOutputStream(); int offSet = 0; byte[] cache; int i = 0; // Decrypt data segments while (inputLen - offSet > 0) { if (inputLen - offSet > MAX_DECRYPT_BLOCK) { cache = cipher.doFinal(sourceBytes, offSet, MAX_DECRYPT_BLOCK); } else { cache = cipher.doFinal(sourceBytes, offSet, inputLen - offSet); } out.write(cache, 0, cache.length); i++; offSet = i * MAX_DECRYPT_BLOCK; } String decryptedData = out.toString("UTF-8"); out.close(); return decryptedData; }
Use of ResponseBodyAdvice
First, an implementation class implements ResponseBodyAdvice, and then adds the annotation @ ControllerAdvice to the class,
@ControllerAdvice public class MyResponseBodyAdvice implements ResponseBodyAdvice<Result> { /** * Encryption string 1 */ private static String md5_keyone; /** * Encryption string II */ private static String md5_keytwo; @PostConstruct public void init() throws Exception { md5_keyone = Utils.PT.getProps("md5_keyone"); md5_keytwo = Utils.PT.getProps("md5_keytwo"); } /** * Determine the type of support * * @param returnType * @param converterType * @return * @see org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice#supports(org.springframework.core.MethodParameter, * java.lang.Class) */ @Override public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) { return returnType.getMethod().getReturnType().isAssignableFrom(Result.class); } /** * Encrypt results * * @param body * @param returnType * @param selectedContentType * @param selectedConverterType * @param request * @param response * @return * @see org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice#beforeBodyWrite(java.lang.Object, * org.springframework.core.MethodParameter, * org.springframework.http.MediaType, java.lang.Class, * org.springframework.http.server.ServerHttpRequest, * org.springframework.http.server.ServerHttpResponse) */ @Override public Result beforeBodyWrite(Result body, MethodParameter returnType,org.springframework.http.MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,ServerHttpResponse response) { String jsonString = JSON.toJSONString(body.getData()); System.out.println(jsonString); // First encryption String data_encode_one = MD5.md5(md5_keyone + jsonString); // Second encryption String data_encode_two = MD5.md5(data_encode_one + md5_keytwo); body.setToken(data_encode_two); return body; } }
Why add @ ControllerAdvice annotation and implement RequestBodyAdvice or ResponseBodyAdvice interface
Now let's analyze why we need to implement the RequestBodyAdvice interface and add the ControllerAdvice annotation at the same time.
Simply put, to execute the afterBodyRead method, you must implement the ResponseBodyAdvice interface.
afterBodyRead method of RequestResponseBodyAdviceChain: call getMatchingAdvice method to get advice of RequestBodyAdvice type
Where: class RequestResponseBodyAdviceChain implements RequestBodyAdvice, ResponseBodyAdvice
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) { Iterator var6 = this.getMatchingAdvice(parameter, RequestBodyAdvice.class).iterator(); while(var6.hasNext()) { RequestBodyAdvice advice = (RequestBodyAdvice)var6.next(); // This advice is the class we define if (advice.supports(parameter, targetType, converterType)) { //If the return value of the supports method is true, the afterBodyRead method of RequestBodyAdvice is executed body = advice.afterBodyRead(body, inputMessage, parameter, targetType, converterType); } } return body; }
getMatchAdvice: get the advice of RequestBodyAdvice type (this advice is defined by us). If it is not the RequestBodyAdvice type, it will not be added to the result set, so this is why we implement RequestBodyAdvice
private <A> List<A> getMatchingAdvice(MethodParameter parameter, Class<? extends A> adviceType) { List<Object> availableAdvice = this.getAdvice(adviceType);//Get the advice of RequestBodyAdvice type (this advice is the class we define to implement the RequestBodyAdvice interface) if (CollectionUtils.isEmpty(availableAdvice)) { return Collections.emptyList(); } else { List<A> result = new ArrayList(availableAdvice.size()); Iterator var5 = availableAdvice.iterator(); while(true) { Object advice; while(true) { if (!var5.hasNext()) { return result; } advice = var5.next(); if (!(advice instanceof ControllerAdviceBean)) { break; } ControllerAdviceBean adviceBean = (ControllerAdviceBean)advice; if (adviceBean.isApplicableToBeanType(parameter.getContainingClass())) { advice = adviceBean.resolveBean(); // The Advice we defined is returned, that is, the Bean object is obtained from the BeanFactory according to the name of the Bean break; } } // Judge whether this class is of RequestBodyAdvice type. If not, it will not be added to the result set, so this is why we implement RequestBodyAdvice if (adviceType.isAssignableFrom(advice.getClass())) { result.add(advice); } } } }
private List<Object> getAdvice(Class<?> adviceType) { if (RequestBodyAdvice.class == adviceType) { return this.requestBodyAdvice; } else if (ResponseBodyAdvice.class == adviceType) { return this.responseBodyAdvice; } else { throw new IllegalArgumentException("Unexpected adviceType: " + adviceType); } }
public Object resolveBean() { if (this.resolvedBean == null) { Object resolvedBean = this.obtainBeanFactory().getBean((String)this.beanOrName); // obtainBeanFactory() returns the BeanFactory object if (!this.isSingleton) { return resolvedBean; } this.resolvedBean = resolvedBean; } return this.resolvedBean; }
Add @ ControllerAdvice annotation
Simply put, @ ControllerAdviceBean can be found only by adding it.
HandlerAdapter literally means processing adapter. Its function is summarized in one sentence, which is to call specific methods to process requests sent by users. When the handler mapping obtains the controller executing the request, the dispatcher servlet will call the corresponding handler adapter according to the controller type corresponding to the controller for processing.
RequestMappingHandlerAdapter is more complex. It can be said that this class is the most complex class in the whole spring MVC, but it is also the most frequently used class in spring MVC. At present, in the use of spring MVC, the processing of requests mainly depends on the combination of RequestMappingHandlerMapping and RequestMappingHandlerAdapter classes. The following focuses on the RequestMappingHandlerAdapter class
RequestMappingHandlerAdapter initialization process:
RequestMappingHandlerAdapter implements the InitializingBean interface, and the Spring container will automatically call its afterpropertieset method.