Controller方法参数的应用及解析

Spring MVC中针对Controller方法参数的应用及解析原理,使用的Spring Boot版本为2.7.10。

文章目录

源码位置

spring mvc中解析请求数据 到Controller方法参数 的源码位置

RequestMappingHandlerAdapter类 - invokeHandlerMethod方法 (Ctrl+F12查找),

找到invocableMethod.invokeAndHandle(webRequest, mavContainer);

RequestMappingHandlerAdapter是处理@RequestMappin标注的Controller请求的适配器,它会使用解析器解析请求参数,绑定到Controller的方法参数中,带着解析后的参数通过反射调用目标Controller方法,并处理Controller方法执行后的返回值:

@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

	ServletWebRequest webRequest = new ServletWebRequest(request, response);
	try {
		WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
		ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

		ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
		
        //指定参数解析器,用于将请求参数转换为Controller方法参数
        if (this.argumentResolvers != null) {
			invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
		}
        
        //指定返回值处理器,用于转换Controller的处理结果并通过http响应前端
		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);

		if (asyncManager.hasConcurrentResult()) {
			Object result = asyncManager.getConcurrentResult();
			mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
			asyncManager.clearConcurrentResult();
			LogFormatUtils.traceDebug(logger, traceOn -> {
				String formatted = LogFormatUtils.formatValue(result, !traceOn);
				return "Resume with async result [" + formatted + "]";
			});
			invocableMethod = invocableMethod.wrapConcurrentResult(result);
		}

        //解析请求参数,调用目标Controller,得到返回值
		invocableMethod.invokeAndHandle(webRequest, mavContainer);
        
		if (asyncManager.isConcurrentHandlingStarted()) {
			return null;
		}

		return getModelAndView(mavContainer, modelFactory, webRequest);
	}
	finally {
		webRequest.requestCompleted();
	}
}

进入invocableMethod.invokeAndHandle(webRequest, mavContainer);,在ServletInvocableHandlerMethod类中,如下:

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {

    //解析请求参数,调用目标Controller,得到返回值returnValue
	Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
	setResponseStatus(webRequest);
	
    //............
    
}    

再进入上面的invokeForRequest方法,在InvocableHandlerMethod类中,进入后如下:

@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
		Object... providedArgs) throws Exception {
	
    //调用解析器解析http请求参数,根据Controller方法参数信息获取支持的解析器并进行解析,args就是解析完成的结果
	Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
	if (logger.isTraceEnabled()) {
		logger.trace("Arguments: " + Arrays.toString(args));
	}
    //将解析完的args作为参数,执行目标Controller方法
	return doInvoke(args);
}

进入上面invokeForRequest方法中的getMethodArgumentValues,进行解析操作

这个方法中,会遍历resolvers解析器实现,通过解析器的supportsParameter方法判断当前解析器是否支持参数解析,如果支持就调用resolveArgument方法进行解析:

protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {

		MethodParameter[] parameters = getMethodParameters();
		if (ObjectUtils.isEmpty(parameters)) {
			return EMPTY_ARGS;
		}

		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) {
				continue;
			}
            
            //判断当前解析器能否支持解析请求参数
			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 ex) {
				if (logger.isDebugEnabled()) {
					String exMsg = ex.getMessage();
					if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
						logger.debug(formatArgumentError(parameter, exMsg));
					}
				}
				throw ex;
			}
		}
		return args;
}

解析器抽象类

下面介绍两个解析器的抽象类,主要针对常用的Controller参数注解及参数类型

AbstractNamedValueMethodArgumentResolver

AbstractNamedValueMethodArgumentResolver 是 Spring MVC 中的一个根据参数命名值,来解析方法参数的抽象基类。命名值的例子包括请求参数、请求头和路径变量。每个命名值都可以有一个名称、一个必需标志和一个默认值,该类提供了一种通用的方式来解析方法参数,它通过模板方法设计模式提供了解析参数的框架。

以下注解的解析器类继承于AbstractNamedValueMethodArgumentResolver,依靠其resolveArgument方法进行参数解析:

  • @MatrixVariable
  • @PathVariable
  • @RequestAttribute
  • @RequestHeader
  • @RequestParam
  • @Value
  • @CookieValue
  • @SessionAttribute
代码详解

下面是AbstractNamedValueMethodArgumentResolver解析方法代码的详细解释:

@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
	
    //`NamedValueInfo`是一个内部类,用于保存参数的元数据信息,如名称、是否必须、默认值等
    //getNamedValueInfo用于获取当前参数的名称、是否必须、默认值等信息(可在注解中指定)
	NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
    
    //如果参数是一个 `Optional` 类型,则获取其嵌套的实际参数类型
	MethodParameter nestedParameter = parameter.nestedIfOptional();

    //解析参数名称中的任何嵌入值或表达式,确保解析后的名称不为 `null`
	Object resolvedName = resolveEmbeddedValuesAndExpressions(namedValueInfo.name);
	if (resolvedName == null) {
		throw new IllegalArgumentException(
				"Specified name must not resolve to null: [" + namedValueInfo.name + "]");
	}

    //通过`resolveName`方法解析参数值,该方法由具体的子类解析器实现,以便根据请求(例如 URL、请求参数等)获取参数值
	Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
	//如果参数值为`null`,且指定了默认值,则使用默认值
    if (arg == null) {
		if (namedValueInfo.defaultValue != null) {
			arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
		}
		else if (namedValueInfo.required && !nestedParameter.isOptional()) {
			handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
		}
		arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
	}
    //如果参数为必需且没有值,则调用 `handleMissingValue` 方法处理这种情况,通常会抛出异常
	else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
		arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
	}

    //如果提供了`binderFactory`,则使用`WebDataBinder`转换参数值为所需的类型。如果转换失败,会抛出相应的转换异常
	if (binderFactory != null) {
		WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
		try {
			arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
		}
		catch (ConversionNotSupportedException ex) {
			throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
					namedValueInfo.name, parameter, ex.getCause());
		}
		catch (TypeMismatchException ex) {
			throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
					namedValueInfo.name, parameter, ex.getCause());
		}
		// 果参数在转换后变为`null`,并且没有默认值且是必需的,则调用`handleMissingValueAfterConversion`方法处理
		if (arg == null && namedValueInfo.defaultValue == null &&
				namedValueInfo.required && !nestedParameter.isOptional()) {
			handleMissingValueAfterConversion(namedValueInfo.name, nestedParameter, webRequest);
		}
	}

    //`handleResolvedValue` 方法允许子类在解析值之后进行进一步的处理
	handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);

	return arg;
}

resolveArgument 方法是 HandlerMethodArgumentResolver 接口的核心方法,用于解析给定的方法参数并返回相应的值。该方法的主要逻辑如下:

  1. 获取 NamedValueInfo:

    NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
    

    NamedValueInfo 是一个内部类,用于保存参数的元数据信息,如名称、是否必须、默认值(可从注解中指定)等。getNamedValueInfo 方法用于获取当前参数的这些信息。

  2. 处理嵌套参数:

    MethodParameter nestedParameter = parameter.nestedIfOptional();
    

    如果参数是一个 Optional 类型,则获取其嵌套的实际参数类型。

  3. 解析参数名中的表达式:

    Object resolvedName = resolveEmbeddedValuesAndExpressions(namedValueInfo.name);
    

    这一步将解析参数名称中的任何嵌入值或表达式,确保解析后的名称不为 null

  4. 解析参数值:

    Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
    

    通过 resolveName 方法解析参数值,该方法由具体的子类实现,以便根据请求(例如 URL、请求参数等)获取参数值。

  5. 处理参数为空的情况:

    • 如果参数值为 null,且指定了默认值,则使用默认值。
    • 如果参数为必需且没有值,则调用 handleMissingValue 方法处理这种情况,通常会抛出异常。
  6. 转换参数值:

    WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
    arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
    

    如果提供了 binderFactory,则使用 WebDataBinder 转换参数值为所需的类型。如果转换失败,会抛出相应的转换异常。

  7. 处理转换后的空值:
    如果参数在转换后变为 null,并且没有默认值且是必需的,则调用 handleMissingValueAfterConversion 方法处理。

  8. 处理解析后的值:

    handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
    

    handleResolvedValue 方法允许子类在解析值之后进行进一步的处理。

代码示例

以下是一个使用自定义 HandlerMethodArgumentResolver 的示例,解析 @RequestParam 注解的参数:

public class CustomRequestParamResolver extends AbstractNamedValueMethodArgumentResolver {

    @Override
    protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
        RequestParam annotation = parameter.getParameterAnnotation(RequestParam.class);
        return new NamedValueInfo(annotation.name(), annotation.required(), annotation.defaultValue());
    }

    @Override
    protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest webRequest) throws Exception {
        return webRequest.getParameter(name);
    }

    @Override
    protected void handleMissingValue(String name, MethodParameter parameter, NativeWebRequest webRequest) throws Exception {
        throw new MissingServletRequestParameterException(name, parameter.getParameterType().getSimpleName());
    }

    @Override
    protected void handleResolvedValue(Object arg, String name, MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) {
        // Custom logic after value resolution
    }
}
总结

AbstractNamedValueMethodArgumentResolver 提供了一种灵活而强大的机制来解析控制器方法中的命名参数。通过使用模板方法设计模式,该类为子类提供了多个钩子方法,以便根据具体需求实现参数解析、默认值处理、参数转换等逻辑。在 Spring MVC 中,它是实现注解参数解析的基础设施,为开发者提供了自定义参数解析的能力。


AbstractMessageConverterMethodArgumentResolver

一个用于通过HttpMessageConverters消息转换器从请求体中读取数据来解析方法参数值的抽象类,以下类型参数的解析器继承AbstractMessageConverterMethodArgumentResolver,依赖其readWithMessageConverters方法解析请求体数据为参数:

  • HttpEntity及其子类类型的参数
  • @RequestPart注解标记的参数
  • @RequestBody注解标记的参数
代码详解

这段代码是 AbstractMessageConverterMethodArgumentResolver 中的 readWithMessageConverters 方法,它的主要功能是使用合适的 HttpMessageConverter 将 HTTP 请求的消息体读取并转换为目标类型的对象。以下是详细的代码解析:

方法参数:

HttpInputMessage inputMessage: 包含了HTTP请求的输入消息,主要是请求头和消息体。

MethodParameter parameter: 代表方法的参数信息,包括类型和注解。

Type targetType: 目标类型,即期望将消息体转换为的类型。

@Nullable
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
	Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {

    /**
     * 从请求头中获取 Content-Type。如果没有指定 Content-Type,则默认使用 application/octet-stream。
     * */
	MediaType contentType;
	boolean noContentType = false;
	try {
        //从请求头中获取 Content-Type
		contentType = inputMessage.getHeaders().getContentType();
	}
	catch (InvalidMediaTypeException ex) {
		throw new HttpMediaTypeNotSupportedException(ex.getMessage());
	}
    //如果没有指定Content-Type,则默认使用application/octet-stream
	if (contentType == null) {
		noContentType = true;
		contentType = MediaType.APPLICATION_OCTET_STREAM;
	}

    //确定targetClass,即将消息体转换为什么类型的对象
	Class<?> contextClass = parameter.getContainingClass();
	Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null);
	if (targetClass == null) {
		ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);
		targetClass = (Class<T>) resolvableType.resolve();
	}

    //获取请求方式
	HttpMethod httpMethod = (inputMessage instanceof HttpRequest ? ((HttpRequest) inputMessage).getMethod() : null);
    //初始化body数据
	Object body = NO_VALUE;

    
    /**
     * 使用 HttpMessageConverter 将消息体转换为目标类型的对象。
     * 如果 converter 能够读取消息体的内容类型并将其转换为 targetType,则进行转换操作
     * */
	EmptyBodyCheckingHttpInputMessage message = null;
	try {
        //转换请求对象用于下文处理
		message = new EmptyBodyCheckingHttpInputMessage(inputMessage);

        //遍历消息转换器
		for (HttpMessageConverter<?> converter : this.messageConverters) {
            //获取转换器类型
			Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
            //判断转换器类型
			GenericHttpMessageConverter<?> genericConverter =
					(converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
            //如果当前转换器是GenericHttpMessageConverter的实现类,使用其canRead方法看能否支持转换
			if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
					(targetClass != null && converter.canRead(targetClass, contentType))) {
                //支持则获取请求体body数据进行转换操作
				if (message.hasBody()) {
					HttpInputMessage msgToUse =
							getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
					body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
							((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
					body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
				}
				else {
					body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
				}
				break;
			}
		}
	}
	catch (IOException ex) {
		throw new HttpMessageNotReadableException("I/O error while reading input message", ex, inputMessage);
	}
	finally {
		if (message != null && message.hasBody()) {
			closeStreamIfNecessary(message.getBody());
		}
	}

    //处理无效或不支持的内容类型
	if (body == NO_VALUE) {
		if (httpMethod == null || !SUPPORTED_METHODS.contains(httpMethod) ||
				(noContentType && !message.hasBody())) {
			return null;
		}
		throw new HttpMediaTypeNotSupportedException(contentType,
				getSupportedMediaTypes(targetClass != null ? targetClass : Object.class));
	}

    //对结果进行日志记录并返回转换后的对象
	MediaType selectedContentType = contentType;
	Object theBody = body;
	LogFormatUtils.traceDebug(logger, traceOn -> {
		String formatted = LogFormatUtils.formatValue(theBody, !traceOn);
		return "Read \"" + selectedContentType + "\" to [" + formatted + "]";
	});

	return body;
}
总结

readWithMessageConverters 方法的主要作用是通过 HttpMessageConverter 将 HTTP 请求的消息体解析为控制器方法参数所需的对象类型。通过这个方法,Spring MVC 可以灵活处理各种不同的请求内容类型,并将其转换为Java对象,供控制器方法使用。

其中,RequestPartMethodArgumentResolver(对应@RequestPart)、RequestResponseBodyMethodProcessor(对应@RequestPart)、HttpEntityMethodProcessor(HttpEntity及其子类参数)三个解析器,会在调用其resolveArgument方法进行请求参数解析时,调用readWithMessageConverters方法参数转换。

@PathVariable

对应解析器:

PathVariableMethodArgumentResolver:方法参数上的单个@PathVariable注解

PathVariableMapMethodArgumentResolver:方法参数为Map<String, String>类型并带有@PathVariable注解

在Spring Boot中,@PathVariable注解用于将URL路径中的变量绑定到方法参数上,这样你就可以在控制器中方便地使用这些参数。它是Spring MVC框架的一部分,常用于RESTful服务中,帮助创建易读和结构化的API。以下是@PathVariable注解的使用详解:

1. 基本用法

@PathVariable通常与@RequestMapping或其派生注解(如@GetMapping@PostMapping等)一起使用,以提取URL路径中的动态部分。例如:

@RestController
@RequestMapping("/users")
public class UserController {

    @GetMapping("/{id}")
    public ResponseEntity<User> getUserById(@PathVariable Long id) {
        User user = userService.findUserById(id);
        return ResponseEntity.ok(user);
    }
}

在上述示例中,@PathVariable Long id用于将URL路径中的{id}部分绑定到控制器方法的id参数上。

2. 多个路径变量

你可以在同一个URL中使用多个@PathVariable,每个变量对应一个方法参数:

@GetMapping("/{userId}/orders/{orderId}")
public ResponseEntity<Order> getOrderForUser(@PathVariable Long userId, @PathVariable Long orderId) {
    Order order = orderService.findOrderForUser(userId, orderId);
    return ResponseEntity.ok(order);
}

3. 指定路径变量名称

如果路径变量名称与方法参数名称不一致,可以在@PathVariable中显式指定名称:

@GetMapping("/products/{productId}")
public ResponseEntity<Product> getProductById(@PathVariable("productId") Long id) {
    Product product = productService.findProductById(id);
    return ResponseEntity.ok(product);
}

在这个例子中,路径变量{productId}被绑定到方法参数id

4. 路径变量的类型转换

Spring Boot会自动将路径变量从字符串转换为适当的Java类型(如LongInteger等)。如果转换失败,会抛出TypeMismatchException异常。

5. 可选的路径变量

虽然@PathVariable不支持直接设置为可选,但可以通过使用多个@RequestMapping或提供一个默认值来处理:

required = false表示productId值不是必须

@GetMapping(value = {"/products", "/products/{productId}"})
public ResponseEntity<?> getProduct(@PathVariable(required = false) Long productId) {
    if (productId != null) {
        Product product = productService.findProductById(productId);
        return ResponseEntity.ok(product);
    } else {
        List<Product> products = productService.findAllProducts();
        return ResponseEntity.ok(products);
    }
}

6. 正则表达式匹配

你可以在路径变量中使用正则表达式以更严格地控制匹配:

@GetMapping("/files/{filename:.+}")
public ResponseEntity<File> getFile(@PathVariable String filename) {
    File file = fileService.loadFile(filename);
    return ResponseEntity.ok(file);
}

这里,{filename:.+}使用正则表达式.+来匹配文件名,包括扩展名。

7.解析器区别

在Spring Framework中,PathVariableMethodArgumentResolverPathVariableMapMethodArgumentResolver是两个不同的解析器,用于处理@PathVariable注解,但它们的用途略有不同:

PathVariableMethodArgumentResolver
  • 用途:此解析器负责解析方法参数上的单个@PathVariable注解。当你想将特定的URI模板变量映射到方法参数时,会用到它。

  • 功能:从URL路径中提取特定的URI变量值,并将其绑定到带有@PathVariable注解的方法参数上。解析器还会处理提取的路径变量值的类型转换。

  • 使用示例

    @GetMapping("/users/{id}")
    public ResponseEntity<User> getUser(@PathVariable Long id) {
        // 'id' 是由 PathVariableMethodArgumentResolver 解析的
    }
    

    在这个示例中,PathVariableMethodArgumentResolver解析{id}路径变量并将其转换为Long类型,然后传递给getUser方法。

PathVariableMapMethodArgumentResolver
  • 用途:当方法参数为Map<String, String>类型并带有@PathVariable注解时,此解析器用于将URI模板变量的集合绑定到该方法参数上。

  • 功能:从请求中解析所有URI变量,并将它们作为map提供给方法参数。当你想以map的形式访问所有路径变量,而不是单独绑定每一个时,这非常有用。

  • 使用示例

    @GetMapping("/owners/{ownerId}/pets/{petId}")
    public ResponseEntity<?> findPet(@PathVariable Map<String, String> pathVars) {
        String ownerId = pathVars.get("ownerId");
        String petId = pathVars.get("petId");
        // 'pathVars' 是由 PathVariableMapMethodArgumentResolver 解析的
    }
    

    在这种情况下,PathVariableMapMethodArgumentResolver创建一个包含ownerIdpetId路径变量的map,并将其绑定到pathVars参数。

关键区别
  • 单个与多个PathVariableMethodArgumentResolver用于直接映射到方法参数的单个路径变量,而PathVariableMapMethodArgumentResolver用于以map形式处理多个路径变量。

  • 参数类型PathVariableMethodArgumentResolver处理带有@PathVariable注解并支持各种数据类型(如LongString)的参数。而PathVariableMapMethodArgumentResolver则特定于Map<String, String>类型的参数。

  • 使用场景:当处理单独的路径变量时,使用PathVariableMethodArgumentResolver。当你需要集体访问所有路径变量或变量名称不确定时,使用PathVariableMapMethodArgumentResolver

总结来说,这两个解析器的选择取决于你在Spring应用程序中如何处理路径变量——是作为单个变量还是作为map中的变量集合。

8.自定义类型参数

@PathVariable 默认不能处理自定义类型的方法参数,需要编写转换器:

自定义类型处理条件
  1. 类型转换器(Converter)
    Spring提供了一个灵活的类型转换机制。为了支持自定义类型,必须为自定义类型实现一个Converter<String, YourType>,并将其注册到Spring的转换服务中。

    实现示例

    public class StringToCustomTypeConverter implements Converter<String, CustomType> {
        @Override
        public CustomType convert(String source) {
            // 自定义转换逻辑
            return new CustomType(source);
        }
    }
    

    注册Converter
    可以通过WebMvcConfigureraddFormatters方法注册转换器。

    @Configuration
    public class WebConfig implements WebMvcConfigurer {
        @Override
        public void addFormatters(FormatterRegistry registry) {
            registry.addConverter(new StringToCustomTypeConverter());
        }
    }
    
  2. 构造函数或静态工厂方法
    自定义类型可以提供一个接受单个String参数的构造函数,或者提供一个静态工厂方法,这样Spring可以通过反射机制直接调用这些方法来创建自定义类型的实例。

    示例

    public class CustomType {
        private String value;
    
        public CustomType(String value) {
            this.value = value;
        }
    
        // 或者静态工厂方法
        public static CustomType fromString(String value) {
            return new CustomType(value);
        }
    
        // getter和setter
    }
    
  3. 全局格式化器和数据绑定
    除了Converter,也可以通过实现Formatter接口来自定义类型格式化,这样可以同时支持字符串与自定义类型的双向转换。

    Formatter示例

    public class CustomTypeFormatter implements Formatter<CustomType> {
        @Override
        public CustomType parse(String text, Locale locale) throws ParseException {
            return new CustomType(text);
        }
    
        @Override
        public String print(CustomType object, Locale locale) {
            return object.toString();
        }
    }
    
使用示例

假设你已经实现并注册了一个合适的ConverterFormatter,那么可以在控制器中这样使用@PathVariable

@RestController
@RequestMapping("/api")
public class MyController {

    @GetMapping("/items/{customVar}")
    public ResponseEntity<String> handleRequest(@PathVariable CustomType customVar) {
        // customVar 将会是自定义类型的一个实例
        return ResponseEntity.ok("Handled custom type: " + customVar.toString());
    }
}
注意事项
  • 类型安全:确保自定义类型的转换逻辑是安全且不会引发异常的,尤其是在处理不受信任的用户输入时。

  • 测试覆盖:为你的转换器和控制器方法编写单元测试,以验证自定义类型的处理行为。

通过上述步骤和配置,@PathVariable可以有效处理自定义类型,提升Spring MVC应用的灵活性和可扩展性。

总结

  • 路径绑定@PathVariable用于从URL路径中提取变量并绑定到方法参数。
  • 灵活使用:可以通过多个路径变量、类型转换和正则表达式来实现复杂的URL匹配。
  • 自动类型转换:Spring自动处理从字符串到Java类型的转换。
  • 使用场景:常用于RESTful API设计中,以实现清晰、语义化的URL结构。

@PathVariable的使用大大增强了Spring MVC应用程序在路径解析和处理方面的灵活性,是开发RESTful API时的重要工具。



@RequestParam

RequestParamMethodArgumentResolver 是 Spring MVC 中用于解析 @RequestParam 注解的方法参数的解析器。它从请求中提取参数值,并将这些值绑定到控制器方法的参数上。

@RequestParam 注解支持多种用法,根据需求不同可以灵活使用。以下是 @RequestParam 注解的各种用法详解:

基本用法

有注解

@GetMapping("/greet")
public String greet(@RequestParam String name) {
    return "Hello, " + name;
}
  • 说明:这里 @RequestParam 注解用于将请求参数 name 的值绑定到方法参数 name 上。
  • 请求 URL/greet?name=John 将返回 "Hello, John"

无注解且为基本类型

@PostMapping("/test/params")
public String getParams(String ua) {
	return "Params are " + ua ;
}
  • 说明@RequestParam 注解的解析器中,会使用supportsParameter方法判断参数是否为简单类型,如果是,则会将请求参数的值绑定到方法参数上。
  • 请求 URL/test/params?ua=John 将返回 "Hello, John"

指定请求参数名称

@GetMapping("/greet")
public String greet(@RequestParam("username") String name) {
    return "Hello, " + name;
}
  • 说明:此处请求参数 username 会绑定到方法参数 name 上。
  • 请求 URL/greet?username=John

设置默认值

@GetMapping("/greet")
public String greet(@RequestParam(name = "name", defaultValue = "Guest") String name) {
    return "Hello, " + name;
}
  • 说明:如果请求参数 name 不存在,将使用默认值 "Guest"
  • 请求 URL/greet 将返回 "Hello, Guest"

参数为可选

@GetMapping("/greet")
public String greet(@RequestParam(name = "name", required = false) String name) {
    return "Hello, " + (name != null ? name : "Guest");
}
  • 说明:参数 name 设置为可选,即使请求中不包含该参数,程序也不会抛出异常。
  • 请求 URL/greet 将返回 "Hello, Guest"

绑定到数组或集合

@GetMapping("/ids")
public String getIds(@RequestParam List<Integer> ids) {
    return "IDs are " + ids;
}
  • 说明:将请求参数 ids 绑定到 List<Integer> 类型的方法参数上。
  • 请求 URL/ids?ids=1&ids=2&ids=3 将返回 "IDs are [1, 2, 3]"

绑定 Map 参数

推荐方式

@GetMapping("/params")
public String getParams(@RequestParam Map<String, String> allParams) {
    return "Params are " + allParams.entrySet();
}
  • 说明:绑定所有请求参数到 Map 类型的方法参数中。
  • 请求 URL/params?name=John&age=30 将返回 "Params are [name=John, age=30]"

不推荐方式

@GetMapping("/api/params")
public String getParams(@RequestParam(name = "name") Map<String, String> allParams) {
	return "Params are " + allParams.entrySet();
}

请求 URL/params?name=John&age=30 将报错Cannot convert value of type ‘java.lang.String’ to required type ‘java.util.Map’,实际请求中字符串无法转为Map

使用 MultiValueMap

@GetMapping("/params")
public String getParams(@RequestParam MultiValueMap<String, String> allParams) {
    return "Params are " + allParams;
}
  • 说明:使用 MultiValueMap 绑定请求参数,适用于同一个参数有多个值的情况。
  • 请求 URL/params?name=John&name=Jane 将返回 "Params are {name=[John, Jane]}"

MultiValueMap介绍:

  • 路径:org.springframework.util.MultiValueMap
  • 作用一个key对应多个value的Map
import com.sun.deploy.util.StringUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

import java.util.Arrays;
import java.util.List;
import java.util.Set;

public class TestDemo {

    public static void main(String[] args) {
        MultiValueMap<String, String> stringMultiValueMap = new LinkedMultiValueMap<>();

        stringMultiValueMap.add("1", "周一");
        stringMultiValueMap.add("1", "星期一");
        stringMultiValueMap.add("2", "周二");
        stringMultiValueMap.add("2", "星期二");
        stringMultiValueMap.add("3", "周三");
        stringMultiValueMap.add("4", "周四");

        //打印所有值
        Set<String> keySet = stringMultiValueMap.keySet();
        for (String key : keySet) {
            List<String> values = stringMultiValueMap.get(key);
            System.out.println(key+":"+StringUtils.join(Arrays.asList(values.toArray())," "));
        }
    }
}

打印结果:

1:周一 星期一
2:周二 星期二
3:周三
4:周四

处理文件上传

@RequestParam 可以与 MultipartFile 一起使用,以支持文件上传:

@PostMapping("/upload")
public String handleFileUpload(@RequestParam("file") MultipartFile file) {
    return "File size: " + file.getSize();
}
  • 说明:绑定上传的文件到 MultipartFile 类型的方法参数。
  • 上传请求POST /upload,请求体包含文件内容,文件由请求体表单上传,名为"file"。

解析器源码

RequestParamMethodArgumentResolver

supportsParameter方法

@Override
public boolean supportsParameter(MethodParameter parameter) {
    if (parameter.hasParameterAnnotation(RequestParam.class)) {
        if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
            RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
            return (requestParam != null && StringUtils.hasText(requestParam.name()));
        }
        else {
            return true;
        }
    }
    else {
        if (parameter.hasParameterAnnotation(RequestPart.class)) {
            return false;
        }
        parameter = parameter.nestedIfOptional();
        if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
            return true;
        }
        else if (this.useDefaultResolution) {
            return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());
        }
        else {
            return false;
        }
    }
}

代码分析

  1. 参数注解检查:

    • 首先检查参数是否有 @RequestParam 注解。如果有,则进入下一步的判断。
  2. Map 类型参数:

    • 如果参数是 Map 类型,并且 @RequestParam 注解的 name 属性非空,则支持该参数。这样做的原因是避免默认情况下将所有请求参数绑定到 Map 中,而是只在明确指定 name 时才支持。
  3. 默认支持:

    • 对于非 Map 类型的参数,只要有 @RequestParam 注解,解析器就支持该参数。
  4. @RequestParam 注解:

    • 如果参数没有 @RequestParam 注解,解析器会进一步检查是否有 @RequestPart 注解。如果有,解析器不支持该参数。
    • 然后,检查参数是否为多部分(即文件上传)类型。如果是,则支持。
    • 最后,如果启用了默认解析(useDefaultResolutiontrue),解析器会检查参数是否为简单类型(例如基本数据类型、包装类型和 String),如果是,解析器支持该参数。即Controller方法参数为简单类型且没有使用注解,也会被支持解析,但只能处理请求requestParams中的参数

resolveName方法

@Override
@Nullable
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
    HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);

    if (servletRequest != null) {
        Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
        if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
            return mpArg;
        }
    }

    Object arg = null;
    MultipartRequest multipartRequest = request.getNativeRequest(MultipartRequest.class);
    if (multipartRequest != null) {
        List<MultipartFile> files = multipartRequest.getFiles(name);
        if (!files.isEmpty()) {
            arg = (files.size() == 1 ? files.get(0) : files);
        }
    }
    if (arg == null) {
        String[] paramValues = request.getParameterValues(name);
        if (paramValues != null) {
            arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
        }
    }
    return arg;
}

代码分析

  1. 请求类型检查:

    • 首先获取 HttpServletRequest 对象,检查请求是否为 Multipart 类型,并尝试从中解析多部分参数。
  2. 解析多部分参数:

    • 使用 MultipartResolutionDelegate 来尝试解析多部分参数。如果解析成功且不为 UNRESOLVABLE(无法解析),则返回解析结果。
  3. 处理 MultipartRequest:

    • 如果请求是 MultipartRequest 类型,尝试获取与参数名对应的文件列表。
    • 如果文件列表不为空,根据文件数量返回单个文件或文件列表。
  4. 处理普通请求参数:

    • 如果之前没有找到相应的参数值,尝试从普通请求参数中获取值。
    • 使用 request.getParameterValues(name) 获取请求参数值数组。
    • 如果参数值数组不为空,根据数组长度返回单个值或值数组。

总结

  • RequestParamMethodArgumentResolver 用于解析 @RequestParam 注解的方法参数。
  • 支持 Map 类型参数需要指定 name 属性。
  • 解析器能够处理多部分请求(例如文件上传)和普通请求参数。
  • 如果启用了默认解析,简单类型参数可以被解析,即使没有 @RequestParam 注解。
  • 对于 MultipartFile 参数或文件列表,解析器能够正确地从请求中提取文件。

RequestParamMapMethodArgumentResolver

RequestParamMapMethodArgumentResolver 是 Spring MVC 中专门用于解析 @RequestParam 注解的 Map 类型参数的解析器。它主要用于将请求参数解析为 MapMultiValueMap 类型的控制器方法参数。以下是对这段代码的详细分析:

supportsParameter 方法

@Override
public boolean supportsParameter(MethodParameter parameter) {
    RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
    return (requestParam != null && Map.class.isAssignableFrom(parameter.getParameterType()) &&
            !StringUtils.hasText(requestParam.name()));
}

说明

  1. 检查 @RequestParam 注解
  • 首先检查参数是否有 @RequestParam 注解。
  1. 检查参数类型是否为 Map
  • 确认参数类型是 Map 或其子类型。
  1. 确保 @RequestParam 注解的 name 属性为空
  • 只有当 name 属性未指定时,此解析器才会支持该参数。这意味着请求的所有参数都将映射到 Map 中,而不是单个命名参数。

resolveArgument 方法

@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
        NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

    ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);

    if (MultiValueMap.class.isAssignableFrom(parameter.getParameterType())) {
        // MultiValueMap
        Class<?> valueType = resolvableType.as(MultiValueMap.class).getGeneric(1).resolve();
        if (valueType == MultipartFile.class) {
            MultipartRequest multipartRequest = MultipartResolutionDelegate.resolveMultipartRequest(webRequest);
            return (multipartRequest != null ? multipartRequest.getMultiFileMap() : new LinkedMultiValueMap<>(0));
        }
        else if (valueType == Part.class) {
            HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
            if (servletRequest != null && MultipartResolutionDelegate.isMultipartRequest(servletRequest)) {
                Collection<Part> parts = servletRequest.getParts();
                LinkedMultiValueMap<String, Part> result = new LinkedMultiValueMap<>(parts.size());
                for (Part part : parts) {
                    result.add(part.getName(), part);
                }
                return result;
            }
            return new LinkedMultiValueMap<>(0);
        }
        else {
            Map<String, String[]> parameterMap = webRequest.getParameterMap();
            MultiValueMap<String, String> result = new LinkedMultiValueMap<>(parameterMap.size());
            parameterMap.forEach((key, values) -> {
                for (String value : values) {
                    result.add(key, value);
                }
            });
            return result;
        }
    }

    else {
        // Regular Map
        Class<?> valueType = resolvableType.asMap().getGeneric(1).resolve();
        if (valueType == MultipartFile.class) {
            MultipartRequest multipartRequest = MultipartResolutionDelegate.resolveMultipartRequest(webRequest);
            return (multipartRequest != null ? multipartRequest.getFileMap() : new LinkedHashMap<>(0));
        }
        else if (valueType == Part.class) {
            HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
            if (servletRequest != null && MultipartResolutionDelegate.isMultipartRequest(servletRequest)) {
                Collection<Part> parts = servletRequest.getParts();
                LinkedHashMap<String, Part> result = CollectionUtils.newLinkedHashMap(parts.size());
                for (Part part : parts) {
                    if (!result.containsKey(part.getName())) {
                        result.put(part.getName(), part);
                    }
                }
                return result;
            }
            return new LinkedHashMap<>(0);
        }
        else {
            Map<String, String[]> parameterMap = webRequest.getParameterMap();
            Map<String, String> result = CollectionUtils.newLinkedHashMap(parameterMap.size());
            parameterMap.forEach((key, values) -> {
                if (values.length > 0) {
                    result.put(key, values[0]);
                }
            });
            return result;
        }
    }
}

说明

  1. 解析 ResolvableType

    • 使用 ResolvableType 来解析方法参数的具体类型,以便区分是 Map 还是 MultiValueMap
  2. 处理 MultiValueMap 类型

    • 检查参数是否是 MultiValueMap 类型。
    • 如果 valueTypeMultipartFile,则处理文件上传请求,并返回 MultiValueMap 类型的文件列表。
    • 如果 valueTypePart,则处理 Part 类型,使用 HttpServletRequest 来获取请求的 Part 对象集合。
    • 否则,处理普通请求参数,将所有请求参数及其值存储在 MultiValueMap 中。
  3. 处理普通 Map 类型

    • 如果参数类型是普通的 Map
      • 如果 valueTypeMultipartFile,则返回文件的 Map
      • 如果 valueTypePart,则返回请求 Part 对象的 Map
      • 否则,返回请求参数的 Map,将每个参数的第一个值作为 Map 的值。
总结
  • 适用场景RequestParamMapMethodArgumentResolver 用于解析 @RequestParam 注解的 Map 类型参数,特别是当没有指定 name 属性时,将请求中所有参数解析为 Map
  • 支持多种类型:支持 MultiValueMap 和普通 Map,分别用于处理多值参数和单值参数。
  • 处理文件上传:能够识别并处理文件上传请求(MultipartFilePart)。
  • 灵活性:根据参数类型自动处理不同的请求类型,确保控制器方法参数能够正确解析和绑定请求数据。
  • 灵活性@RequestParam 可用于绑定请求参数到方法参数,可以指定参数名称、设置默认值、处理缺失参数。
  • 集合支持:可以绑定到数组、ListMap 等集合类型。
  • 文件上传:支持绑定 MultipartFile 类型参数以处理文件上传。
  • 多值处理:可以使用 MultiValueMap 绑定多个值的参数



@RequestPart

@RequestPart 注解用于在 Spring MVC 中处理多部分请求(multipart request),即通常用于上传文件的 HTTP 请求。通过使用 @RequestPart,我们可以将请求中的表单数据绑定到方法参数上。以下是结合 RequestPartMethodArgumentResolver 源代码对 @RequestPart 注解的详细讲解。

基本用法

@RequestPart 用于从请求表单中获取数据,例如文件或 JSON 数据。可以将 @RequestPart 注解用于如下方法参数:

  • MultipartFilejavax.servlet.http.Part 类型的参数
  • 使用 HttpMessageConverter 转换的非文件参数

使用示例

以下是一些 @RequestPart 的用法示例:

  1. 上传单个文件

    @RestController
    @RequestMapping("/test")
    public class ServerController {
    
        @PostMapping("/upload")
        public String handleFileUpload(@RequestPart("file") MultipartFile file) {
            // 处理文件
            return "File uploaded successfully: " + file.getOriginalFilename();
        }
        
    }    
    

    part单文件

  2. 上传文件和表单数据

    @RestController
    @RequestMapping("/test")
    public class ServerController {
        
        @PostMapping("/uploadWithData")
        public String handleFileUploadWithData(
                @RequestPart("file") MultipartFile file,
                @RequestPart("data") String metadata) {
            // 处理文件和元数据
            return "File and data uploaded successfully: " + file.getOriginalFilename()+", \n Data received: "+metadata;
        }
        
    }
    

    part文件与字符串

  3. 上传文件与 JSON 数据

    @RestController
    @RequestMapping("/test")
    public class ServerController {
        
        @PostMapping("/uploadJson")
        public String handleJsonUpload(@RequestPart("file") MultipartFile file, @RequestPart("data") Ua dataObject) {
            // 处理 JSON 数据
            return "File uploaded successfully: " + file.getOriginalFilename()+", \n Data received: " + dataObject.toString();
        }
        
    }
    

    part文件与json

在这些示例中,@RequestPart 用于从请求的表单中提取所需的数据。对于文件上传,它会将文件作为 MultipartFilePart 处理,对于 JSON 数据,它会利用消息转换器将数据转换为指定类型的对象。这样,我们可以方便地在控制器方法中处理复杂的多部分请求。


解析器代码分析

构造方法

RequestPartMethodArgumentResolver 有两个构造方法:

public RequestPartMethodArgumentResolver(List<HttpMessageConverter<?>> messageConverters) {
    super(messageConverters);
}

public RequestPartMethodArgumentResolver(List<HttpMessageConverter<?>> messageConverters,
        List<Object> requestResponseBodyAdvice) {
    super(messageConverters, requestResponseBodyAdvice);
}
  • 这些构造方法接受 HttpMessageConverter 列表用于将请求体转换为 Java 对象,并可选地接受 RequestBodyAdviceResponseBodyAdvice 以进行请求和响应的处理。
supportsParameter 方法
@Override
public boolean supportsParameter(MethodParameter parameter) {
    if (parameter.hasParameterAnnotation(RequestPart.class)) {
        return true;
    }
    else {
        if (parameter.hasParameterAnnotation(RequestParam.class)) {
            return false;
        }
        return MultipartResolutionDelegate.isMultipartArgument(parameter.nestedIfOptional());
    }
}
  • 检查方法参数是否支持 @RequestPart 注解。若参数有 @RequestPart 注解,则返回 true
  • 若参数被 @RequestParam 注解,则返回 false,因为 @RequestParam 是用于查询参数或表单数据的。
  • 对于没有任何注解的参数,使用 MultipartResolutionDelegate 来检查参数是否为多部分请求。
resolveArgument 方法
@Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
        NativeWebRequest request, @Nullable WebDataBinderFactory binderFactory) throws Exception {

    HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
    Assert.state(servletRequest != null, "No HttpServletRequest");

    RequestPart requestPart = parameter.getParameterAnnotation(RequestPart.class);
    boolean isRequired = ((requestPart == null || requestPart.required()) && !parameter.isOptional());
	
    // 取@RequestPart注解name属性值,为空则取Controller方法参数变量名,都为空则报错
    String name = getPartName(parameter, requestPart);
    parameter = parameter.nestedIfOptional();
    Object arg = null;
	
    // 请求参数如果可被解析为 `MultipartFile` 或 `Part`,则直接获取
    Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
    if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
        arg = mpArg;
    }
    
    // 不能被解析为`MultipartFile`或`Part`,则通过 `HttpMessageConverter` 将请求的内容转换为所需类型
    else {
        try {
            HttpInputMessage inputMessage = new RequestPartServletServerHttpRequest(servletRequest, name);
            //调用父类AbstractMessageConverterMethodArgumentResolver的readWithMessageConverters,将请求体表单参数转为Controller方法参数
            arg = readWithMessageConverters(inputMessage, parameter, parameter.getNestedGenericParameterType());
            
            //binderFactory不为空就根据其获取转换类进行转换
            if (binderFactory != null) {
                WebDataBinder binder = binderFactory.createBinder(request, arg, name);
                if (arg != null) {
                    //进行验证
                    validateIfApplicable(binder, parameter);
                    if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
                        throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
                    }
                }
                if (mavContainer != null) {
                    mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
                }
            }
        }
        catch (MissingServletRequestPartException | MultipartException ex) {
            if (isRequired) {
                throw ex;
            }
        }
    }

    // 请求部分为必需但缺失,抛出异常
    if (arg == null && isRequired) {
        if (!MultipartResolutionDelegate.isMultipartRequest(servletRequest)) {
            throw new MultipartException("Current request is not a multipart request");
        }
        else {
            throw new MissingServletRequestPartException(name);
        }
    }
    return adaptArgumentIfNecessary(arg, parameter);
}
  • 检查当前请求是否为 HttpServletRequest 类型。
  • 获取 @RequestPart 注解并确定其是否为必需。
  • 解析请求中的相应部分:
    • 若部分可被解析为 MultipartFilePart,则直接获取。
    • 否则,通过 HttpMessageConverter 将请求的内容转换为所需类型。
  • 验证转换后的对象,如果验证失败且需要抛出异常,则抛出 MethodArgumentNotValidException
  • 如果请求部分为必需但缺失,抛出 MissingServletRequestPartExceptionMultipartException
getPartName 方法
private String getPartName(MethodParameter methodParam, @Nullable RequestPart requestPart) {
    String partName = (requestPart != null ? requestPart.name() : "");
    if (partName.isEmpty()) {
        partName = methodParam.getParameterName();
        if (partName == null) {
            throw new IllegalArgumentException("Request part name for argument type [" +
                    methodParam.getNestedParameterType().getName() +
                    "] not specified, and parameter name information not found in class file either.");
        }
    }
    return partName;
}
  • 确定请求部分的名称:若 @RequestPart 注解有指定名称,则使用该名称;否则,使用方法参数名称。


与@RequestParam区别

@RequestParam无法处理表单参数值为json的情况,无法将其转为Entity,@RequestPart可以。使用表单同时传文件与json串时,需要用@RequestPart才能将json转为需要的实体类。

  1. @RequestParam适用于name-value表单字段,而@RequestPart经常被用于处理复杂内容(例如JSON, XML)
  2. 当方法的参数类型不是String或者原生的MultipartFile/Part时,@RequstParam需要注册并使用ConverterPropertyEditor进行类型转换,而@RequestPart可通过请求体中的Content-Type直接转换(其解析器源码内部使用HttpMessageConverters进行转换)

@RequestPart@RequestParam 是 Spring 框架中用于从 HTTP 请求中提取参数的两种注解,它们的使用场景和处理方式有所不同。以下是它们之间的详细区别:

1. 基本用途
  • @RequestParam:

    • 用于提取请求参数(query parameters)或表单数据(form data)。
    • 适用于 application/x-www-form-urlencoded 编码的请求。
    • 通常用于获取 URL 查询参数或 POST 表单数据。
  • @RequestPart:

    • 用于提取请求中的多部分数据(multipart data),通常用于文件上传。
    • 适用于 multipart/form-data 编码的请求。
    • 可以处理复杂对象,例如文件上传和 JSON 数据。
2. 处理的数据类型
  • @RequestParam:

    • 可以处理基本数据类型(如 Stringint)以及简单的对象类型。
    • 可以将请求参数自动转换为控制器方法参数的类型。
  • @RequestPart:

    • 处理多部分数据,可以是 MultipartFilejavax.servlet.http.Part,或通过 HttpMessageConverter 转换的对象类型。
    • 支持文件上传以及多部分请求中嵌套的 JSON 数据。
3. 使用场景
  • @RequestParam:

    • 获取查询参数:/api/data?id=123
      @GetMapping("/api/data")
      public String getData(@RequestParam("id") int id) {
          return "Data ID: " + id;
      }
      
  • @RequestPart:

    • 上传文件和数据:
      @PostMapping("/upload")
      public String uploadFile(@RequestPart("file") MultipartFile file, 
                               @RequestPart("info") String info) {
          // 处理文件和附加信息
          return "File uploaded: " + file.getOriginalFilename();
      }
      
4. 解析机制
  • @RequestParam:

    • 直接从请求参数中解析。
    • 适用于单值参数和基本集合类型(例如 List<String>)。
  • @RequestPart:

    • 通过 Spring 的 HttpMessageConverter 机制解析复杂对象。
    • 可以处理复杂结构的请求体数据(如嵌套的 JSON)。
5. 必填属性
  • @RequestParam:

    • required 属性默认为 true,表示参数是必需的;可以设置为 false 来使参数可选。
    • 可以提供 defaultValue 以在参数缺失时使用默认值。
  • @RequestPart:

    • required 属性也默认为 true,表示请求部分是必需的;可设置为 false 来允许缺失。
6. 处理示例
  • 使用 @RequestParam 处理查询参数

    @GetMapping("/api/users")
    public String getUser(@RequestParam(name = "username", defaultValue = "guest") String username) {
        return "Hello, " + username;
    }
    

    在上述例子中,如果 username 参数没有提供,将使用默认值 “guest”。

  • 使用 @RequestPart 处理文件上传

    @PostMapping("/uploadFile")
    public String handleFileUpload(@RequestPart("file") MultipartFile file) {
        // 处理上传的文件
        return "Uploaded: " + file.getOriginalFilename();
    }
    

    该例子中,@RequestPart 用于获取上传的文件并返回文件名。

总结
  • @RequestParam 主要用于解析非文件类型的简单参数。
  • @RequestPart 主要用于解析复杂的多部分数据,包括文件和嵌套结构的数据。

两者的选择取决于请求数据的内容和格式。在处理文件上传或多部分请求时,@RequestPart 是更合适的选择,而 @RequestParam 更适合处理简单的请求参数。




@RequestAttribute

@RequestAttribute 是 Spring Framework 中的一个注解,用于将 HTTP 请求属性绑定到控制器方法的参数上。该注解可以用来访问请求范围内的数据,这些数据通常在请求处理器或拦截器中设置。

使用场景

  • 传递数据:在请求到达控制器之前,在拦截器或过滤器中将某些数据添加到请求属性中,控制器可以通过 @RequestAttribute 获取这些数据。

  • 简化数据访问:避免通过 HttpServletRequest 对象手动提取属性。

代码示例

假设有一个拦截器,在请求到达控制器之前,将用户对象添加到请求属性中:

public class UserInterceptor extends HandlerInterceptorAdapter {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        User user = findUserFromSession(request); // 假设这个方法从会话中获取用户
        request.setAttribute("currentUser", user);
        return true;
    }
}

在控制器中,我们可以使用 @RequestAttribute 来获取这个用户对象:

@Controller
public class UserController {

    @GetMapping("/user/profile")
    public String getUserProfile(@RequestAttribute("currentUser") User user, Model model) {
        model.addAttribute("user", user);
        return "profile";
    }
}

在这个例子中,currentUser 是通过拦截器设置到请求中的属性,然后在控制器方法中使用 @RequestAttribute 注解直接获取。

解析器详解

RequestAttributeMethodArgumentResolver 解析器是 HandlerMethodArgumentResolver 的实现类,用于解析 @RequestAttribute 注解的参数。

public class RequestAttributeMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver {

	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		return parameter.hasParameterAnnotation(RequestAttribute.class);
	}

	@Override
	protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
		RequestAttribute ann = parameter.getParameterAnnotation(RequestAttribute.class);
		Assert.state(ann != null, "No RequestAttribute annotation");
		return new NamedValueInfo(ann.name(), ann.required(), ValueConstants.DEFAULT_NONE);
	}

	@Override
	@Nullable
	protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request){
		return request.getAttribute(name, RequestAttributes.SCOPE_REQUEST);
	}

	@Override
	protected void handleMissingValue(String name, MethodParameter parameter) throws ServletException {
		throw new ServletRequestBindingException("Missing request attribute '" + name +
				"' of type " +  parameter.getNestedParameterType().getSimpleName());
	}

}
方法解析
  1. supportsParameter 方法:

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(RequestAttribute.class);
    }
    
    • 这个方法用于判断给定的参数是否支持,即参数上是否有 @RequestAttribute 注解。
  2. createNamedValueInfo 方法:

    @Override
    protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
        RequestAttribute ann = parameter.getParameterAnnotation(RequestAttribute.class);
        Assert.state(ann != null, "No RequestAttribute annotation");
        return new NamedValueInfo(ann.name(), ann.required(), ValueConstants.DEFAULT_NONE);
    }
    
    • 创建 NamedValueInfo 对象,包含注解的名称、是否必须等信息。
  3. resolveName 方法:

    @Override
    @Nullable
    protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) {
        return request.getAttribute(name, RequestAttributes.SCOPE_REQUEST);
    }
    
    • 从请求范围中解析并返回属性的值。
  4. handleMissingValue 方法:

    @Override
    protected void handleMissingValue(String name, MethodParameter parameter) throws ServletException {
        throw new ServletRequestBindingException("Missing request attribute '" + name +
                "' of type " + parameter.getNestedParameterType().getSimpleName());
    }
    
    • 处理缺失的属性值,如果属性是必须的但未找到,则抛出 ServletRequestBindingException 异常。

总结

  • @RequestAttribute 提供了一种简洁的方式来访问请求属性。
  • RequestAttributeMethodArgumentResolver 解析这些注解参数,简化了从请求中提取属性的工作。
  • 在实践中,这通常用于从拦截器中传递信息到控制器方法。

@RequestParam区别

@RequestAttribute注解的参数在项目里是自己解析出来的,并不是前端传递的。具体一点,在项目里的拦截器里会对Token信息进行解析,解析出来的参数重新放在请求里(用httpServletRequest.setAttribute(name, value)),后边接口接收参数时就用这个注解。

@RequestParam注解则表示这个参数是通过前端传递过来的,如果请求里没有这个参数,则会报错400 Bad Request。这个注解用来解析请求路径里的参数(get请求)或者post请求中form表单格式的请求参数;





@RequestHeader

@RequestHeader 是 Spring Framework 中的一个注解,用于将 HTTP 请求头中的值绑定到控制器方法的参数上。它使得从请求头中提取信息变得简单而直接,例如从请求中获取用户代理、语言设置或自定义标头。

使用 @RequestHeader 注解

对应RequestHeaderMethodArgumentResolver解析器处理

@RequestHeader 注解可用于获取单个请求头的值,并将其绑定到方法参数上:

@Controller
public class MyController {

    @GetMapping("/displayHeader")
    public String displayHeader(@RequestHeader("User-Agent") String userAgent, Model model) {
        model.addAttribute("userAgent", userAgent);
        return "displayHeader";
    }
}

在这个例子中,控制器方法 displayHeader 使用 @RequestHeader 注解来获取 User-Agent 请求头的值,并将其传递给 userAgent 参数。

可选参数

  • value: 指定请求头的名称。
  • required: 是否为必需的请求头,默认为 true。如果设置为 false,则请求头不存在时参数值为 null
  • defaultValue: 当请求头不存在时使用的默认值。
@GetMapping("/defaultLanguage")
public String defaultLanguage(
        @RequestHeader(value = "Accept-Language", defaultValue = "en") String language, Model model) {
    model.addAttribute("language", language);
    return "displayLanguage";
}

在这个例子中,Accept-Language 请求头不存在时,使用默认值 "en"

使用 @RequestHeader Map 参数

对应RequestHeaderMapMethodArgumentResolver解析器处理

当需要获取所有请求头时,可以使用 Map<String, String> 类型的参数:

@GetMapping("/headers")
public String getHeaders(@RequestHeader Map<String, String> headers, Model model) {
    model.addAttribute("headers", headers);
    return "displayHeaders";
}

RequestHeaderMethodArgumentResolver 解析器

RequestHeaderMethodArgumentResolver 是一个处理 @RequestHeader 注解的解析器,实现了 HandlerMethodArgumentResolver 接口。以下是其主要方法的解析:

@Override
@Nullable
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
    
    //取请求头数据
	String[] headerValues = request.getHeaderValues(name);
	
    //不为空返回,为空返回null
    if (headerValues != null) {
		return (headerValues.length == 1 ? headerValues[0] : headerValues);
	}
	else {
		return null;
	}
}

RequestHeaderMapMethodArgumentResolver 解析器

RequestHeaderMapMethodArgumentResolver 是另一个解析器,用于将所有请求头绑定到 Map 参数中。与 RequestHeaderMethodArgumentResolver 不同,它不处理单个请求头,而是处理整个请求头集合。

要求参数类型为MultiValueMap与普通Map及其实现,将请求头全部参数绑定到MultiValueMap与普通Map及其实现中

@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
		NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

    //MultiValueMap的处理
	Class<?> paramType = parameter.getParameterType();
	if (MultiValueMap.class.isAssignableFrom(paramType)) {
		MultiValueMap<String, String> result;
		if (HttpHeaders.class.isAssignableFrom(paramType)) {
			result = new HttpHeaders();
		}
		else {
			result = new LinkedMultiValueMap<>();
		}
		for (Iterator<String> iterator = webRequest.getHeaderNames(); iterator.hasNext();) {
			String headerName = iterator.next();
			String[] headerValues = webRequest.getHeaderValues(headerName);
			if (headerValues != null) {
				for (String headerValue : headerValues) {
					result.add(headerName, headerValue);
				}
			}
		}
		return result;
	}
    //Map的处理
	else {
		Map<String, String> result = new LinkedHashMap<>();
		for (Iterator<String> iterator = webRequest.getHeaderNames(); iterator.hasNext();) {
			String headerName = iterator.next();
			String headerValue = webRequest.getHeader(headerName);
			if (headerValue != null) {
				result.put(headerName, headerValue);
			}
		}
		return result;
	}
}

总结

  • @RequestHeader 注解用于绑定请求头到方法参数。
  • RequestHeaderMethodArgumentResolver 解析单个请求头。
  • RequestHeaderMapMethodArgumentResolver 解析所有请求头并将其绑定到 Map 类型参数中。
  • 提供了灵活的参数处理机制,支持默认值和可选参数。



@RequestBody

@RequestBody 是 Spring MVC 中用于将 HTTP 请求体映射到方法参数的注解。它主要用于从请求中读取 JSON 或 XML 格式的数据并自动将其转换为 Java 对象。下面结合 RequestResponseBodyMethodProcessor 类的代码详细讲解 @RequestBody 的用法和机制。

基本用法

@RequestBody 通常用于处理 POST、PUT 等请求方法,它将请求体的内容转换为 Java 对象。下面是一个简单的示例:

@PostMapping("/api/users")
public ResponseEntity<User> createUser(@RequestBody User user) {
    // 处理用户数据
    return ResponseEntity.ok(user);
}

在这个例子中,请求体中的 JSON 数据会被自动转换为 User 对象,前提是请求的 Content-Type 头正确设置为 application/json

搭配其他注解

@PostMapping("/test/params")
public String getParams(@RequestBody Ua date, @RequestParam String tname) {
	return "Params are " + date.getName() +", and " + tname;
}

8c1286cd66f60e49d44b06e1906e71e

解析器RequestResponseBodyMethodProcessor

RequestResponseBodyMethodProcessor 的作用

RequestResponseBodyMethodProcessor 是一个处理器,用于处理 @RequestBody@ResponseBody 注解。它通过 HttpMessageConverter 将请求体解析为 Java 对象,并将 Java 对象序列化为 HTTP 响应体。

构造函数
public RequestResponseBodyMethodProcessor(List<HttpMessageConverter<?>> converters) {
    super(converters);
}
  • 该构造函数接受一个 HttpMessageConverter 的列表,HttpMessageConverter 负责将 HTTP 请求体转换为 Java 对象,以及将 Java 对象转换为 HTTP 响应体。
  • 支持多种数据格式,如 JSON、XML 等,具体取决于配置的 HttpMessageConverter
支持的参数和返回类型
@Override
public boolean supportsParameter(MethodParameter parameter) {
    return parameter.hasParameterAnnotation(RequestBody.class);
}

@Override
public boolean supportsReturnType(MethodParameter returnType) {
    return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
            returnType.hasMethodAnnotation(ResponseBody.class));
}
  • supportsParameter 方法用于检查方法参数是否使用了 @RequestBody 注解。
  • supportsReturnType 方法用于检查方法返回类型或类上是否使用了 @ResponseBody 注解。
参数解析
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
		NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

	parameter = parameter.nestedIfOptional();
    
    //调用`HttpMessageConverter`转换器,将请求体数据转为Controller方法参数对象(主要依据为请求头中的contentType)
	Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
	//获取Controller方法参数名称
    String name = Conventions.getVariableNameForParameter(parameter);

	if (binderFactory != null) {
		WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
		if (arg != null) {
            
            //使用WebDataBinder进行参数校验
			validateIfApplicable(binder, parameter);
			if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
				throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
			}
		}
		if (mavContainer != null) {
			mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
		}
	}

	return adaptArgumentIfNecessary(arg, parameter);
}
  • resolveArgument 方法负责将 HTTP 请求体转换为 Java 对象。
  • 调用了 readWithMessageConverters 方法,该方法通过 HttpMessageConverter 读取并转换请求体。
读取请求体

进入上面的readWithMessageConverters方法内部

@Override
protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter,
        Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {

    HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
    Assert.state(servletRequest != null, "No HttpServletRequest");
    ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(servletRequest);
	
    //将请求体中json数据转为Controller方法参数,
    //进入的是AbstractMessageConverterMethodArgumentResolver的readWithMessageConverters方法
    Object arg = readWithMessageConverters(inputMessage, parameter, paramType);
    if (arg == null && checkRequired(parameter)) {
        throw new HttpMessageNotReadableException("Required request body is missing: " +
                parameter.getExecutable().toGenericString(), inputMessage);
    }
    return arg;
}
  • readWithMessageConverters 方法创建一个 ServletServerHttpRequest 对象,然后使用适当的 HttpMessageConverter 将请求体转换为目标 Java 对象。
  • 如果请求体为空且 @RequestBody 标记为 required,则抛出 HttpMessageNotReadableException 异常。

AbstractMessageConverterMethodArgumentResolver的转换方法

针对@RequestBody注解的解析器RequestResponseBodyMethodProcessor,在对请求体数据进行转换时,最终会进入AbstractMessageConverterMethodArgumentResolver的转换方法readWithMessageConverters中,如下代码处进行关键处理:

  1. 遍历所有messageConverters转换器实现。
  2. if条件中使用转换器的canRead方法判断,哪个转换器实现可以处理当前类型的参数转换。
  3. 如果有支持转换的转换器,调用其read方法,将当前请求体中的json数据转为Controller方法参数。

d1eae3929757ced1fc93a26bfc97d37

最后的body即为转换完成的Controller方法参数实例,此处的read方法执行断点会进入AbstractJackson2HttpMessageConverter中,其内部使用ObjectMapper将json转为Controller方法参数对象

使用注意事项

  1. 请求体类型@RequestBody 主要用于 JSON 或 XML 格式的数据,必须正确设置 Content-Type 头。
  2. 数据校验:可以结合 @Valid 注解使用,以自动触发 Java Bean Validation 机制来验证请求体数据的合法性。
  3. 异常处理:当请求体无法转换为目标对象时,会抛出 HttpMessageNotReadableException 异常,通常需要全局异常处理器来捕获和处理此类异常。

总结

@RequestBody 是处理 HTTP 请求体数据的强大工具,适用于 RESTful API 开发。通过结合 HttpMessageConverter 和数据验证机制,它可以极大简化客户端和服务器之间的数据交互。RequestResponseBodyMethodProcessor 则是 Spring 框架中用于实现这一功能的核心组件,通过它的支持,开发者可以方便地处理各种格式的数据请求和响应。





无注解参数

Controller方法参数未使用任何注解,且类型不是简单类型时,由ModelAttributeMethodProcessor进行解析处理,比如:未使用注解,类型为自定义类型的参数。

Controller方法参数未使用任何注解,且类型是简单类型时,由RequestParamMethodArgumentResolver进行解析处理,比如:未使用注解,类型为String的参数。

注意

  • 简单类型:包括基本数据类型、包装类型、字符串、日期、枚举等。
  • 无注解时,无论参数为何种类型,只能处理requestParams中带有数据的情况,即ip:port?key=value&key=value形式,请求体中的参数无法被解析到参数中

代码示例

无注解情况下,只能解析请求requestParams参数,如:ip:port?key=value&key1=value1,只能将key、value解析到方法参数中,即便有请求体也无法解析

实体类
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;

public class Ua {

    private String name;
    private String age;

    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date birthday;

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }
}
第一种情况

自定义类型参数不加注解,与带@RequestParam注解的简单类型参数

@PostMapping("/test/params")
public String getParams(Ua demo, @RequestParam String tname) {
	return "Params are " + demo.getName() +", and " + tname;
}

请求结果:请求体携带自定义类型json数据时,方法参数Ua对象为null,但是tname有值:

15d8501137a873d9c8e90cc6ced133e

更改请求,添加非请求体参数,正常解析:

http://127.0.0.1:9000/test/params?tname=wzy&name=John&age=30&birthday=2024-08-07

b6aebdbefc00af3bc9fce6af044ec18

第二种情况

带有@RequestBody注解的复杂类型,与不带注解的基本类型

@PostMapping("/test/params")
public String getParams(@RequestBody Ua ua, String tname) {
	return "Params are " + ua.getName() +", and " + tname;
}

结果:因为@RequestBody注解,请求体被解析为ua对象;tname默认被@RequestParam解析:

c2095f88ecee2fdf102a4452169b319

第三种情况

简单类型与自定义类型均没有注解

@GetMapping("/test/params")
public String getParams(Ua ua, String tname) {

	return "Params name: " + ua.getName() +" age: "+ ua.getAge() + " birthday: " + ua.getBirthday() +", and tname: " + tname;
	
}

处理结果:当请求中的参数与方法参数能一一对应时(自定义类型则是进行属性对应),可以成功解析

http://127.0.0.1:9000/test/params?tname=wzy&name=John&age=30&birthday=2024-08-07

c6cf936e5a22ecee9fa8a08e2c18b7f

第四种情况

两个或多个自定义类型不加注解

实体类

public class Ub {

    private String id;
    private String gender;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }
}

Controller

@GetMapping("/test/params")
public String getParams(Ua ua, Ub ub) {
     return "Ua name: " + ua.getName() +" age: "+ ua.getAge() + " birthday: " + ua.getBirthday()
            +", and Ub id: " + ub.getId() +" gender: "+ ub.getGender();
}

解析结果,与第三种差不多,需要参数与实体类属性能对应得上:

http://127.0.0.1:9000/test/params?tname=wzy&name=John&age=30&birthday=2024-08-07&id=1&gender=gril

3e16003cc5ee6589207149165397d12

解析器源码

Controller方法参数未使用任何注解,且类型是简单类型时,由RequestParamMethodArgumentResolver进行解析处理,不是简单类型时,由ModelAttributeMethodProcessor进行解析处理。这里介绍ModelAttributeMethodProcessor

ModelAttributeMethodProcessor支持解析的参数,由其supportsParameter方法决定

  • 使用@ModelAttribute注解必定被ServletModelAttributeMethodProcessor处理
  • 参数未使用任何注解(annotationNotRequired属性为true),且参数为复杂类型,也会被ServletModelAttributeMethodProcessor处理
@Override
public boolean supportsParameter(MethodParameter parameter) {
    return (parameter.hasParameterAnnotation(ModelAttribute.class) ||
            (this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));
}
  1. parameter.hasParameterAnnotation(ModelAttribute.class)
  • 功能:检查方法参数是否使用了@ModelAttribute注解。
  • 解释:如果参数明确标注了@ModelAttribute注解,那么ModelAttributeMethodProcessor会负责处理该参数。这是显式指定模型属性的方式,适用于需要从请求中绑定复杂对象的情况。
  1. this.annotationNotRequired
  • 功能:这是一个布尔值,可理解为参数是否有注解。
  1. !BeanUtils.isSimpleProperty(parameter.getParameterType())
  • 功能:检查参数的类型是否是“简单”类型。
  • 解释:
    • BeanUtils.isSimpleProperty方法用于判断给定类型是否为简单类型(例如,基本数据类型、包装类型、字符串、日期、枚举等)。
    • 如果参数类型不是简单类型,则表示它是一个复杂对象(如自定义Java对象),需要通过模型属性处理。

参数解析方法

@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
        NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

    Assert.state(mavContainer != null, "ModelAttributeMethodProcessor requires ModelAndViewContainer");
    Assert.state(binderFactory != null, "ModelAttributeMethodProcessor requires WebDataBinderFactory");

    //获取方法参数名称,无注解时为参数类名小写
    String name = ModelFactory.getNameForParameter(parameter);
    //获取注解
    ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
    if (ann != null) {
        mavContainer.setBinding(name, ann.binding());
    }

    Object attribute = null;
    BindingResult bindingResult = null;

    if (mavContainer.containsAttribute(name)) {
        attribute = mavContainer.getModel().get(name);
    }
    else {
        try {
            //根据请求参数创建参数实例,即创建一个空的方法参数对象,此时未将数据填入其中
            attribute = createAttribute(name, parameter, binderFactory, webRequest);
        }
        catch (BindException ex) {
            if (isBindExceptionRequired(parameter)) {
                // 没有BindingResult参数 -> 抛出BindException
                throw ex;
            }
            // 否则,暴露null/empty值及相关的BindingResult
            if (parameter.getParameterType() == Optional.class) {
                attribute = Optional.empty();
            }
            else {
                attribute = ex.getTarget();
            }
            bindingResult = ex.getBindingResult();
        }
    }

    if (bindingResult == null) {
        // 创建WebDataBinder,用于Bean属性绑定和验证,将请求中的参数添加到Controller方法参数中
        // 在构建时绑定失败的情况下跳过。
        WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
        if (binder.getTarget() != null) {
            if (!mavContainer.isBindingDisabled(name)) {
                //此处执行属性绑定
                bindRequestParameters(binder, webRequest);
            }
            validateIfApplicable(binder, parameter);
            if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
                throw new BindException(binder.getBindingResult());
            }
        }
        // 值类型适配,也涵盖java.util.Optional
        if (!parameter.getParameterType().isInstance(attribute)) {
            attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
        }
        bindingResult = binder.getBindingResult();
    }

    // 在模型的末尾添加解析的属性和BindingResult
    Map<String, Object> bindingResultModel = bindingResult.getModel();
    mavContainer.removeAttributes(bindingResultModel);
    mavContainer.addAllAttributes(bindingResultModel);

    return attribute;
}

属性绑定bindRequestParameters方法

会调用ServletModelAttributeMethodProcessorbindRequestParameters(),然后进一步调用ServletRequestDataBinderbind()方法,该方法会利用请求构建一个ServletRequestParameterPropertyValues对象,取出请求中的Params(即ip:port?key=value&key=value中的key、value),所以只有在请求的Params中添加参数才会在无注解情况下被解析到Controller方法参数中,而请求体参数不行。




@MatrixVariable

矩阵变量参数,对应的参数解析器:

  • MatrixVariableMethodArgumentResolver
  • MatrixVariableMapMethodArgumentResolver

使用前注意,如果你的项目使用了spring security,注意配置防火墙放行带有分号的路径请求

@Bean
public HttpFirewall StrictHttpFirewall() {
	StrictHttpFirewall shf = new StrictHttpFirewall();

	//允许所有方式的请求通过
	shf.setUnsafeAllowAnyHttpMethod(true);

	//如果请求URL地址中在编码之前或者之后,包含了分号, 即;、%3b、%3B,则该请求会被拒绝,可以通过setAllowSemicolon方法开启或者关闭这一规则。
	shf.setAllowSemicolon(true);

	return shf;
	
}

使用示例

单个参数

@RestController
@RequestMapping("/test")
public class ServerController {

    @GetMapping(value = "/matrix/{param}", produces = {MediaType.APPLICATION_JSON_VALUE})
    public String aaa(@PathVariable("param") String param, @MatrixVariable("var") String var) {
        return "param: " + param +", "+"var: " + var;
    }

}    

请求路径:

http://127.0.0.1:9000/test/matrix/param1;var=matrixVar

结果:

param: param1, var: matrixVar

多个参数

@RestController
@RequestMapping("/test")
public class ServerController {

    @GetMapping(value = "/matrix/{param1}/{param2}", produces = {MediaType.APPLICATION_JSON_VALUE})
    public String bbb(@PathVariable String param1, @PathVariable String param2,
                      @MatrixVariable(pathVar = "param1",name = "var") String var,
                      @MatrixVariable(pathVar = "param1",name = "var1") String var1,
                      @MatrixVariable(pathVar = "param2",name = "var") String var2) {

        return "路径1: " + param1 +", "+"路径2: " + param2 +
                "\n路径1的参数var: " + var + ",  路径1的参数var1: " + var1 +",  路径2的参数var: " + var2;
    }

}  

请求路径:

http://127.0.0.1:9000/test/matrix/param1;var=matrixVar;var1=matrixVar/param2;var=matrixVar2

结果:

路径1: param1, 路径2: param2
路径1的参数var: matrixVar,  路径1的参数var1: matrixVar,  路径2的参数var: matrixVar2

Map类型参数

Map类型参数
接口方法:

@RestController
@RequestMapping("/test")
public class ServerController {

    @GetMapping(value = "/matrix/{param1}/{param2}", produces = {MediaType.APPLICATION_JSON_VALUE})
    public String bbb(@PathVariable String param1, @PathVariable String param2,
                      @MatrixVariable(pathVar = "param1") Map one,
                      @MatrixVariable(pathVar = "param2") Map two) {


        return "路径1: " + one +",  "+"路径2: " + two;
    }

}

请求路径:

http://127.0.0.1:9000/test/matrix/param1;var=matrixVar;var1=matrixVar/param2;var=matrixVar2

结果:

路径1: {var=matrixVar, var1=matrixVar},  路径2: {var=matrixVar2}



@ModelAttribute

在Spring MVC中,@ModelAttribute注解用于将方法参数或方法返回值绑定到一个命名模型属性(model attribute),这个模型属性将被提供给视图(view)进行渲染。它可以用于方法级别和参数级别,每种情况具有不同的作用。下面结合代码详细解释@ModelAttribute的用法。

用法

  1. 方法级别:在方法级别使用@ModelAttribute时,它通常用于准备视图所需的数据。该方法会在每个请求处理方法执行之前被调用,其返回值会添加到模型(Model)中。方法级别的@ModelAttribute通常用于初始化通用数据,比如下拉列表的数据。

  2. 参数级别:在参数级别使用@ModelAttribute时,它用于将HTTP请求参数绑定到一个对象,并将该对象添加到模型中。Spring会自动将请求中的参数映射到对象的字段中,适用于需要绑定表单提交的场景。

代码示例

解析请求参数,无视图

类似@RequestParam的用法,直接取请求参数

@RestController
public class ServerController {

	@GetMapping("/test/params")
    public String getParams(@ModelAttribute(name = "tname") String name) {
        return name;
    }	

}
  • 请求地址为/test/params?tname=John,controller会返回一个John
  • 注意:@ModelAttribute注解参数name必须指定,否则方法参数获取不到值
视图模型应用

准备模板与依赖

在项目resources下建立templates文件夹,存放thymeleaf模板页面

thymeleaf

将如下页面放入templates文件夹中,名字为test.html:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="UTF-8">
  <title>test</title>
</head>
<body>
<h1>Hello,An Demo</h1>
</body>
</html>

thymeleaf的pom:

<!-- Spring Boot Thymeleaf Starter -->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

方法参数使用@ModelAttribut,向模型中添加参数,用于页面显示

/test/params请求来临时,会创建Ua对象放入Model模型中,然后页面会取出Ua的相关属性进行展示

注意:此处用@Controller,而不是@RestController,因为@RestController不会将返回的字符串解析为视图

@Controller
public class ServerController {

    @GetMapping("/test/params")
    public String getParams(@ModelAttribute(name = "ua") Ua ua) {
        ua.setName("wzy");
        ua.setAge("30");
        return "test";
    }
    
}    

修改templates中test.html为:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="UTF-8">
  <title>test</title>
</head>
<body>
<h1>Hello,An Demo</h1>
<p th:text="${ua.name}"></p>
<p th:text="${ua.age}"></p>
</body>
</html>

然后直接访问ip:port/test/params,无需传任何参数,结果为:

@ModelAttribute页面

无返回值方法上使用@ModelAttribut,向模型中添加参数,用于页面显示

/test/params请求来临时:

  1. 会先调用setModel方法取出请求参数test数值,放入模型中;
  2. 然后使用getParams方法返回视图页面,页面会取出模型Model中的相关属性进行展示

注意:此处用@Controller,而不是@RestController,因为@RestController不会将返回的字符串解析为视图

@Controller
public class ServerController {

    @ModelAttribute
    public void setModel(@RequestParam String test, Model model) {
        model.addAttribute("attributeOne", test);
    }

    @GetMapping("/test/params")
    public String getParams() {
        return "test";
    }
    
}    

修改templates中test.html为:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="UTF-8">
  <title>test</title>
</head>
<body>
<h1>Hello,An Demo</h1>
<p th:text="${attributeOne}"></p>
</body>
</html>

然后直接访问ip:port/test/params?test=测试数据,携带test参数,结果为:

@ModelAtteibute页面2

有返回值方法上使用@ModelAttribut,向模型中添加参数,用于页面显示

/test/params请求来临时:

  1. 会先调用setModel方法将Ua对象存入Model模型中,名字默认为ua;
  2. 然后再调用getParams方法返回视图页面,页面会取出模型Model中的相关属性进行展示

注意:此处用@Controller,而不是@RestController,因为@RestController不会将返回的字符串解析为视图

@Controller
public class ServerController {

    @ModelAttribute
    public Ua setModel() {
        Ua ua = new Ua();
        ua.setName("wzy");
        ua.setAge("20");
        return ua;
    }

    @GetMapping("/test/params")
    public String getParams() {
        return "test";
    }
    
}    

修改templates中test.html为:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="UTF-8">
  <title>test</title>
</head>
<body>
<h1>Hello,An Demo</h1>
<p th:text="${ua.name}"></p>
<p th:text="${ua.age}"></p>
</body>
</html>

然后直接访问ip:port/test/params,无需传任何参数,结果为:

@ModelAttribute页面三


代码分析

ModelAttributeMethodProcessor解析器用于@ModelAttribute的处理,这里再贴出其代码进行分析

以下是resolveArgument方法的详细解析,它展示了@ModelAttribute如何在Spring内部处理请求参数绑定:

@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
        NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

    Assert.state(mavContainer != null, "ModelAttributeMethodProcessor requires ModelAndViewContainer");
    Assert.state(binderFactory != null, "ModelAttributeMethodProcessor requires WebDataBinderFactory");

    String name = ModelFactory.getNameForParameter(parameter);
    ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
    if (ann != null) {
        mavContainer.setBinding(name, ann.binding());
    }

    Object attribute = null;
    BindingResult bindingResult = null;

    if (mavContainer.containsAttribute(name)) {
        attribute = mavContainer.getModel().get(name);
    }
    else {
        // 创建属性实例
        try {
            attribute = createAttribute(name, parameter, binderFactory, webRequest);
        }
        catch (BindException ex) {
            if (isBindExceptionRequired(parameter)) {
                // 没有BindingResult参数 -> 抛出BindException
                throw ex;
            }
            // 否则,暴露null/empty值及相关的BindingResult
            if (parameter.getParameterType() == Optional.class) {
                attribute = Optional.empty();
            }
            else {
                attribute = ex.getTarget();
            }
            bindingResult = ex.getBindingResult();
        }
    }

    if (bindingResult == null) {
        // Bean属性绑定和验证
        // 在构建时绑定失败的情况下跳过。
        WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
        if (binder.getTarget() != null) {
            if (!mavContainer.isBindingDisabled(name)) {
                bindRequestParameters(binder, webRequest);
            }
            validateIfApplicable(binder, parameter);
            if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
                throw new BindException(binder.getBindingResult());
            }
        }
        // 值类型适配,也涵盖java.util.Optional
        if (!parameter.getParameterType().isInstance(attribute)) {
            attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
        }
        bindingResult = binder.getBindingResult();
    }

    // 在模型的末尾添加解析的属性和BindingResult
    Map<String, Object> bindingResultModel = bindingResult.getModel();
    mavContainer.removeAttributes(bindingResultModel);
    mavContainer.addAllAttributes(bindingResultModel);

    return attribute;
}

详细解释

  • 方法签名resolveArgument方法用于将请求参数解析并绑定到控制器方法的参数上。MethodParameter表示方法参数,ModelAndViewContainer用于存储模型数据,NativeWebRequest代表原始的HTTP请求,WebDataBinderFactory用于创建WebDataBinder实例。

  • 获取模型名称ModelFactory.getNameForParameter(parameter)方法获取模型属性的名称,通常是参数的名称。

  • 注解处理:如果参数上存在@ModelAttribute注解,会检查并设置是否需要绑定。

  • 模型检查:如果模型中已经存在该名称的属性,则直接使用该属性,否则需要创建一个新的属性实例。

  • 属性实例创建:通过createAttribute方法创建属性实例,如果绑定失败,会捕获BindException异常并根据参数类型决定返回值。

  • 数据绑定和验证:使用WebDataBinder对请求参数进行绑定和验证。验证后,如果出现绑定错误且需要抛出异常,则抛出BindException

  • 类型转换:对绑定后的对象进行类型转换,确保其类型与参数类型一致。

  • 模型更新:将解析后的属性和绑定结果(BindingResult)添加到模型中。

总结

  • @ModelAttribute的主要作用:用于将HTTP请求参数绑定到Java对象,并将该对象作为模型属性添加到视图中。适用于表单处理和数据初始化。

  • 区别于@RequestParam@RequestParam用于绑定单个请求参数值,而@ModelAttribute用于绑定整个表单数据。

  • 数据验证与转换:结合WebDataBinder实现数据绑定、验证和类型转换。

通过@ModelAttribute注解,开发者可以轻松地处理复杂的表单数据绑定,将请求参数自动映射为Java对象,从而简化控制器代码。




Model参数

关于模型Model

在Spring MVC中,“模型”(Model)是指在控制器和视图之间传递数据的机制。模型的主要作用是将处理后的数据传递给视图,以便在前端显示给用户。模型通常与控制器和视图一同使用,形成MVC(Model-View-Controller)设计模式的一部分。

1. 模型的作用
  • 数据传递:模型用于在控制器和视图之间传递数据。在处理用户请求的过程中,控制器将数据存储在模型中,视图从模型中获取数据并将其呈现给用户。

  • 封装数据:模型封装了应用程序中的业务数据和状态。它通常包括应用程序所需的数据结构和方法。

  • 分离关注点:通过将数据处理逻辑与表示逻辑分开,模型有助于实现代码的关注点分离。这种分离使代码更易于维护和测试。

2. Spring MVC中的模型接口

Spring MVC提供了多种接口来支持模型功能:

  • Model:一个接口,提供了一种将数据存储为键值对的简单方式。

    @RequestMapping("/home")
    public String home(Model model) {
        model.addAttribute("message", "Welcome to the home page!");
        return "home";
    }
    

    在这个示例中,model.addAttribute方法用于将数据添加到模型中,视图可以通过模型中的属性名称访问数据。

  • ModelMap:实现了Map接口,提供了键值对的数据结构,允许更灵活的对象存储。

    @RequestMapping("/profile")
    public String userProfile(ModelMap modelMap) {
        modelMap.addAttribute("username", "JohnDoe");
        return "profile";
    }
    
  • ModelAndView:同时持有模型和视图信息,允许在一个对象中设置视图名称和模型数据。

    @RequestMapping("/greeting")
    public ModelAndView greeting() {
        ModelAndView modelAndView = new ModelAndView("greet");
        modelAndView.addObject("message", "Hello, World!");
        return modelAndView;
    }
    
3. 使用场景
  • 显示用户数据:在用户请求页面时,将用户的个人信息加载到模型中,并在视图中显示。

  • 表单处理:在表单提交后,使用模型将表单数据传递到控制器进行处理。

  • 错误消息显示:在用户操作失败时,将错误消息存储到模型中,并在视图中显示给用户。

4. 例子

下面是一个综合示例,演示如何在Spring MVC中使用模型:

@Controller
public class UserController {

    @RequestMapping("/welcome")
    public String welcomeUser(Model model) {
        model.addAttribute("name", "Alice");
        model.addAttribute("greeting", "Hello");
        return "welcome"; // 返回视图名称
    }
}

在这个示例中,welcomeUser方法将用户的名字和问候语添加到模型中,然后返回视图名称。在视图中,可以通过使用模型属性名访问这些数据。

总结

模型在Spring MVC中是一个重要的组成部分,负责将数据从控制器传递到视图。通过使用模型,开发者可以实现数据与表示的分离,提高代码的组织性和可维护性。在实际开发中,选择合适的模型接口(如ModelModelMapModelAndView)可以根据具体需求灵活地传递和管理数据。

使用示例

假设我们有一个控制器方法,它将 Model 作为参数,并返回一个 Model

@Controller
public class ServerController {

    @GetMapping("/test/details")
    public String showProductDetails(@RequestParam("id") String productId, Model model) {
        // 将数据添加到模型中
        model.addAttribute("productId", productId);
        model.addAttribute("productName", "Sample Product");
        model.addAttribute("productPrice", 100.0);

        return "test"; // 返回视图名称
    }

    @GetMapping("/test/update")
    public String updateProduct(@RequestParam("id") String productId, Model model) {
        // 更新产品逻辑
        model.addAttribute("updateStatus", "success");
        model.addAttribute("updatedProductId", productId);
        return "test";
    }

}

在这个示例中:

  • showProductDetails 方法使用 Model 作为参数,并将产品详情添加到模型中。然后,它返回视图名称 test
  • updateProduct 方法同理,但二者返回的视图内容不同,证明不同请求Model不共享。

效果展示

在项目resources下建立templates文件夹,存放如下thymeleaf模板页面,名为test.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="UTF-8">
  <title>test</title>
</head>
<body>
<h1>Hello,An Demo</h1>
<p th:text="${productId}"></p>
<p th:text="${productName}"></p>
<p th:text="${productPrice}"></p>
<p th:text="${updateStatus}"></p>
<p th:text="${updatedProductId}"></p>
</body>
</html>

访问:

http://127.0.0.1:9000/test/details?id=001

ModelMethod

访问:

http://127.0.0.1:9000/test/update?id=002

Model2

解析器代码解析

ModelMethodProcessor 是 Spring MVC 中的一个处理器,用于支持将 Model 对象作为控制器方法的参数和返回值。Model 对象用于在控制器和视图之间传递数据。

ModelMethodProcessor 实现了两个接口:HandlerMethodArgumentResolverHandlerMethodReturnValueHandler

  1. supportsParameter 方法:

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return Model.class.isAssignableFrom(parameter.getParameterType());
    }
    
    • 这个方法用于检查控制器方法的参数是否是 Model 类型。如果参数类型是 Model,则返回 true,表示该处理器支持此参数。
  2. resolveArgument 方法:

    @Override
    @Nullable
    public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
    
        Assert.state(mavContainer != null, "ModelAndViewContainer is required for model exposure");
        return mavContainer.getModel();
    }
    
    • 该方法用于将 Model 参数解析为具体的对象。在这里,它从 ModelAndViewContainer 中获取模型对象并返回,以便在控制器方法中使用。
  3. supportsReturnType 方法:

    @Override
    public boolean supportsReturnType(MethodParameter returnType) {
        return Model.class.isAssignableFrom(returnType.getParameterType());
    }
    
    • 该方法用于检查控制器方法的返回值是否是 Model 类型。如果返回值是 Model,则返回 true,表示该处理器支持此返回类型。
  4. handleReturnValue 方法:

    @Override
    public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
            ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
    
        if (returnValue == null) {
            return;
        }
        else if (returnValue instanceof Model) {
            mavContainer.addAllAttributes(((Model) returnValue).asMap());
        }
        else {
            // should not happen
            throw new UnsupportedOperationException("Unexpected return type [" +
                    returnType.getParameterType().getName() + "] in method: " + returnType.getMethod());
        }
    }
    
    • 该方法处理控制器方法的返回值。如果返回值是 Model,它会将模型中的所有属性添加到 ModelAndViewContainer 中,以便在视图中使用。

总结

ModelMethodProcessor 通过支持将 Model 作为控制器方法的参数和返回值,帮助开发者在控制器和视图之间传递数据。使用 Model 可以轻松地将数据绑定到视图,便于在前端页面中显示动态内容。




HttpEntity参数

在Spring MVC中,RequestEntityResponseEntity是用于处理HTTP请求和响应的类,提供了丰富的API来处理HTTP头信息、请求体和响应状态码。它们在控制器中被广泛使用,以便更灵活地处理HTTP请求和响应。以下是它们的详细用法:

RequestEntity

RequestEntity是一个扩展的HTTP请求封装类,用于包含HTTP请求头、HTTP方法、请求体和URL信息。它通常用于处理复杂的请求,比如需要读取自定义HTTP头信息的请求。

用法示例
import org.springframework.http.HttpMethod;
import org.springframework.http.RequestEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import java.net.URI;

@RestController
public class ServerController {

    //Ua为自定义类型,包括name、age、birthday属性,此处可将请求体的json转为Ua对象
    @PostMapping("/test/params")
    public String getParams(RequestEntity<Ua> requestEntity) {
        HttpMethod method = requestEntity.getMethod();
        URI url = requestEntity.getUrl();
        Ua body = requestEntity.getBody();
        Type type = requestEntity.getType();
        HttpHeaders headers = requestEntity.getHeaders();

        // 处理请求
        return "Received request: " + method + " " + url + " with body: " + body;
    }
}

请求与返回结果:

RequestEntity

RestTemplate用法

MyRequest body = ... //具体请求体参数

RequestEntity<MyRequest> request = RequestEntity
	   .post("https://example.com/{foo}", "bar")
       .accept(MediaType.APPLICATION_JSON)
       .body(body);
	   
ResponseEntity<MyResponse> response = template.exchange(request, MyResponse.class);
功能和优点
  • 读取HTTP头信息:可以轻松获取请求中的所有头信息。

  • 获取请求体:可以直接获取请求体,避免手动解析。

  • 获取请求方法和URL:方便地获取请求的方法类型(如GET、POST)和请求的URL。

  • 注意点:RequestEntity无法获取requestParams中的数据,即如下地址中的name、age等参数:

    127.0.0.1:9000/test/params?name=John&age=30&birthday=2024-08-07
    

ResponseEntity

ResponseEntity是Spring中用来表示HTTP响应的一个封装类,可以包括HTTP状态码、HTTP头和响应体。它用于在控制器中灵活地返回HTTP响应。

用法示例
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ServerController {

    @GetMapping("/test/response")
    public ResponseEntity<Ua> handleResponse() {
        HttpHeaders headers = new HttpHeaders();
        headers.add("Custom-Header", "foo");

        Ua ua = new Ua();
        ua.setName("wzy");
        ua.setAge("20");
        ua.setBirthday(new Date());

        return new ResponseEntity(ua, headers, HttpStatus.OK);

        //created创建响应方式
        //URI location = ...;
        //return ResponseEntity.created(location).header("MyResponseHeader", "MyValue").body("Hello World");
    }
}

请求及返回结果

body:

httpentity返回

状态与header:

响应状态与header

RestTemplate用法

ResponseEntity<String> entity = template.getForEntity("https://example.com", String.class);
String body = entity.getBody();
MediaType contentType = entity.getHeaders().getContentType();
HttpStatus statusCode = entity.getStatusCode();
功能和优点
  • 设置HTTP状态码:可以轻松设置响应的HTTP状态码,例如200 OK, 404 Not Found等。
  • 设置HTTP头信息:可以添加自定义的HTTP头信息到响应中。
  • 设置响应体:可以方便地设置响应体的内容。

常见场景

  1. 处理复杂请求和响应RequestEntityResponseEntity适合用于需要自定义HTTP头和状态码的场景。

  2. 异常处理:在异常处理中,可以使用ResponseEntity返回特定的HTTP状态码和错误信息。

  3. 跨域资源共享(CORS):通过设置HTTP头来控制CORS策略。


解析器源码

对应解析器为HttpEntityMethodProcessor

public class HttpEntityMethodProcessor extends AbstractMessageConverterMethodProcessor {

    //该解析器支持处理的Controller方法参数类型
    //可处理HttpEntity或RequestEntity类型
	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		return (HttpEntity.class == parameter.getParameterType() ||
				RequestEntity.class == parameter.getParameterType());
	}
	
    //该解析器支持处理的Controller方法返回值类型
    //可处理HttpEntity并且不是RequestEntity的类型
	@Override
	public boolean supportsReturnType(MethodParameter returnType) {
		return (HttpEntity.class.isAssignableFrom(returnType.getParameterType()) &&
				!RequestEntity.class.isAssignableFrom(returnType.getParameterType()));
	}

    //参数解析方法
	@Override
	@Nullable
	public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory)
			throws IOException, HttpMediaTypeNotSupportedException {

        //请求转换
		ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
        //获取参数类型
		Type paramType = getHttpEntityType(parameter);
		if (paramType == null) {
			throw new IllegalArgumentException("HttpEntity parameter '" + parameter.getParameterName() +
					"' in method " + parameter.getMethod() + " is not parameterized");
		}
		
        //获取请求体数据并使用转换器转为RequestEntity泛型中的类型参数,然后返回
		Object body = readWithMessageConverters(webRequest, parameter, paramType);
		if (RequestEntity.class == parameter.getParameterType()) {
			return new RequestEntity<>(body, inputMessage.getHeaders(),
					inputMessage.getMethod(), inputMessage.getURI());
		}
		else {
			return new HttpEntity<>(body, inputMessage.getHeaders());
		}
	}

}

总结

  • RequestEntity:用于处理复杂的请求,包含完整的请求信息(方法、头、体、URL)。
  • ResponseEntity:用于构建复杂的响应,提供对HTTP头和状态码的精细控制。

这两个类为处理HTTP请求和响应提供了极大的灵活性和可扩展性,能够满足复杂的Web应用需求。通过结合使用RequestEntityResponseEntity,开发者可以更灵活地处理HTTP通信中的各种场景。




无注解Map参数

Controller方法参数为Map且没有任何注解时,spring会默认将当前模型注入到map中,可由此map参数来改模型数据,对应解析器为MapMethodProcessor

应用示例

以下是使用示例:

getInfo 方法中,使用了 Map 参数。MapMethodProcessor 会注入当前模型到此参数中,允许控制器直接修改模型。

@Controller
public class ServerController {

    // 方法参数示例
    @GetMapping("/test/info")
    public String getInfo(Map<String, Object> model) {
        model.put("info", "This is some information");
        return "test"; // 视图可以访问模型属性
    }

}    

在项目resources下建立templates文件夹,放入如下thymeleaf页面。名为test.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="UTF-8">
  <title>test</title>
</head>
<body>
<h1>Hello,An Demo</h1>
<p th:text="${info}"></p>
</body>
</html>

效果展示

地址:127.0.0.1:9000/test/info

Map参数

代码解析

MapMethodProcessor 是 Spring 中用于处理控制器方法参数和返回值为Map的一个类。它实现了 HandlerMethodArgumentResolverHandlerMethodReturnValueHandler 接口。以下是对该代码的详细解析以及在控制器中的应用举例。

supportsParameter 方法
@Override
public boolean supportsParameter(MethodParameter parameter) {
    return (Map.class.isAssignableFrom(parameter.getParameterType()) &&
            parameter.getParameterAnnotations().length == 0);
}
  • 目的:检查方法参数是否由此处理器支持。
  • 逻辑:如果参数类型是 Map 并且参数没有任何注解(例如 @RequestParam, @ModelAttribute 等),则返回 true。这意味着只有未标注注解的 Map 参数才会被处理。
resolveArgument 方法
@Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
        NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

    Assert.state(mavContainer != null, "ModelAndViewContainer is required for model exposure");
    return mavContainer.getModel();
}
  • 目的:为方法参数解析出对应的参数值。
  • 逻辑:返回 ModelAndViewContainer 中的模型(Model)对象,这允许控制器方法访问并操作将被渲染到视图中的模型数据。
supportsReturnType 方法
@Override
public boolean supportsReturnType(MethodParameter returnType) {
    return Map.class.isAssignableFrom(returnType.getParameterType());
}
  • 目的:检查方法返回值类型是否由此处理器支持。
  • 逻辑:如果返回类型是 Map,则返回 true,表示此处理器可以处理返回 Map 的方法。
handleReturnValue 方法
@Override
@SuppressWarnings({"rawtypes", "unchecked"})
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
        ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

    if (returnValue instanceof Map){
        mavContainer.addAllAttributes((Map) returnValue);
    }
    else if (returnValue != null) {
        // should not happen
        throw new UnsupportedOperationException("Unexpected return type [" +
                returnType.getParameterType().getName() + "] in method: " + returnType.getMethod());
    }
}
  • 目的:处理控制器方法的返回值。
  • 逻辑:如果返回值是 Map,则将 Map 中的所有条目添加到 ModelAndViewContainer 的模型属性中。如果返回值不是 Map,则抛出 UnsupportedOperationException,这种情况不应该发生,因为在 supportsReturnType 方法中已经进行了检查。

总结

  • MapMethodProcessor:用于处理控制器方法中的 Map 参数和返回值。
  • 使用场景:自动将模型作为 Map 提供给没有显式注解的方法,并允许方法返回 Map 来填充模型。
  • 限制:仅适用于未标注其他注解的 Map 类型,并期望 Map 返回类型。

通过这个处理器,开发者可以方便地在控制器中处理模型数据,并确保模型数据可以轻松地在视图中展示和修改。




Errors 或 BindingResult类型参数

在Controller中使用@Valid注解进行校验时,BindingResult会存储检验错误信息,要求在校验参数后有BindingResult参数,见下面示例

使用示例

实体类

为name属性添加非空校验,并在Controller中使用@Valid注解用于检验

import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
import javax.validation.constraints.NotBlank;

public class Ua {

    @NotBlank(message = "name不能为空")
    private String name;

    private String age;

    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date birthday;

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Ua{" +
                "name='" + name + '\'' +
                ", age='" + age + '\'' +
                ", birthday=" + birthday +
                '}';
    }
}

Controller

使用@Valid时确保pom有如下依赖:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

submitUser 方法中,BindingResult 参数紧跟在 @RequestBody 参数 user 之后。ErrorsMethodArgumentResolver 解析器会为 BindingResult 参数提供与 user 对象绑定的验证结果。

@RestController
public class ServerController {

	//user参数校验出现问题后,其校验错误信息会存到result中
    @PostMapping("/test/submitUser")
    public String submitUser(@RequestBody @Valid Ua user,BindingResult result) {

        //如果BindingResult中有错误信息,则将其取出,并将错误信息集合转为按逗号隔开元素的字符串
        if(result.hasErrors()){
          return String.join(",", result.getAllErrors().stream()
                   .map(error ->error.getDefaultMessage()).collect(Collectors.joining(",")));

        }

        //无错误返回成功
        return "success";
    }
    
}    

请求结果

errors

解析器代码解析

ErrorsMethodArgumentResolver 是 Spring MVC 中的一个类,用于在控制器方法中解析 ErrorsBindingResult 类型的参数。该类实现了 HandlerMethodArgumentResolver 接口,主要用于处理验证错误和绑定结果。

public class ErrorsMethodArgumentResolver implements HandlerMethodArgumentResolver {

	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		Class<?> paramType = parameter.getParameterType();
		return Errors.class.isAssignableFrom(paramType);
	}

	@Override
	@Nullable
	public Object resolveArgument(MethodParameter parameter,
			@Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest,
			@Nullable WebDataBinderFactory binderFactory) throws Exception {

		Assert.state(mavContainer != null,
				"Errors/BindingResult argument only supported on regular handler methods");

        // 此处model的key值为:org.springframework.validation.BindingResult.ua
        // 此处model的value值为:BeanPropertyBindingResult,包含errors信息,即name不能为空
		ModelMap model = mavContainer.getModel();
		String lastKey = CollectionUtils.lastElement(model.keySet());
        //如果key值以BindingResult类名,即org.springframework.validation.BindingResult开始
		if (lastKey != null && lastKey.startsWith(BindingResult.MODEL_KEY_PREFIX)) {
            //则根据key值取出BindingResult,返回到Controller方法参数
			return model.get(lastKey);
		}
		
        //找不到抛出异常
		throw new IllegalStateException(
				"An Errors/BindingResult argument is expected to be declared immediately after " +
				"the model attribute, the @RequestBody or the @RequestPart arguments " +
				"to which they apply: " + parameter.getMethod());
	}

}

关键点解析

  1. supportsParameter 方法:

    • 用于判断当前参数是否是 ErrorsBindingResult 类型。
    • 只有在参数类型是 Errors 或其子类时,才会返回 true
  2. resolveArgument 方法:

    • 这个方法用于从 ModelAndViewContainer(存储当前请求的视图模型信息) 中获取 ErrorsBindingResult 对象。
    • 首先检查 mavContainer 是否为 null,因为 ErrorsBindingResult 参数只能在常规处理方法中使用。
    • 然后从 ModelMap 中获取最后一个键,并检查它是否以 BindingResult.MODEL_KEY_PREFIX 开头。
    • 如果找到了这样的键,则返回对应的 BindingResult
    • 如果没有找到合适的 BindingResult,则抛出 IllegalStateException,说明 ErrorsBindingResult 参数必须紧跟在其对应的模型属性参数之后。



@Value注解

用来读取配置值到参数中

控制器用法示例

以下是一个示例,演示如何在控制器方法中使用 @Value 注解来注入配置值:

配置文件(application.yaml)
server:
  port: 9000

app:
  welcomeMessage:
   Hello, welcome to our application!
控制器类
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/test")
public class GreetingController {

    @GetMapping("/greet")
    @ResponseBody
    public String greet(@Value("${app.welcomeMessage}") String welcomeMessage) {
        // 使用注入的值来返回响应
        return welcomeMessage;
    }
}

执行结果

请求

http://127.0.0.1:9000/test/greet

结果

Hello, welcome to our application!
示例解析
  • @Value 注解:方法参数 welcomeMessage@Value 注解标注,注入了配置文件中的 app.welcomeMessage 的值。

  • 注入的值:通过 @Value("${app.welcomeMessage}") 注入配置文件中的欢迎信息。

  • 控制器方法greet 方法通过返回 welcomeMessage 实现了对注入值的使用。

应用场景

  • 配置值注入:将应用配置文件中的值注入到控制器方法参数中,适用于需要根据配置动态调整行为的场景。

  • 简化测试和配置管理:通过 @Value 注解可以方便地更改应用的配置,而不需要修改代码。

  • 支持外部化配置:有助于实现 Spring Boot 的外部化配置特性,通过 @Value 注入不同环境下的配置值。

解析器

ExpressionValueMethodArgumentResolver 是一个用于解析 Spring MVC 控制器方法参数的解析器。它支持解析带有 @Value 注解的方法参数。

支持的参数类型
  • @Value 注解:用于将外部化配置的值注入到 Spring Bean 的字段、方法或构造函数中。在控制器方法参数上使用时,可以从配置文件、环境变量、系统属性等地方读取值。
源码解析
public class ExpressionValueMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver {

	public ExpressionValueMethodArgumentResolver(@Nullable ConfigurableBeanFactory beanFactory) {
		super(beanFactory);
	}
	
    //判断方法参数是否被 @Value 注解标注
	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		return parameter.hasParameterAnnotation(Value.class);
	}
	
    //从 `@Value` 注解中提取信息,创建 `NamedValueInfo` 对象
	@Override
	protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
		Value ann = parameter.getParameterAnnotation(Value.class);
		Assert.state(ann != null, "No Value annotation");
		return new ExpressionValueNamedValueInfo(ann);
	}
	
    //由于 `@Value` 注解没有直接名称解析,返回 `null`,使用其他方式解析
	@Override
	@Nullable
	protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest webRequest) throws Exception {
		// No name to resolve
		return null;
	}
	
    //因为 `@Value` 注解本身不是强制性的,该方法直接抛出不支持操作的异常
	@Override
	protected void handleMissingValue(String name, MethodParameter parameter) throws ServletException {
		throw new UnsupportedOperationException("@Value is never required: " + parameter.getMethod());
	}

	private static final class ExpressionValueNamedValueInfo extends NamedValueInfo {

		private ExpressionValueNamedValueInfo(Value annotation) {
			super("@Value", false, annotation.value());
		}
	}
}

请求到来时,ExpressionValueMethodArgumentResolver的解析操作实际由父类ExpressionValueMethodArgumentResolver的如下方法触发

@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
		NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

	NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
	MethodParameter nestedParameter = parameter.nestedIfOptional();

	Object resolvedName = resolveEmbeddedValuesAndExpressions(namedValueInfo.name);
	if (resolvedName == null) {
		throw new IllegalArgumentException(
				"Specified name must not resolve to null: [" + namedValueInfo.name + "]");
	}
	
    //调用`ExpressionValueMethodArgumentResolver`的同名方法,固定返回null
	Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
	if (arg == null) {
        //有@Value注解标记在方法参数上,此处不为null
		if (namedValueInfo.defaultValue != null) {
            //使用表达式解析出对应配置的值
			arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
		}
		else if (namedValueInfo.required && !nestedParameter.isOptional()) {
			handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
		}
		arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
	}
	
	//................
	
}	



@CookieValue

@CookieValue 是 Spring MVC 提供的一个注解,用于在控制器方法中绑定 HTTP 请求中的 Cookie 值到方法参数上。它可以帮助开发者更方便地从请求中提取 Cookie 信息并进行处理。

用法示例

以下是使用 @CookieValue 的详细示例和说明:

示例代码

Controller

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@RestController
public class ServerController {

    @GetMapping("/test/read-cookie")
    public String readCookie(@CookieValue(value = "sessionId", defaultValue = "defaultSessionId") String sessionId) {
        // 处理逻辑
        return "Session ID: " + sessionId;
    }
}

调用示例

public class TestMain {
    public static void main(String[] args) {
        try {

            URL url = new URL("http://localhost:9000/test/read-cookie");

            HttpURLConnection connection = (HttpURLConnection) url.openConnection();

            connection.setRequestMethod("GET");

            // 设置Cokkie
            connection.setRequestProperty("Cookie", "sessionId=123456");

            // 获取响应码
            int responseCode = connection.getResponseCode();
            System.out.println("Response Code: " + responseCode);

            // 如果响应成功,读取响应值
            if (responseCode == HttpURLConnection.HTTP_OK) {
                BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
                String inputLine;
                StringBuilder response = new StringBuilder();

                while ((inputLine = in.readLine()) != null) {
                    response.append(inputLine);
                }
                in.close();

                System.out.println("Response: " + response.toString());
            } else {
                System.out.println("GET request failed.");
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

调用上面的readCookie方法,结果为:

Response Code: 200
Response: Session ID: 123456
说明
  • @CookieValue 注解:用于从请求中提取名为 "sessionId" 的 Cookie 的值。如果该 Cookie 不存在,则使用 defaultValue 指定的默认值 "defaultSessionId"

  • 方法返回值:在这个示例中,控制器方法返回一个字符串,包含从 Cookie 中读取的 sessionId。这可以用于验证用户的会话状态等。

  • @ResponseBody 注解:用于指示 Spring MVC 将返回值直接写入 HTTP 响应体中,而不是解析为视图名称。

处理多个 Cookie

你还可以在一个方法中读取多个 Cookie:

@GetMapping("/read-multiple-cookies")
@ResponseBody
public String readMultipleCookies(@CookieValue("sessionId") String sessionId,
                                  @CookieValue("username") String username) {
    return "Session ID: " + sessionId + ", Username: " + username;
}

设置和删除 Cookie

  • 设置 Cookie:可以通过 HttpServletResponse 在控制器方法中设置新的 Cookie。
@GetMapping("/set-cookie")
@ResponseBody
public String setCookie(HttpServletResponse response) {
    Cookie cookie = new Cookie("username", "JohnDoe");
    cookie.setMaxAge(7 * 24 * 60 * 60); // 7 天
    response.addCookie(cookie);
    return "Cookie has been set";
}
  • 删除 Cookie:要删除一个 Cookie,可以设置其最大年龄为零。
@GetMapping("/delete-cookie")
@ResponseBody
public String deleteCookie(HttpServletResponse response) {
    Cookie cookie = new Cookie("username", null);
    cookie.setMaxAge(0);
    response.addCookie(cookie);
    return "Cookie has been deleted";
}

注意事项

  1. 默认值:使用 defaultValue 属性可以在 Cookie 不存在时提供默认值。

  2. 必需属性:如果没有提供默认值且请求中缺少指定的 Cookie,Spring 将抛出异常。

  3. 类型转换:Spring 会自动将 Cookie 值转换为方法参数的类型,支持基本数据类型和简单对象类型。

通过使用 @CookieValue,开发者可以轻松从请求中提取和操作 Cookie,增强应用的会话管理和用户跟踪能力。这对于需要维护用户状态或个性化用户体验的 Web 应用程序特别有用。

源码解析

@CookieValue注解对应的解析器为ServletCookieValueMethodArgumentResolver,下面是其解析注解值的方法源码:

@Override
@Nullable
protected Object resolveName(String cookieName, MethodParameter parameter,
                             NativeWebRequest webRequest) throws Exception {

    //从请求中提取出HttpServletRequest对象,并且断言请求对象不为空。如果请求对象为空,将抛出一个异常
    HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
    Assert.state(servletRequest != null, "No HttpServletRequest");
	
    //从请求中获取指定名称的Cookie,cookieName即为@CookieValue注解name或value属性指定的cookie名称
    Cookie cookieValue = WebUtils.getCookie(servletRequest, cookieName);
    
    //如果参数类型是Cookie,则直接返回Cookie对象
    if (Cookie.class.isAssignableFrom(parameter.getNestedParameterType())) {
        return cookieValue;
    }
    //如果cookieValue不为空,并且参数不是Cookie类型,则返回解码后的Cookie值
    else if (cookieValue != null) {
        //对Cookie值进行解码
        return this.urlPathHelper.decodeRequestString(servletRequest, cookieValue.getValue());
    }
    else {
        return null;
    }
}



WebRequest参数

在Spring MVC中,ServletRequestMethodArgumentResolver类负责解析特定的请求参数类型,使得这些参数可以在控制器方法中直接使用。WebRequest是其中一种支持的类型,它提供了对HTTP请求的更高级别的抽象,结合了Servlet API的功能。

使用WebRequest参数的示例

假设我们有一个控制器方法,希望获取请求中的某些参数或者头信息。WebRequest接口可以用来简化对请求参数、头信息以及请求范围变量的访问。

控制器示例
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.WebRequest;

@RestController
public class ServerController {

    @GetMapping("/test/WebRequest")
    public String readRe(WebRequest webRequest) {
        // 获取请求参数
        String paramValue = webRequest.getParameter("paramName");

        // 获取请求头信息
        String headerValue = webRequest.getHeader("test-header");

        // ETag标签值,可以是资源内容的哈希值,会在响应时添加到响应头(key为ETag)给客户端
        String ETagValue = "ETagValue";
        // 检查请求的资源是否修改,会检查请求头携带的If-None-Match参数,值为上次响应中响应头的ETag标签值
        boolean isAttributeExists = webRequest.checkNotModified(ETagValue);

        // 返回一些信息
        return String.format("Param: %s, Header: %s, Is Not Modified: %s",
                paramValue, headerValue, isAttributeExists);
    }
}

代码解释

  • 获取请求参数webRequest.getParameter("paramName")方法用于获取请求中的参数值。这类似于在HttpServletRequest中使用getParameter方法。

  • 获取请求头信息webRequest.getHeader("headerName")方法可以获取请求头的值,与HttpServletRequestgetHeader功能相似。

  • 检查是否修改webRequest.checkNotModified("ETagValue")方法用于检查请求的ETag值是否匹配。这对于处理HTTP缓存是非常有用的,可以减少不必要的传输。

    Etag(Entity Tag)是一种实体标签,用于标识互联网上唯一的内容。它通常用于缓存和网页重载,以提高网页加载速度。

    Etag是由服务器生成的唯一标识,用于表示请求的资源在服务器上的唯一版本:

    • 当客户端请求一个资源时,服务器会返回该资源的Etag值,Etag值存在于响应的header中,key为Etag
    • 下次客户端再次请求该资源时,会发送If-None-Match请求头,其中包含之前接收到的Etag值。
    • 服务器收到请求后,检查请求头中的Etag值与当前资源在服务器上的Etag值是否一致。
    • 如果一致,说明资源未发生变化,服务器会返回304 Not Modified状态码,客户端收到304状态码后,从本地缓存中获取资源,而不是重新从服务器获取。
    • 如果不一致,说明资源发生了变化,服务器会返回新的资源和200 OK状态码。

    Etag的目的是减少网络传输,提高网页加载速度。通过使用Etag,可以避免不必要的数据传输,从而节省网络流量和提高页面加载速度。

调用展示

调用上面的控制器示例代码,传递请求参数与自定义请求头,并验证ETag。注意:请求传递If-None-Match时,其值必须有双引号

添加paramName参数及请求头test-header,没有ETag值

ETage无

添加paramName参数及请求头test-header,ETag值与服务器中的不一致

ETag不一样

添加paramName参数及请求头test-header,ETag值与服务器中的一致

ETag一致

其他

多种检测方式:除了使用ETag,还可以使用checkNotModified(long)方法结合Last-Modified时间戳来检测资源的修改状态。long参数即为时间戳

HTTP规范:根据HTTP规范,推荐同时使用强ETagLast-Modified来提高缓存机制的准确性和效率。对应的是checkNotModified(@Nullable String etag, long lastModifiedTimestamp)方法

使用场景

  • 读取请求参数:当需要在方法中访问多个请求参数或头信息时,使用WebRequest可以使代码更简洁。

  • 条件请求处理:如上例中使用的checkNotModified方法,可以在服务器确定内容没有改变时返回304状态码,从而优化性能。

  • 请求属性管理WebRequest允许在请求范围内添加或访问属性,这对处理临时数据非常有帮助。

总结

使用WebRequest作为方法参数可以简化对请求信息的访问,并且通过Spring的依赖注入机制,使得代码更加模块化和易于测试。这种抽象比传统的HttpServletRequest提供了更灵活的接口,适合用于大多数Spring MVC的应用场景。





ServletRequest参数

在Spring MVC中,ServletRequestMethodArgumentResolver用于解析控制器方法参数类型为ServletRequest及其他相关类型的参数。以下是一个关于如何在控制器中使用ServletRequest作为参数的示例。

使用示例

以下是一个控制器方法的示例,展示了如何使用ServletRequest来访问HTTP请求的详细信息:

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

import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;

@RestController
public class ServerController {
    
    @GetMapping("/test/ServletRequest")
    public String getRequestInfo(ServletRequest request) {

        HttpServletRequest httpRequest = (HttpServletRequest) request;

        // 获取请求的URI
        String requestURI = httpRequest.getRequestURI();

        // 获取请求的HTTP方法(GET, POST等)
        String method = httpRequest.getMethod();

        // 获取请求的参数,格式为key=value
        String queryString = httpRequest.getQueryString();

        // 获取客户端IP地址
        String clientIP = httpRequest.getRemoteAddr();
		
        // 获取请求的参数,直接用key取value
        String param = httpRequest.getParameter("paramName");

        return String.format("Request URI: %s, Method: %s, Query: %s, Client IP: %s, Param: %s",
                requestURI, method, queryString, clientIP, param);
    }

}    

当请求为GET 127.0.0.1:9000/test/ServletRequest?paramName=wzy时,结果为:

Request URI: /test/ServletRequest, Method: GET, Query: paramName=wzy, Client IP: 127.0.0.1, Param: wzy

除此之外,也可以结合ServletRequest与其他参数一起使用,比如:

@PostMapping("/test/ServletRequest")
public String getRequestInfo(ServletRequest request, @RequestBody Ua ua) {
	//只要请求体的json能被正常解析为Ua对象,就没有问题
    //..........
}

代码解释

  • ServletRequest参数:通过将ServletRequest作为控制器方法的参数,我们可以直接访问HTTP请求对象。Spring会自动将当前请求对象注入到这个参数中。

  • 类型转换:我们通常将ServletRequest转换为HttpServletRequest以使用更丰富的API。

  • 获取请求信息

    • URI:使用getRequestURI()方法获取请求的URI。
    • HTTP方法:使用getMethod()获取请求的方法类型(如GET、POST)。
    • 查询参数:使用getQueryString()获取请求的查询字符串。
    • 客户端IP:使用getRemoteAddr()获取请求的客户端IP地址。

用途

使用ServletRequest作为参数可以帮助我们获取关于HTTP请求的详细信息,这在以下场景中非常有用:

  1. 日志记录:记录请求信息以进行分析或监控。
  2. 自定义请求处理:根据请求信息自定义处理逻辑。
  3. 调试:在开发过程中,查看请求的详细信息以便于调试。

小结

在Spring MVC中,ServletRequestMethodArgumentResolver允许我们轻松地在控制器方法中获取HTTP请求的详细信息,便于处理请求和实现自定义逻辑。通过这种方式,我们可以根据请求的不同特性进行更细致的控制和响应。




MultipartRequest参数

在Spring MVC中,MultipartRequest用于处理多部分请求(例如文件上传)。ServletRequestMethodArgumentResolver支持将控制器方法参数解析为MultipartRequest,以便处理上传的文件和其他多部分数据。下面是一个使用MultipartRequest的控制器方法示例。

使用示例

以下示例展示了如何在控制器中使用MultipartRequest来处理文件上传:

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartRequest;

import java.io.IOException;
import java.util.List;

@RestController
@RequestMapping("/upload")
public class FileUploadController {

    @PostMapping("/files")
    public String handleFileUpload(MultipartRequest request) {
        // 获取所有文件
        List<MultipartFile> files = request.getFiles("file");

        StringBuilder response = new StringBuilder();

        // 处理每个文件
        for (MultipartFile file : files) {
            // 处理文件,例如保存到本地
            String filename = file.getOriginalFilename();
            long fileSize = file.getSize();
            response.append("Uploaded file: ").append(filename)
                    .append(" (").append(fileSize).append(" bytes)\n");

            // 这里可以添加文件保存逻辑,例如:file.transferTo(new File("/path/to/save/" + filename));

        }

        return response.toString();
    }
}

代码解释

  • MultipartRequest参数:通过将MultipartRequest作为控制器方法的参数,可以访问上传的文件和其他多部分数据。Spring会自动将当前请求对象注入到这个参数中。

  • 获取文件:使用getFiles(String name)方法可以获取请求中所有名为file的文件。MultipartRequest允许我们访问多部分请求中的多个文件。

  • 处理文件

    • 获取文件的原始文件名:使用getOriginalFilename()方法。
    • 获取文件的大小:使用getSize()方法。
    • 可以对文件进行处理,例如保存到服务器的文件系统中。
  • 错误处理:在文件处理过程中,可以捕获并处理IOException,例如在文件保存失败时返回错误信息。

用途

使用MultipartRequest可以方便地处理Web应用中的文件上传功能,通常用于:

  1. 文件上传:在Web应用中接收和处理用户上传的文件。
  2. 批量上传:支持同时上传多个文件,并对每个文件进行单独处理。
  3. 文件校验:在上传文件之前或之后进行文件类型、大小等校验。

小结

MultipartRequest在Spring MVC中提供了一种强大而灵活的方式来处理文件上传请求。通过这种方式,可以轻松实现文件上传功能,同时保持对请求中其他多部分数据的访问和控制。




ServletResponse参数

在Spring MVC中,ServletResponseMethodArgumentResolver用来解析ServletResponse类型的参数。它允许控制器方法直接访问和操作HttpServletResponse对象。这对于需要手动控制HTTP响应的情况非常有用,比如设置响应头、写入响应体、或更改响应状态码。以下是如何在控制器中使用ServletResponse参数的一个示例:

示例代码

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

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@RestController
@RequestMapping("/test")
public class ResponseController {

    @GetMapping("/download")
    public void downloadFile(HttpServletResponse response) throws IOException {
        // 设置响应头
        response.setContentType("application/octet-stream");
        response.setHeader("Content-Disposition", "attachment; filename=\"example.txt\"");

        // 写入响应体
        String content = "This is an example file for download.";
        response.getOutputStream().write(content.getBytes());

        // 设置响应状态码
        response.setStatus(HttpServletResponse.SC_OK);
    }
}

代码说明

  1. 方法参数 HttpServletResponsedownloadFile方法接受一个HttpServletResponse对象作为参数,Spring会自动注入这个对象。

  2. 设置响应头

    • response.setContentType("application/octet-stream"):设置响应的内容类型为二进制流,这通常用于文件下载。
    • response.setHeader("Content-Disposition", "attachment; filename=\"example.txt\""):设置内容处置为附件下载,并指定文件名为example.txt
  3. 写入响应体

    • 使用response.getOutputStream().write(content.getBytes())直接向响应输出流中写入数据。在这个例子中,我们写入一个简单的字符串作为文件内容。
  4. 设置响应状态码

    • response.setStatus(HttpServletResponse.SC_OK):设置响应状态码为200,表示请求成功。

使用场景

  • 文件下载:提供文件下载功能,动态生成文件内容并传输给客户端。
  • 自定义响应:在需要设置特殊响应头或处理错误响应时,直接操作HttpServletResponse
  • 流媒体传输:对音频、视频流等进行传输时,精确控制HTTP响应。

注意事项

  • 确保数据完整性:在写入响应流时,确保数据完整性和正确性,避免出现部分响应或数据损坏。

  • 关闭流:虽然HttpServletResponse的流在请求结束时由容器自动关闭,但在复杂操作中,手动管理流的打开和关闭仍然是一个好习惯。

  • 设置响应头的时机:在写入响应体之前设置好所有需要的响应头,因为一旦开始写入响应体,响应头就不能再被修改。

  • 还可以同时操作请求与响应:

    @GetMapping("/download")
    public void downloadFile(HttpServletRequest request, HttpServletResponse response) throws IOException {
    	
    	//获取请求路径信息
    	request.getRequestURI();
    	
    	// 设置响应头
        response.setContentType("application/octet-stream");
            
    }        
    

这种方法允许在Spring MVC控制器中直接控制HTTP响应,使得开发者能够实现更多样化的Web功能。




HttpSession参数

在Spring MVC中,HttpSession用于管理用户会话。ServletRequestMethodArgumentResolver支持将控制器方法参数解析为HttpSession,以便在请求处理过程中访问和管理会话数据。下面是一个使用HttpSession的控制器方法示例。

使用示例

以下示例展示了如何在控制器中使用HttpSession来存储和获取会话属性:

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

import javax.servlet.http.HttpSession;

@RestController
@RequestMapping("/session")
public class SessionController {

    // 设置会话属性
    @GetMapping("/set")
    public String setSessionAttribute(HttpSession session) {
        session.setAttribute("username", "john_doe");
        return "Session attribute 'username' set to 'john_doe'";
    }

    // 获取会话属性
    @GetMapping("/get")
    public String getSessionAttribute(HttpSession session) {
        String username = (String) session.getAttribute("username");
        if (username != null) {
            return "Session attribute 'username' is: " + username;
        } else {
            return "No 'username' attribute in session";
        }
    }

    // 删除会话属性
    @GetMapping("/remove")
    public String removeSessionAttribute(HttpSession session) {
        session.removeAttribute("username");
        return "Session attribute 'username' removed";
    }
}

代码解释

  • HttpSession参数:通过将HttpSession作为控制器方法的参数,可以访问和管理用户会话数据。Spring会自动将当前会话对象注入到这个参数中。

  • 设置会话属性:使用session.setAttribute(String name, Object value)方法将会话属性设置为键值对。

  • 获取会话属性:使用session.getAttribute(String name)方法获取会话属性值。如果属性不存在,返回null

  • 删除会话属性:使用session.removeAttribute(String name)方法删除指定的会话属性。

用途

使用HttpSession可以方便地在Web应用中管理用户会话状态,通常用于:

  1. 用户身份管理:在用户登录后,保存用户信息以维持会话状态。
  2. 会话数据共享:在同一用户会话中跨请求共享数据。
  3. 会话级别存储:临时存储在会话范围内的数据,例如购物车信息。

小结

HttpSession在Spring MVC中提供了一种简单而有效的方式来管理用户会话数据,支持在多个请求间保持状态。




@SessionAttribute注解

用法

@SessionAttribute 注解用于访问存储在会话中的属性。在 Spring MVC 中,它用于将会话属性绑定到控制器方法参数上。它允许开发人员轻松地访问会话中的数据,而无需手动操作会话对象。

使用示例

假设我们有一个存储用户信息的会话属性,下面是如何使用 @SessionAttribute 注解来访问它的示例:

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.SessionAttribute;
import org.springframework.ui.Model;

@Controller
public class UserController {

    @GetMapping("/user")
    public String getUserInfo(@SessionAttribute("user") User user, Model model) {
        // 使用会话中的用户对象
        model.addAttribute("user", user);
        return "userProfile";
    }
}

在上面的例子中:

  • @SessionAttribute("user") 注解将会话中名为 “user” 的属性绑定到 User 类型的参数 user 上。
  • 如果会话中没有名为 “user” 的属性,并且该属性被标记为 required=true(默认值),则会抛出异常。

SessionAttributeMethodArgumentResolver 解析

SessionAttributeMethodArgumentResolver 是一个参数解析器,用于处理带有 @SessionAttribute 注解的控制器方法参数。它继承自 AbstractNamedValueMethodArgumentResolver,实现了从会话中解析参数的逻辑。

详细解析代码
public class SessionAttributeMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver {

	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		return parameter.hasParameterAnnotation(SessionAttribute.class);
	}

	@Override
	protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
		SessionAttribute ann = parameter.getParameterAnnotation(SessionAttribute.class);
		Assert.state(ann != null, "No SessionAttribute annotation");
		return new NamedValueInfo(ann.name(), ann.required(), ValueConstants.DEFAULT_NONE);
	}

	@Override
	@Nullable
	protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) {
		return request.getAttribute(name, RequestAttributes.SCOPE_SESSION);
	}

	@Override
	protected void handleMissingValue(String name, MethodParameter parameter) throws ServletException {
		throw new ServletRequestBindingException("Missing session attribute '" + name +
				"' of type " + parameter.getNestedParameterType().getSimpleName());
	}

}
代码解析
  1. supportsParameter 方法:

    • 检查方法参数是否带有 @SessionAttribute 注解。如果有,则此解析器支持该参数。
  2. createNamedValueInfo 方法:

    • 从参数中提取 @SessionAttribute 注解,并创建 NamedValueInfo 对象。此对象包含了注解中定义的属性名称和是否为必需属性。
  3. resolveName 方法:

    • 通过 request.getAttribute(name, RequestAttributes.SCOPE_SESSION) 从会话中获取属性值。
  4. handleMissingValue 方法:

    • 如果会话中不存在所需的属性且它是必需的,则抛出 ServletRequestBindingException,表示会话属性缺失。
总结

SessionAttributeMethodArgumentResolver 使得在 Spring MVC 中使用 @SessionAttribute 访问会话属性变得简单和直接。通过这个解析器,开发人员可以在不直接操作 HttpSession 的情况下轻松访问会话数据。该类处理了解析会话属性的常见需求,并在会话属性缺失时提供了错误处理机制。





SessionStatus参数

SessionStatus 是一个接口,用于指示会话处理是否完成。在 Spring MVC 中,它常用于在处理完业务逻辑后,标记会话处理已完成,以便清除会话中的会话属性。这对于基于会话的表单控制器尤其有用,因为它允许在完成会话处理后手动清理会话。

代码示例

假设我们有一个基于会话的表单控制器,用于处理用户注册:

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.support.SessionStatus;
import org.springframework.ui.Model;

@Controller
@SessionAttributes("user")
public class RegistrationController {

    @GetMapping("/register")
    public String showRegistrationForm(Model model) {
        // 在模型中添加一个新的用户对象以绑定表单数据
        model.addAttribute("user", new User());
        return "registrationForm";
    }

    @PostMapping("/register")
    public String processRegistration(User user, SessionStatus sessionStatus) {
        // 处理用户注册逻辑,例如保存用户到数据库
        // 注册完成后,标记会话处理已完成
        sessionStatus.setComplete();
        return "registrationSuccess";
    }
}
示例解析
  • @SessionAttributes("user"):指定控制器使用的会话属性名称。在这个例子中,会话中将保存一个名为 "user" 的属性。

  • showRegistrationForm 方法:显示用户注册表单。创建一个新的 User 对象并将其添加到模型中。由于 @SessionAttributes 注解,该用户对象将存储在会话中。

  • processRegistration 方法

    • 处理表单提交的用户数据。
    • 调用 sessionStatus.setComplete(),标记会话处理已完成,这会清除会话中由 @SessionAttributes 注解管理的所有属性。
使用场景
  • 清理会话:在控制器方法执行完毕后,使用 SessionStatus 可以显式地指示会话处理结束,并清除与该会话关联的属性。这在表单向导或跨多个请求处理的会话中尤其重要。

  • 确保资源释放:使用 SessionStatus 可以确保不再需要的会话资源得到释放,避免会话中保留不必要的数据。

通过 SessionStatus,开发者可以更精细地控制会话的生命周期和管理会话中的数据。





InputStream参数

InputStream 作为控制器方法参数的用法主要用于处理二进制请求数据,例如上传文件或者读取原始请求体。Spring MVC 可以通过 ServletRequestMethodArgumentResolver 自动将 InputStream 注入到控制器方法中。

使用示例

下面是一个使用 InputStream 的示例,演示如何在控制器方法中处理请求体数据:

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

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;

@RestController
@RequestMapping("/test")
public class TextController {

    @PostMapping("/upload")
    public String upload(InputStream inputStream) throws IOException {

        byte[] buffer = new byte[1024];
        int bytesRead;
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

        // 读取请求体中的数据
        while ((bytesRead = inputStream.read(buffer)) != -1) {
            outputStream.write(buffer, 0, bytesRead);
        }

        // 将字节数据转换为字符串(假设数据是文本)
        String content = new String(outputStream.toByteArray(), StandardCharsets.UTF_8);

        // 处理数据,例如保存到文件或者数据库
        // 这里只是简单打印出来
        System.out.println("Received content: " + content);

        return content;
    }
}

请求体添加字符串的请求结果

inputStream字符串

代码解释

  1. 缓冲区读取:代码中使用 byte[] buffer 定义了一个缓冲区,每次从 InputStream 中读取最多 1024 字节的数据。这种方法可以有效地处理数据流而不必一次性加载到内存中。

  2. ByteArrayOutputStream:创建了一个 ByteArrayOutputStream 实例,用于收集从输入流中读取的字节。ByteArrayOutputStream 会动态调整其内部缓冲区的大小,因此适合用来收集流式数据。

  3. 循环读取:通过 while 循环逐块读取 InputStream 中的数据,直到数据读取完毕(read 方法返回 -1),并将读取到的数据写入 ByteArrayOutputStream 中。

  4. 转换为字符串:使用 ByteArrayOutputStream.toByteArray() 获取所有已读取的字节,并将其转换为字符串。

  5. 打印输出:最终的字符串内容被打印到控制台中,模拟数据处理操作。

注意事项

  • 异常处理:在实际开发中,应在方法中添加异常处理逻辑来处理可能的 IOException

  • 字符编码:确保使用与输入数据相匹配的字符编码(这里假设是 UTF-8),以便正确转换字节为字符串。

小结

使用 InputStream 处理原始请求数据是处理非标准格式或大数据流的有效方法。此方法允许开发人员直接访问请求体数据,而无需中间对象映射。





OutputStream参数

在Spring MVC中,ServletResponseMethodArgumentResolver允许在控制器方法中直接注入OutputStream实例。这对于需要直接操作输出流的场景非常有用,比如生成动态文件或流式数据输出。以下是如何在控制器中使用OutputStream参数的一个示例:

示例代码

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

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;

@RestController
@RequestMapping("/test")
public class FileController {

    //请求时,浏览器自动下载文件
    @GetMapping("/stream")
    public void streamFile(OutputStream outputStream, HttpServletResponse response) throws IOException {
        // 设置响应头
        response.setContentType("application/txt");
        response.setHeader("Content-Disposition", "inline; filename=\"example.txt\"");

        // 模拟的文件内容
        byte[] content = createPdfContent();

        // 将内容写入输出流
        outputStream.write(content);
        outputStream.flush();
    }

    private byte[] createPdfContent() {
        // 生成内容的逻辑,这里只是返回一些示例字节数据
        String text = "This is an example TXT content.";
        return text.getBytes();
    }
}

代码说明

  1. 方法参数 OutputStreamstreamFile方法接受一个OutputStream对象作为参数,Spring会自动注入这个对象。

  2. 设置响应头

    • response.setContentType("application/txt"):设置响应的内容类型为txt文档。
    • response.setHeader("Content-Disposition", "inline; filename=\"example.txt\""):设置内容处置为内联显示,并指定文件名为example.txt
  3. 写入输出流

    • 使用outputStream.write(content)直接向输出流中写入数据。在这个例子中,我们写入模拟的内容。
  4. 刷新流

    • outputStream.flush()确保所有数据被写入并刷新输出流。

使用场景

  • 生成动态文件:用于生成动态生成的文件(如PDF、Excel等)并传输给客户端。
  • 流媒体输出:对于音视频流等需要持续输出的场景,直接操作输出流可以提高性能。
  • 自定义响应:在需要精确控制响应内容和格式时,直接使用输出流可以提供更高的灵活性。

注意事项

  • 确保流的完整性:确保所有需要的数据被完整写入输出流,以避免数据丢失或响应不完整。
  • 正确设置响应头:在写入响应体之前设置好所有需要的响应头,因为一旦开始写入响应体,响应头就不能再被修改。
  • 流的自动关闭:在Spring中,OutputStream在请求结束时由容器自动关闭,但在复杂操作中,确保流的正确管理仍然是一个好习惯。

这种用法允许开发者在Spring MVC中直接控制HTTP响应输出,提供灵活且高效的解决方案。





Reader参数

在 Spring MVC 中,当控制器方法的参数类型为 Reader 时,Spring 会通过 ServletRequestMethodArgumentResolver 自动解析并注入一个 Reader 实例,用于读取请求体的数据。下面是一个使用 Reader 参数的示例:

示例代码

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

import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;

@RestController
@RequestMapping("/test")
public class ApiController {

    @PostMapping("/readContent")
    public String readContent(Reader reader) throws IOException {
        // 使用 BufferedReader 来读取请求体内容
        StringBuilder content = new StringBuilder();
        try (BufferedReader bufferedReader = new BufferedReader(reader)) {
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                content.append(line).append("\n");
            }
        }

        // 打印读取到的请求体内容
        System.out.println("Received request body:");
        System.out.println(content);

        return content.toString();
    }
}

请求结果

reader读取

代码说明

  1. 参数类型 Reader:方法 readContent 中的 Reader 参数被 Spring 自动解析并注入。这是通过 ServletRequestMethodArgumentResolversupportsParameter 方法检查支持的参数类型来实现的。

  2. 读取请求体Reader 提供了一个字符流,可以方便地处理文本数据。通过 BufferedReader 包装后,使用 readLine() 方法逐行读取请求体内容。

  3. 处理和输出:读取的内容被附加到 StringBuilder 中,最后打印到控制台并返回响应消息。

使用场景

使用 Reader 作为方法参数的典型场景是处理大型文本数据或者原始的请求体内容,这种方法通常用于处理 application/jsontext/plain 等内容类型的数据请求。与 InputStream 不同,Reader 更适合文本数据的处理,因为它是字符流而不是字节流。

说明

  1. 适合文本数据Reader 是字符流,更适合读取和处理文本数据,而 InputStream 是字节流,更适合处理二进制数据。

  2. 与内容类型结合:确保在 HTTP 请求头中设置正确的 Content-Type,如 text/plainapplication/json,以便服务器正确解析请求体的格式。

这种方法通常用于需要手动解析请求体数据的场合,可以为开发者提供较高的灵活性来处理复杂的输入数据格式。





Writer参数

在Spring MVC中,ServletResponseMethodArgumentResolver允许在控制器方法中直接注入Writer实例。这对于生成和输出文本内容非常有用,例如生成HTML、JSON或纯文本响应。以下是如何在控制器中使用Writer参数的一个示例:

示例代码

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

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Writer;

@RestController
@RequestMapping("/test")
public class TextController {

    @GetMapping("/write")
    public void writeText(Writer writer, HttpServletResponse response) throws IOException {
        // 设置响应头
        response.setContentType("text/plain");
        response.setCharacterEncoding("UTF-8");

        // 写入文本内容
        writer.write("Hello, this is a text response from Spring MVC!");
        
        // 刷新缓冲区
        writer.flush();
    }
}

请求地址:

http://127.0.0.1:9000/test/write

返回结果:

Hello, this is a text response from Spring MVC!

代码说明

  1. 方法参数 WriterwriteText方法接受一个Writer对象作为参数,Spring会自动注入这个对象。

  2. 设置响应头

    • response.setContentType("text/plain"):设置响应的内容类型为纯文本。
    • response.setCharacterEncoding("UTF-8"):设置字符编码为UTF-8,确保支持多语言字符集。
  3. 写入内容

    • 使用writer.write()方法向响应中写入文本数据。在这个例子中,我们输出一段简单的文本消息。
  4. 刷新缓冲区

    • writer.flush()确保所有数据被写入并刷新缓冲区,确保客户端能够立即接收到完整的响应内容。

使用场景

  • 动态生成文本内容:可以用于生成动态文本内容并返回给客户端,比如文本文件或简单的文本信息。
  • 生成JSON或XML响应:在不使用视图模板的情况下,可以手动构建和返回JSON或XML数据。
  • 自定义文本输出:在需要精确控制文本输出格式和内容时,直接使用Writer可以提供更高的灵活性。

注意事项

  • 正确设置响应头:在写入响应体之前设置好所有需要的响应头,因为一旦开始写入响应体,响应头就不能再被修改。
  • 字符编码:确保字符编码正确设置,以避免出现乱码或字符显示错误的问题。
  • 流的自动关闭:在Spring中,Writer在请求结束时由容器自动关闭,但在复杂操作中,确保流的正确管理仍然是一个好习惯。

这种用法允许开发者在Spring MVC中直接控制文本输出,提供灵活且高效的解决方案。




HttpMethod参数

在Spring MVC中,HttpMethod 可以用作控制器方法的参数。Spring会通过ServletRequestMethodArgumentResolver自动解析请求的方法(GET、POST等)并注入一个 HttpMethod 实例。下面是一个使用 HttpMethod 参数的示例:

示例代码

import org.springframework.http.HttpMethod;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/test")
public class ApiController {

    @RequestMapping(value = "/checkMethod", method = {RequestMethod.GET, RequestMethod.POST, RequestMethod.PUT, RequestMethod.DELETE})
    public String checkRequestMethod(HttpMethod method) {
        // 输出请求方法
        System.out.println("Received request with HTTP method: " + method);

        // 根据请求方法返回不同的响应
        switch (method) {
            case GET:
                return "This is a GET request";
            case POST:
                return "This is a POST request";
            case PUT:
                return "This is a PUT request";
            case DELETE:
                return "This is a DELETE request";
            default:
                return "Unsupported HTTP method";
        }
    }
}

代码说明

  1. 参数类型 HttpMethod:方法 checkRequestMethod 中的 HttpMethod 参数被 Spring 自动解析并注入,表示请求的 HTTP 方法。

  2. 输出请求方法:通过 System.out.println 输出接收到的 HTTP 方法。

  3. 根据请求方法响应:使用 switch 语句,根据请求的方法返回不同的响应消息。

使用场景

使用 HttpMethod 作为参数的场景通常是需要在控制器方法中区分不同的 HTTP 请求类型并执行相应的处理逻辑。例如,可以在同一个方法中处理 GET 和 POST 请求,并根据请求方法执行不同的操作。这种方法通常用于需要灵活处理多种请求方法的RESTful API中。

注意事项

  1. 支持的请求方法:确保 @RequestMapping 中指定的请求方法与 switch 语句中处理的请求方法相匹配。

  2. 兼容性:这种方法适用于 Spring MVC,并需要确保项目中正确配置了 Spring 的注解驱动(如 @EnableWebMvc)。

通过这种方式,可以有效地处理各种HTTP请求方法,提高代码的灵活性和可维护性。





Principal参数

使用场景

假设有一个控制器方法需要访问当前用户的 Principal 信息,可以这样定义:

@RestController
public class UserController {

    @GetMapping("/user")
    public ResponseEntity<String> getUserInfo(Principal principal) {
        return ResponseEntity.ok("Hello, " + principal.getName());
    }
}

在这个例子中,PrincipalMethodArgumentResolver 会自动将当前请求的用户 Principal 对象注入到 getUserInfo 方法的 principal 参数中。

针对的参数类型

这段代码专门用于解析控制器方法中类型为 Principal 的参数。Principal 接口表示经过身份验证的用户,通常用于获取用户的身份信息。

解析器代码详细解释

PrincipalMethodArgumentResolver

supportsParameter 方法

@Override
public boolean supportsParameter(MethodParameter parameter) {
    return Principal.class.isAssignableFrom(parameter.getParameterType());
}
  • 功能:检查给定的方法参数是否由 Principal 类型(或其子类型)组成。
  • 返回值:如果参数类型是 Principal 或其子类,返回 true;否则返回 false
  • 作用:通过这个方法,Spring MVC 确定 PrincipalMethodArgumentResolver 适用于哪些参数。

resolveArgument 方法

@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
        NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

    HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
    if (request == null) {
        throw new IllegalStateException("Current request is not of type HttpServletRequest: " + webRequest);
    }

    Principal principal = request.getUserPrincipal();
    if (principal != null && !parameter.getParameterType().isInstance(principal)) {
        throw new IllegalStateException("Current user principal is not of type [" +
                parameter.getParameterType().getName() + "]: " + principal);
    }

    return principal;
}
  • 参数说明

    • MethodParameter parameter:正在解析的方法参数。
    • ModelAndViewContainer mavContainer:用于处理视图和模型的容器,可以为 null
    • NativeWebRequest webRequest:当前请求的抽象接口,提供对底层 HTTP 请求的访问。
    • WebDataBinderFactory binderFactory:用于创建数据绑定器的工厂,可以为 null
  • 功能

    1. webRequest 中获取 HttpServletRequest 对象。
    2. 检查 HttpServletRequest 对象是否为空,如果为空则抛出异常。
    3. 调用 request.getUserPrincipal() 方法获取当前请求的 Principal 对象。
    4. 如果 Principal 对象不为空,但与期望的参数类型不匹配,则抛出异常。
    5. 返回 Principal 对象。
  • 异常处理

    • 如果当前请求不是 HttpServletRequest 类型,会抛出 IllegalStateException
    • 如果 Principal 对象类型与方法参数类型不匹配,会抛出 IllegalStateException

作用总结

PrincipalMethodArgumentResolver 的作用是自动解析和注入控制器方法中 Principal 类型的参数,提供了一种简化从 HTTP 请求中获取用户身份信息的方式。这在需要访问当前用户信息的控制器方法中非常有用,例如获取用户名称、角色等。




PushBuilder参数

PushBuilder 是 Java Servlet 4.0 中引入的一个接口,用于支持 HTTP/2 服务端推送。通过使用 PushBuilder,服务器可以在客户端请求特定资源时,提前推送其他相关资源(如 CSS、JavaScript、图像等)到客户端,以减少延迟并提高性能。

在 Spring MVC 中,ServletRequestMethodArgumentResolver 可以自动将 PushBuilder 注入到控制器方法参数中。下面是一个使用 PushBuilder 的示例,演示如何在控制器方法中使用它来推送资源:

使用示例

假设我们有一个控制器,需要在用户访问主页时推送相关的 CSS 和 JavaScript 文件:

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

import javax.servlet.http.PushBuilder;

@RestController
@RequestMapping("/home")
public class HomeController {

    @GetMapping
    public String home(PushBuilder pushBuilder) {
        if (pushBuilder != null) {
            // 推送 CSS 文件
            pushBuilder.path("/static/css/style.css").push();
            // 推送 JavaScript 文件
            pushBuilder.path("/static/js/script.js").push();
        }
        return "Welcome to the homepage!";
    }
}

代码解释

  • PushBuilder 参数:通过将 PushBuilder 作为控制器方法的参数,可以在方法中使用它来推送资源。Spring MVC 会自动检测服务器是否支持 HTTP/2 推送,并相应地注入 PushBuilder 实例。

  • 检查 PushBuilder 是否为 null:如果服务器不支持 HTTP/2 推送,PushBuilder 将为 null。因此,在使用 PushBuilder 推送资源之前,最好进行 null 检查。

  • 推送资源:使用 pushBuilder.path("/path/to/resource").push() 方法推送指定路径的资源到客户端。

注意事项

  • 服务器支持:确保服务器支持 HTTP/2 并启用了服务端推送功能。常见的支持 HTTP/2 的服务器包括 Tomcat 9+、Jetty 9.3+、Undertow 2.0+ 等。

  • 适当使用:仅在需要提前加载的资源上使用服务端推送,因为不当的推送可能导致带宽浪费。

小结

通过使用 PushBuilder,可以在适当的时候提升 Web 应用的性能和用户体验。不过,实际使用时需要结合客户端和服务器的能力进行优化。HTTP/2 的服务端推送功能适用于需要降低页面加载时间、减少请求数量的场景。




RedirectAttributes重定向参数

典型使用场景

在控制器方法中,RedirectAttributes通常用于重定向操作,例如:

@PostMapping("/submitForm")
public String submitForm(RedirectAttributes redirectAttributes) {

    // 添加重定向参数
    redirectAttributes.addAttribute("success", true);
    
    // 添加Flash属性
    redirectAttributes.addFlashAttribute("message", "表单提交成功!");

    // 重定向到其他页面
    return "redirect:/formSuccess";
}

在上述示例中,重定向到/formSuccess页面时,success参数将作为查询参数附加到URL中,而message作为Flash属性可以在目标页面中访问。这样可以在重定向之后提供用户反馈。

获取Flash属性

@Controller
public class OrderController {

    @GetMapping("/formSuccess")
    @ResponseBody
    public String orderList(Model model) {
        return (String) model.getAttribute("message");
    }
    
}

另一种重定向获取Flash属性方法

@Controller
public class OrderController {
    @PostMapping("/order")
    public String order(HttpServletRequest req) {
        FlashMap flashMap = (FlashMap) req.getAttribute(DispatcherServlet.OUTPUT_FLASH_MAP_ATTRIBUTE);
        flashMap.put("name", "江南一点雨");
        return "redirect:/orderlist";
    }

    @GetMapping("/orderlist")
    @ResponseBody
    public String orderList(Model model) {
        return (String) model.getAttribute("name");
    }
}

Flash属性存于FlashMap中,FlashMap借助 session,重定向前通过 FlashMapManager 将信息放入FlashMap, 重定向后 再借助 FlashMapManagersession中找到重定向后需要的 FalshMap,从而进行数据传递。

解析器代码分析

在Spring MVC中,RedirectAttributesMethodArgumentResolver类用于解析处理器方法中的RedirectAttributes参数。RedirectAttributes接口在重定向过程中用于传递参数和Flash属性。具体来说,该解析器类支持的处理器方法参数是RedirectAttributes类型。

public class RedirectAttributesMethodArgumentResolver implements HandlerMethodArgumentResolver {

	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		return RedirectAttributes.class.isAssignableFrom(parameter.getParameterType());
	}

	@Override
	public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

		Assert.state(mavContainer != null, "RedirectAttributes argument only supported on regular handler methods");

		ModelMap redirectAttributes;
		if (binderFactory != null) {
			DataBinder dataBinder = binderFactory.createBinder(webRequest, null, DataBinder.DEFAULT_OBJECT_NAME);
			redirectAttributes = new RedirectAttributesModelMap(dataBinder);
		}
		else {
			redirectAttributes  = new RedirectAttributesModelMap();
		}
		mavContainer.setRedirectModel(redirectAttributes);
		return redirectAttributes;
	}

}

代码解析

  • supportsParameter方法:该方法用于判断处理器方法参数是否支持RedirectAttributes类型。当方法参数是RedirectAttributes类型时,返回true,表示支持这种类型的参数。

  • resolveArgument方法:该方法用于解析RedirectAttributes参数,并在重定向过程中传递相关属性。

    1. 检查ModelAndViewContainer:确保mavContainer不为null,因为RedirectAttributes仅支持常规的处理器方法。

    2. 创建RedirectAttributesModelMap对象

      • 如果提供了binderFactory,则使用DataBinder创建一个RedirectAttributesModelMap对象。这有助于在重定向过程中绑定请求参数。
      • 如果没有提供binderFactory,则直接创建一个RedirectAttributesModelMap对象。
    3. 设置重定向模型:通过调用mavContainer.setRedirectModel(redirectAttributes),将创建的RedirectAttributesModelMap对象设置为重定向模型。

    4. 返回重定向属性:返回创建的RedirectAttributes对象以供处理器方法使用。

RedirectAttributes的作用

RedirectAttributes接口在Spring MVC中用于以下情况:

  • 重定向参数:通过addAttribute方法添加的参数会作为查询参数附加到重定向URL中。

  • Flash属性:使用addFlashAttribute方法添加的属性在重定向后临时存储,并在重定向目标方法中使用。Flash属性在会话中暂时保存,以便在重定向之后能够访问,但在随后的请求中将被移除。




Locale参数

在Spring MVC中,Locale可以作为控制器方法的参数使用。Spring会通过ServletRequestMethodArgumentResolver自动解析请求的区域信息(Locale)并将其注入到控制器方法中。以下是一个使用Locale参数的示例:

示例代码

import org.springframework.web.bind.annotation.*;
import java.util.Locale;

@RestController
@RequestMapping("/test")
public class LocaleController {

    @GetMapping("/greet")
    public String greet(Locale locale) {
        // 输出请求的区域信息
        System.out.println("Received request with locale: " + locale);

        // 根据区域信息返回不同的欢迎消息
        if (Locale.CHINA.equals(locale)) {
            return "你好!";
        } else if (Locale.FRANCE.equals(locale)) {
            return "Bonjour!";
        } else {
            return "Hello!";
        }
    }
}

返回你好,打印结果为

Received request with locale: zh_CN

代码说明

  1. 参数类型 Locale:方法 greet 中的 Locale 参数由 Spring 自动解析并注入,表示请求的区域信息。

  2. 输出区域信息:通过 System.out.println 输出接收到的区域信息(Locale)。

  3. 根据区域信息返回响应:使用 if-else 语句,根据请求的区域信息返回不同的欢迎消息。

使用场景

  • 国际化支持:根据用户请求的区域信息动态地提供本地化内容,例如欢迎消息、日期格式化等。
  • 内容定制化:根据用户的语言和国家/地区定制响应内容,以提高用户体验。
  • 统计与分析:获取用户的区域信息用于统计和分析,以便更好地理解用户分布和偏好。

注意事项

  1. 区域信息来源:Spring会根据请求头中的Accept-Language来解析区域信息,因此确保客户端请求中包含正确的语言头。

  2. 默认区域设置:如果没有指定语言头,Spring会使用默认的区域信息,这通常是在应用程序配置中指定的。

  3. 兼容性:确保在Spring MVC环境中使用,且项目中已配置国际化支持(如MessageSource等)。

通过这种方式,可以方便地根据用户的区域信息来定制应用程序的行为,提高国际化支持和用户体验。




TimeZone参数

在Spring MVC中,可以在控制器方法中使用TimeZone作为参数类型。这可以用于根据客户端的时区信息执行一些操作,例如格式化日期和时间。Spring会通过ServletRequestMethodArgumentResolver自动解析并注入客户端的时区信息。以下是一个使用TimeZone参数的示例:

示例代码

import org.springframework.web.bind.annotation.*;
import java.util.TimeZone;
import java.text.SimpleDateFormat;
import java.util.Date;

@RestController
@RequestMapping("/test")
public class TimeZoneController {

    @GetMapping("/currentTime")
    public String getCurrentTime(TimeZone timeZone) {
        // 设置日期格式
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        
        // 设置时区
        dateFormat.setTimeZone(timeZone);

        // 格式化当前日期时间
        String currentTime = dateFormat.format(new Date());

        // 输出请求的时区信息
        System.out.println("Received request with timezone: " + timeZone.getID());

        // 返回格式化后的当前时间
        return "Current time in your timezone (" + timeZone.getID() + "): " + currentTime;
    }
}

请求结果为:

Received request with timezone: Asia/Shanghai

代码说明

  1. 参数类型 TimeZone:方法 getCurrentTime 中的 TimeZone 参数由 Spring 自动解析并注入,表示请求的时区信息。

  2. 设置日期格式:使用SimpleDateFormat来格式化日期和时间。

  3. 设置时区:通过dateFormat.setTimeZone(timeZone)设置SimpleDateFormat的时区。

  4. 格式化当前时间:根据客户端的时区信息格式化当前时间,并将其返回给客户端。

  5. 输出时区信息:通过System.out.println输出接收到的时区信息。

使用场景

  • 时间格式化:根据用户的时区信息动态地格式化日期和时间,以提供更好的用户体验。
  • 时间差计算:在服务器和客户端之间进行时间差计算时,获取客户端的时区信息可以帮助正确地进行时间转换和计算。
  • 国际化支持:在国际化应用中,根据用户的时区信息显示本地化的时间信息。

注意事项

  1. 时区信息来源:Spring会根据请求头或应用的配置来解析时区信息。确保客户端请求中包含正确的时区信息。

  2. 默认时区设置:如果没有指定时区信息,Spring会使用服务器的默认时区,这通常是在应用程序配置中指定的。

  3. 兼容性:确保在Spring MVC环境中使用,并根据需要在应用中正确配置国际化和时区支持。

通过这种方式,可以方便地根据用户的时区信息来定制应用程序的行为,提高国际化支持和用户体验。




ZoneId参数

在Spring MVC中,可以在控制器方法中使用ZoneId作为参数类型。ZoneId用于表示时区ID,这是Java 8中引入的一种新的处理时区的方式。Spring会通过ServletRequestMethodArgumentResolver自动解析并注入客户端的时区ID。以下是一个使用ZoneId参数的示例:

示例代码

import org.springframework.web.bind.annotation.*;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;

@RestController
@RequestMapping("/test")
public class ZoneIdController {

    @GetMapping("/currentTime")
    public String getCurrentTime(ZoneId zoneId) {
        // 获取当前日期时间并转换为指定时区
        ZonedDateTime zonedDateTime = ZonedDateTime.now(zoneId);
        
        // 格式化日期时间
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z");
        String formattedDateTime = zonedDateTime.format(formatter);

        // 输出请求的ZoneId信息
        System.out.println("Received request with ZoneId: " + zoneId);

        // 返回格式化后的当前时间
        return "Current time in your ZoneId (" + zoneId + "): " + formattedDateTime;
    }
}

请求结果:

Received request with ZoneId: Asia/Shanghai

代码说明

  1. 参数类型 ZoneId:方法 getCurrentTime 中的 ZoneId 参数由 Spring 自动解析并注入,表示请求的时区ID。

  2. 获取当前时间并转换时区:使用ZonedDateTime.now(zoneId)获取当前时间并转换为指定的时区。

  3. 格式化日期时间:通过DateTimeFormatter格式化ZonedDateTime对象,以生成可读的日期时间字符串。

  4. 输出ZoneId信息:通过System.out.println输出接收到的ZoneId信息,便于调试和日志记录。

使用场景

  • 全球用户支持:根据用户的时区ID显示正确的时间和日期信息,提供全球用户支持。
  • 调度和提醒:在应用中,根据用户所在时区设置调度任务和发送提醒。
  • 数据分析:在数据分析中,根据用户时区分析不同时区用户的行为模式。

注意事项

  1. 时区信息来源:Spring会通过请求头或应用的配置来解析时区ID。确保客户端请求中包含正确的时区信息。

  2. 默认时区设置:如果没有指定时区ID,Spring可能使用服务器的默认时区,这通常是在应用程序配置中指定的。

  3. Java 8:确保应用程序使用Java 8或更高版本,以支持ZoneIdZonedDateTime

Spring Boot是一个开源的Java框架,用于简化Spring应用程序的开发和部署。ControllerSpring Boot中的一个重要组件,用于处理来自客户端的HTTP请求,并返回相应的响应。 在Spring Boot的Controller中,参数解析是将客户端请求中的参数值转换为Controller方法参数的过程。Spring Boot支持多种参数解析方式,包括: 1. PathVariable:通过URL路径中的变量值进行参数解析。在Controller方法参数列表中使用@PathVariable注解来指定变量名称,并通过在URL中使用{变量名}的方式来传递参数值。 2. RequestParam:通过URL中的查询字符串或表单参数进行解析。在Controller方法参数列表中使用@RequestParam注解来指定参数名称,并通过URL中使用?参数名=参数值的方式来传递参数值。 3. RequestBody:通过请求体中的内容进行解析。在Controller方法参数列表中使用@RequestBody注解来指定参数类型,并自动将请求体中的内容转换为对应的Java对象。 4. RequestHeader:通过请求头中的参数进行解析。在Controller方法参数列表中使用@RequestHeader注解来指定参数名称,并根据请求头中的参数值进行解析。 5. CookieValue:通过请求中的Cookie进行解析。在Controller方法参数列表中使用@CookieValue注解来指定参数名称,并根据请求中的Cookie值进行解析。 上述这些参数解析方式可以灵活地组合使用,在Controller方法参数列表中可以同时使用多个注解来实现多种参数解析方式。这样可以方便地获取客户端请求中的各种参数值,并进行相应的处理和业务逻辑操作。 总而言之,Spring Boot的Controller中的参数解析功能使得处理客户端请求变得更加简单和灵活,开发者可以根据具体的需求选择合适的参数解析方式,并通过注解来指定参数的名称和类型,从而精确地获取和处理请求中的参数值。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值