springmvc application-custom parameter parser

This article mainly learns the extension points of springmvc through a custom parameter parser. The
main operation that I want to accomplish is:
through a custom annotation, the json format string in the input parameter is converted to the specified type through the custom parameter parser list array

application

Custom parameter parser

public class MyHandlerMethodArgumentResolverTest implements HandlerMethodArgumentResolver {
    
    
    @Override
    public boolean supportsParameter(MethodParameter methodParameter) {
    
    
        Json ann = (Json) methodParameter.getParameterAnnotation(Json.class);
        System.out.println(methodParameter);
        return ann != null;
    }

    @Override
    public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer,
            NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
    
    
        Json ann = (Json) methodParameter.getParameterAnnotation(Json.class);
        String param1 = ann.value();
        System.out.println(param1);
        System.out.println(ann.array());

        String parameter = nativeWebRequest.getParameter(param1);
        return JSON.parseArray(parameter, ann.array());
    }
}

Add the custom parameter parser to the spring container

@Configuration
@ComponentScan("com.springmvc")
@EnableWebMvc
public class AppConfig implements WebMvcConfigurer {
    
    

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
    
    
        resolvers.add(new MyHandlerMethodArgumentResolverTest());
    }

    // @Override
    // public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    
    
    //     converters.add(new MappingJackson2HttpMessageConverter());
    //     converters.add(new StringHttpMessageConverter());
    // }
}

Custom annotation Json

@Target({
    
    ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Json {
    
    
    String value() default "";

    Class<?> array();
}

controller interface


@RequestMapping("/testMyAnno")
public String testMyAnno(@Json(value = "testList", array = ImageInfo.class) List<ImageInfo> testList,
        @RequestParam("haha") String userName) {
    
    
    System.out.println("list信息是:" + testList);
    System.out.println("userName是:" + userName);
    UserInfo userInfo = new UserInfo(userName, testList);
    System.out.println(userInfo.toString());
    return "success";
}

Request url:

http://127.0.0.1:8080/testMyAnno.do?testList=[{
    
    "imageType":3,"imageUrl":"http://f.souche.com/40accdeff31be8bf84fbb69e632e37d9.png"},{
    
    "imageType":4,"imageUrl":"http://f.souche.com/40accdeff31be8bf84fbb69e632e37d9.png"},{
    
    "imageType":6,"imageUrl":"http://f.souche.com/40accdeff31be8bf84fbb69e632e37d9.png"},{
    
    "imageType":1,"imageUrl":"http://f.souche.com/40accdeff31be8bf84fbb69e632e37d9.png"},{
    
    "imageType":2,"imageUrl":"http://f.souche.com/40accdeff31be8bf84fbb69e632e37d9.png"}]&haha=yesdy

Need to explain, the effect I want to achieve here is:
for the parameters annotated with @Json, directly parse the String string into an array of the Class type specified in the @Json annotation

Source code

Add custom parameter parser to spring

First of all, how to give a custom parameter parser to spring.
For springmvc projects, spring will initialize the requestMappingHandlerAdapter object, which is initialized by @Bean in the following class

org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport

@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
    
    
}

In this method, there is a line of code, which is related to adding the custom parameter parser to the container, which is

adapter.setCustomArgumentResolvers(this.getArgumentResolvers());

Here, getArgumentResolvers will be called to obtain all the parameter resolvers. In the processing logic of this method, all addArgumentResolvers() methods of WebMvconfigurer will be called. Therefore, we only need to override addArgumentResolvers() in the implementation class that inherits WebMvcConfigurer. Then add the custom parameter parser to the resolvers

The WebMvcConfigurationSupport class is when we add the @EnableWebMvc annotation, it will inject a full configuration class that inherits this class through @Import

This method will not be explained too much, debug look at the source code, it is clear at a glance, there is no complicated logic

How to use a custom parameter parser

We customize the parameter parser and put it in the container. Then all requests will be executed to org.springframework.web.servlet.DispatcherServlet#doDispatch. In this method, the handlerMapping and handlerAdapter used by the current method are resolved. After that, the target method will be called.
Before calling the target method, the input parameters will be parsed one by one. In
this article, we only care about the logic of parameter parsing, so the call chain is like this

org.springframework.web.servlet.DispatcherServlet#doDispatch
  org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter#handle
    org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter#handleInternal
      org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#invokeHandlerMethod
        org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle
          org.springframework.web.method.support.InvocableHandlerMethod#invokeForRequest
            org.springframework.web.method.support.InvocableHandlerMethod#getMethodArgumentValues

Determine which parameter parser to use

The core of parameter parsing is in these lines of code. The outer layer of this code block is a for loop, which will traverse all args.

argumentResolvers.supportsParameter(parameter): In this line of code, all the parameter parsers will be taken to judge the current parameter args[i] to see which parameter parser can parse the parameter

/**
 * 遍历所有的参数解析器,看哪个解析器可以解析,如果参数解析器返回true,就表示可以解析
 * 如果我们要扩展参数解析器,就需要看下这里的逻辑
 * 需要学习下这里的argumentResolvers是在哪里赋值的
 */
if (this.argumentResolvers.supportsParameter(parameter)) {
    
    
  try {
    
    
    /**
     * 这里就是用parameter对应的解析器去解析该参数
     */
    args[i] = this.argumentResolvers.resolveArgument(
        parameter, mavContainer, request, this.dataBinderFactory);
    continue;
  }
  catch (Exception ex) {
    
    
    if (logger.isDebugEnabled()) {
    
    
      logger.debug(getArgumentResolutionErrorMessage("Failed to resolve", i), ex);
    }
    throw ex;
  }
}
/**
 * 遍历所有的参数处理器,找到处理该parameter的处理器,然后存入到map集合中,
 * 第二次获取处理该参数的处理器时,就无须再次遍历所有的support方法,直接从map缓存中获取即可
 * @param parameter
 * @return
 */
@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
    
    
  HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
  if (result == null) {
    
    
    for (HandlerMethodArgumentResolver methodArgumentResolver : this.argumentResolvers) {
    
    
      if (logger.isTraceEnabled()) {
    
    
        logger.trace("Testing if argument resolver [" + methodArgumentResolver + "] supports [" +
            parameter.getGenericParameterType() + "]");
      }
      /**
       * 核心方法就是这里,上面会遍历所有的参数解析器,依次去调用对应的supports方法,判断是否可以处理parameter
       * 如果可以,就返回true,然后会把对应的methodArgumentResolver和对应的parameter关联起来
       */
      if (methodArgumentResolver.supportsParameter(parameter)) {
    
    
        result = methodArgumentResolver;
        this.argumentResolverCache.put(parameter, result);
        break;
      }
    }
  }
  return result;
}

This method is one of the most core methods: to determine whether the parameter parser can parse the current parameters;
here will get all the parameter parsers, including our custom, for example: our custom parameter parser A supports parsing args[ i] After that, we will put A into the argumentResolverCache cache.
The idea here is: we only need to provide a parameter parser, and specify which parameters our custom parameter parser should parse, and spring will get all of them. The parameter parser is to match the parameters to be parsed. This design pattern should be a template design pattern. In short, this is the idea

Use the corresponding parameter parser to parse the parameters

The next core method is to analyze the logic

public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
    NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
    
    
  /**
   * 这个方法是实际解析参数的逻辑,首先会先获取到参数对应的解析器
   */
  HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
  if (resolver == null) {
    
    
    throw new IllegalArgumentException("Unknown parameter type [" + parameter.getParameterType().getName() + "]");
  }
  /**
   * 这里调用的就是处理的方法
   */
  return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}

As you can see, spring will obtain the parameter parser used by this parameter from the map collection according to the parameter, and then resolve
it. The resolveArgument here will call the parsing method in the custom parameter parser.

In short: the idea is like this
1. Spring provides an interface for the parameter parser. If we want to customize it, we will implement the interface
2. Then put the custom parameter parser in the spring container
3. When the method is called , Spring will take spring's own + our custom parameter parser to traverse
4. After judging which parser can be parsed by the parameter, it will pass the parameter to the corresponding parameter parser for analysis

Guess you like

Origin blog.csdn.net/CPLASF_/article/details/110234959