Emmmm… I'm on my way to a project recently , When a method needs to receive only one or fewer parameters, it is too lazy to write entity classes to receive them , use spring We all know that , Receive a single parameter and use it @RequestParam Just annotations , But the front end corresponds to Content-type It needs to be changed to
application/x-www-form-urlencoded
, So it is specially marked on the interface document . however … I don't know if the format of the front end is changed, but the parameters are still used json The format has not been changed to the key value pair or what is the reason , It's always said that the parameters can't be transmitted , Tell me to change back json Formatted .. I'm really lazy , The other one doesn't think it's necessary , Create a new entity with just one or two parameters , It's too wasteful , But it's been a long time since I've had this problem , I haven't found a way , So take this opportunity , Opened my development artifact ,www.baidu.com… Enter my question , I haven't found a solution for a long time , And then I wanted to take a look Spring What's going on inside , Just follow the source code a little bit , Let's talk about my solution .
<> One ,RequestMappingHandlerAdapter
RequestMappingHandlerAdapter
Realized HandlerAdapter Interface , seeing the name of a thing one thinks of its function , express handler Of adapter, there handler refer to Spring A method of processing a specific request Controller How to do it , in other words HandlerAdapter Refers to the adaptation of the current request to a Handler Processor for .RequestMappingHandlerAdapter yes HandlerAdapter A concrete implementation of , It is mainly used to adapt a request to @RequestMapping Type Handler handle , This includes the processing of request data and response data .
// Here you can get a list of handler method parameter parsers List<HandlerMethodArgumentResolver>
argumentResolvers= requestMappingHandlerAdapter.getArgumentResolvers()
If you want to process response parameters, use
// Here you can get the processor that returns the value of the handler method List<HandlerMethodReturnValueHandler> originalHandlers =
requestMappingHandlerAdapter.getReturnValueHandlers();
You can get this list , Then we need to add our own defined processor. It shouldn't be too troublesome ?( The custom policy processing of returned data is not discussed here , There are other articles on the Internet , You can find it if you need )
<> Two ,HandlerMethodArgumentResolver
The policy interface solution parameter is substituted into the parameter value in the context of the given request ( Source code annotation of translation )
The simple understanding is : It takes care of you Handler All the input parameters in the method : Including automatic packaging , automatic recode , Verification and so on .
——————————————————————————————————————————
At this time, I already know what type is stored in the list obtained in the first step , In short , We just need to implement this policy class , Just write our own algorithm or logic
There are two methods to implement in this interface :
The role of the first method : Whether the parameters associated with the given method are supported by the parser .( If you return true, Then use this class for parameter conversion , If you return false, Then continue to find the next policy class )
The role of the second method : The solution parameter is the value of the argument from the given request .
from WebDataBinderFactory Provides a way to create a WebDataBinder Required data binding and type conversion target time instances .( In a nutshell , It's about converting parameter values , What is returned is the parsed parameter value )
<> Three ,RequestParamMethodArgumentResolver
This class is used to handle Controller There are some advantages in the method @RequestParam Processor specific annotation .
This method is first called to determine whether the processor is used to parse the parameters , So we see it , If the parameter has RequestParam annotation , This class will be used for processing , So can we follow suit ?
<> Four ,MyHandlerMethodArgumentResolver
It's nothing to say , On their own definition of the parameter parser .
Go straight to the code
/** * @BelongsProject: * @BelongsPackage: * @Author: hef * @CreateTime:
2020-06-20 18:49 * @Description: describe */ public class
MyHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver { /**
* This is how to deal with it @RequestParam Annotated native policy class */ private RequestParamMethodArgumentResolver
requestParamMethodArgumentResolver; /** * All parameter structure */ public
MyHandlerMethodArgumentResolver(RequestParamMethodArgumentResolver
requestParamMethodArgumentResolver) { this.requestParamMethodArgumentResolver =
requestParamMethodArgumentResolver; } /** * When the parameter is preceded by @RequestParam When annotating , Will use this Parser *
<p> * notes : The return value of this method determines : Do you want to use this parser to parse the parameter */ @Override public boolean supportsParameter(
MethodParameter methodParameter) { // Obviously , It is to judge whether there is such an annotation return methodParameter.
hasParameterAnnotation(RequestParam.class); } /** * Analytic parameters */ @Override public
ObjectresolveArgument(MethodParameter methodParameter, ModelAndViewContainer
modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory
webDataBinderFactory) throws Exception { final String applicationJson =
"application/json"; HttpServletRequest request = nativeWebRequest.
getNativeRequest(HttpServletRequest.class); if (request == null) { throw new
RuntimeException(" request must not be null!"); } // Get content type String contentType =
request.getContentType(); // If the type is of json Then run your own analytical method if (null != contentType &&
contentType.contains(applicationJson )) { // Get parameter name String parameterName =
methodParameter.getParameterName(); // Get parameter type Class<?> parameterType =
methodParameter.getParameterType(); // because json The data is put in the stream , So read the stream ,
// however ServletRequest Of getReader() and getInputStream() Two methods can only be called once , And not both .
// So here is the need to write a custom HttpServletRequestWrapper, The main function is to read stream data repeatedly String read = getRead(
request.getReader()); // transformation json JSONObject jsonObject = JSON.parseObject(read);
Object o1; if (jsonObject == null) {
// One possibility here is, for example get request , Parameters are spliced in URL behind , But if we still read the data in the stream, we will not be able to read it Map<String, String[]>
parameterMap= request.getParameterMap(); o1 = parameterMap.get(parameterName); }
else { o1 = jsonObject.get(parameterName); } Object arg = null;
// If the value has been obtained, then do the type conversion if (o1 != null) { WebDataBinder binder =
webDataBinderFactory.createBinder(nativeWebRequest, null, parameterName); arg =
binder.convertIfNecessary(o1, parameterType, methodParameter); } return arg; }
// Otherwise, run the original strategy class . Object o = requestParamMethodArgumentResolver.resolveArgument(
methodParameter, modelAndViewContainer, nativeWebRequest, webDataBinderFactory);
return o; } /** * Flow string * * @param bf * @return */ private static String getRead
(BufferedReader bf) { StringBuilder sb = new StringBuilder(); try { char[] buff
= new char[1024]; int len; while ((len = bf.read(buff)) != -1) { sb.append(buff,
0, len); } } catch (IOException e) { e.printStackTrace(); } return sb.toString()
; } }
<> Four ,ConfigArgumentResolvers
My strategy class has been written , So how to add to the configuration ?
/** * @BelongsProject: * @BelongsPackage: * @Author: hef * @CreateTime:
2020-06-20 18:49 * @Description: describe */ @Configuration public class
ConfigArgumentResolvers { private final RequestMappingHandlerAdapter
requestMappingHandlerAdapter; public ConfigArgumentResolvers(
RequestMappingHandlerAdapter requestMappingHandlerAdapter) { this.
requestMappingHandlerAdapter= requestMappingHandlerAdapter; }
//springBoot Execute at startup @PostConstruct private void addArgumentResolvers() { //
Get the parameter resolution set defined by the framework List<HandlerMethodArgumentResolver> argumentResolvers =
requestMappingHandlerAdapter.getArgumentResolvers();
MyHandlerMethodArgumentResolver myHandlerMethodArgumentResolver=
getMyHandlerMethodArgumentResolver(argumentResolvers); //
ha.getArgumentResolvers() What we get is an immutable collection , So we need to create a new set to place the parameter parser List<
HandlerMethodArgumentResolver> myArgumentResolvers = new ArrayList<>(
argumentResolvers.size() + 1);
// One thing to note here is that custom processors need to be placed in the RequestParamMethodArgumentResolver front
// Why? ? Because if you put it behind it , So it has been disposed of , We can't get into our own strategy // So put the custom policy first , safe ! //
The custom parser , Place in the first ; And keep the original parser myArgumentResolvers.add(
myHandlerMethodArgumentResolver); myArgumentResolvers.addAll(argumentResolvers);
// Set the new set in requestMappingHandlerAdapter.setArgumentResolvers(
myArgumentResolvers); } /** * obtain MyHandlerMethodArgumentResolver example */ private
MyHandlerMethodArgumentResolvergetMyHandlerMethodArgumentResolver( List<
HandlerMethodArgumentResolver> argumentResolversList) { // Original treatment RequestParam Class of
RequestParamMethodArgumentResolver requestParamMethodArgumentResolver= null; if
(argumentResolversList == null) { throw new RuntimeException(
"argumentResolverList must not be null!"); } for (HandlerMethodArgumentResolver
argumentResolver: argumentResolversList) { if (
requestParamMethodArgumentResolver!= null) { break; } if (argumentResolver
instanceof RequestParamMethodArgumentResolver) { //
Because we still need to use the original class in our own strategy , So you need to get this object instance requestParamMethodArgumentResolver = (
RequestParamMethodArgumentResolver) argumentResolver; } } if (
requestParamMethodArgumentResolver== null) { throw new RuntimeException(
"RequestParamMethodArgumentResolver not be null!"); } // Instantiate custom parameter parser return new
MyHandlerMethodArgumentResolver(requestParamMethodArgumentResolver); } }
<> Five ,MyHttpServletRequestWrapper
This is custom HttpServletRequest, Ensure that the stream data can be obtained repeatedly
/** * @BelongsProject: * @BelongsPackage: * @Author: hef * @CreateTime:
2020-06-22 16:29 * @Description: describe */ public class MyHttpServletRequestWrapper
extends HttpServletRequestWrapper { private final byte[] body; public
MyHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request); // Get this one time before reading the stream parameterMap, Otherwise, the data cannot be parsed after reading the stream , //
as a result of org.apache.catalina.connector.Request There's something in it usingInputStream and
usingReader Whether two global variable record streams have been read
//org.apache.catalina.connector.Request Inside parseParameters Method is used to parse request parameters (Parse
request parameters.) // There is a judgment before parsing the parameters , If the stream has been read The request parameters are no longer resolved // // if
(usingInputStream || usingReader) { This is the judgment in the source code // success = true; // return; // }
// If you ask once before , that org.apache.catalina.util.ParameterMap There will be one in it locked state , If you've read it once
It becomes locked Then the subsequent rereading is after reading and parsing map // /** // * The current lock state of this parameter
map. // */ // private boolean locked = false; request.getParameterMap(); body =
ReadAsChars(request).getBytes(Charset.forName("UTF-8")); } @Override public
BufferedReadergetReader() throws IOException { return new BufferedReader(new
InputStreamReader(getInputStream())); } @Override public ServletInputStream
getInputStream() throws IOException { final ByteArrayInputStream bais = new
ByteArrayInputStream(body); return new ServletInputStream() { @Override public
int read() throws IOException { return bais.read(); } @Override public boolean
isFinished() { return false; } @Override public boolean isReady() { return false
; } @Override public void setReadListener(ReadListener readListener) { } }; }
/** * Analytic flow * @param request * @return */ public static String ReadAsChars(
ServletRequest request) { InputStream is = null; StringBuilder sb = new
StringBuilder(); try { is = request.getInputStream(); byte[] b = new byte[4096];
for (int n; (n = is.read(b)) != -1;) { sb.append(new String(b, 0, n)); } } catch
(IOException e) { e.printStackTrace(); } finally { if (null != is) { try { is.
close(); } catch (IOException e) { e.printStackTrace(); } } } return sb.toString
(); } }
<> Six ,HttpServletRequestReplacedFilter
Replace the original Request object , Use custom
/** * @BelongsProject: * @BelongsPackage: * @Author: hef * @CreateTime:
2020-06-22 16:47 * @Description: describe */ @Component public class
HttpServletRequestReplacedFilter implements Filter { @Override public void
doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException { ServletRequest requestWrapper = null; if(
requestinstanceof HttpServletRequest) { requestWrapper = new
MyHttpServletRequestWrapper((HttpServletRequest) request); } if(null ==
requestWrapper) { chain.doFilter(request, response); } else { chain.doFilter(
requestWrapper, response); } } }
<> Seven , summary
If you want to @RequestBody You can also use this method to receive parameters in the form of a form , It's easier to deal with , You just need to instantiate the custom processor and pass it in to the other two processors
/** * analysis Content-Type by application/json The default parser for is RequestResponseBodyMethodProcessor
*/ private RequestResponseBodyMethodProcessor requestResponseBodyMethodProcessor
; /** *
analysis Content-Type by application/x-www-form-urlencoded The default parser for is ServletModelAttributeMethodProcessor
*/ private ServletModelAttributeMethodProcessor
servletModelAttributeMethodProcessor;
By this point, it has been achieved RequestParam Annotations are also acceptable Json Format data , I didn't do any more tests , We have not found out the specific relevance problems , Follow up if there is a code friend what problems can be discussed with a message , I have a chicken with vegetables , I hope that the writing is not good, and the great God can give me more advice , esteem it a favor !
Technology