WebMvcConfigurer.addArgumentResolvers自定义参数处理器不生效的原理与解决方案

实现自定义参数解析器时不生效

今天想用自己定义的参数解析器对参数进行去空处理,结果并没有生效,于是就来研究下其中的原因。

代码

以下是相关实现代码。

WebMvcConfigurer

package com.hzsh.kpi;

import com.hzsh.util.params_trim.ArgumentResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

@org.springframework.context.annotation.Configuration
public class SilasWebConfiguration implements WebMvcConfigurer {
    @Bean
    ArgumentResolver argumentResolver(){
        return  new ArgumentResolver();
    }
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(argumentResolver());
        System.out.println(resolvers);
    }
}

HandlerMethodArgumentResolver

package com.hzsh.util.params_trim;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.springframework.beans.BeanUtils;
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;

import com.hzsh.util.StringUtils;

public class ArgumentResolver implements HandlerMethodArgumentResolver {

	/**
	 * 用于判定是否需要处理该参数分解,返回true为需要,并会去调用下面的方法resolveArgument。
	 * 
	 * @param parameter
	 * @return
	 */
	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		return parameter.hasParameterAnnotation(RequestParamsTrim.class);
	}

	/**
	 * 真正用于处理参数分解的方法,返回的Object就是controller方法上的形参对象。
	 * 
	 * @param parameter
	 * @param mavContainer
	 * @param webRequest
	 * @param binderFactory
	 * @return
	 * @throws Exception
	 */
	@Override
	public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
		// 未经过处理的字段名 music_name
		String sourceTemp;
		// 经过处理的字段名 musicName
		String executeTemp;
		// 传参的值 例如 music_name=asdmsakdlasjd 的value
		String[] values;

		String parameterType = parameter.getParameterType().getTypeName();
		Class clazz = parameter.getParameterType();// 获得参数类型
		if (Map.class.isAssignableFrom(clazz)) {// 判断是否为Map类型
			// 是Map类型,则返回Map结果
			Map<String, String> resultMap = new HashMap<String, String>();
			// 遍历请求参数
			for (Iterator<String> itr = webRequest.getParameterNames(); itr.hasNext();) {
				sourceTemp = itr.next();
				executeTemp = sourceTemp;
				// music_name -> musicName
				executeTemp = StringUtils.underline2Camel(executeTemp);
				// 去赋值
				values = webRequest.getParameterValues(sourceTemp);
				String value = values[0].trim();
				if(value!=null&&!value.equals("")){//不为空则赋值
					resultMap.put(executeTemp, value);
				}
			}
			return resultMap;
		} else {// 否则,返回类对象
				// 生成结果的对象
			Object resultObject = BeanUtils.instantiate(parameter.getParameterType());
			// 得到bean中的方法
			Field[] frr = parameter.getParameterType().getDeclaredFields();
			// 遍历请求参数
			for (Iterator<String> itr = webRequest.getParameterNames(); itr.hasNext();) {
				sourceTemp = itr.next();
				executeTemp = sourceTemp;
				// 下划线转驼峰命名法 music_name -> musicName
				StringUtils.underline2Camel(executeTemp);
				// 去赋值
				for (int i = 0; i < frr.length; i++) {
					frr[i].setAccessible(true);
					if (executeTemp.equals(frr[i].getName())) {
						values = webRequest.getParameterValues(sourceTemp);
						String value = values[0].trim();
						if(value!=null&&!value.equals("")){//不为空则赋值
							frr[i].set(resultObject, value);
						}
					}
				}
			}
			return resultObject;
		}
	}

}

RequestParamsTrim注解

package com.hzsh.util.params_trim;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.PARAMETER})  //作用范围
@Retention(RetentionPolicy.RUNTIME)  //生效时期
@Documented  //文档化
public @interface RequestParamsTrim {

}

Controller

package com.hzsh.kpi.controller;

import java.util.Map;

import org.springframework.web.bind.annotation.RestController;

import com.hzsh.util.params_trim.RequestParamsTrim;

@RestController
public class KpiController {
	
	public Map<String,String> getInfo(@RequestParamsTrim Map<String,String> map){
		System.out.println(map);
		return map;
	}

}

方法栈跟踪

以下是调用addArgumentResolvers方法的源码跟踪方法栈

addArgumentResolvers:18, SilasWebConfiguration (com.hzsh.kpi)

addArgumentResolvers:123, WebMvcConfigurerComposite (org.springframework.web.servlet.config.annotation)

addArgumentResolvers:108, DelegatingWebMvcConfiguration (org.springframework.web.servlet.config.annotation)

getArgumentResolvers:692, WebMvcConfigurationSupport (org.springframework.web.servlet.config.annotation)

requestMappingHandlerAdapter:565, WebMvcConfigurationSupport (org.springframework.web.servlet.config.annotation)

requestMappingHandlerAdapter:467, WebMvcAutoConfiguration$EnableWebMvcConfiguration (org.springframework.boot.autoconfigure.web.servlet)

CGLIB$requestMappingHandlerAdapter$3:-1, WebMvcAutoConfiguration$EnableWebMvcConfiguration$$EnhancerBySpringCGLIB$$29de854a (org.springframework.boot.autoconfigure.web.servlet)

invoke:-1, WebMvcAutoConfiguration$EnableWebMvcConfiguration$$EnhancerBySpringCGLIB$$29de854a$$FastClassBySpringCGLIB$$d45ad650 (org.springframework.boot.autoconfigure.web.servlet)

invokeSuper:228, MethodProxy (org.springframework.cglib.proxy)

intercept:365, ConfigurationClassEnhancer$BeanMethodInterceptor (org.springframework.context.annotation)

requestMappingHandlerAdapter:-1, WebMvcAutoConfiguration$EnableWebMvcConfiguration$$EnhancerBySpringCGLIB$$29de854a (org.springframework.boot.autoconfigure.web.servlet)

invoke0:-1, NativeMethodAccessorImpl (sun.reflect)

invoke:62, NativeMethodAccessorImpl (sun.reflect)

invoke:43, DelegatingMethodAccessorImpl (sun.reflect)

invoke:498, Method (java.lang.reflect)

instantiate:154, SimpleInstantiationStrategy (org.springframework.beans.factory.support)

instantiateUsingFactoryMethod:583, ConstructorResolver (org.springframework.beans.factory.support)

instantiateUsingFactoryMethod:1246, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)

createBeanInstance:1096, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)

doCreateBean:535, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)

createBean:495, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)

lambda$doGetBean$0:317, AbstractBeanFactory (org.springframework.beans.factory.support)

getObject:-1, 1761217448 (org.springframework.beans.factory.support.AbstractBeanFactory$$Lambda$112)

getSingleton:222, DefaultSingletonBeanRegistry (org.springframework.beans.factory.support)

doGetBean:315, AbstractBeanFactory (org.springframework.beans.factory.support)

getBean:199, AbstractBeanFactory (org.springframework.beans.factory.support)

preInstantiateSingletons:759, DefaultListableBeanFactory (org.springframework.beans.factory.support)

finishBeanFactoryInitialization:867, AbstractApplicationContext (org.springframework.context.support)

refresh:548, AbstractApplicationContext (org.springframework.context.support)

refresh:142, ServletWebServerApplicationContext (org.springframework.boot.web.servlet.context)

refresh:754, SpringApplication (org.springframework.boot)

refreshContext:386, SpringApplication (org.springframework.boot)

run:307, SpringApplication (org.springframework.boot)

run:1242, SpringApplication (org.springframework.boot)

run:1230, SpringApplication (org.springframework.boot)

main:17, Application (com.hzsh.kpi)

通过代码跟踪,可知,创建RequestMappingHadnlerAdapter时会调用所有的WebMvcConfigurer的实现类的addArgumentResolvers方法,将对应的argumentResolver添加
RequestMappingHadnlerAdapter的customArgumentResolvers属性中。

getArgumentResolver获取要用到的参数处理器

org.springframework.web.method.support.HandlerMethodArgumentResolverComposite#getArgumentResolver

getArgumentResolver:134, HandlerMethodArgumentResolverComposite (org.springframework.web.method.support)

supportsParameter:108, HandlerMethodArgumentResolverComposite (org.springframework.web.method.support)

getMethodArgumentValues:159, InvocableHandlerMethod (org.springframework.web.method.support)

invokeForRequest:131, InvocableHandlerMethod (org.springframework.web.method.support)

invokeAndHandle:102, ServletInvocableHandlerMethod (org.springframework.web.servlet.mvc.method.annotation)

invokeHandlerMethod:891, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation)

handleInternal:797, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation)

handle:87, AbstractHandlerMethodAdapter (org.springframework.web.servlet.mvc.method)

doDispatch:991, DispatcherServlet (org.springframework.web.servlet)

doService:925, DispatcherServlet (org.springframework.web.servlet)

processRequest:981, FrameworkServlet (org.springframework.web.servlet)

doPost:884, FrameworkServlet (org.springframework.web.servlet)

service:661, HttpServlet (javax.servlet.http)

service:858, FrameworkServlet (org.springframework.web.servlet)

service:742, HttpServlet (javax.servlet.http)

internalDoFilter:231, ApplicationFilterChain (org.apache.catalina.core)

doFilter:166, ApplicationFilterChain (org.apache.catalina.core)

doFilter:52, WsFilter (org.apache.tomcat.websocket.server)

internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)

doFilter:166, ApplicationFilterChain (org.apache.catalina.core)

doFilterInternal:99, RequestContextFilter (org.springframework.web.filter)

doFilter:107, OncePerRequestFilter (org.springframework.web.filter)

internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)

doFilter:166, ApplicationFilterChain (org.apache.catalina.core)

doFilterInternal:109, HttpPutFormContentFilter (org.springframework.web.filter)

doFilter:107, OncePerRequestFilter (org.springframework.web.filter)

internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)

doFilter:166, ApplicationFilterChain (org.apache.catalina.core)

doFilterInternal:93, HiddenHttpMethodFilter (org.springframework.web.filter)

doFilter:107, OncePerRequestFilter (org.springframework.web.filter)

internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)

doFilter:166, ApplicationFilterChain (org.apache.catalina.core)

doFilterInternal:200, CharacterEncodingFilter (org.springframework.web.filter)

doFilter:107, OncePerRequestFilter (org.springframework.web.filter)

internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)

doFilter:166, ApplicationFilterChain (org.apache.catalina.core)

invoke:198, StandardWrapperValve (org.apache.catalina.core)

invoke:96, StandardContextValve (org.apache.catalina.core)

invoke:493, AuthenticatorBase (org.apache.catalina.authenticator)

invoke:140, StandardHostValve (org.apache.catalina.core)

invoke:81, ErrorReportValve (org.apache.catalina.valves)

invoke:87, StandardEngineValve (org.apache.catalina.core)

service:342, CoyoteAdapter (org.apache.catalina.connector)

service:800, Http11Processor (org.apache.coyote.http11)

process:66, AbstractProcessorLight (org.apache.coyote)

process:806, AbstractProtocol$ConnectionHandler (org.apache.coyote)

doRun:1498, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)

run:49, SocketProcessorBase (org.apache.tomcat.util.net)

runWorker:1149, ThreadPoolExecutor (java.util.concurrent)

run:624, ThreadPoolExecutor$Worker (java.util.concurrent)

run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)

run:748, Thread (java.lang)

在第一次访问时会遍历所有argumentResolver

在这里插入图片描述
在这里插入图片描述

把对应argumentResolver#supportsParameter(此方法需要自己实现)返回值为true的放入argumentResolverCache缓存中,并且break打断循环。

通过上面可以知道,所有的参数处理器中只会调用其中的一个处理器,那么就是意味着排序靠前的才会被使用。
通过调试可知argumentResolvers里面共有31个:

this.argumentResolvers = {LinkedList@8577}  size = 31
 0 = {ProxyingHandlerMethodArgumentResolver@8580} 
 1 = {RequestParamMethodArgumentResolver@8582} 
 2 = {RequestParamMapMethodArgumentResolver@8584} 
 3 = {PathVariableMethodArgumentResolver@8585} 
 4 = {PathVariableMapMethodArgumentResolver@8586} 
 5 = {MatrixVariableMethodArgumentResolver@8587} 
 6 = {MatrixVariableMapMethodArgumentResolver@8588} 
 7 = {ServletModelAttributeMethodProcessor@8589} 
 8 = {RequestResponseBodyMethodProcessor@8590} 
 9 = {RequestPartMethodArgumentResolver@8591} 
 10 = {RequestHeaderMethodArgumentResolver@8592} 
 11 = {RequestHeaderMapMethodArgumentResolver@8593} 
 12 = {ServletCookieValueMethodArgumentResolver@8594} 
 13 = {ExpressionValueMethodArgumentResolver@8595} 
 14 = {SessionAttributeMethodArgumentResolver@8596} 
 15 = {RequestAttributeMethodArgumentResolver@8597} 
 16 = {ServletRequestMethodArgumentResolver@8598} 
 17 = {ServletResponseMethodArgumentResolver@8599} 
 18 = {HttpEntityMethodProcessor@8600} 
 19 = {RedirectAttributesMethodArgumentResolver@8601} 
 20 = {ModelMethodProcessor@8602} 
 21 = {MapMethodProcessor@8603} 
 22 = {ErrorsMethodArgumentResolver@8604} 
 23 = {SessionStatusMethodArgumentResolver@8605} 
 24 = {UriComponentsBuilderMethodArgumentResolver@8606} 
 25 = {SilasArgumentResolver@8607} 
 26 = {SortHandlerMethodArgumentResolver@8610} 
 27 = {PageableHandlerMethodArgumentResolver@8611} 
 28 = {ProxyingHandlerMethodArgumentResolver@8612} 
 29 = {RequestParamMethodArgumentResolver@8613} 
 30 = {ServletModelAttributeMethodProcessor@8666} 

MapMethodProcessor优先于自定义

  • 因为我自定义的参数解析析下标为25,并且是作用在参数类型为Map:@RequestParamsTrim Map<String,String> map
  • 而 下标为21的参数解析器:21 = {MapMethodProcessor@8603}
    同样是作用在Map上面的,所以MapMethodProcessor这个参数解析器会优先被使用。

在这里插入图片描述

解决办法

扩展RequestMappingHandlerAdapter

从上面分析可以知道,要实现使用自己的参数解析器,其中一个方法就是想办法让自定义的解析器排序在前面就行了。那么如果实现参数解析器的排序呢?
在这里插入图片描述
那么关键是RequestMappingHandlerAdapter是在什么时候将customArgumentRsolvers加入到argumentResolvers里面的呢,让我们再次跟踪下RequestMappingHandlerAdapter实例化bean过程的源码。
从类图可知,其实现了InitializingBean接口
在这里插入图片描述
实现afterPropertiesSet方法
在这里插入图片描述
将断点打在此方法上,发现直到getDefaultArgumentResolvers()方法后argumentResolvers里面放入了30个argumentResolver,那么由此可知,关键代码就在
getDefaultArgumentResolvers方法上,以下是方法源码。


	private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
		List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();

		// Annotation-based argument resolution
		resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
		resolvers.add(new RequestParamMapMethodArgumentResolver());
		resolvers.add(new PathVariableMethodArgumentResolver());
		resolvers.add(new PathVariableMapMethodArgumentResolver());
		resolvers.add(new MatrixVariableMethodArgumentResolver());
		resolvers.add(new MatrixVariableMapMethodArgumentResolver());
		resolvers.add(new ServletModelAttributeMethodProcessor(false));
		resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
		resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));
		resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
		resolvers.add(new RequestHeaderMapMethodArgumentResolver());
		resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
		resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
		resolvers.add(new SessionAttributeMethodArgumentResolver());
		resolvers.add(new RequestAttributeMethodArgumentResolver());

		// Type-based argument resolution
		resolvers.add(new ServletRequestMethodArgumentResolver());
		resolvers.add(new ServletResponseMethodArgumentResolver());
		resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
		resolvers.add(new RedirectAttributesMethodArgumentResolver());
		resolvers.add(new ModelMethodProcessor());
		resolvers.add(new MapMethodProcessor());
		resolvers.add(new ErrorsMethodArgumentResolver());
		resolvers.add(new SessionStatusMethodArgumentResolver());
		resolvers.add(new UriComponentsBuilderMethodArgumentResolver());

		// Custom arguments
		if (getCustomArgumentResolvers() != null) {
			resolvers.addAll(getCustomArgumentResolvers());
		}

		// Catch-all
		resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
		resolvers.add(new ServletModelAttributeMethodProcessor(true));

		return resolvers;
	}

但是通过源码发现其顺序直接写死的,其默认的一此处理器是在自定义的之前。所以证明RequestMappingHandlerAdapter是没有预留按需调整顺序的接口了。那只能从IOC容器预留的扩展接口来对RequestMappingHandlerAdapter实例进行修改了。

BeanPostProcessor进行Bean的后置处理

通过spring Bean初始化过程可知,在完成初始化(调用完invokeInitMethods)愿意付出后,会调用BeanPostProcessor后置处理的后置方法
在这里插入图片描述
在这里插入图片描述

自定义BeanPostProcessor

不需要借助mvc框架的WebMvcConfigurer接口,直接使用IOC容器的扩展接口BeanPostProcessor,实现BeanPostProcessor的postProcessAfterInitialization方法,直接将自定义的处理器添加到到RequestMappingHandlerAdapter的argumentResolvers属性里面即可轻易实现.

以下是实现代码

import com.hzsh.util.params_trim.SilasArgumentResolver;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;

import java.util.ArrayList;
import java.util.List;

public class ResolverBeanPostProcessor implements BeanPostProcessor {

    @Autowired
    SilasArgumentResolver silasArgumentResolver;

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("-------------------------------"+beanName);
        if(beanName.equals("requestMappingHandlerAdapter")){
            //requestMappingHandlerAdapter进行修改
            RequestMappingHandlerAdapter adapter = (RequestMappingHandlerAdapter)bean;
            List<HandlerMethodArgumentResolver> argumentResolvers = adapter.getArgumentResolvers();

            //添加自定义参数处理器
            argumentResolvers = addArgumentResolvers(argumentResolvers);

            adapter.setArgumentResolvers(argumentResolvers);
        }
        return bean;
    }

    private List<HandlerMethodArgumentResolver> addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();

        //将自定的添加到最前面
        resolvers.add(silasArgumentResolver);
        //将原本的添加后面
        resolvers.addAll(argumentResolvers);
        return  resolvers;
    }
}

相关学习路线

JAVA资深架构师成长路线->开源框架解读->Spring框架源码解读

  • 22
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
在 Spring Boot 中,我们可以通过实现 Converter 接口来自定义类型转换器,但是有时候我们发现自定义的转换器并没有生效。这可能是因为 Spring Boot 默认使用的是 GenericConversionService,而不是我们自定义的 ConversionService。 解决这个问题的方法有两种: 1. 在自定义转换器上添加 @Component 注解,将其注册到 Spring 容器中,并在需要使用该转换器的地方使用 @Autowired 注入。这样就可以确保我们自定义的转换器会被使用。 2. 自定义一个 ConversionService,将其注册到 Spring 容器中,并在需要使用该转换器的地方使用 @Qualifier 注解指定使用我们自定义的转换器。这种方法需要手动配置 ConversionService,但是可以更灵活地控制转换器的使用。 下面是第二种方法的示例代码: ```java @Configuration public class ConversionConfig { @Bean public ConversionService conversionService() { DefaultConversionService conversionService = new DefaultConversionService(); // 注册自定义转换器 conversionService.addConverter(new MyConverter()); return conversionService; } } ``` 在需要使用自定义转换器的地方,可以使用 @Qualifier 注解指定使用我们自定义的 ConversionService,例如: ```java @RestController public class DemoController { @Autowired @Qualifier("conversionService") private ConversionService conversionService; @GetMapping("/test") public String test(@RequestParam("myParam") MyParam myParam) { // 使用自定义转换器将字符串转换为自定义类型 MyParam // ... return "success"; } } ``` 希望能帮助到你!
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值