Detailed explanation on parameter analysis of spring MVC method in Controller layer

In spring MVC, the Controller is responsible for handling the requests distributed by the dispatcher servlet. The following article mainly introduces the relevant materials about the parameter resolution of spring MVC's Controller layer methods. Friends who need it can refer to it

catalogue

Custom parameter parser
Realization effect
Implementation and configuration
Spring provides parsers
Some use Tricky
summary

Version used:

spring-boot: 2.1.6.RELEASE

sping: 5.1.8.RELEASE

java: openjdk 11.0.13

 

Custom parameter parser

In order to implement a custom parameter parser, you only need to implement HandlerMethodArgumentResolver and add it to WebMvcConfigurer#addArgumentResolvers.

Realization effect

The following is an example of adding a client type parameter to get the request header. Define the annotation @ ClientTypeMark and enum ClientType to inject the value in the header.

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

  

public enum ClientType {
 
    WEB,
 
    MOBILE,
 
    UNKNOWN
 
    ;
 
}

The final effect is that in the Controller method, it can be implemented as follows.

@RestController
 
@RequestMapping("/client")
 
public class ClientTypeEndpoint {
 
 
 
    @GetMapping("/cur")
 
    public ClientType getCurClientType(@ClientTypeMark ClientType clientType) {
 
        return clientType;
 
    }
 
}

  

Implementation and configuration

Implement the HandlerMethodArgumentResolver as follows, which is used to parse the client type in the header and convert it to ClientType.

package io.github.donespeak.springbootsamples.web.common;
 
import org.apache.commons.lang3.StringUtils;
 
import org.springframework.core.MethodParameter;
 
import org.springframework.web.bind.support.WebDataBinderFactory;
 
import org.springframework.web.context.request.NativeWebRequest;
 
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
 
import org.springframework.web.method.support.ModelAndViewContainer;
 
public class CurClientTypeMethodArgumentResolver implements HandlerMethodArgumentResolver {
 
    private final String[] CLIENT_TYPE_HEADER_NAMES = {"client-type", "CLIENT-TYPE", "Client-Type"};
 
    // Select the parameters to be injected: judge whether the parameters meet the parsing conditions
 
    @Override
 
    public boolean supportsParameter(MethodParameter param) {
 
        return param.hasParameterAnnotation(ClientTypeMark.class)
 
                && ClientType.class.isAssignableFrom(param.getParameterType());
 
    }
 
    // The return value will be injected into the selected parameter
 
    @Override
 
    public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer,
 
            NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
 
        String clientType = null;
 
        for (String clientTypeHeader: CLIENT_TYPE_HEADER_NAMES) {
 
            clientType = nativeWebRequest.getHeader(clientTypeHeader);
 
            if (StringUtils.isNotBlank(clientType)) {
 
                break;
 
            }
 
        }
 
        try {
 
            return StringUtils.isBlank(clientType) ? ClientType.UNKNOWN : ClientType.valueOf(clientType.toUpperCase());
 
        } catch (Exception e) {
 
            return ClientType.UNKNOWN;
        }
    }
}

In order to make the configuration effective, you can add a parser as follows.

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
 
        // Parse parameters of @ CurUserId annotation
 
        argumentResolvers.add(new CurClientTypeMethodArgumentResolver());
 
    }
 
}

  

At this point, the work of the custom parameter parser is completed.

Spring provides parsers

When you complete the above custom parser, you can generally know how Spring implements other parameter parsing in the Controller layer.

The following are some default parameter parsers for Spring Mvc.

package org.springframework.web.servlet.mvc.method.annotation;
 
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
 
        implements BeanFactoryAware, InitializingBean {
 
    ...
 
    /**
 
     * Return the list of argument resolvers to use including built-in resolvers
 
     * and custom resolvers provided via {@link #setCustomArgumentResolvers}.
 
     */
 
    private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
 
        List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();
 
        // Annotation-based argument resolution
 
        // Resolution: @ RequestParam(required = false)
 
        resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
 
        // Resolution: @ RequestParam Map
 
        resolvers.add(new RequestParamMapMethodArgumentResolver());
 
        // Resolution: @ PathVariable
 
        resolvers.add(new PathVariableMethodArgumentResolver());
 
        // Parse: @ PathVariable Map
 
        resolvers.add(new PathVariableMapMethodArgumentResolver());
 
        // Resolution: @ MatrixVariable
 
        resolvers.add(new MatrixVariableMethodArgumentResolver());
 
        // Resolution: @ MatrixVariable Map
 
        resolvers.add(new MatrixVariableMapMethodArgumentResolver());
 
        // Resolution: @ ModelAttribute(required = false)
 
        resolvers.add(new ServletModelAttributeMethodProcessor(false));
 
        // Resolution: @ RequestBody
 
        resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
 
        // Resolution: @ RequestPart
 
        resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));
 
        // Resolution: @ RequestHeader
 
        resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
 
        // Resolution: @ RequestHeader Map
 
        resolvers.add(new RequestHeaderMapMethodArgumentResolver());
 
        // Resolution: @ CookieValue
 
        resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
 
        // Resolution: @ Value
 
        resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
 
        // Resolution: @ SessionAttribute
 
        resolvers.add(new SessionAttributeMethodArgumentResolver());
 
        // Resolution: @ RequestAttribute
 
        resolvers.add(new RequestAttributeMethodArgumentResolver());
 
        // Type-based argument resolution
 
        // Resolution: WebRequest, ServletRequest, MultipartRequest, HttpSession
 
        //      Principal,Locale,TimeZone,java.time.ZoneId,InputStream
 
        //      java.io.Reader,org.springframework.http.HttpMethod
 
        resolvers.add(new ServletRequestMethodArgumentResolver());
 
        // Parsing: ServletResponse, OutputStream, Writer
 
        resolvers.add(new ServletResponseMethodArgumentResolver());
 
        // Resolution: @ HttpEntity, @ RequestEntity
 
        resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
 
        // Parsing: RedirectAttributes
 
        resolvers.add(new RedirectAttributesMethodArgumentResolver());
 
        // Resolution: org springframework. ui. Model, the value is ModelAndViewContainer#getModel
 
        resolvers.add(new ModelMethodProcessor());
 
        // Resolution: Map, with the value of ModelAndViewContainer#getModel
 
        resolvers.add(new MapMethodProcessor());
 
        // Resolution: org springframework. validation. Errors
 
        resolvers.add(new ErrorsMethodArgumentResolver());
 
        // Resolution: org springframework. web. bind. support. SessionStatus
 
        resolvers.add(new SessionStatusMethodArgumentResolver());
 
        // Resolution: UriComponentsBuilder or ServletUriComponentsBuilder
 
        resolvers.add(new UriComponentsBuilderMethodArgumentResolver());
 
        // Custom arguments
 
        if (getCustomArgumentResolvers() != null) {
 
            resolvers.addAll(getCustomArgumentResolvers());
 
        }
 
        // Catch-all
 
        // Resolution: @ RequestParam(required = true)
 
        resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
 
        // Resolution: @ ModelAttribute(required = true)
 
        resolvers.add(new ServletModelAttributeMethodProcessor(true));
 
        return resolvers;
    }
    ...
 
}

  

Some use Tricky

Adjust the order of parsers

In a development using spring security, the defined usercredentials implementation class implements both UserDetails and an Account interface. When in use, you want to achieve the following effects.

public void doSomething(@AuthenticationPrincipal Account accout) {}

  

However, because spring data is used at the same time, the ProxyingHandlerMethodArgumentResolver provided by spring data will inject Account before AuthenticationPrincipalArgumentResolver. ProxyingHandlerMethodArgumentResolver will intercept all non - org The interface defined in the spring framework and try to assign a value. In order to assign values to Account normally, you need to adjust their positions. The implementation is as follows:

 

import java.util.LinkedList;
 
import java.util.List;
 
import org.springframework.beans.BeansException;
 
import org.springframework.beans.factory.config.BeanPostProcessor;
 
import org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver;
 
import org.springframework.stereotype.Component;
 
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
 
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
 
/**
 
 * Move the AuthenticationPrincipalArgumentResolver to the front. If no adjustment is made,
 
 * Because ProxyingHandlerMethodArgumentResolver is in front of AuthenticationPrincipalArgumentResolver,
 
 * As a result, the @ authenticationprincipal account cannot be injected
 
 */
 
public class ArgumentResolverBeanPostProcessor implements BeanPostProcessor {
 
    private static final String BEAN_REQUEST_MAPPING_HANDLER_ADAPTER = "requestMappingHandlerAdapter";
 
    @Override
 
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
 
        if (beanName.equals(BEAN_REQUEST_MAPPING_HANDLER_ADAPTER)) {
 
            RequestMappingHandlerAdapter adapter = (RequestMappingHandlerAdapter)bean;
 
            List<HandlerMethodArgumentResolver> argumentResolvers = adapter.getArgumentResolvers();
 
            LinkedList<HandlerMethodArgumentResolver> resolversAdjusted = new LinkedList<>(argumentResolvers);
 
            argumentResolvers.stream().forEach(r -> {
 
                if (AuthenticationPrincipalArgumentResolver.class.isInstance(r)) {
 
                    resolversAdjusted.addFirst(r);
 
                } else {
 
                    resolversAdjusted.add(r);
 
                }
            });
adapter.setArgumentResolvers(resolversAdjusted);
 
        }
        return bean;
    }
 
}

  

summary

This is the end of the article on parameter resolution of spring MVC methods at the Controller layer

Keywords: Java

Added by steven21 on Sat, 01 Jan 2022 00:29:07 +0200