springboot系列--web相关知识探索三

news2024/11/30 0:27:02

一、前言

web相关知识探索二中研究了请求是如何映射到具体接口(方法)中的,本次文章主要研究请求中所带的参数是如何映射到接口参数中的,也即请求参数如何与接口参数绑定。主要有四种、分别是注解方式、Servlet API方式、复杂参数、以及自定义对象参数。本次主要研究注解方式以及Servlet API方式。

二、 注解方式

接口参数绑定主要涉及的一下注解:

@PathVariable、@RequestHeader、@ModelAttribute、@RequestParam、@CookieValue、@RequestBody、@MatrixVariable

其中@MatrixVariable矩阵变量注解实际开发中基本不用,跳过研究。主要研究一下springmvc是如何将请求中的参数绑定到下面一个个注解修饰的参数中的。

一、测试用例 

@RestController
public class MvcTestController {

    /**
     *
     * @param id
     * @param name
     * @param pv 这个map可以接受url上的所有值,
     *           key是id;value:url路径上的值
     *           key是name;value:
     * @param userAgent
     * @param header
     * @param age
     * @param list
     * @param params
     * @param _ga
     * @param cookie
     * @return
     */

    @GetMapping("/demo/{id}/test/{name}")
    public Map<String,Object> test(@PathVariable("id") Integer id,
                                     @PathVariable("name") String name,
                                     @PathVariable Map<String,String> pv,
                                     @RequestHeader("User-Agent") String userAgent,
                                     @RequestHeader Map<String,String> header,
                                     @RequestParam("age") Integer age,
                                     @RequestParam("list") List<String> list,
                                     @RequestParam Map<String,String> params,
                                     @CookieValue("_ga") String _ga,
                                     @CookieValue("_ga") Cookie cookie){


        Map<String,Object> map = new HashMap<>();

        map.put("id",id);
        map.put("name",name);
        map.put("pv",pv);
        map.put("userAgent",userAgent);
        map.put("headers",header);
        map.put("age",age);
        map.put("list",list);
        map.put("params",params);
        map.put("_ga",_ga);
        return map;
    }


    @GetMapping("/test/servlet")
    public Map testServlet(HttpServletRequest request){
        Map<String,Object> map = new HashMap<>();
        map.put("request",request);
        return map;
    }


    @PostMapping("/test/method")
    public Map testPostMethod(@RequestBody String content){
        Map<String,Object> map = new HashMap<>();
        map.put("content",content);
        return map;
    }
}

首先,请求进来统一是有前端控制器(中央处理器),也即是DispatcherServlet这个类进行处理。然后中央处理器就会去调用处理器映射器,也即是handlerMapping,handlerMapping通过请求方式以及请求路径找到匹配的handler(也就是我们常说的接口,也是具体的处理方法),之后DispatcherServlet调用处理器适配器,也就是handlerAdatper根据具体的handler规则去执行对应的handler。

二·、原理

get请求:http://localhost:8080/demo/1/test/zhangsan,进入到上一章研究到的,通过handlerMapping获取到具体的handler地方。

1、HandlerMapping中找到能处理请求的Handler(Controller.method())

2、然后为当前Handler 找一个适配器 HandlerAdapter,这个适配器一般就是RequestMappingHandlerAdapter

3、适配器执行目标方法并确定方法参数的每一个值

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    boolean multipartRequestParsed = false;
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

    try {
        try {
            ModelAndView mv = null;
            Exception dispatchException = null;

            try {
                processedRequest = this.checkMultipart(request);
                multipartRequestParsed = processedRequest != request;
                // 这里就是上一张研究到获取合适的handler的地方
                mappedHandler = this.getHandler(processedRequest);
                if (mappedHandler == null) {
                    this.noHandlerFound(processedRequest, response);
                    return;
                }
// 这里就是获取到合适的处理器适配器的地方,源码在下方
                HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
       
                String method = request.getMethod();
                boolean isGet = "GET".equals(method);
                if (isGet || "HEAD".equals(method)) {
                    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                    if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
                        return;
                    }
                }

                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }
                   // 这里是利用处理器适配器执行具体的handler     
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }

                this.applyDefaultViewName(processedRequest, mv);
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            } catch (Exception var20) {
                dispatchException = var20;
            } catch (Throwable var21) {
                dispatchException = new NestedServletException("Handler dispatch failed", var21);
            }

            this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
        } catch (Exception var22) {
            this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
        } catch (Throwable var23) {
            this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
        }

    } finally {
        if (asyncManager.isConcurrentHandlingStarted()) {
            if (mappedHandler != null) {
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            }
        } else if (multipartRequestParsed) {
            this.cleanupMultipart(processedRequest);
        }

    }
}

一、找到合适的处理器适配器 

// 找到合适的HandlerAdapter 
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    // 遍历所有的处理器适配器,直到找到合适的 ,总共会有4种   
    if (this.handlerAdapters != null) {
            Iterator var2 = this.handlerAdapters.iterator();

            while(var2.hasNext()) {
                HandlerAdapter adapter = (HandlerAdapter)var2.next();
                // 这里的handler刚好就是HandlerMethod 对象,具体可以查看上一张获取handler。
                if (adapter.supports(handler)) {
                    return adapter;
                }
            }
        }

        throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
    }



// 判断是否支持当前handler,
    public final boolean supports(Object handler) {
        return handler instanceof HandlerMethod && this.supportsInternal((HandlerMethod)handler);
    }

 

0--支持方法上标注@RequestMapping、@GetMpping等注解的处理器适配器

1--支持函数式编程的

 二、利用处理器适配器执行具体的handler

从mv = ha.handle(processedRequest, response, mappedHandler.getHandler());方法进入

 

 一、参数解析器--HandlerMethodArgumentResolver

1、参数解析器主要是确定将要执行的目标方法的每一个参数的值是什么

2、SpringMVC目标方法能写多少种参数类型。取决于参数解析器

例如:目标方法(controller中的方法,也即是具体接口)使用了@RequestParam注解,springmvc就会使用RequestParamMethodArgumentResolvers这个参数解析器进行解析,,其他注解同理,会用其他解析器解析。

// 这是参数解析器接口,具体实现有27种,
public interface HandlerMethodArgumentResolver {
    // 当前解析器是否支持解析这种参数
    boolean supportsParameter(MethodParameter var1);

    // 支持就调用 resolveArgument
    @Nullable
    Object resolveArgument(MethodParameter var1, @Nullable ModelAndViewContainer var2, NativeWebRequest var3, @Nullable WebDataBinderFactory var4) throws Exception;
}
二、返回值处理器--HandlerMethodReturnValueHandler

返回值处理器主要就是处理返回值类型的,有多少种返回值处理器就能处理多少种返回值类型。这期主要研究参数解析器,返回值处理器另外再研究。

public interface HandlerMethodReturnValueHandler {
    boolean supportsReturnType(MethodParameter var1);

    void handleReturnValue(@Nullable Object var1, MethodParameter var2, ModelAndViewContainer var3, NativeWebRequest var4) throws Exception;
}
 三、将参数解析器、返回值处理器包装到执行handler对象中

    @Nullable
    protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
        ServletWebRequest webRequest = new ServletWebRequest(request, response);

        ModelAndView var15;
        try {
            WebDataBinderFactory binderFactory = this.getDataBinderFactory(handlerMethod);
            ModelFactory modelFactory = this.getModelFactory(handlerMethod, binderFactory);
            ServletInvocableHandlerMethod invocableMethod = this.createInvocableHandlerMethod(handlerMethod);
            if (this.argumentResolvers != null) {
                // invocableMethod这个对象就是执行具体的handler的,把参数解析器包装进去
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
            }
// invocableMethod这个对象就是执行具体的handler的,把返回值处理器包装进去
            if (this.returnValueHandlers != null) {
                invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
            }

            invocableMethod.setDataBinderFactory(binderFactory);
            invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
            ModelAndViewContainer mavContainer = new ModelAndViewContainer();
            mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
            modelFactory.initModel(webRequest, mavContainer, invocableMethod);
            mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
            AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
            asyncWebRequest.setTimeout(this.asyncRequestTimeout);
            WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
            asyncManager.setTaskExecutor(this.taskExecutor);
            asyncManager.setAsyncWebRequest(asyncWebRequest);
            asyncManager.registerCallableInterceptors(this.callableInterceptors);
            asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
            Object result;
            if (asyncManager.hasConcurrentResult()) {
                result = asyncManager.getConcurrentResult();
                mavContainer = (ModelAndViewContainer)asyncManager.getConcurrentResultContext()[0];
                asyncManager.clearConcurrentResult();
                LogFormatUtils.traceDebug(this.logger, (traceOn) -> {
                    String formatted = LogFormatUtils.formatValue(result, !traceOn);
                    return "Resume with async result [" + formatted + "]";
                });
                // 执行具体的handler,也就是controller中的接口方法
                invocableMethod = invocableMethod.wrapConcurrentResult(result);
            }

            invocableMethod.invokeAndHandle(webRequest, mavContainer, new Object[0]);
            if (asyncManager.isConcurrentHandlingStarted()) {
                result = null;
                return (ModelAndView)result;
            }

            var15 = this.getModelAndView(mavContainer, modelFactory, webRequest);
        } finally {
            webRequest.requestCompleted();
        }

        return var15;
    }

四、真正执行目标方法

 真正执行目标方法是在这个类ServletInvocableHandlerMethod里面的invokeAndHandle方法里的       

Object returnValue = this.invokeForRequest(webRequest, mavContainer, providedArgs);这个方法

真正执行目标方法有三个重要的步骤:

1、将目标方法中的参数与请求进来的参数进行绑定以及判断是否能否绑定等等。也即是对请求参数进行解析,这里就需要用到参数解析器了

2、通过反射调用目标方法进行执行

3、将目标方法执行后的数据绑定,也就是对返回值进行处理,这里就用到返回值处理器了

    public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
        //这里是真正执行目标方法,执行这个方法,就会到controller里面的接口方法,下图可见
        Object returnValue = this.invokeForRequest(webRequest, mavContainer, providedArgs);
        this.setResponseStatus(webRequest);
        if (returnValue == null) {
            if (this.isRequestNotModified(webRequest) || this.getResponseStatus() != null || mavContainer.isRequestHandled()) {
                this.disableContentCachingIfNecessary(webRequest);
                mavContainer.setRequestHandled(true);
                return;
            }
        } else if (StringUtils.hasText(this.getResponseStatusReason())) {
            mavContainer.setRequestHandled(true);
            return;
        }

        mavContainer.setRequestHandled(false);
        Assert.state(this.returnValueHandlers != null, "No return value handlers");

        try {
            this.returnValueHandlers.handleReturnValue(returnValue, this.getReturnValueType(returnValue), mavContainer, webRequest);
        } catch (Exception var6) {
            if (logger.isTraceEnabled()) {
                logger.trace(this.formatErrorForReturnValue(returnValue), var6);
            }

            throw var6;
        }
    }

// 执行目标方法源码       
@Nullable
    public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
  // 这里就是将请求参数与目标方法中的参数进行绑定
      Object[] args = this.getMethodArgumentValues(request, mavContainer, providedArgs);
        if (logger.isTraceEnabled()) {
            logger.trace("Arguments: " + Arrays.toString(args));
        }
        // 通过反射执行目标方法
        return this.doInvoke(args);
    }

一、将请求参数与目标方法上的参数进行绑定

1、判断是否支持解析当前参数,如果有一个不支持就会报错

// 这段代码主要就是确定  controllerr中具体接口方法上的参数的值。也就是把请求参数的值与目标方法参数进行绑定
  protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
        // 这里就是获取controllerr中具体接口方法上的参数、以及对应的类型,参数的位置等等,也就是获取参数声明信息
		MethodParameter[] parameters = this.getMethodParameters();
        // 如果目标方法没有参数,也就不需要绑定,直接返回
		if (ObjectUtils.isEmpty(parameters)) {
            return EMPTY_ARGS;
        } else {
			// 创建一个数组,长度是目标方法参数的个数
            Object[] args = new Object[parameters.length];

			// 循环遍历目标方法参数
            for(int i = 0; i < parameters.length; ++i) {
                MethodParameter parameter = parameters[i];
                parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
                args[i] = findProvidedArgument(parameter, providedArgs);
                if (args[i] == null) {
					// 判断当前解析器是否支持当前接口方法中的这个参数类型
                    if (!this.resolvers.supportsParameter(parameter)) {
                        throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
                    }

                    try {
						// 这里将进行参数解析,解析绑定后,将进行下一个参数的解析与绑定
                        args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
                    } catch (Exception var10) {
                        if (logger.isDebugEnabled()) {
                            String exMsg = var10.getMessage();
                            if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
                                logger.debug(formatArgumentError(parameter, exMsg));
                            }
                        }

                        throw var10;
                    }
                }
            }

            return args;
        }
    }
	
	
	
	
		// 判断解析器是否支持当前参数类型的解析,解析器上面有讲过,有27中解析器
	    public boolean supportsParameter(MethodParameter parameter) {
        return this.getArgumentResolver(parameter) != null;
    }
	
	
	
	
	@Nullable
    private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
		// this.argumentResolverCache刚进来的时候是空的
        HandlerMethodArgumentResolver result = (HandlerMethodArgumentResolver)this.argumentResolverCache.get(parameter);
        
		if (result == null) {
			// 第一次是空的,会进到这里,然后	挨个遍历27个解析器
			Iterator var3 = this.argumentResolvers.iterator();

            while(var3.hasNext()) {
                HandlerMethodArgumentResolver resolver = (HandlerMethodArgumentResolver)var3.next();
				// 这里判断是否支持,具体源码在下方
                if (resolver.supportsParameter(parameter)) {
                    result = resolver;
					// 解析出来后放入本地缓存中,下次进来就不用再判断了
                    this.argumentResolverCache.put(parameter, resolver);
                    break;
                }
            }
        }

        return result;
    }
	
	
	
	
	
	// 判断解析器是否支持当前参数解析,这里只拿PathVariable注解解析器源码举例
	    public boolean supportsParameter(MethodParameter parameter) {
			// 判断当前参数是否使用了PathVariable注解,没使用就不支持解析
        if (!parameter.hasParameterAnnotation(PathVariable.class)) {
            return false;
			// 到了这里就说明当前参数一定是PathVariable注解修饰的,然后判断是不是map,如果不是map那么就能够支持
        } else if (!Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
            return true;
        } else {
			// 到这里就说明参数是map类型的,然后还需要做其他判断,也就是PathVariable注解中的value一定要有值,如果没有值,就说明这个注解的map想要全部接受url
			// 上的参数,这种就需要用另外一个解析器进行处理了,如果有值就说明url上的参数是map类型的,名字是PathVariable上的value属性的值。然后会将这个mapp参数值映射到
			// 这个参数上
            PathVariable pathVariable = (PathVariable)parameter.getParameterAnnotation(PathVariable.class);
            return pathVariable != null && StringUtils.hasText(pathVariable.value());
        }
    }

2、通过了参数解析判断后,将进行真正的参数解析。 

   
	// 这里主要是进行参数解析这个方法是在HandlerMethodArgumentResolverComposite这个类下
   @Nullable
    public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
	// 拿到所有参数解析器        
	   HandlerMethodArgumentResolver resolver = this.getArgumentResolver(parameter);
        if (resolver == null) {
            throw new IllegalArgumentException("Unsupported parameter type [" + parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
        } else {
			// 调用参数解析器,解析参数的方法
            return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
        }
    }
	
	
	
	// 这里是拿到这个参数对应的解析器,例如是@PathVariable注解修饰的参数,那就会拿到@PathVariable的参数解析器
	    @Nullable
    private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
	// 这里首先会从缓存中获取,由于之前在做判断是否支持这种类型的参数时,有做过缓存处理,所以这里可以直接获取到这个参数类型对应的参数解析器
        HandlerMethodArgumentResolver result = (HandlerMethodArgumentResolver)this.argumentResolverCache.get(parameter);
        if (result == null) {
            Iterator var3 = this.argumentResolvers.iterator();

            while(var3.hasNext()) {
                HandlerMethodArgumentResolver resolver = (HandlerMethodArgumentResolver)var3.next();
                if (resolver.supportsParameter(parameter)) {
                    result = resolver;
                    this.argumentResolverCache.put(parameter, resolver);
                    break;
                }
            }
        }

        return result;
    }
	
	
	
	
	
	// 真正开始解析参数,这个方法是在AbstractNamedValueMethodArgumentResolver这个类里面,应该是一个公共方法,类似于模板方法的公共方法,处理公共逻辑
	@Nullable
    public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
		// 这里将注解信息封装成一个对象,例如注解里标识这个参数以什么样的名字绑定,是否是必须得,以及默认值。例如:@PathVariable(value = "id",required = false)这里面的属性
	   NamedValueInfo namedValueInfo = this.getNamedValueInfo(parameter);
        MethodParameter nestedParameter = parameter.nestedIfOptional();
		// 这里就是从封装对象解析出接口方法要绑定的名字,例如这个注解@PathVariable("id"),解析出来就是id这个名字
        Object resolvedName = this.resolveEmbeddedValuesAndExpressions(namedValueInfo.name);
        if (resolvedName == null) {
            throw new IllegalArgumentException("Specified name must not resolve to null: [" + namedValueInfo.name + "]");
        } else {
		// 这里开始将url上对应的值与id这个名字进行绑定,也即是对id进行赋值,这里将参数解析绑定后,下面逻辑可以不用看了
            Object arg = this.resolveName(resolvedName.toString(), nestedParameter, webRequest);
            if (arg == null) {
                if (namedValueInfo.defaultValue != null) {
                    arg = this.resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
                } else if (namedValueInfo.required && !nestedParameter.isOptional()) {
                    this.handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
                }

                arg = this.handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
            } else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
                arg = this.resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
            }

            if (binderFactory != null) {
                WebDataBinder binder = binderFactory.createBinder(webRequest, (Object)null, namedValueInfo.name);

                try {
                    arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
                } catch (ConversionNotSupportedException var11) {
                    throw new MethodArgumentConversionNotSupportedException(arg, var11.getRequiredType(), namedValueInfo.name, parameter, var11.getCause());
                } catch (TypeMismatchException var12) {
                    throw new MethodArgumentTypeMismatchException(arg, var12.getRequiredType(), namedValueInfo.name, parameter, var12.getCause());
                }

                if (arg == null && namedValueInfo.defaultValue == null && namedValueInfo.required && !nestedParameter.isOptional()) {
                    this.handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
                }
            }

            this.handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
            return arg;
        }
    }
	
	
	
		// 获取参数注解信息
	    private NamedValueInfo getNamedValueInfo(MethodParameter parameter) {
		// 先从缓存中获取
        NamedValueInfo namedValueInfo = (NamedValueInfo)this.namedValueInfoCache.get(parameter);
        if (namedValueInfo == null) {
		// 这个方法是从注解中获取注解属性信息
            namedValueInfo = this.createNamedValueInfo(parameter);
			// 这里其实就是重新更新一下namedValueInfo值中的defaultValue属性。
            namedValueInfo = this.updateNamedValueInfo(parameter, namedValueInfo);
            this.namedValueInfoCache.put(parameter, namedValueInfo);
        }

        return namedValueInfo;
    }
	
	
	 // 从注解中获取注解属性信息,拿PathVariable注解举例
	    protected AbstractNamedValueMethodArgumentResolver.NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
		// 这里获取的是一个注解
        PathVariable ann = (PathVariable)parameter.getParameterAnnotation(PathVariable.class);
        Assert.state(ann != null, "No PathVariable annotation");
		// 这里会把注解传入这个对象,里面会把属性保存到这个对象中
        return new PathVariableNamedValueInfo(ann);
    }



	// 把属性保存到这个对象中
    private static class PathVariableNamedValueInfo extends AbstractNamedValueMethodArgumentResolver.NamedValueInfo {
        public PathVariableNamedValueInfo(PathVariable annotation) {
		// 父类NamedValueInfo
            super(annotation.name(), annotation.required(), "\n\t\t\n\t\t\n\ue000\ue001\ue002\n\t\t\t\t\n");
        }
    }
	
	
	
	
	    protected static class NamedValueInfo {
        private final String name;
        private final boolean required;
        @Nullable
        private final String defaultValue;

        public NamedValueInfo(String name, boolean required, @Nullable String defaultValue) {
            this.name = name;
            this.required = required;
            this.defaultValue = defaultValue;
        }
    }
	
	
	
	
	
		// 这里是对id进行赋值的源码,name:就是接口方法中使用注解属性的值,也就是@PathVariable("id")里面的id
	    @Nullable
    protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
		/**
		*HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE的值为org.springframework.web.servlet.HandlerMapping.uriTemplateVariables
		*request请求信息里面在org.springframework.web.servlet.HandlerMapping.uriTemplateVariables这个key下,保存了url上的请求参数
		*从请求中获取到数据后,保存在map当中,其中key为:注解属性的id,value为请求携带的值,就拿@PathVariable("id"),
		这个注解来说吧,key-id,value-请求请来的值。(@GetMapping("/demo/{id}/test/{name}")是从这里面取到的id、name为key)这里是由于请求一进来会有一个UrlPathHelper类里面的方法将url里面的路径变量全部解析出来
		*然后提前保存到请求域当中。所以uriTemplateVars是请求域中的值,但是并未绑定到接口参数上,
		*/
        Map<String, String> uriTemplateVars = (Map)request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, 0);
	// 这里就是从请求域中的值里面获取到id对应的value        
	   return uriTemplateVars != null ? uriTemplateVars.get(name) : null;
    }

 

三、Servlet API方式 

当我们的接口参数是WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId这些类型时,springmvc会有专门的处理器进行处理。也就是ServletRequestMethodArgumentResolver 这个处理器。主要源码如下。

@Override
	public boolean supportsParameter(MethodParameter parameter) {
		Class<?> paramType = parameter.getParameterType();
		return (WebRequest.class.isAssignableFrom(paramType) ||
				ServletRequest.class.isAssignableFrom(paramType) ||
				MultipartRequest.class.isAssignableFrom(paramType) ||
				HttpSession.class.isAssignableFrom(paramType) ||
				(pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) ||
				Principal.class.isAssignableFrom(paramType) ||
				InputStream.class.isAssignableFrom(paramType) ||
				Reader.class.isAssignableFrom(paramType) ||
				HttpMethod.class == paramType ||
				Locale.class == paramType ||
				TimeZone.class == paramType ||
				ZoneId.class == paramType);
	}

一、判断是否支持HttpservletRequest类型参数

 

 二、解析出参数

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2188947.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[大语言模型-算法优化] 微调技术-LoRA算法原理及优化应用详解

[大语言模型-算法优化] 微调技术-LoRA算法原理及优化应用详解 前言: 古人云: 得卧龙者&#xff0c;得天下。 然在当今大语言模型流行的时代&#xff0c;同样有一句普世之言: 会微调技术者&#xff0c;得私域大模型部署之道&#xff01; 在众多微调技术中&#xff0c;LoRA (…

单细胞scDist细胞扰动差异分析学习

scDist通过分析不同状态下细胞的距离来找到差异最大的细胞亚群(见下图的A)&#xff0c;然后再分析每一个细胞亚群的PCA通过线性的混合模型并结合最终的系数去预估不同干预方式下细胞群之间的距离。 Augur是通过对每一个细胞进行AUC评分并排序最终找到扰动最佳的细胞群&#xf…

等额本金和等额本息是什么意思?

等额本金和等额本息是两种常见的贷款还款方式&#xff0c;它们各自有着不同的特点和适用场景。下面我将用通俗易懂的语言来解释这两种还款方式&#xff1a; 等额本金 定义&#xff1a;等额本金指的是在贷款期限内&#xff0c;每月偿还相同数额的本金&#xff0c;而利息则随着剩…

FPGA远程烧录bit流

FPGA远程烧录bit流 Vivado支持远程编译并下载bit流到本地xilinx开发板。具体操作就是在连接JTAG的远程电脑上安装hw_server.exe。比如硬件板在实验室或者是其他地方&#xff0c;开发代码与工程在本地计算机&#xff0c;如何将bit流烧录到实验室或者远程开发板&#xff1f; vi…

Socket套接字(客户端,服务端)和IO多路复用

Socket套接字&#xff08;客户端&#xff0c;服务端&#xff09; 目录 socket是什么一、在客户端1. 创建套接字2. 设置服务器地址3. 连接到服务器4. 发送数据5. 接收数据6. 关闭连接 二、内核态与用户态切换三、系统调用与上下文切换的关系四、在服务端1. 创建 Socket (用户态…

【Linux】进程地址空间(初步了解)

文章目录 1. 奇怪的现象2. 虚拟地址空间3. 关于页表4. 为什么要有虚拟地址 1. 奇怪的现象 我们先看一个现象&#xff1a; 为什么父子进程从“同一块地址中”读取到的值不一样呢&#xff1f; 因为这个地址不是物理内存的地址 &#xff0c;如果是物理内存的地址是绝对不可能出…

C++【类和对象】(友元、内部类与匿名对象)

文章目录 1.友元2.内部类3.匿名对象结语 1.友元 友元提供了⼀种突破类访问限定符封装的方式&#xff0c;友元分为&#xff1a;友元函数和友元类&#xff0c;在函数声明或者类声明的前面加friend&#xff0c;并且把友元声明放到⼀个类的里面。外部友元函数可访问类的私有和保护…

【安全科普】从“微信文件助手隐私泄漏”看社交平台网络安全

随着互联网技术的飞速发展&#xff0c;社交平台已经成为了人们日常生活中不可或缺的一部分。人们通过社交平台与亲朋好友保持联系&#xff0c;分享生活点滴&#xff0c;获取资讯信息。然而&#xff0c;与此同时&#xff0c;社交平台上的网络安全风险也日益凸显。近期&#xff0…

简单的a+b-C语言

1.问题&#xff1a; 输入两个整数a和b&#xff0c;计算ab的和。 2.解答&#xff1a; scanf()函数是通用终端格式化输入函数&#xff0c;它从标准输入设备(键盘) 读取输入的信息。可以读入任何固有类型的数据并自动把数值变换成适当的机内格式。 scanf()函数返回值分为3种&…

分布式学习02-CAP理论

文章目录 CAP三指标一致性可用性分区容错性 CAP不可能三角P存在的必要性CP理论AP理论 CAP理论对分布式系统的特性做了高度抽象&#xff0c;将其抽象为一致性、可用性、分区容错性。 并对特征间的冲突做了总结&#xff1a;CAP不可能三角。 CAP三指标 一致性&#xff08;Consis…

【NIO基础】基于 NIO 中的组件实现对文件的操作(文件编程),FileChannel 详解

目录 1、FileChannel (1&#xff09;获取 FileChannel (2&#xff09;读取文件 (3&#xff09;写入文件 (4&#xff09;关闭通道 (5&#xff09;当前位置与文件大小 (6&#xff09;强制写入磁盘 2、两个 FileChannel 之间的数据传输 (1&#xff09;使用 transferTo()…

HTML的修饰(CSS) -- 第三课

文章目录 前言一、CSS是什么&#xff1f;二、使用方式1. 基本语法2. 引入方式1.行内式2.内嵌式3. 链入式 3. 选择器1. 标签选择器2.类选择器3. id选择器4. 通配符选择器 4. css属性1. 文本样式属性2. 文本外观属性 5. 元素类型及其转换1. 元素的类型2. 元素的转换 6.css高级特性…

isinstance()学习

aa {} if isinstance(aa,dict):print("是")aa 2 if isinstance(aa,dict):print("是")aa 2 if isinstance(aa,int):print("是")aa [] if isinstance(aa,list):print("list")aa [1,2,3] if isinstance(aa,list):print("list"…

模拟算法(4)_外观数列

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 模拟算法(4)_外观数列 收录于专栏【经典算法练习】 本专栏旨在分享学习算法的一点学习笔记&#xff0c;欢迎大家在评论区交流讨论&#x1f48c; 目录 1. 题目链…

选择排序:直接选择排序、堆排序

目录 直接选择排序 1.选择排序的基本思想 2.直接选择排序的基本思想 3.直接插入排序的代码思路步骤 4.直接选择排序代码 5.直接选择排序的特性总结 堆排序 一、排升序&#xff0c;建大堆 1.利用向上调整函数建大堆 1.1.建立大堆的思路 1.2.以下是具体步骤&#xff1a…

Android Framework AMS(01)AMS启动及相关初始化1-4

该系列文章总纲链接&#xff1a;专题总纲目录 Android Framework 总纲 本章关键点总结 & 说明&#xff1a; 说明&#xff1a;本章节主要涉及systemserver启动AMS及初始化AMS相关操作。同时由于该部分内容分析过多&#xff0c;因此拆成2个章节&#xff0c;本章节是第一章节&…

Solidity 存储和内存管理:深入理解与高效优化

在 Solidity 中&#xff0c;存储和内存管理是编写高效智能合约的关键组成部分。合约执行的每一步操作都可能涉及到数据的存储和读取&#xff0c;而这些操作对 gas 的消耗有很大影响。因此&#xff0c;理解 Solidity 的存储模型以及如何优化数据的管理对于合约的安全性、性能和成…

pytorch之梯度累加

1.什么是梯度&#xff1f; 梯度可以理解为一个多变量函数的变化率&#xff0c;它告诉我们在某一点上&#xff0c;函数的输出如何随输入的变化而变化。更直观地说&#xff0c;梯度指示了最优化方向。 在机器学习中的作用&#xff1a;在训练模型时&#xff0c;我们的目标是最小…

day2网络编程项目的框架

基于终端的 UDP云聊天系统 开发环境 Linux 系统GCCUDPmakefilesqlite3 功能描述 通过 UDP 网络使服务器与客户端进行通信吗&#xff0c;从而实现云聊天。 Sqlite数据库 用户在加入聊天室前&#xff0c;需要先进行用户登录或注册操作&#xff0c;并将注册的用户信息&#xf…

P4、P4D、HelixSwarm 各种技术问题咨询

多年大型项目P4仓库运维经验&#xff0c;为你解决各种部署以及标准工业化流程问题。 Perforce 官网SDPHelixCore GuideHelixSwarm GuideHelixSwarm Download