springmvc应用-自定义参数解析器

本文主要是通过自定义参数解析器学习springmvc的扩展点
主要想完成的操作是:
通过自定义一个注解,将入参中的 json格式的字符串,通过自定义的参数解析器转换为指定类型的list数组

应用

自定义参数解析器

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

将自定义参数解析器加入到spring容器中

@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());
    // }
}

自定义注解Json

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

    Class<?> array();
}

controller接口


@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";
}

请求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

需要解释一下,我这里想要实现的效果就是:
对于加了@Json注解的参数,直接将其String字符串解析为@Json注解中指定的Class类型的数组

源码

将自定义参数解析器加入到spring中

首先来说如何将一个自定义的参数解析器交给spring
对于springmvc项目,spring会初始化requestMappingHandlerAdapter对象,就是在下面这个类中,通过@Bean初始化的

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

@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
    
    
}

在这个方法中,有一行代码,和我们将自定义参数解析器加入到容器中有关系,就是

adapter.setCustomArgumentResolvers(this.getArgumentResolvers());

这里会调用getArgumentResolvers获取到所有的参数解析器,在这个方法的处理逻辑中,会调用所有WebMvconfigurer的addArgumentResolvers()方法,所以,我们只需要在继承了WebMvcConfigurer的实现类中,覆写addArgumentResolvers(),然后将自定义的参数解析器,添加到resolvers 中即可

WebMvcConfigurationSupport这个类是在我们添加@EnableWebMvc注解的时候,是会通过@Import注入一个继承了该类的全配置类

这个方法就不做过多的解释了,debug看下源码,就一目了然了,没有什么复杂的逻辑

如何使用自定义参数解析器

我们自定义了参数解析器,并且放入到了容器中,那所有的请求,都会执行到org.springframework.web.servlet.DispatcherServlet#doDispatch,在这个方法中,解析到当前方法所使用的handlerMapping和handlerAdapter之后,就会开始调用目标方法
在调用目标方法之前,会对入参一一进行解析
我们这篇文章,只关心参数解析的逻辑,所以调用链是这样的

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

判断使用哪个参数解析器解析

对参数的解析,核心的就是在这几行代码中,在这个代码块外层是一个for循环,会遍历所有的args

argumentResolvers.supportsParameter(parameter):这行代码中,会拿着所有的参数解析器,去对当前参数args[i],进行判断,看哪个参数解析器可以解析该参数

/**
 * 遍历所有的参数解析器,看哪个解析器可以解析,如果参数解析器返回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;
}

这个方法就是最为核心的方法之一:判断参数解析器是否可以解析当前参数;
这里会获取到所有的参数解析器,包括我们自定义的,举例:我们自定义的参数解析器A支持解析args[i]之后,那就会把A放入到argumentResolverCache这个缓存中
这里的思想就是:我们只需要提供参数解析器,并且指定我们自定义的参数解析器要解析哪种参数,spring自己会获取到所有的参数解析器,去和要解析的参数进行匹配,这种设计模式应该是模板设计模式,总之思想就是这样的

使用对应的参数解析器解析参数

下一个比较核心的方法就是去解析的逻辑

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

可以看到,spring会从map集合中根据parameter,获取这个参数所使用的参数解析器,然后去解析
这里的resolveArgument就会去调用到自定义参数解析器中的解析方法

总之:思想就是这样的
1、spring提供了参数解析器的接口,如果我们要自定义,就实现该接口
2、然后把自定义的参数解析器放到spring容器中
3、在方法被调用的时候,spring会拿着spring自己的 + 我们自定义的参数解析器去遍历
4、判断到参数可以被哪个解析器解析之后,就会把该参数交给对应的参数解析器去解析

猜你喜欢

转载自blog.csdn.net/CPLASF_/article/details/110234959