文章目录
实现自定义参数解析器时不生效
今天想用自己定义的参数解析器对参数进行去空处理,结果并没有生效,于是就来研究下其中的原因。
代码
以下是相关实现代码。
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;
}
}