日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

SpringBoot是如何解析HTTP参数的?

發布時間:2025/3/21 javascript 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 SpringBoot是如何解析HTTP参数的? 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章轉載自公眾號??清幽之地的博客?,?作者 清幽之地

前言

前幾天筆者在寫Rest接口的時候,看到了一種傳值方式是以前沒有寫過的,就萌生了一探究竟的想法。在此之前,有篇文章曾涉及到這個話題,但那篇文章著重于處理流程的分析,并未深入。

本文重點來看幾種傳參方式,看看它們都是如何被解析并應用到方法參數上的。

一、HTTP請求處理流程

不論在SpringBoot還是SpringMVC中,一個HTTP請求會被?DispatcherServlet類接收,它本質是一個?Servlet,因為它繼承自?HttpServlet。在這里,Spring負責解析請求,匹配到?Controller類上的方法,解析參數并執行方法,最后處理返回值并渲染視圖。

我們今天的重點在于解析參數,對應到上圖的?目標方法調用這一步驟。既然說到參數解析,那么針對不同類型的參數,肯定有不同的解析器。Spring已經幫我們注冊了一堆這東西。

它們有一個共同的接口?HandlerMethodArgumentResolver。?supportsParameter用來判斷方法參數是否可以被當前解析器解析,如果可以就調用?resolveArgument去解析。

public interface HandlerMethodArgumentResolver {//判斷方法參數是否可以被當前解析器解析boolean supportsParameter(MethodParameter var1);//解析參數@NullableObject 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對象中獲取值就行。比如像這樣:

Stringparameter=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。

面對這樣一個請求,我們在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 和 valueMap<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中的參數轉換為?MutablePropertyValuespvs對象。

然后接下來就是循環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 {@Overridepublic boolean supportsParameter(MethodParameter parameter) {return parameter.hasParameterAnnotation(RequestXuner.class);}@Overridepublic 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 {@Overrideprotected 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"; }

六、總結

本文內容通過相關示例代碼展示了Spring中部分解析器解析參數的過程。說到底,無論參數如何變化,參數類型再怎么復雜。

它們都是通過HTTP請求發送過來的,那么就可以通過?HttpServletRequest來獲取到一切。Spring做的就是通過注解,盡量適配大部分應用場景。

總結

以上是生活随笔為你收集整理的SpringBoot是如何解析HTTP参数的?的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。