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

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 前端技术 > javascript >内容正文

javascript

springboot处理参数再转发请求_SpringBoot是如何解析HTTP参数的

發(fā)布時(shí)間:2024/9/19 javascript 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 springboot处理参数再转发请求_SpringBoot是如何解析HTTP参数的 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

前言

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

本文重點(diǎn)來(lái)看幾種傳參方式,看看它們都是如何被解析并應(yīng)用到方法參數(shù)上的。

一、HTTP請(qǐng)求處理流程

不論在SpringBoot還是SpringMVC中,一個(gè)HTTP請(qǐng)求會(huì)被DispatcherServlet類接收,它本質(zhì)是一個(gè)Servlet,因?yàn)樗^承自HttpServlet。在這里,Spring負(fù)責(zé)解析請(qǐng)求,匹配到Controller類上的方法,解析參數(shù)并執(zhí)行方法,最后處理返回值并渲染視圖。

image

我們今天的重點(diǎn)在于解析參數(shù),對(duì)應(yīng)到上圖的目標(biāo)方法調(diào)用這一步驟。既然說(shuō)到參數(shù)解析,那么針對(duì)不同類型的參數(shù),肯定有不同的解析器。Spring已經(jīng)幫我們注冊(cè)了一堆這東西。

它們有一個(gè)共同的接口HandlerMethodArgumentResolver。supportsParameter用來(lái)判斷方法參數(shù)是否可以被當(dāng)前解析器解析,如果可以就調(diào)用resolveArgument去解析。

public interface HandlerMethodArgumentResolver {

//判斷方法參數(shù)是否可以被當(dāng)前解析器解析

boolean supportsParameter(MethodParameter var1);

//解析參數(shù)

@Nullable

Object resolveArgument(MethodParameter var1,

@Nullable ModelAndViewContainer var2,

NativeWebRequest var3,

@Nullable WebDataBinderFactory var4)throws Exception;

}

二、RequestParam

在Controller方法中,如果你的參數(shù)標(biāo)注了RequestParam注解,或者是一個(gè)簡(jiǎn)單數(shù)據(jù)類型。

@RequestMapping("/test1")

@ResponseBody

public String test1(String t1, @RequestParam(name = "t2",required = false) String t2,HttpServletRequest request){

logger.info("參數(shù):{},{}",t1,t2);

return "Java";

}

我們的請(qǐng)求路徑是這樣的:http://localhost:8080/test1?t1=Jack&t2=Java

如果按照以前的寫法,我們直接根據(jù)參數(shù)名稱或者RequestParam注解的名稱從Request對(duì)象中獲取值就行。比如像這樣:

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

在Spring中,這里對(duì)應(yīng)的參數(shù)解析器是RequestParamMethodArgumentResolver。與我們的想法差不多,就是拿到參數(shù)名稱后,直接從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

如果我們需要前端傳輸更多的參數(shù)內(nèi)容,那么通過(guò)一個(gè)POST請(qǐng)求,將參數(shù)放在Body中傳輸是更好的方式。當(dāng)然,比較友好的數(shù)據(jù)格式當(dāng)屬JSON。

image

面對(duì)這樣一個(gè)請(qǐng)求,我們?cè)贑ontroller方法中可以通過(guò)RequestBody注解來(lái)接收它,并自動(dòng)轉(zhuǎn)換為合適的Java Bean對(duì)象。

@ResponseBody

@RequestMapping("/test2")

public String test2(@RequestBody SysUser user){

logger.info("參數(shù)信息:{}",JSONObject.toJSONString(user));

return "Hello";

}

在沒(méi)有Spring的情況下,我們考慮一下如何解決這一問(wèn)題呢?

首先呢,還是要依靠Request對(duì)象。對(duì)于Body中的數(shù)據(jù),我們可以通過(guò)request.getReader()方法來(lái)獲取,然后讀取字符串,最后通過(guò)JSON工具類再轉(zhuǎn)換為合適的Java對(duì)象。

比如像下面這樣:

@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數(shù)據(jù):{}",builder.toString());

SysUser sysUser = JSONObject.parseObject(builder.toString(), SysUser.class);

logger.info("轉(zhuǎn)換后的Bean:{}",JSONObject.toJSONString(sysUser));

return "Java";

}

當(dāng)然,在實(shí)際場(chǎng)景中,上面的SysUser.class需要?jiǎng)討B(tài)獲取參數(shù)類型。

在Spring中,RequestBody注解的參數(shù)會(huì)由RequestResponseBodyMethodProcessor類來(lái)負(fù)責(zé)解析。

它的解析由父類AbstractMessageConverterMethodArgumentResolver負(fù)責(zé)。整個(gè)過(guò)程我們分為三個(gè)步驟來(lái)看。

1、獲取請(qǐng)求輔助信息

在開始之前需要先獲取請(qǐng)求的一些輔助信息,比如HTTP請(qǐng)求的數(shù)據(jù)格式,上下文Class信息、參數(shù)類型Class、HTTP請(qǐng)求方法類型等。

protected 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 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、確定消息轉(zhuǎn)換器

上面獲取到的輔助信息是有作用的,就是要確定一個(gè)消息轉(zhuǎn)換器。消息轉(zhuǎn)換器有很多,它們的共同接口是HttpMessageConverter。在這里,Spring幫我們注冊(cè)了很多轉(zhuǎn)換器,所以需要循環(huán)它們,來(lái)確定使用哪一個(gè)來(lái)做消息轉(zhuǎn)換。

如果是JSON數(shù)據(jù)格式的,會(huì)選擇MappingJackson2HttpMessageConverter來(lái)處理。它的構(gòu)造函數(shù)正是指明了這一點(diǎn)。

public MappingJackson2HttpMessageConverter(ObjectMapper objectMapper) {

super(objectMapper, new MediaType[]{

MediaType.APPLICATION_JSON,

new MediaType("application", "*+json")});

}

3、解析

既然確定了消息轉(zhuǎn)換器,那么剩下的事就很簡(jiǎn)單了。通過(guò)Request獲取Body,然后調(diào)用轉(zhuǎn)換器解析就好了。

protected 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包的內(nèi)容了,不再深究。雖然寫出來(lái)的過(guò)程比較啰嗦,但實(shí)際上主要就是為了尋找兩個(gè)東西:

方法解析器RequestResponseBodyMethodProcessor

消息轉(zhuǎn)換器MappingJackson2HttpMessageConverter

都找到之后調(diào)用方法解析即可。

四、GET請(qǐng)求參數(shù)轉(zhuǎn)換Bean

還有一種寫法是這樣的,在Controller方法上用Java Bean接收。

@RequestMapping("/test3")

@ResponseBody

public String test3(SysUser user){

logger.info("參數(shù):{}",JSONObject.toJSONString(user));

return "Java";

}

然后用GET方法請(qǐng)求:

http://localhost:8080/test3?id=1001&name=Jack&password=1234&address=北京市海淀區(qū)

URL后面的參數(shù)名稱對(duì)應(yīng)Bean對(duì)象里面的屬性名稱,也可以自動(dòng)轉(zhuǎn)換。那么,這里它又是怎么做的呢 ?

筆者首先想到的就是Java的反射機(jī)制。從Request對(duì)象中獲取參數(shù)名稱,然后和目標(biāo)類上的方法一一對(duì)應(yīng)設(shè)置值進(jìn)去。

比如像下面這樣:

public String test3(SysUser user,HttpServletRequest request)throws Exception {

//從Request中獲取所有的參數(shù)key 和 value

Map parameterMap = request.getParameterMap();

Iterator> iterator = parameterMap.entrySet().iterator();

//獲取目標(biāo)類的對(duì)象

Object target = user.getClass().newInstance();

Field[] fields = target.getClass().getDeclaredFields();

while (iterator.hasNext()){

Map.Entry 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還有一種內(nèi)省機(jī)制可以完成這件事。我們可以獲取目標(biāo)類的屬性描述符對(duì)象,然后拿到它的Method對(duì)象, 通過(guò)invoke來(lái)設(shè)置。

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();

}

}

然后在上面的循環(huán)中,我們就可以調(diào)用這個(gè)方法來(lái)實(shí)現(xiàn)。

while (iterator.hasNext()){

Map.Entry next = iterator.next();

String key = next.getKey();

String value = next.getValue()[0];

setProperty(userInfo,key,value);

}

為什么要說(shuō)到內(nèi)省機(jī)制呢?因?yàn)镾pring在處理這件事的時(shí)候,最終也是靠它處理的。

簡(jiǎn)單來(lái)說(shuō),它是通過(guò)BeanWrapperImpl來(lái)處理的。關(guān)于BeanWrapperImpl有個(gè)很簡(jiǎn)單的使用方法:

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最后就會(huì)調(diào)用到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);

//設(shè)置

writeMethod.invoke(BeanWrapperImpl.this.getWrappedInstance(), value);

}

}

通過(guò)上面的方式,就完成了GET請(qǐng)求參數(shù)到Java Bean對(duì)象的自動(dòng)轉(zhuǎn)換。

回過(guò)頭來(lái),我們?cè)倏碨pring。雖然我們上面寫的很簡(jiǎn)單,但真正用起來(lái)還需要考慮的很多很多。Spring中處理這種參數(shù)的解析器是ServletModelAttributeMethodProcessor。

它的解析過(guò)程在其父類ModelAttributeMethodProcessor.resolveArgument()方法。整個(gè)過(guò)程,我們也可以分為三個(gè)步驟來(lái)看。

1、獲取目標(biāo)類的構(gòu)造函數(shù)

根據(jù)參數(shù)類型,先生成一個(gè)目標(biāo)類的構(gòu)造函數(shù),以供后面綁定數(shù)據(jù)的時(shí)候使用。

2、創(chuàng)建數(shù)據(jù)綁定器WebDataBinder

WebDataBinder繼承自DataBinder。而DataBinder主要的作用,簡(jiǎn)言之就是利用BeanWrapper給對(duì)象的屬性設(shè)值。

3、綁定數(shù)據(jù)到目標(biāo)類,并返回

在這里,又把WebDataBinder轉(zhuǎn)換成ServletRequestDataBinder對(duì)象,然后調(diào)用它的bind方法。

接下來(lái)有個(gè)很重要的步驟是,將request中的參數(shù)轉(zhuǎn)換為MutablePropertyValues pvs對(duì)象。

然后接下來(lái)就是循環(huán)pvs,調(diào)用setPropertyValue設(shè)置屬性。當(dāng)然了,最后調(diào)用的其實(shí)就是BeanWrapperImpl#BeanPropertyHandler.setValue()。

下面有段代碼可以更好的理解這一過(guò)程,效果是一樣的:

//模擬Request參數(shù)

Map map = new HashMap();

map.put("id","1001");

map.put("name","Jack");

map.put("password","123456");

map.put("address","北京市海淀區(qū)");

//將request對(duì)象轉(zhuǎn)換為MutablePropertyValues對(duì)象

MutablePropertyValues propertyValues = new MutablePropertyValues(map);

SysUser sysUser = new SysUser();

//創(chuàng)建數(shù)據(jù)綁定器

ServletRequestDataBinder binder = new ServletRequestDataBinder(sysUser);

//bind數(shù)據(jù)

binder.bind(propertyValues);

System.out.println(JSONObject.toJSONString(sysUser));

五、自定義參數(shù)解析器

我們說(shuō)所有的消息解析器都實(shí)現(xiàn)了HandlerMethodArgumentResolver接口。我們也可以定義一個(gè)參數(shù)解析器,讓它實(shí)現(xiàn)這個(gè)接口就好了。

首先,我們可以定義一個(gè)RequestXuner注解。

@Target({ElementType.PARAMETER})

@Retention(RetentionPolicy.RUNTIME)

@Documented

public @interface RequestXuner {

String name() default "";

boolean required() default false;

String defaultValue() default "default";

}

然后是實(shí)現(xiàn)了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){

//獲取參數(shù)上的注解

RequestXuner annotation = methodParameter.getParameterAnnotation(RequestXuner.class);

String name = annotation.name();

//從Request中獲取參數(shù)值

String parameter = nativeWebRequest.getParameter(name);

return "HaHa,"+parameter;

}

}

不要忘記需要配置一下。

@Configuration

public class WebMvcConfiguration extends WebMvcConfigurationSupport {

@Override

protected void addArgumentResolvers(List resolvers) {

resolvers.add(new XunerArgumentResolver());

}

}

一頓操作后,在Controller中我們可以這樣使用它:

@ResponseBody

@RequestMapping("/test4")

public String test4(@RequestXuner(name="xuner") String xuner){

logger.info("參數(shù):{}",xuner);

return "Test4";

}

六、總結(jié)

本文內(nèi)容通過(guò)相關(guān)示例代碼展示了Spring中部分解析器解析參數(shù)的過(guò)程。說(shuō)到底,無(wú)論參數(shù)如何變化,參數(shù)類型再怎么復(fù)雜。

它們都是通過(guò)HTTP請(qǐng)求發(fā)送過(guò)來(lái)的,那么就可以通過(guò)HttpServletRequest來(lái)獲取到一切。Spring做的就是通過(guò)注解,盡量適配大部分應(yīng)用場(chǎng)景。

總結(jié)

以上是生活随笔為你收集整理的springboot处理参数再转发请求_SpringBoot是如何解析HTTP参数的的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。