亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

SpringBoot如何解析參數(shù)的深入理解

 更新時(shí)間:2019年05月13日 10:23:23   作者:清幽之地  
這篇文章主要給大家介紹了關(guān)于SpringBoot是如何解析參數(shù)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用SpringBoot具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧

前言

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

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

一、HTTP請求處理流程

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

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

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;
}

它們有一個(gè)共同的接口HandlerMethodArgumentResolver。supportsParameter用來判斷方法參數(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è)簡單數(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";
}

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

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

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

在Spring中,這里對應(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)容,那么通過一個(gè)POST請求,將參數(shù)放在Body中傳輸是更好的方式。當(dāng)然,比較友好的數(shù)據(jù)格式當(dāng)屬JSON。

面對這樣一個(gè)請求,我們在Controller方法中可以通過RequestBody注解來接收它,并自動(dòng)轉(zhuǎn)換為合適的Java Bean對象。

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

在沒有Spring的情況下,我們考慮一下如何解決這一問題呢?

首先呢,還是要依靠Request對象。對于Body中的數(shù)據(jù),我們可以通過request.getReader()方法來獲取,然后讀取字符串,最后通過JSON工具類再轉(zhuǎn)換為合適的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數(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í)際場景中,上面的SysUser.class需要?jiǎng)討B(tài)獲取參數(shù)類型。

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

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

1、獲取請求輔助信息

在開始之前需要先獲取請求的一些輔助信息,比如HTTP請求的數(shù)據(jù)格式,上下文Class信息、參數(shù)類型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、確定消息轉(zhuǎn)換器

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

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

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

3、解析

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

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

  • 方法解析器RequestResponseBodyMethodProcessor
  • 消息轉(zhuǎn)換器MappingJackson2HttpMessageConverter

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

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

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

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

然后用GET方法請求:

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

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

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

比如像下面這樣:

public String test3(SysUser user,HttpServletRequest request)throws Exception {
	//從Request中獲取所有的參數(shù)key 和 value
	Map<String, String[]> parameterMap = request.getParameterMap();
	Iterator<Map.Entry<String, String[]>> iterator = parameterMap.entrySet().iterator();
	//獲取目標(biāo)類的對象
	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還有一種內(nèi)省機(jī)制可以完成這件事。我們可以獲取目標(biāo)類的屬性描述符對象,然后拿到它的Method對象, 通過invoke來設(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è)方法來實(shí)現(xiàn)。

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

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

簡單來說,它是通過BeanWrapperImpl來處理的。關(guān)于BeanWrapperImpl有個(gè)很簡單的使用方法:

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

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

回過頭來,我們再看Spring。雖然我們上面寫的很簡單,但真正用起來還需要考慮的很多很多。Spring中處理這種參數(shù)的解析器是ServletModelAttributeMethodProcessor。

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

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主要的作用,簡言之就是利用BeanWrapper給對象的屬性設(shè)值。

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

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

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

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

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

//模擬Request參數(shù)
Map<String,Object> map = new HashMap();
map.put("id","1001");
map.put("name","Jack");
map.put("password","123456");
map.put("address","北京市海淀區(qū)");

//將request對象轉(zhuǎn)換為MutablePropertyValues對象
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ù)解析器

我們說所有的消息解析器都實(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<HandlerMethodArgumentResolver> 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ān)示例代碼展示了Spring中部分解析器解析參數(shù)的過程。說到底,無論參數(shù)如何變化,參數(shù)類型再怎么復(fù)雜。

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

好了,以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對腳本之家的支持。

相關(guān)文章

  • MyBatis整合Redis實(shí)現(xiàn)二級緩存的示例代碼

    MyBatis整合Redis實(shí)現(xiàn)二級緩存的示例代碼

    這篇文章主要介紹了MyBatis整合Redis實(shí)現(xiàn)二級緩存的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-08-08
  • Java基于BIO實(shí)現(xiàn)文件上傳功能

    Java基于BIO實(shí)現(xiàn)文件上傳功能

    這篇文章主要為大家詳細(xì)介紹了Java基于BIO實(shí)現(xiàn)文件上傳功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-11-11
  • Java實(shí)現(xiàn)文件名倒序排序的技術(shù)指南

    Java實(shí)現(xiàn)文件名倒序排序的技術(shù)指南

    在實(shí)際開發(fā)過程中,我們經(jīng)常需要對文件進(jìn)行操作和處理,一個(gè)常見的需求是按文件名倒序排列文件列表,以便于文件的管理和查找,本文將介紹如何在Java中實(shí)現(xiàn)文件名倒序排序,并提供詳細(xì)的代碼案例,需要的朋友可以參考下
    2024-08-08
  • java實(shí)現(xiàn)消息隊(duì)列的兩種方式(小結(jié))

    java實(shí)現(xiàn)消息隊(duì)列的兩種方式(小結(jié))

    本文主要介紹了兩種java實(shí)現(xiàn)消息隊(duì)列的方式,利用Spring消息模板發(fā)送消息和Apache ActiveMQ官方實(shí)例發(fā)送消息,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2018-12-12
  • Java設(shè)計(jì)模式編程中的工廠方法模式和抽象工廠模式

    Java設(shè)計(jì)模式編程中的工廠方法模式和抽象工廠模式

    這篇文章主要介紹了Java設(shè)計(jì)模式編程中的工廠方法模式和抽象工廠模式,設(shè)計(jì)模式的建立有利于團(tuán)隊(duì)協(xié)作時(shí)代碼的共同維護(hù),需要的朋友可以參考下
    2016-01-01
  • java導(dǎo)出到excel常用的幾種方式總結(jié)

    java導(dǎo)出到excel常用的幾種方式總結(jié)

    導(dǎo)出excel是咱Java開發(fā)的必備技能啦,之前項(xiàng)目有這個(gè)功能,現(xiàn)在將其獨(dú)立出來,分享一下,下面這篇文章主要給大家介紹了關(guān)于java導(dǎo)出到excel常用的幾種方式,需要的朋友可以參考下
    2023-05-05
  • 使用Vue+Spring Boot實(shí)現(xiàn)Excel上傳功能

    使用Vue+Spring Boot實(shí)現(xiàn)Excel上傳功能

    這篇文章主要介紹了使用Vue+Spring Boot實(shí)現(xiàn)Excel上傳,需要的朋友可以參考下
    2018-11-11
  • Java正則表達(dá)式處理特殊字符轉(zhuǎn)義的方法

    Java正則表達(dá)式處理特殊字符轉(zhuǎn)義的方法

    由于正則表達(dá)式定了一些特殊字符,而有時(shí)候需要對這些特殊字符進(jìn)行匹配的話就需要進(jìn)行轉(zhuǎn)義了,下面這篇文章主要給大家介紹了Java正則表達(dá)式處理特殊字符轉(zhuǎn)義的方法,需要的朋友可以參考借鑒,下面來一起看看吧。
    2017-01-01
  • DolphinScheduler容錯(cuò)Master源碼分析

    DolphinScheduler容錯(cuò)Master源碼分析

    這篇文章主要為大家介紹了DolphinScheduler容錯(cuò)Master源碼分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-02-02
  • SpringMvc+Mybatis+Pagehelper分頁詳解

    SpringMvc+Mybatis+Pagehelper分頁詳解

    這篇文章主要介紹了SpringMvc+Mybatis+Pagehelper分頁詳解,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下的相關(guān)資料
    2017-01-01

最新評論