您好,登錄后才能下訂單哦!
這篇文章給大家分享的是有關(guān)SpringBoot解析參數(shù)的案例的內(nèi)容。小編覺(jué)得挺實(shí)用的,因此分享給大家做個(gè)參考,一起跟隨小編過(guò)來(lái)看看吧。
一、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í)行方法,最后處理返回值并渲染視圖。
我們今天的重點(diǎn)在于解析參數(shù),對(duì)應(yīng)到上圖的目標(biāo)方法調(diào)用這一步驟。既然說(shuō)到參數(shù)解析,那么針對(duì)不同類型的參數(shù),肯定有不同的解析器。Spring已經(jīng)幫我們注冊(cè)了一堆這東西。
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用來(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
如果按照以前的寫(xiě)法,我們直接根據(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。
面對(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)求輔助信息
在開(kāi)始之前需要先獲取請(qǐng)求的一些輔助信息,比如HTTP請(qǐng)求的數(shù)據(jù)格式,上下文Class信息、參數(shù)類型Class、HTTP請(qǐng)求方法類型等。
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幫我們注冊(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 <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)容了,不再深究。雖然寫(xiě)出來(lái)的過(guò)程比較啰嗦,但實(shí)際上主要就是為了尋找兩個(gè)東西:
方法解析器RequestResponseBodyMethodProcessor
消息轉(zhuǎn)換器MappingJackson2HttpMessageConverter
都找到之后調(diào)用方法解析即可。
四、GET請(qǐng)求參數(shù)轉(zhuǎn)換Bean
還有一種寫(xiě)法是這樣的,在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<String, String[]> parameterMap = request.getParameterMap(); Iterator<Map.Entry<String, String[]>> iterator = parameterMap.entrySet().iterator(); //獲取目標(biāo)類的對(duì)象 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)類的屬性描述符對(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<String, String[]> 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。雖然我們上面寫(xiě)的很簡(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<String,Object> 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<HandlerMethodArgumentResolver> resolvers) { resolvers.add(new XunerArgumentResolver()); } }
一頓操作后,在Controller中我們可以這樣使用它:
@ResponseBody @RequestMapping("/test4") public String test4(@RequestXuner(name="xuner") String xuner){ logger.info("參數(shù):{}",xuner); return "Test4"; }
感謝各位的閱讀!關(guān)于“SpringBoot解析參數(shù)的案例”這篇文章就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,讓大家可以學(xué)到更多知識(shí),如果覺(jué)得文章不錯(cuò),可以把它分享出去讓更多的人看到吧!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。