In a Web request, parameters are simply placed in the address bar or the body of the request, and individual requests may be placed in the request header.

In the address bar, we can get the parameters as follows:

String javaboy = request.getParameter("name ");

In the body of the request, if it is in the form of key/value, we can get the parameters as follows:

String javaboy = request.getParameter("name ");

If it is in JSON form, we can get the input stream as follows, parse it into a JSON string, and convert it to an object using the JSON tool:

BufferedReader reader = new BufferedReader(new InputStreamReader(request.getInputStream()));
String json = reader.readLine();
reader.close();
User user = new ObjectMapper().readValue(json, User.class);

If the parameter is placed in the request header, we can get it as follows:

String javaboy = request.getHeader("name");

If you’re using the JSP /Servlet technology stack, there are several ways to get parameters.

If you use the SpringMVC framework, you may find it too rich to get parameters. The various annotations include @RequestParam, @RequestBody, @RequestHeader, @PathVariable, etc. Parameter can be key/value form, can also be in the form of JSON, very rich! However, no matter how rich, the lowest level of getting parameters is just a few of the above.

How does SpringMVC extract the parameters from the request and give them to us directly? For example, the following interface:

@RestController public class HelloController { @GetMapping("/hello") public String hello(String name) { return "hello "+name; }}

We all know that the name parameter is extracted from HttpServletRequest. How is it extracted? This is the topic Songe would like to share with you today.

1. Custom parameter parsers

To understand this, let’s first define a parameter parser.

Custom parser needs to realize HandlerMethodArgumentResolver interface parameters, now let’s look at the interface:

public interface HandlerMethodArgumentResolver {
    boolean supportsParameter(MethodParameter parameter);
    @Nullable
    Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;

}

There are only two methods in this interface:

  • SupportSparameter: This method indicates whether the parameter parser is enabled, returning true to indicate enabled, and false to indicate not enabled.
  • ResolveArgument: This is the specific parsing process, which is the process of getting the parameters from the request, and the return value of the method corresponds to the value of the parameters in the interface.

A custom parameter parser only needs to implement this interface.

Suppose I now have a requirement (it’s actually convenient to get the current login user name in Spring Security, just for this case, don’t argue) :

If I add @currentUsername annotation to the parameter of the interface, let’s say I use Spring Security in the system Security framework. The value of this parameter is the user name currently logged in, as follows:

@RestController
public class HelloController {
    @GetMapping("/hello")
    public String hello(@CurrentUserName String name) {
        return "hello "+name;
    }
}

To do this, it’s pretty easy. First, we’ll customize an @CurrentUsername annotation like this:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface CurrentUserName {
}

There is nothing to explain in this comment.

Next we custom parser CurrentUserNameHandlerMethodArgumentResolver parameters, as follows:

public class CurrentUserNameHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver { @Override public boolean supportsParameter(MethodParameter parameter) { return parameter.getParameterType().isAssignableFrom(String.class)&&parameter.hasParameterAnnotation(CurrentUserName.class); } @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); return user.getUsername(); }}
  • SupportSparameter: If the parameter type is String and there is one on the parameter@CurrentUserNameAnnotation, the parameter parser is used.
  • ResolveArgument: The return value of this method is the parameter value, and the current login user name can be obtained from the SecurityContextHolder.

Finally, we configure the custom parameter parser into the HandlerAdapter as follows:

@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) { resolvers.add(new CurrentUserNameHandlerMethodArgumentResolver()); }}

At this point, the configuration is complete.

Next, start the project, and after the user has logged in successfully, access the /hello interface, and you will see the data of the current logged in user returned.

This is our custom parameter type parser. As you can see, it’s very Easy.

In for SpringMVC, also has a lot of HandlerMethodArgumentResolver by default implementation class, they handle problems are similar, elder brother to give you an example.

2.PrincipalMethodArgumentResolver

If we are using Spring Security in our project, we can obtain the current logged-in user information by:

@GetMapping("/hello2")
public String hello2(Principal principal) {
    return "hello " + principal.getName();
}

That is, simply add a parameter of type Principal to the parameter of the current interface, which describes the information of the current logged-in user. If you are not familiar with Spring Security, you can reply SS in the background of the official account [Jiangnan A Little Rain].

So how does this function work? Of course is PrincipalMethodArgumentResolver at work!

Let’s take a look at the parameter parser:

public class PrincipalMethodArgumentResolver implements HandlerMethodArgumentResolver { @Override public boolean supportsParameter(MethodParameter parameter) { return Principal.class.isAssignableFrom(parameter.getParameterType()); } @Override public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); if (request == null) { throw new IllegalStateException("Current request is not of type HttpServletRequest: " + webRequest); } Principal principal = request.getUserPrincipal(); if (principal ! = null && ! parameter.getParameterType().isInstance(principal)) { throw new IllegalStateException("Current user principal is not of type [" + parameter.getParameterType().getName() + "]: " + principal); } return principal; }}
  • SupportSparameter: This method mainly determines whether the parameter type is Principal or not. If the parameter type is Principal, it is supported.
  • ResolveArgument: The logic of this method is simple. It gets the native request and then gets the Principal object from the request.

Is it very simple, with this, we can always load the current login user information.

3.RequestParamMapMethodArgumentResolver

Let me give you another example:

@RestController public class HelloController { @PostMapping("/hello") public void hello(@RequestParam MultiValueMap map) Throws IOException {// }}

The interface to a lot of friend may be written, using the Map to receive front-end of parameters, then the parameters of the used here is RequestParamMapMethodArgumentResolver parser.

public class RequestParamMapMethodArgumentResolver implements HandlerMethodArgumentResolver { @Override public boolean supportsParameter(MethodParameter parameter) { RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class); return (requestParam ! = null && Map.class.isAssignableFrom(parameter.getParameterType()) && ! StringUtils.hasText(requestParam.name())); } @Override public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter); if (MultiValueMap.class.isAssignableFrom(parameter.getParameterType())) { // MultiValueMap Class<? > valueType = resolvableType.as(MultiValueMap.class).getGeneric(1).resolve(); if (valueType == MultipartFile.class) { MultipartRequest multipartRequest = MultipartResolutionDelegate.resolveMultipartRequest(webRequest); return (multipartRequest ! = null ? multipartRequest.getMultiFileMap() : new LinkedMultiValueMap<>(0)); } else if (valueType == Part.class) { HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class); if (servletRequest ! = null && MultipartResolutionDelegate.isMultipartRequest(servletRequest)) { Collection<Part> parts = servletRequest.getParts(); LinkedMultiValueMap<String, Part> result = new LinkedMultiValueMap<>(parts.size()); for (Part part : parts) { result.add(part.getName(), part); } return result; } return new LinkedMultiValueMap<>(0); } else { Map<String, String[]> parameterMap = webRequest.getParameterMap(); MultiValueMap<String, String> result = new LinkedMultiValueMap<>(parameterMap.size()); parameterMap.forEach((key, values) -> { for (String value : values) { result.add(key, value); }}); return result; } } else { // Regular Map Class<? > valueType = resolvableType.asMap().getGeneric(1).resolve(); if (valueType == MultipartFile.class) { MultipartRequest multipartRequest = MultipartResolutionDelegate.resolveMultipartRequest(webRequest); return (multipartRequest ! = null ? multipartRequest.getFileMap() : new LinkedHashMap<>(0)); } else if (valueType == Part.class) { HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class); if (servletRequest ! = null && MultipartResolutionDelegate.isMultipartRequest(servletRequest)) { Collection<Part> parts = servletRequest.getParts(); LinkedHashMap<String, Part> result = CollectionUtils.newLinkedHashMap(parts.size()); for (Part part : parts) { if (! result.containsKey(part.getName())) { result.put(part.getName(), part); } } return result; } return new LinkedHashMap<>(0); } else { Map<String, String[]> parameterMap = webRequest.getParameterMap(); Map<String, String> result = CollectionUtils.newLinkedHashMap(parameterMap.size()); parameterMap.forEach((key, values) -> { if (values.length > 0) { result.put(key, values[0]); }}); return result; }}}}
  • SupportSparameter: The parameter type is Map and is used@RequestParamAnnotate, and@RequestParamThe parameter parser can be used if the name attribute is not configured in the annotation.
  • Resolveargument: The resolveArgument is divided into two cases: MultipartFile, Part, or other common requests. The first two can handle file uploading, and the third is the common parameter. If it is a normal Map, then directly get the original request parameters into a Map collection and return it.

4. Summary

Several simple and you are in front of the case, there are such as complex PathVariableMethodArgumentResolver and RequestParamMethodArgumentResolver elder brother detailed talk with you later. There is also a question about where these parameter parsers are called, which will be shared in Songo’s upcoming SpringMVC source code parsing series. It’s weekend, so have a good weekend with this simple knowledge