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