一、背景
在实际开发中,某些场景下要求开发者对前台传入的参数进行个性化解析、校验、或者获取header中信息等等,这时候我们就需要利用自定义参数解析器来满足这些需求
二、DispatcherServlet运行流程
DispatcherServlet 的结构如下,可以看出这个本质上还是个servlet
在实际的调用逻辑中,前端请求过来之后执行 servlet 的 service() 方法,方法核心调用 DispatcherServlet 的 doservice() 方法,其中最核心的是 dodispatch() 方法(前面的流程可自行查看源码)
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
// 获取处理请求的handler
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
// 获取对应hander的处理器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
// 真正的处理请求
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
这其中 getHandler() 方法遍历所有 Handlermapping 类,如果没找到,则执行 noHandlerFound() 方法报NohandlerFoundException 就是平时遇到的404,如果查到了就会调用 getHanderAdapter() 方法来查询能够处理 HandlerAdapter
下面再来看下 DispatcherServlet 的初始化(直接百度的图,没看到出处,有问题私信我哈~)
/**
* Initialize the strategy objects that this servlet uses.
* <p>May be overridden in subclasses in order to initialize further strategy objects.
*/
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
这里面的 initHandlerAdapter(context) 就是初始化 HanderAdapter 的过程。
三、参数解析器运作流程
上面提到了一个请求来了之后springmvc的大致运转过程,下面来看下正常的参数解析器是怎么运作的。
@RequestParam 和 @RequestBody 是开发者接触较多的注解了,为什么这些注解能够对get/post请求发送的参数进行解析呢?根本原因在于 HandlerMethodArgumentResolver,上面我们看到请求来了之后寻找对应的 handleradapter 进行处理,那这些handleradapter 从哪来,就是上面的 initHanderAdapter 方法了
/**
* Initialize the HandlerAdapters used by this class.
* <p>If no HandlerAdapter beans are defined in the BeanFactory for this namespace,
* we default to SimpleControllerHandlerAdapter.
*/
private void initHandlerAdapters(ApplicationContext context) {
this.handlerAdapters = null;
if (this.detectAllHandlerAdapters) {
// Find all HandlerAdapters in the ApplicationContext, including ancestor contexts.
Map<String, HandlerAdapter> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerAdapters = new ArrayList<HandlerAdapter>(matchingBeans.values());
// We keep HandlerAdapters in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerAdapters);
}
}
else {
try {
HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
this.handlerAdapters = Collections.singletonList(ha);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerAdapter later.
}
}
// Ensure we have at least some HandlerAdapters, by registering
// default HandlerAdapters if no other adapters are found.
if (this.handlerAdapters == null) {
this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
if (logger.isDebugEnabled()) {
logger.debug("No HandlerAdapters found in servlet '" + getServletName() + "': using default");
}
}
}
一般情况下,我们的请求的 HandlerAdapter 是 RequestMappingHandlerAdapter (感兴趣可以打断点验证下)
这三个私有变量就是解析器相关的,然后重点看下 HanderMethodArgumentResolver
/*
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.method.support;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
/**
* Strategy interface for resolving method parameters into argument values in
* the context of a given request.
*
* @author Arjen Poutsma
* @since 3.1
* @see HandlerMethodReturnValueHandler
*/
public interface HandlerMethodArgumentResolver {
/**
* Whether the given {@linkplain MethodParameter method parameter} is
* supported by this resolver.
* @param parameter the method parameter to check
* @return {@code true} if this resolver supports the supplied parameter;
* {@code false} otherwise
*/
boolean supportsParameter(MethodParameter parameter);
/**
* Resolves a method parameter into an argument value from a given request.
* A {@link ModelAndViewContainer} provides access to the model for the
* request. A {@link WebDataBinderFactory} provides a way to create
* a {@link WebDataBinder} instance when needed for data binding and
* type conversion purposes.
* @param parameter the method parameter to resolve. This parameter must
* have previously been passed to {@link #supportsParameter} which must
* have returned {@code true}.
* @param mavContainer the ModelAndViewContainer for the current request
* @param webRequest the current request
* @param binderFactory a factory for creating {@link WebDataBinder} instances
* @return the resolved argument value, or {@code null}
* @throws Exception in case of errors with the preparation of argument values
*/
Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception;
}
就两个方法,supportsParameter 判断那个方法参数需要解析,resolveArgument 进行具体的解析操作
四、自定义参数解析器
RequestParamMapMethodArgumentResolver 源码
/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.method.annotation;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.core.MethodParameter;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestParam;
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;
/**
* Resolves {@link Map} method arguments annotated with an @{@link RequestParam}
* where the annotation does not specify a request parameter name.
* See {@link RequestParamMethodArgumentResolver} for resolving {@link Map}
* method arguments with a request parameter name.
*
* <p>The created {@link Map} contains all request parameter name/value pairs.
* If the method parameter type is {@link MultiValueMap} instead, the created
* map contains all request parameters and all there values for cases where
* request parameters have multiple values.
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @since 3.1
* @see RequestParamMethodArgumentResolver
*/
public class RequestParamMapMethodArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
if (requestParam != null) {
if (Map.class.isAssignableFrom(parameter.getParameterType())) {
return !StringUtils.hasText(requestParam.name());
}
}
return false;
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
Class<?> paramType = parameter.getParameterType();
Map<String, String[]> parameterMap = webRequest.getParameterMap();
if (MultiValueMap.class.isAssignableFrom(paramType)) {
MultiValueMap<String, String> result = new LinkedMultiValueMap<String, String>(parameterMap.size());
for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
for (String value : entry.getValue()) {
result.add(entry.getKey(), value);
}
}
return result;
}
else {
Map<String, String> result = new LinkedHashMap<String, String>(parameterMap.size());
for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
if (entry.getValue().length > 0) {
result.put(entry.getKey(), entry.getValue()[0]);
}
}
return result;
}
}
}
可以看出参数解析分几步
1、supportsParameter 限定参数解析器的范围(标注@RequestParam的才会)
2、resolveArgumentr 进行对应的解析操作
3、initHanderAdapter 参数解析器初始化,这样在遍历的时候才会有我们的参数解析器
下面我们来自定义参数解析器
1、自定义注解
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestParamNew {
}
2、实现 HandlerMethodArgumentResolver 接口
public class RequestParamNewMethodArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
// 注解@RequestParamNew 会被下面的解析器处理
return parameter.hasParameterAnnotation(RequestParamNew.class);
// 方法参数是 TestLogin 或者 它的子类的对象 会被下面的解析器处理
// return TestLogin.class.isAssignableFrom(parameter.getParameterType());
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
// TODO 实际的解析处理,博主这里就简单的返回了个新的对象
TestRequest testRequest = new TestRequest();
testRequest.setNickName("mockNickNameWithResolver");
testRequest.setRemark("mockRemarkWithResolver");
return testRequest;
}
}
3、注册自定义解析器
@Configuration
public class MyWebConfig extends WebMvcConfigurationSupport {
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new RequestParamNewMethodArgumentResolver());
super.addArgumentResolvers(argumentResolvers);
}
}
在实际运行过程中,感兴趣可以在 org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#getArgumentResolvers 这里打个断点看下,新的解析器注册上去了,这时候spring还在初始化中
在 org.springframework.web.servlet.DispatcherServlet#doDispatch 这两个方法打下断点,可以看到实际处理的mapperHandler是哪个,具体的HandlerAdapter是哪个,然后在ha的值里面可以看到
其他总结
- 自定义注解:http://www.voidcn.com/article/p-wwlcfeqi-bru.html
-
isAssignableFrom() :是从类继承的角度去判断,是判断是否为某个类的父类。父类.class.isAssignableFrom(子类.class)
isAssignableFrom()方法的调用者和参数都是Class对象
-
在实际调试的时候遇到一个问题,就是我一直没有注册上自定义参数解析器,发现是因为在spring-mvc.xml里配置了<mvc:annotation-driven />,导致注解类不生效,这个问题还在学习中,目前还不清楚
-
get请求多个参数时,在不采用自定义解析器的情况下,可以通过实体去解析
-
第一种:直接传,不要任何注解,但是这样对参数的校验或者传参等就比较麻烦了
-
第二种:@ModelAttribute,前端传入方式跟第一种一样
-
-
swagger
-
引入 swagger:https://www.jianshu.com/p/5c1111d3b99f
-
swagger 404:https://blog.csdn.net/neulily2005/article/details/83788725?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.channel_param 这个错误,是因为静态资源路径映射问题导致。
-
参考博客:
SpringMVC DispatcherServlet执行流程及源码分析:https://www.jianshu.com/p/0f981efdfbbd
【Spring MVC】DispatcherServlet详解(容器初始化超详细过程源码分析):https://blog.csdn.net/qq_38410730/article/details/79426673?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param
SpringMVC 自定义参数解析器:https://blog.csdn.net/guanzhengyinqin/article/details/85255840
SpringMVC 自定义参数解析器:https://www.cnblogs.com/jmcui/p/11909524.html
SpringBoot用实体接收Get请求传递过来的多个参数(绝对可用):https://blog.csdn.net/qq_19734597/article/details/88897710?utm_medium=distribute.pc_relevant.none-task-blog-title-2&spm=1001.2101.3001.4242