目录
一、请求参数处理
1.rest使用与原理
//可以根据设置method限制请求的方法类型
@RequestMapping(value = "/user",method = RequestMethod.PUT)
但表单< form > 只能提交POST和GET请求,
如何提交DELETE和PUT请求?
spring:
mvc:
hiddenmethod:
filter:
enabled: true #开启页面表单的Rest功能
<!--表单method=post,隐藏域 _method=put/delete-->
<form action="/user" method="post">
<input name="_method" type="hidden" value="delete"/>
<input value="提交" type="submit"/>
</form>
//请求都要通过HiddenHttpMethodFilter过滤器,这个过滤器将替换了请求方法
//我们可以自定义过滤器,把_method 这个名字换成 _m
//自定义filter
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
methodFilter.setMethodParam("_m");
return methodFilter;
}
Rest原理:
//查看HiddenHttpMethodFilter源码
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
//先获得requset请求
HttpServletRequest requestToUse = request;
//判断请求方法是不是POST,所以method一定要设置成POST
if ("POST".equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) {
//得到参数_method的值
//public static final String DEFAULT_METHOD_PARAM = "_method";
//private String methodParam = DEFAULT_METHOD_PARAM;
String paramValue = request.getParameter(this.methodParam);
if (StringUtils.hasLength(paramValue)) {
//转成大写
String method = paramValue.toUpperCase(Locale.ENGLISH);
if (ALLOWED_METHODS.contains(method)) {
//将request包装,并重新设置method
requestToUse = new HttpMethodRequestWrapper(request, method);
}
}
}
//通过filter,通过的请求是requestToUse
filterChain.doFilter(requestToUse, response);
}
2.请求映射原理
HttpServlet中的doGet方法
FrameworkServlet实现了doGet,并在其中调用了processRequest(request, response)
processRequest核心部分调用了doService方法,是一个抽象方法。
查看他的子类,发现了DispatcherServlet实现了doService方法,
doService内部调用了doDispatch方法,处理请求
doDispatch处打断点,debug
在网页端发送GET请求。
1.DispatchServlet收到了,我发送的user请求
2. 在内部中通过getHandler得到了处理user请求的HelloController的getUser方法。
所以我们重点分析这个方法。
processedRequest为前面对Request的封装。
Step into getHandler
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//循环查找handlerMapping
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
RequestMappingHandlerMapping:保存了所有@RequestMapping 和handler的映射规则。
RequestMappingHandlerMapping其内部的mappingRegistry中记录了它能处理什么请求,其中就包含了GET /user。
进入for循环后
handler = mapping.getHandler(request);
就能获取对应的Handler
Step into getHandler
再次Step into getHandlerInternal
再次Step into getHandlerInternal
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
//得到了请求路径
String lookupPath = initLookupPath(request);
//获得了读锁
this.mappingRegistry.acquireReadLock();
try {
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
finally {
this.mappingRegistry.releaseReadLock();
}
}
Step into lookupHandlerMethod
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<>();
//通过getMappingsByDirectPath,得到处理/user的match
List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);
//筛选出处理GET /user的match,加入matches
if (directPathMatches != null) {
addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);
}
if (!matches.isEmpty()) {
//默认取得第一个处理GET /user的match
Match bestMatch = matches.get(0);
//如果matches有多个,还会进行判断是否合法,不合法会报错
if (matches.size() > 1) {
Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
matches.sort(comparator);
bestMatch = matches.get(0);
if (logger.isTraceEnabled()) {...}
if (CorsUtils.isPreFlightRequest(request)) {...}
else {
Match secondBestMatch = matches.get(1);
if (comparator.compare(bestMatch, secondBestMatch) == 0) {
Method m1 = bestMatch.getHandlerMethod().getMethod();
Method m2 = secondBestMatch.getHandlerMethod().getMethod();
String uri = request.getRequestURI();
throw new IllegalStateException(
"Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
}
}
}
request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.getHandlerMethod());
handleMatch(bestMatch.mapping, lookupPath, request);
//最后返回HandlerMethod
return bestMatch.getHandlerMethod();
}
else {
return handleNoMatch(this.mappingRegistry.getRegistrations().keySet(), lookupPath, request);
}
}
总结
:
所有的请求映射都在HandlerMapping中。
- SpringBoot自动配置了默认 的 RequestMappingHandlerMapping等等(在WebMvcAutoConfigration中
- 请求进来,挨个尝试所有的HandlerMapping看是否有请求信息。
- 如果有就找到这个请求对应的handler
- 如果没有就是下一个 HandlerMapping
- 我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping。自定义 HandlerMapping
二、普通参数与基本注解
1.注解
@PathVariable
获取请求参数。rest风格请求
参数为Map,表示将键值对存入这个map
@RequestHeader
获取请求头。
参数为Map,表示将键值对存入这个map
@RequestAttribute
获取请求域的内容,一般用于转发
@RequestParam
获取请求参数,一般风格
参数为Map,表示将键值对存入这个map
@MatrixVariable
矩阵变量
@CookieValue
获取Cookie
参数为Map,表示将键值对存入这个map
@RequestBody
获取请求体
@RestController
public class ParameterTestController {
// car/2/owner/zhangsan
@GetMapping("/car/{id}/owner/{username}")
public Map<String,Object> getCar(@PathVariable("id") Integer id,
@PathVariable("username") String name,
@PathVariable Map<String,String> pv,
@RequestHeader("User-Agent") String userAgent,
@RequestHeader Map<String,String> header,
@RequestParam("age") Integer age,
@RequestParam("inters") List<String> inters,
@RequestParam Map<String,String> params,
@CookieValue("_ga") String _ga,
@CookieValue("_ga") Cookie cookie){
Map<String,Object> map = new HashMap<>();
// map.put("id",id);
// map.put("name",name);
// map.put("pv",pv);
// map.put("userAgent",userAgent);
// map.put("headers",header);
map.put("age",age);
map.put("inters",inters);
map.put("params",params);
map.put("_ga",_ga);
System.out.println(cookie.getName()+"===>"+cookie.getValue());
return map;
}
@PostMapping("/save")
public Map postMethod(@RequestBody String content){
Map<String,Object> map = new HashMap<>();
map.put("content",content);
return map;
}
//1、语法: 请求路径:/cars/sell;low=34;brand=byd,audi,yd
//2、SpringBoot默认是禁用了矩阵变量的功能
// 手动开启:原理。对于路径的处理。UrlPathHelper进行解析。
// removeSemicolonContent(移除分号内容)支持矩阵变量的
//3、矩阵变量必须有url路径变量才能被解析
@GetMapping("/cars/{path}")
public Map carsSell(@MatrixVariable("low") Integer low,
@MatrixVariable("brand") List<String> brand,
@PathVariable("path") String path){
Map<String,Object> map = new HashMap<>();
map.put("low",low);
map.put("brand",brand);
map.put("path",path);
return map;
}
// /boss/1;age=20/2;age=10
@GetMapping("/boss/{bossId}/{empId}")
public Map boss(@MatrixVariable(value = "age",pathVar = "bossId") Integer bossAge,
@MatrixVariable(value = "age",pathVar = "empId") Integer empAge){
Map<String,Object> map = new HashMap<>();
map.put("bossAge",bossAge);
map.put("empAge",empAge);
return map;
}
}
@GetMapping("/goto")
public String goToPage(HttpServletRequest request){
request.setAttribute("msg","成功了");
request.setAttribute("code",200);
return "forward:success";
}
@ResponseBody
@GetMapping("/success")
public Map success(@RequestAttribute("msg") String msg,
@RequestAttribute("code") Integer code,
HttpServletRequest request){
Object msg1 = request.getAttribute("msg");
Map<String, Object> map=new HashMap<>();
map.put("reqMethod",msg1);
map.put("annotation",msg);
return map;
}
//1、语法: 请求路径:/cars/sell;low=34;brand=byd,audi,yd
// 每个请求变量后面可以加上多个矩阵变量,用;分隔
//2、SpringBoot默认是禁用了矩阵变量的功能
// 手动开启:原理。对于路径的处理。UrlPathHelper进行解析。
// removeSemicolonContent(移除分号内容)支持矩阵变量的
//3、矩阵变量必须有url路径变量才能被解析
//cars/sell;low=34;brand=byd,audi,yd
//矩阵变量对应的路径变量必须写成{xxx}的形式
@GetMapping("/cars/{path}")
public Map carsSell(@MatrixVariable("low") Integer low,
@MatrixVariable("brand") List<String> brand,
@PathVariable("path") String path){
Map<String,Object> map = new HashMap<>();
map.put("low",low);
map.put("brand",brand);
map.put("path",path);
return map;
}
// /boss/1;age=20/2;age=10
//如果两个矩阵变量名称一样,使用pathVar确定
@GetMapping("/boss/{bossId}/{empId}")
public Map boss(@MatrixVariable(value = "age",pathVar = "bossId") Integer bossAge,
@MatrixVariable(value = "age",pathVar = "empId") Integer empAge){
Map<String,Object> map = new HashMap<>();
map.put("bossAge",bossAge);
map.put("empAge",empAge);
return map;
}
//对应配置类
//两种方式,一种是实现WebMvcConfigurer接口;另一种是添加使用@Bean添加WebMvcConfigurer组件
@Configuration
public class WebConfig /*implements WebMvcConfigurer*/ {
// @Override
// public void configurePathMatch(PathMatchConfigurer configurer) {
// UrlPathHelper helper=new UrlPathHelper();
// helper.setRemoveSemicolonContent(false);
// configurer.setUrlPathHelper(helper);
// }
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper helper=new UrlPathHelper();
helper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(helper);
}
};
}
}
2.复杂参数
Map、Model(map、model里面的数据会被放在request的请求域 request.setAttribute)、
Errors/BindingResult、
RedirectAttributes( 重定向携带数据)、
ServletResponse(response)、
SessionStatus、UriComponentsBuilder、
ServletUriComponentsBuilder
//例如,我发送请求,转发到/success
@GetMapping("/params")
public String testParam(Map<String,Object>map,
Model model,
HttpServletRequest request,
HttpServletResponse response){
map.put("map","hello world");
model.addAttribute("model","hello world");
request.setAttribute("request","hello world");
Cookie cookie=new Cookie("c1","v1");
response.addCookie(cookie);
return "forward:/success";
}
@ResponseBody
@GetMapping("/success")
//required =false,表示这个参数在RequestAttribute比一定要有
public Map success(@RequestAttribute(value = "msg",required = false) String msg,
@RequestAttribute(value = "code",required = false) Integer code,
HttpServletRequest request){
Object msg1 = request.getAttribute("msg");
Map<String, Object> map=new HashMap<>();
Object request1 = request.getAttribute("request");
Object model = request.getAttribute("model");
Object map1 = request.getAttribute("map");
map.put("reqMethod",msg1);
map.put("annotation",msg);
map.put("request",request1);
map.put("model",model);
map.put("map",map1);
return map;
}
3.Servlet API
WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId
这些Servlet提供的,也可以作为参数。
ServletRequestMethodArgumentResolver可以解析
4.自定义对象参数
//目标方法能够解析提交的参数,并封装到我们自定义的bean中
@PostMapping("/saveuser")
public Person saveUser(Person person){
return person;
}
三、参数处理原则(原理
即如何处理Controller中的方法传递的参数(包括参数的注解
@GetMapping("/car/{id}/owner/{username}")
public Map<String,Object> getCar(@PathVariable("id") Integer id,
@PathVariable("username") String name,
@PathVariable Map<String,String> pv,
@RequestHeader("User-Agent") String userAgent,
@RequestHeader Map<String,String> header,
@RequestParam("age") Integer age,
@RequestParam("inters")List<String> inters,
@RequestParam Map<String,String> params){
Map<String,Object> map=new HashMap<>();
return map;
}
//http://localhost:8888/car/3/owner/lisi?age=18&inters=basketball&inters=game
发送请求,在DispatcherServlet的doDispatch添加断点,debug
1.HandlerAdapter
//上文已经介绍过了,这里获得可以处理这个请求的handle(就是对应的Controller.method())
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
//接下来执行到这里
//为当前Handler 找一个适配器 HandlerAdapte
//适配器执行目标方法并解析确定方法参数的每一个值
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
Step into getHandlerAdapter
可以看出这个方法就是遍历所有的Adapter,找到支持这个handler的adapter并返回。在这里就是返回了RequestMappingHandlerAdapter
2.执行目标方法
//回到DispatcherServlet,接下来执行到这里
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
Step into handle
最终会进入RequestMappingHandlerAdapter的handleInternal
方法
单步执行,到
// No synchronization on session demanded at all...
mav = invokeHandlerMethod(request, response, handlerMethod);
Step into invokeHandlerMethod
//这个方法中有这么一部分
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) {
//为invocableMethod设置参数解析器
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
//为invocableMethod设置返回值处理器
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
3.参数解析器
确定将要执行的目标方法的每一个参数的值是什么;
SpringMVC目标方法能写多少种参数类型。取决于参数解析器。
可以看出这个类HandlerMethodArgumentResolver有两个方法。
第二个方法:判断当前解析器是否支持解析这种参数
第一个方法:支持就调用 resolveArgument,解析参数
4.参数返回值处理器
参数返回值处理器就决定了我们返回值能是什么类型
5.解析目标方法每一个参数
继续单步执行,直到
//执行并处理
invocableMethod.invokeAndHandle(webRequest, mavContainer);
Step into invokeAndHandle
//只看方法名称就可以看出,是处理请求并返回结果的方法
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
Step into invokeForRequest
//只看方法名称就可以看出,是得到目标方法的参数并返回的方法
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
Step into getMethodArgumentValues
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
//getMethodParameters方法获得参数信息(包括注解信息、类型等
MethodParameter[] parameters = getMethodParameters();
if (ObjectUtils.isEmpty(parameters)) {
return EMPTY_ARGS;
}
Object[] args = new Object[parameters.length];
//遍历所有parameter ,并将值存入args
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) {
continue;
}
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}
try {
//Step into这里
//resolveArgument解析得到参数的值
//内部是查找对应解析器resolver,调用它的resolveArgument方法解析参数
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}
catch (Exception ex) {
// Leave stack trace for later, exception may actually be resolved and handled...
if (logger.isDebugEnabled()) {
String exMsg = ex.getMessage();
if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
logger.debug(formatArgumentError(parameter, exMsg));
}
}
throw ex;
}
}
return args;
}
Step into resolveArgument
//解析参数
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
**Step into HandlerMethodArgumentResolver **
//挨个判断所有参数解析器那个支持解析这个参数
//方式很简单
//先去argumentResolverCache看看有没有
//没有就遍历argumentResolvers,找到后加入Cache,方便下次直接找,最后返回
@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
if (result == null) {
for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
if (resolver.supportsParameter(parameter)) {
result = resolver;
this.argumentResolverCache.put(parameter, result);
break;
}
}
}
return result;
}
//返回到HandlerMethodArgumentResolver 的 resolveArgument 方法后,继续向下执行
//到return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
//最后调用resolver.resolveArgument的方法进行解析得到值并返回
这就是解析完第一个参数id的值。
总结
:当请求发送来时,在DispatcherServlet中调用getHandler得到能处理请求的handler,之后根据handler找到能够支持它的adapter,adapter调用handle方法执行handler来处理请求(包括解析参数[选择resolver处理方法参数]和执行方法体[doInvoke])
简单分析自定义类型的参数如何解析
?
ServletModelAttributeMethodProcessor 这个参数处理器支持解析自定义参数。判断是否支持的逻辑很简单(判断是否为简单类型)
进入resolveArgument方法解析,
其中重要的一个是WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
WebDataBinder :web数据绑定器,将请求参数的值绑定到指定的JavaBean里面。
WebDataBinder 利用它里面的 Converters 将请求数据转成指定的数据类型。再次封装到JavaBean中
查找方式:GenericConversionService,在设置每一个值的时候,找它里面的所有converter哪个可以将这个数据类型(request带来参数的字符串)转换到指定的类型。
我们可以给WebDataBinder里面放自己的Converter。
//1、WebMvcConfigurer定制化SpringMVC的功能
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new Converter<String, Pet>() {
@Override
public Pet convert(String source) {
if(!StringUtils.isEmpty(source)){
Pet pet = new Pet();
....处理
return pet;
}
return null;
}
});
}
};
}
6.目标方法执行并完成
//为这个方法打上断点,
//在DispatcherServlet的doDispatch加断点
//进行debug
//分析它对参数map,model是如何处理,以及如何执行方法体并返回
@GetMapping("/params")
public String testParam(Map<String,Object>map,
Model model,
HttpServletRequest request,
HttpServletResponse response){
map.put("map","hello world");
model.addAttribute("model","hello world");
request.setAttribute("request","hello world");
Cookie cookie=new Cookie("c1","v1");
response.addCookie(cookie);
return "forward:/success";
}
//前面的步骤类似,我们进入DispatcherServlet的doDispatch
//Step into handle
//一直Step into 到 invokeForRequest
@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
//上面我们分析了这个方法(用来处理方法参数)
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
logger.trace("Arguments: " + Arrays.toString(args));
}
return doInvoke(args);
}
执行完getMethodArgumentValues,四个参数都解析完成,我们发现map和model参数是同一个,都是BindingAwareModelMap类型的。
Step into getMethodArgumentValues进行简要分析
发现得到处理map参数的resolver,执行它的resolveArgument进行解析后得到的是BindingAwareModelMap对象(这里的和上图不一样,是因为我重新debug了一次)。
而这个方法调用的是mavContainer.getModel();
,进入mavContainer,查看这个方法,发现他返回的是return this.defaultModel;
,而这个变量已经被初始化了。
BindingAwareModelMap既实现了Model,又实现了Map,所以参数map和model被处理后都会返回同一个。
private final ModelMap defaultModel = new BindingAwareModelMap();
参数都解析完成后,回到方法体执行,执行完成后会跳回setResponseStatus方法处,继续向下执行。
执行到
处理了返回值
到这里不仅model的数据加入到了Container,view的数据也加入了Container。数据都放到了ModelAndViewContainer
//在返回之前再进行一些处理
return getModelAndView(mavContainer, modelFactory, webRequest);
至此结束了handle的执行,返回了ModelAndView对象
。
回到了doDispatch方法。
7.处理派发结果
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
Step into
//进入到
//对界面渲染
view.render(mv.getModelInternal(), request, response);
再Step into
@Override
public void render(@Nullable Map<String, ?> model, HttpServletRequest request,
HttpServletResponse response) throws Exception {
if (logger.isDebugEnabled()) {
logger.debug("View " + formatViewName() +
", model " + (model != null ? model : Collections.emptyMap()) +
(this.staticAttributes.isEmpty() ? "" : ", static attributes " + this.staticAttributes));
}
//这个方法将model的值迁移到mergedModel
Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
prepareResponse(request, response);
//这个方法将mergedModel中的值放入请求域,Step into
renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
}
再Step into
exposeModelAsRequestAttributes(model, request);
//至此终于把model的数据放入了request域中。
protected void exposeModelAsRequestAttributes(Map<String, Object> model,
HttpServletRequest request) throws Exception {
model.forEach((name, value) -> {
if (value != null) {
request.setAttribute(name, value);
}
else {
request.removeAttribute(name);
}
});
}
至此终于分析完了为什么map和model的数据能放入到request域
中,他们是在页面渲染部分实现
的,当然上面说的所有方法也不仅仅实现了所说的功能,之后会再学习总结。
再总结一下
:
handle方法处理了请求参数,执行了方法体,记录了方法体返回值这些信息,并返回ModelAndView保存。
processDispatchResult实现了页面渲染等工作。