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

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

String javaboy = request.getParameter("name ");
Copy the code

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

String javaboy = request.getParameter("name ");
Copy the code

If it is JSON, we get the input stream as follows, parse it into A JSON string, and then convert it into an object using JSON tools:

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

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

String javaboy = request.getHeader("name");
Copy the code

If you’re using a Jsp/Servlet stack, there are no other ways to get parameters.

@requestParam, @requestBody, @requesTheader, @pathVariable, Parameters can be in the form of key/value or JSON. However, no matter how rich, there are no more low-level ways to get parameters than the above.

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

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

We all know that the name parameter is extracted from HttpServletRequest, but how did it get extracted? That’s what Songo wants to talk about today.

1. Customize the parameter parser

To understand this problem, let’s first customize 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;

}
Copy the code

There are only two methods in this interface:

  • SupportsParameter: This method indicates whether to enable the parameter parser, returning true to enable it, and false to disable it.
  • ResolveArgument: this is the process of retrieving parameters from the request. 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 quite convenient to get the current login user name in Spring Security, but this is just for the sake of this case, no problem) :

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

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

To do this, it’s easy. First we’ll create a custom @currentUsername annotation like this:

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

There is nothing to explain about this note.

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();
        returnuser.getUsername(); }}Copy the code
  • SupportsParameter: If the parameter type is String and is specified on the parameter@CurrentUserNameAnnotation, the parameter parser is used.
  • ResolveArgument: The return value of this method is the value of the argument. The current login user name is retrieved from 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(newCurrentUserNameHandlerMethodArgumentResolver()); }}Copy the code

At this point, the configuration is complete.

Next, start the project. Once the user has logged in successfully, access the/Hello interface and see that data is returned to the current logged in user.

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 use Spring Security in our project, we can get the current logged-in user information as follows:

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

That is, add the Principal type parameter to the parameters of the current interface, which describes the current login user information. If you are not familiar with Spring Security, you can reply to ss at the background of the public account.

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);
		}

		returnprincipal; }}Copy the code
  • SupportsParameter: This method checks whether the parameter type is Principal. If the parameter type is Principal, it is supported.
  • ResolveArgument: The logic of this method is simple: first get the original request and return the Principal object from the request.

Isn’t it very simple? With this, we can load the current login user information at any time.

3.RequestParamMapMethodArgumentResolver

Songo gives you another example:

@RestController
public class HelloController {
    @PostMapping("/hello")
    public void hello(@RequestParam MultiValueMap map) throws IOException {
        / / to omit...}}Copy the code

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())) {
			// MultiValueMapClass<? > 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); }});returnresult; }}else {
			// Regular MapClass<? > 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]); }});returnresult; }}}}Copy the code
  • SupportsParameter: The parameter type is Map and is used@RequestParamNotes, 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, and other common requests. The first two cases can handle file upload, and the third one is a common argument. If it is a normal Map, you can simply retrieve the original request parameters and return them in a Map set.

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 Songgo’s recent SpringMVC source code parsing series. Ok, today is the weekend, and I hope you have a nice weekend with this simple knowledge