温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

SpringBoot解析参数的案例

发布时间:2021-02-05 13:32:51 来源:亿速云 阅读:216 作者:小新 栏目:编程语言

这篇文章给大家分享的是有关SpringBoot解析参数的案例的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。

一、HTTP请求处理流程

不论在SpringBoot还是SpringMVC中,一个HTTP请求会被DispatcherServlet类接收,它本质是一个Servlet,因为它继承自HttpServlet。在这里,Spring负责解析请求,匹配到Controller类上的方法,解析参数并执行方法,最后处理返回值并渲染视图。

SpringBoot解析参数的案例

我们今天的重点在于解析参数,对应到上图的目标方法调用这一步骤。既然说到参数解析,那么针对不同类型的参数,肯定有不同的解析器。Spring已经帮我们注册了一堆这东西。

private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {	List<HandlerMethodArgumentResolver> resolvers = new ArrayList();	resolvers.add(new RequestParamMethodArgumentResolver(this.getBeanFactory(), false));	resolvers.add(new RequestParamMapMethodArgumentResolver());	resolvers.add(new PathVariableMethodArgumentResolver());	resolvers.add(new PathVariableMapMethodArgumentResolver());	resolvers.add(new MatrixVariableMethodArgumentResolver());	resolvers.add(new MatrixVariableMapMethodArgumentResolver());	resolvers.add(new ServletModelAttributeMethodProcessor(false));	resolvers.add(new RequestResponseBodyMethodProcessor(this.getMessageConverters(), this.requestResponseBodyAdvice));	resolvers.add(new RequestPartMethodArgumentResolver(this.getMessageConverters(), this.requestResponseBodyAdvice));	resolvers.add(new RequestHeaderMethodArgumentResolver(this.getBeanFactory()));	resolvers.add(new RequestHeaderMapMethodArgumentResolver());	resolvers.add(new ServletCookieValueMethodArgumentResolver(this.getBeanFactory()));	resolvers.add(new ExpressionValueMethodArgumentResolver(this.getBeanFactory()));	resolvers.add(new SessionAttributeMethodArgumentResolver());	resolvers.add(new RequestAttributeMethodArgumentResolver());	resolvers.add(new ServletRequestMethodArgumentResolver());	resolvers.add(new ServletResponseMethodArgumentResolver());	resolvers.add(new HttpEntityMethodProcessor(this.getMessageConverters(), this.requestResponseBodyAdvice));	resolvers.add(new RedirectAttributesMethodArgumentResolver());	resolvers.add(new ModelMethodProcessor());	resolvers.add(new MapMethodProcessor());	resolvers.add(new ErrorsMethodArgumentResolver());	resolvers.add(new SessionStatusMethodArgumentResolver());	resolvers.add(new UriComponentsBuilderMethodArgumentResolver());	if (this.getCustomArgumentResolvers() != null) {	resolvers.addAll(this.getCustomArgumentResolvers());	}	resolvers.add(new RequestParamMethodArgumentResolver(this.getBeanFactory(), true));	resolvers.add(new ServletModelAttributeMethodProcessor(true));	return resolvers; }

它们有一个共同的接口HandlerMethodArgumentResolver。supportsParameter用来判断方法参数是否可以被当前解析器解析,如果可以就调用resolveArgument去解析。

public interface HandlerMethodArgumentResolver {  //判断方法参数是否可以被当前解析器解析  boolean supportsParameter(MethodParameter var1);  //解析参数  @Nullable  Object resolveArgument(MethodParameter var1, 	@Nullable ModelAndViewContainer var2, 	NativeWebRequest var3, 	@Nullable WebDataBinderFactory var4)throws Exception; }

二、RequestParam

在Controller方法中,如果你的参数标注了RequestParam注解,或者是一个简单数据类型。

@RequestMapping("/test1") @ResponseBody public String test1(String t1, @RequestParam(name = "t2",required = false) String t2,HttpServletRequest request){	logger.info("参数:{},{}",t1,t2);	return "Java"; }

我们的请求路径是这样的:http://localhost:8080/test1?t1=Jack&t2=Java

如果按照以前的写法,我们直接根据参数名称或者RequestParam注解的名称从Request对象中获取值就行。比如像这样:

String parameter = request.getParameter("t1");

在Spring中,这里对应的参数解析器是RequestParamMethodArgumentResolver。与我们的想法差不多,就是拿到参数名称后,直接从Request中获取值。

protected Object resolveName(String name, MethodParameter parameter, 	NativeWebRequest request) throws Exception {	HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);	//...省略部分代码...	if (arg == null) {	String[] paramValues = request.getParameterValues(name);	if (paramValues != null) {	arg = paramValues.length == 1 ? paramValues[0] : paramValues;	}	}	return arg; }

三、RequestBody

如果我们需要前端传输更多的参数内容,那么通过一个POST请求,将参数放在Body中传输是更好的方式。当然,比较友好的数据格式当属JSON。

SpringBoot解析参数的案例

面对这样一个请求,我们在Controller方法中可以通过RequestBody注解来接收它,并自动转换为合适的Java Bean对象。

@ResponseBody @RequestMapping("/test2") public String test2(@RequestBody SysUser user){  logger.info("参数信息:{}",JSONObject.toJSONString(user));  return "Hello"; }

在没有Spring的情况下,我们考虑一下如何解决这一问题呢?

首先呢,还是要依靠Request对象。对于Body中的数据,我们可以通过request.getReader()方法来获取,然后读取字符串,最后通过JSON工具类再转换为合适的Java对象。

比如像下面这样:

@RequestMapping("/test2") @ResponseBody public String test2(HttpServletRequest request) throws IOException {  BufferedReader reader = request.getReader();  StringBuilder builder = new StringBuilder();  String line;  while ((line = reader.readLine()) != null){  	builder.append(line);  }  logger.info("Body数据:{}",builder.toString());  SysUser sysUser = JSONObject.parseObject(builder.toString(), SysUser.class);  logger.info("转换后的Bean:{}",JSONObject.toJSONString(sysUser));  return "Java"; }

当然,在实际场景中,上面的SysUser.class需要动态获取参数类型。

在Spring中,RequestBody注解的参数会由RequestResponseBodyMethodProcessor类来负责解析。

它的解析由父类AbstractMessageConverterMethodArgumentResolver负责。整个过程我们分为三个步骤来看。

1、获取请求辅助信息

在开始之前需要先获取请求的一些辅助信息,比如HTTP请求的数据格式,上下文Class信息、参数类型Class、HTTP请求方法类型等。

protected <T> Object readWithMessageConverters(){	 	boolean noContentType = false;	MediaType contentType;	try {	contentType = inputMessage.getHeaders().getContentType();	} catch (InvalidMediaTypeException var16) {	throw new HttpMediaTypeNotSupportedException(var16.getMessage());	}	if (contentType == null) {	noContentType = true;	contentType = MediaType.APPLICATION_OCTET_STREAM;	}	Class<?> contextClass = parameter.getContainingClass();	Class<T> targetClass = targetType instanceof Class ? (Class)targetType : null;	if (targetClass == null) {	ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);	targetClass = resolvableType.resolve();	}	HttpMethod httpMethod = inputMessage instanceof HttpRequest ?	 ((HttpRequest)inputMessage).getMethod() : null;	//....... }

2、确定消息转换器

上面获取到的辅助信息是有作用的,就是要确定一个消息转换器。消息转换器有很多,它们的共同接口是HttpMessageConverter。在这里,Spring帮我们注册了很多转换器,所以需要循环它们,来确定使用哪一个来做消息转换。

如果是JSON数据格式的,会选择MappingJackson2HttpMessageConverter来处理。它的构造函数正是指明了这一点。

public MappingJackson2HttpMessageConverter(ObjectMapper objectMapper) {	super(objectMapper, new MediaType[]{	MediaType.APPLICATION_JSON, 	new MediaType("application", "*+json")}); }

3、解析

既然确定了消息转换器,那么剩下的事就很简单了。通过Request获取Body,然后调用转换器解析就好了。

protected <T> Object readWithMessageConverters(){  if (message.hasBody()) {	 HttpInputMessage msgToUse = this.getAdvice().beforeBodyRead(message, parameter, targetType, converterType);	 body = genericConverter.read(targetType, contextClass, msgToUse);	 body = this.getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);  } }

再往下就是Jackson包的内容了,不再深究。虽然写出来的过程比较啰嗦,但实际上主要就是为了寻找两个东西:

  • 方法解析器RequestResponseBodyMethodProcessor

  • 消息转换器MappingJackson2HttpMessageConverter

都找到之后调用方法解析即可。

四、GET请求参数转换Bean

还有一种写法是这样的,在Controller方法上用Java Bean接收。

@RequestMapping("/test3") @ResponseBody public String test3(SysUser user){  logger.info("参数:{}",JSONObject.toJSONString(user));  return "Java"; }

然后用GET方法请求:

http://localhost:8080/test3?id=1001&name=Jack&password=1234&address=北京市海淀区

URL后面的参数名称对应Bean对象里面的属性名称,也可以自动转换。那么,这里它又是怎么做的呢 ?

笔者首先想到的就是Java的反射机制。从Request对象中获取参数名称,然后和目标类上的方法一一对应设置值进去。

比如像下面这样:

public String test3(SysUser user,HttpServletRequest request)throws Exception {	//从Request中获取所有的参数key 和 value	Map<String, String[]> parameterMap = request.getParameterMap();	Iterator<Map.Entry<String, String[]>> iterator = parameterMap.entrySet().iterator();	//获取目标类的对象	Object target = user.getClass().newInstance();	Field[] fields = target.getClass().getDeclaredFields();	while (iterator.hasNext()){	Map.Entry<String, String[]> next = iterator.next();	String key = next.getKey();	String value = next.getValue()[0];	for (Field field:fields){	String name = field.getName();	if (key.equals(name)){	field.setAccessible(true);	field.set(target,value);	break;	}	}	}	logger.info("userInfo:{}",JSONObject.toJSONString(target));	return "Python"; }

除了反射,Java还有一种内省机制可以完成这件事。我们可以获取目标类的属性描述符对象,然后拿到它的Method对象, 通过invoke来设置。

private void setProperty(Object target,String key,String value) {  try {	 PropertyDescriptor propDesc = new PropertyDescriptor(key, target.getClass());	 Method method = propDesc.getWriteMethod();	 method.invoke(target, value);  } catch (Exception e) {	 e.printStackTrace();  } }

然后在上面的循环中,我们就可以调用这个方法来实现。

while (iterator.hasNext()){	Map.Entry<String, String[]> next = iterator.next();	String key = next.getKey();	String value = next.getValue()[0];	setProperty(userInfo,key,value); }

为什么要说到内省机制呢?因为Spring在处理这件事的时候,最终也是靠它处理的。

简单来说,它是通过BeanWrapperImpl来处理的。关于BeanWrapperImpl有个很简单的使用方法:

SysUser user = new SysUser(); BeanWrapper wrapper = new BeanWrapperImpl(user.getClass()); wrapper.setPropertyValue("id","20001"); wrapper.setPropertyValue("name","Jack"); Object instance = wrapper.getWrappedInstance(); System.out.println(instance);

wrapper.setPropertyValue最后就会调用到BeanWrapperImpl#BeanPropertyHandler.setValue()方法。

它的setValue方法和我们上面的setProperty方法大致相同。

private class BeanPropertyHandler extends PropertyHandler {  //属性描述符  private final PropertyDescriptor pd;  public void setValue(@Nullable Object value) throws Exception {  	//获取set方法  	Method writeMethod = this.pd.getWriteMethod();  	ReflectionUtils.makeAccessible(writeMethod);  	//设置  	writeMethod.invoke(BeanWrapperImpl.this.getWrappedInstance(), value);  } }

通过上面的方式,就完成了GET请求参数到Java Bean对象的自动转换。

回过头来,我们再看Spring。虽然我们上面写的很简单,但真正用起来还需要考虑的很多很多。Spring中处理这种参数的解析器是ServletModelAttributeMethodProcessor。

它的解析过程在其父类ModelAttributeMethodProcessor.resolveArgument()方法。整个过程,我们也可以分为三个步骤来看。

1、获取目标类的构造函数

根据参数类型,先生成一个目标类的构造函数,以供后面绑定数据的时候使用。

2、创建数据绑定器WebDataBinder

WebDataBinder继承自DataBinder。而DataBinder主要的作用,简言之就是利用BeanWrapper给对象的属性设值。

3、绑定数据到目标类,并返回

在这里,又把WebDataBinder转换成ServletRequestDataBinder对象,然后调用它的bind方法。

接下来有个很重要的步骤是,将request中的参数转换为MutablePropertyValues pvs对象。

然后接下来就是循环pvs,调用setPropertyValue设置属性。当然了,最后调用的其实就是BeanWrapperImpl#BeanPropertyHandler.setValue()

下面有段代码可以更好的理解这一过程,效果是一样的:

//模拟Request参数 Map<String,Object> map = new HashMap(); map.put("id","1001"); map.put("name","Jack"); map.put("password","123456"); map.put("address","北京市海淀区"); //将request对象转换为MutablePropertyValues对象 MutablePropertyValues propertyValues = new MutablePropertyValues(map); SysUser sysUser = new SysUser(); //创建数据绑定器 ServletRequestDataBinder binder = new ServletRequestDataBinder(sysUser); //bind数据 binder.bind(propertyValues); System.out.println(JSONObject.toJSONString(sysUser));

五、自定义参数解析器

我们说所有的消息解析器都实现了HandlerMethodArgumentResolver接口。我们也可以定义一个参数解析器,让它实现这个接口就好了。

首先,我们可以定义一个RequestXuner注解。

@Target({ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RequestXuner {  String name() default "";  boolean required() default false;  String defaultValue() default "default"; }

然后是实现了HandlerMethodArgumentResolver接口的解析器类。

public class XunerArgumentResolver implements HandlerMethodArgumentResolver {  @Override  public boolean supportsParameter(MethodParameter parameter) {   return parameter.hasParameterAnnotation(RequestXuner.class);  }  @Override  public Object resolveArgument(MethodParameter methodParameter,          ModelAndViewContainer modelAndViewContainer,          NativeWebRequest nativeWebRequest,          WebDataBinderFactory webDataBinderFactory){	//获取参数上的注解   RequestXuner annotation = methodParameter.getParameterAnnotation(RequestXuner.class);   String name = annotation.name();	//从Request中获取参数值   String parameter = nativeWebRequest.getParameter(name);   return "HaHa,"+parameter;  } }

不要忘记需要配置一下。

@Configuration public class WebMvcConfiguration extends WebMvcConfigurationSupport {  @Override  protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {   resolvers.add(new XunerArgumentResolver());  } }

一顿操作后,在Controller中我们可以这样使用它:

@ResponseBody @RequestMapping("/test4") public String test4(@RequestXuner(name="xuner") String xuner){  logger.info("参数:{}",xuner);  return "Test4"; }

感谢各位的阅读!关于“SpringBoot解析参数的案例”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,让大家可以学到更多知识,如果觉得文章不错,可以把它分享出去让更多的人看到吧!

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI