普通参数与基本注解
注解
在这里我们将学到web开发的相关注解
- @PathVariable(路径变量)
- @RequestHeader(请求头)
- @RequestParam(获取请求参数)
- @CookieValue(cookie信息)
- @RequestBody(获取请求体【POST】)
- @RequestAttribute(获取request域属性)
- @MatrixVariable(矩阵变量)
@PathVariable
路径变量注解,在rest风格的情况下,我们通常将参数格式写成 /name/Tom/id/1 ,那么需要获取路径中的请求参数,就需要使用到@PathVariable
@GetMapping("/car/{id}/brand/{name}")
public Map<String,Object> test(@PathVariable("id") Integer id,
@PathVariable("name") String brand,
//不定义变量名的注解,可以定义为map形式,直接封装成k=v的格式
@PathVariable Map<String,String> pv){
HashMap<String, Object> map = new HashMap<>();
map.put("id",id);
map.put("name",brand);
map.put("pv",pv);
return map;
}
@RequestHeader
请求头注解,可以获取客户端发送请求时,请求头中携带的信息
@GetMapping("/car/{id}/brand/{name}")
public Map<String,Object> test(@RequestHeader("User-Agent") String user_agent,
@RequestHeader Map<String,String> header){
HashMap<String, Object> map = new HashMap<>();
map.put("ka",user_agent);
map.put("header",header);
return map;
}
@RequestParam
此注解可以获取请求参数,如: /name/Tom/id/1?gender=boy&inter=basketball&inter=football,相同的名称可以使用list来接收
@GetMapping("/car/{id}/brand/{name}")
public Map<String,Object> test(@RequestParam("carname") String carname,
@RequestParam("inter")List<String> inter{
HashMap<String, Object> map = new HashMap<>();
map.put("carname",carname);
map.put("inter",inter);
return map;
}
@CookieValue
同请求头类似,可以获取cookie属性,也可以将获取到的属性封装为cookie对象
@GetMapping("/car/{id}/brand/{name}")
public Map<String,Object> test(@CookieValue("Idea-21d59d66") String cookieValue,
@CookieValue("Idea-21d59d66") Cookie cookies){
HashMap<String, Object> map = new HashMap<>();
map.put("cookie",cookieValue);
map.put("cookies",cookies);
return map;
}
@RequestBody
此注解可以封装表单中的信息
@PostMapping("/user")
public Map<String,Object> getUser(@RequestBody() String context){
HashMap<String, Object> map = new HashMap<>();
map.put("context",context);
return map;
}
@RequestAttribute
获取请求域中的信息,模拟页面转发,goPage将数据写入到request域中,在其他方法中读取
@Controller
public class GoController {
@GetMapping("/goto")
public String goPage(HttpServletRequest request){
request.setAttribute("msg","成功了。。。");
request.setAttribute("code","200");
return "forward:/success";
}
@ResponseBody
@GetMapping("/success")
public Map<String,Object> getmap(@RequestAttribute("msg") String msg,
@RequestAttribute("code") String code){
HashMap<String, Object> map = new HashMap<>();
map.put("msg",msg);
map.put("code",code);
return map;
}
}
@MatrixVariable
读取矩阵变量,这就得介绍一下什么是矩阵变量了,语法如下,每一个变量之间用分号隔开
语法: 请求路径:/cars/sell;low=34;brand=byd,audi,yd
// /car/sell;low=20;hight=40
@GetMapping("/car/{path}")
public Map test2(@MatrixVariable("low") String low,
@MatrixVariable("brand") List<String> brand,
@PathVariable("path") String path){
HashMap<String, Object> map = new HashMap<>();
map.put("low",low);
map.put("brand",brand);
map.put("path",path);
return map;
}
进行如上设置还不够,SpringBoot关闭了矩阵变量,我们可以从 源码中探索
所有自动配置的属性都存放在WebMvcAutoConfiguration,在这里面对请求路径进行了设置
public void configurePathMatch(PathMatchConfigurer configurer) {
if (this.mvcProperties.getPathmatch().getMatchingStrategy() == MatchingStrategy.PATH_PATTERN_PARSER) {
configurer.setPatternParser(new PathPatternParser());
}
configurer.setUseSuffixPatternMatch(this.mvcProperties.getPathmatch().isUseSuffixPattern());
configurer.setUseRegisteredSuffixPatternMatch(this.mvcProperties.getPathmatch().isUseRegisteredSuffixPattern());
this.dispatcherServletPath.ifAvailable((dispatcherPath) -> {
String servletUrlMapping = dispatcherPath.getServletUrlMapping();
if (servletUrlMapping.equals("/") && this.singleDispatcherServlet()) {
//主要看这个路径帮助器
UrlPathHelper urlPathHelper = new UrlPathHelper();
urlPathHelper.setAlwaysUseFullPath(true);
configurer.setUrlPathHelper(urlPathHelper);
}
});
}
urlPathHelper中有一个removeSemicolonContent属性被设置为true,而其代表阶段路径中分号后的数据,也就是说我们要想其生效,必须修改值为false
我们发现urlPathHelper所在的类继承了WebMvcConfigurer,WebMvcConfigurer下有一个属性为configurePathMatch,也就是说我们只要重新进行配置即可
@Configuration(proxyBeanMethods = false)
public class MyConfig {
@Bean
public WebMvcConfigurer getConfig(){
return new WebMvcConfigurer() {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
// 不移除;后面的内容。矩阵变量功能就可以生效
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
};
}
}
在使用矩阵变量时,可能会出现两个变量名相同的情况,比如我要找1号boss年龄变量为20,2号员工年龄变量为30,发送的请求为:/boss/1;age=20/2;age=30
这个时候我们需要使用@MatrixVariable注解下的pathVar来指定变量名称
// /boss/1;age=20/2;age=10
@GetMapping("/boss/{bossId}/{empId}")
public Map test3(@MatrixVariable(value = "age",pathVar = "bossId") String boss,
@MatrixVariable(value = "age",pathVar = "empId") String emp){
HashMap<String, Object> map = new HashMap<>();
map.put("boss",boss);
map.put("emp",emp);
return map;
}
参数处理原理
所有请求过的都会过DispatcherServlet的doDispatch方法,先说一下大概的逻辑再一一详细讲解
- 调用getHandler方法,找到处理该请求的Handler(这个在小黄之前的文章中有提到过) 点击跳转
- 调用getHandlerAdapter方法,为当前的handler找到一个适配器HandlerAdapter (requestMappingHandlerAdapter)
- 执行适配器的handler方法,确定方法参数的每一个值
先来看第二步,为当前的Handler找到一个适配器
SpringBoot为我们提供了4种适配器,毫无疑问这里需要的是requestMappingHandlerAdapter
requestMappingHandlerAdapter——支持方法上标注注解@RequestMapping
HandlerFuncationAdapter——支持函数式编程
执行适配器的handler方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
执行目标方法
mav = this.invokeHandlerMethod(request, response, handlerMethod);
这个方法中我们注意到两个对象
argumentResolvers
参数解析器,里面包含了27种SpringBoot提供的参数解析器,比如RequestParam、PathVariable等,通过这里我们也可以得知,在方法形参中,我们有这么多的注解可以标识
而这个参数解析器的方法非常有意思,一共就两个方法,一个是判断一个是执行,所以这个的工作原理非常清晰
- 判断当前解析器是否支持这种函数
- 支持的话就调用resolveArgument
returnValueHandlers
返回值处理器,这里我们可以看到处理请求方法允许的返回值
接下来主要看以下方法,此方法
invocableMethod.invokeAndHandle(webRequest, mavContainer, new Object[0]);
这个方法进去之后,会执行以下两条语句,通过断点调试的方法,发现第一条语句执行完成之后,先跳转到controller中的断点再接着执行第二条语句,这里可以说明处理请求的方法在this.invokeForRequest()中
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
Object returnValue = this.invokeForRequest(webRequest, mavContainer, providedArgs);
this.setResponseStatus(webRequest);
}
通过以下方法来获取参数值
Object[] args = this.getMethodArgumentValues(request, mavContainer, providedArgs);
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
MethodParameter[] parameters = this.getMethodParameters();
if (ObjectUtils.isEmpty(parameters)) {
return EMPTY_ARGS;
} else {
Object[] args = new Object[parameters.length];
//遍历请求参数
for(int i = 0; i < parameters.length; ++i) {
MethodParameter parameter = parameters[i];
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
args[i] = findProvidedArgument(parameter, providedArgs);
if (args[i] == null) {
//判断27个参数解析器是否支持解析该参数
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}
try {
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
} catch (Exception var10) {
if (logger.isDebugEnabled()) {
String exMsg = var10.getMessage();
if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
logger.debug(formatArgumentError(parameter, exMsg));
}
}
throw var10;
}
}
}
return args;
}
}
挨个判断27个参数解析器是否支持该参数
@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
HandlerMethodArgumentResolver result = (HandlerMethodArgumentResolver)this.argumentResolverCache.get(parameter);
if (result == null) {
Iterator var3 = this.argumentResolvers.iterator();
while(var3.hasNext()) {
HandlerMethodArgumentResolver resolver = (HandlerMethodArgumentResolver)var3.next();
if (resolver.supportsParameter(parameter)) {
result = resolver;
this.argumentResolverCache.put(parameter, resolver);
break;
}
}
}
return result;
}
解析参数的值
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
AbstractNamedValueMethodArgumentResolver.NamedValueInfo namedValueInfo = this.getNamedValueInfo(parameter);
MethodParameter nestedParameter = parameter.nestedIfOptional();
Object resolvedName = this.resolveEmbeddedValuesAndExpressions(namedValueInfo.name);
if (resolvedName == null) {
throw new IllegalArgumentException("Specified name must not resolve to null: [" + namedValueInfo.name + "]");
} else {
Object arg = this.resolveName(resolvedName.toString(), nestedParameter, webRequest);
if (arg == null) {
if (namedValueInfo.defaultValue != null) {
arg = this.resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
} else if (namedValueInfo.required && !nestedParameter.isOptional()) {
this.handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
}
arg = this.handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
} else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
arg = this.resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
}
if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, (Object)null, namedValueInfo.name);
try {
arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
} catch (ConversionNotSupportedException var11) {
throw new MethodArgumentConversionNotSupportedException(arg, var11.getRequiredType(), namedValueInfo.name, parameter, var11.getCause());
} catch (TypeMismatchException var12) {
throw new MethodArgumentTypeMismatchException(arg, var12.getRequiredType(), namedValueInfo.name, parameter, var12.getCause());
}
if (arg == null && namedValueInfo.defaultValue == null && namedValueInfo.required && !nestedParameter.isOptional()) {
this.handleMissingValueAfterConversion(namedValueInfo.name, nestedParameter, webRequest);
}
}
this.handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
return arg;
}
}
总结
- 调用getHandler方法,找到处理该请求的Handler(这个在小黄之前的文章中有提到过) 点击跳转
- 调用getHandlerAdapter方法,为当前的handler找到一个适配器HandlerAdapter (requestMappingHandlerAdapter)
- 执行适配器的handler方法,确定方法参数的每一个值
- 执行目标方法,过程中可以发现两个对象,分别是参数解析器、返回值处理器
- 调用this.getMethodArgumentValues获取参数值的集合
- 遍历请求参数
- 挨个判断请求参数是否能被27个参数解析器解析
- 解析参数的值