本文主要是通过自定义参数解析器学习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、判断到参数可以被哪个解析器解析之后,就会把该参数交给对应的参数解析器去解析