侧边栏壁纸
博主头像
coydone博主等级

记录学习,分享生活的个人站点

  • 累计撰写 306 篇文章
  • 累计创建 51 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

请求参数处理

coydone
2022-04-05 / 0 评论 / 0 点赞 / 275 阅读 / 11,893 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2022-04-24,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

请求映射

Rest使用与原理

Rest风格支持(使用HTTP请求方式动词来表示对资源的操作)

以前:/getUser获取用户 /deleteUser删除用户 /editUser修改用户  /saveUser保存用户
现在: /user GET-获取用户  DELETE-删除用户  PUT-修改用户   POST-保存用户
核心Filter:HiddenHttpMethodFilter
用法:
	1、表单method=post,隐藏域 _method=put 
	2、SpringBoot中手动开启
		spring.mvc.hiddenmethod.filter = true
	自定义 _method 名字 :自己定义hiddenHttpMethodFilter
@RequestMapping(value = "/user",method = RequestMethod.GET)
public String getUser(){
    return "GET-张三";
}

@RequestMapping(value = "/user",method = RequestMethod.POST)
public String saveUser(){
    return "POST-张三";
}

@RequestMapping(value = "/user",method = RequestMethod.PUT)
public String putUser(){
    return "PUT-张三";
}

@RequestMapping(value = "/user",method = RequestMethod.DELETE)
public String deleteUser(){
    return "DELETE-张三";
}

//原理:SpringBoot源码
@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
    return new OrderedHiddenHttpMethodFilter();
}

//自定义filter
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
    HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
    methodFilter.setMethodParam("_m");
    return methodFilter;
}

spring:
  mvc:
    hiddenmethod:
      filter:
        enabled: true   #开启页面表单的Rest功能

Rest原理(表单提交要使用REST的时候)

1、表单提交会带上_method=PUT

2、请求过来被HiddenHttpMethodFilter拦截。

  • 请求是否正常,并且是POST

  • 获取到_method的值。兼容请求:PUT、DELETE、PATCH

    • 原生request(是post方式),包装模式requesWrapper重写了getMethod方法,返回的是传入的值。

    • 过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用requesWrapper的。

Rest使用客户端工具

  • 如PostMan直接发送Put、delete等方式请求,无需Filter。
//HiddenHttpMethodFilter源码
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    HttpServletRequest requestToUse = request;
    if ("POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null) {
        String paramValue = request.getParameter(this.methodParam);
 //_method
        if (StringUtils.hasLength(paramValue)) {
            String method = paramValue.toUpperCase(Locale.ENGLISH);
 //转大写
            if (ALLOWED_METHODS.contains(method)) {
 //PUT、DELETE、PATCH
                requestToUse = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method);
            }
        }
    }

    filterChain.doFilter((ServletRequest)requestToUse, response);
}
//ALLOWED_METHODS的值为PUT、DELETE、PATCH
static {
	ALLOWED_METHODS = Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(), HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));
}

private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {
    private final String method;

    public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
        super(request);
        this.method = method;
    }

    public String getMethod() {
        return this.method;
    }
}

请求映射原理

DispatcherServlet继承树

在FrameworkServlet中重写了doGet()、doPost()方法。

//doGet()/doPost()→processRequest()→doService()
//子类中重写了doService()
protected abstract void doService(HttpServletRequest request, HttpServletResponse response) throws Exception;

//DispatcherServlet
//doService()中this.doDispatch(request, response);

SpringMVC功能核心都在org.springframework.web.servlet.DispatcherServlet中doDispatch()中。

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HttpServletRequest processedRequest = request;
		HandlerExecutionChain mappedHandler = null;
		boolean multipartRequestParsed = false;

		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

		try {
			ModelAndView mv = null;
			Exception dispatchException = null;

			try {
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);

				// 找到当前请求使用哪个Handler(Controller的方法)处理
				mappedHandler = getHandler(processedRequest);
                
                //HandlerMapping:处理器映射

RequestMappingHandlerMapping:保存了所有@RequestMapping和handler的映射规则。

所有的请求映射都在HandlerMapping中。

  • SpringBoot自动配置欢迎页的WelcomePageHandlerMapping。访问能访问到index.html。

  • SpringBoot自动配置了默认的RequestMappingHandlerMapping。

  • 请求进来,挨个尝试所有的HandlerMapping看是否有请求信息。如果有就找到这个请求对应的handler,如果没有就是下一个 HandlerMapping。

  • 我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping。自定义 HandlerMapping。

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    if (this.handlerMappings != null) {
        for (HandlerMapping mapping : this.handlerMappings) {
            HandlerExecutionChain handler = mapping.getHandler(request);
            if (handler != null) {
                return handler;
            }
        }
    }
    return null;
}

普通参数与基本注解

注解

@PathVariable(路径变量)
@RequestHeader(获取请求头)
@RequestParam(获取请求参数)
@CookieValue(获取cookie值)
@RequestAttribute(获取request域属性)
@RequestBody(获取请求体)
@Matrixvariable(矩阵变量)
	页面开发,cookie禁用了,session里面的内容怎么使用;
	没禁用前:session.set(a,b) → jsessionid → cookie → 每次发请求携带。
	禁用后:url重写: /abc;jsesssionid=xxxx 把cookie的值使用矩阵变量的方式传递
	/cars/se11;1ow=34;brand=byd;brand=audi;brand=yd  ;后的参数都是矩阵变量
@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("age",age);
        map.put("inters",inters); 
        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;
    }
}

//配置类设置矩阵变量生效
@Bean
public webMvcconfigurer webMvcConfigurer(){
    return new webMvcConfigurer() {
        @override
        public void configurePathMatch(PathMatchconfigurer configurer) {
            UrlPathHelper urlPathHelper = new UrlPathHelper();
            //不移除;后面的内容。矩阵变量功能就可以生效
            urlPathHelper.setRemovesemicoloncontent(false);
            configurer.setUrlPathHelper(urlPathHelper) ;
        }
    };
}

Servlet API

WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId。

ServletRequestMethodArgumentResolver 以上的部分参数:

@Override
public boolean supportsParameter(MethodParameter parameter) {
    Class<?> paramType = parameter.getParameterType();
    return (WebRequest.class.isAssignableFrom(paramType) ||
            ServletRequest.class.isAssignableFrom(paramType) ||
            MultipartRequest.class.isAssignableFrom(paramType) ||
            HttpSession.class.isAssignableFrom(paramType) ||
            (pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) ||
            Principal.class.isAssignableFrom(paramType) ||
            InputStream.class.isAssignableFrom(paramType) ||
            Reader.class.isAssignableFrom(paramType) ||
            HttpMethod.class == paramType ||
            Locale.class == paramType ||
            TimeZone.class == paramType ||
            ZoneId.class == paramType);
}

复杂参数

Map、Model(map、model里面的数据会被放在request的请求域 request.setAttribute)、Errors/BindingResult、RedirectAttributes( 重定向携带数据)、ServletResponse(response)、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder。

Map<String,Object> map,  Model model, HttpServletRequest request 
都是可以给request域中放数据
request.getAttribute();

Map、Model类型的参数,会返回 mavContainer.getModel();
→ BindingAwareModelMap 是Model 也是Map
	mavContainer.getModel(); 获取到值

自定义对象参数:可以自动类型转换与格式化,可以级联封装。

参数处理原理

  • HandlerMapping中找到能处理请求的Handler(Controller.method())

  • 为当前Handler找一个适配器HandlerAdapter(RequestMappingHandlerAdapter)。

  • 适配器执行目标方法并确定方法参数的每一个值。

1、HandlerAdapter

2、执行目标方法

// Actually invoke the handler. 
//DispatcherServlet -- doDispatch()
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

//执行目标方法
mav = invokeHandlerMethod(request, response, handlerMethod); 

//ServletInvocableHandlerMethod
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
//获取方法的参数值
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);

3、设置参数解析器

HandlerMethodArgumentResolver确定将要执行的目标方法的每一个参数的值是什么,SpringMVC目标方法能写多少种参数类型。取决于参数解析器。

当前解析器是否支持解析这种参数,支持就调用 resolveArgument()。

4、返回值处理器

5、确定目标方法每一个参数的值

(1)InvocableHandlerMethod:getMethodArgumentValues(),挨个判断所有参数解析器那个支持解析这个参数。

(2)解析这个参数的值:调用各自 HandlerMethodArgumentResolver 的 resolveArgument() 方法即可。

(3)自定义类型参数,封装POJO。ServletModelAttributeMethodProcessor这个参数处理器支持。

判断参数是否为简单类型:isSimpleValueType()。

POJO封装过程:在ServletModelAttributeMethodProcessor类中体现。

WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
WebDataBinder :web数据绑定器,将请求参数的值绑定到指定的JavaBean里面
WebDataBinder 利用它里面的Converters 将请求数据转成指定的数据类型。再次封装到JavaBean中
GenericConversionService:在设置每一个值的时候,找它里面的所有converter那个可以将这个数据类型
	(request带来参数的字符串)转换到指定的类型(JavaBean→Integer)

未来我们可以给WebDataBinder里面放自己的Converter。
private static final class StringToNumber<T extends Number> implements Converter<String, T>

自定义Converter

//1、WebMvcConfigurer定制化SpringMVC的功能
@Bean
public WebMvcConfigurer webMvcConfigurer(){
    return new WebMvcConfigurer() {
        @Override
        public void configurePathMatch(PathMatchConfigurer configurer) {
            UrlPathHelper urlPathHelper = new UrlPathHelper();
            // 不移除;后面的内容。矩阵变量功能就可以生效
            urlPathHelper.setRemoveSemicolonContent(false);
            configurer.setUrlPathHelper(urlPathHelper);
        }

        @Override
        public void addFormatters(FormatterRegistry registry) {
            registry.addConverter(new Converter<String, Pet>() {

                @Override
                public Pet convert(String source) {
                    // 阿猫,3
                    if(!StringUtils.isEmpty(source)){
                        Pet pet = new Pet();
                        String[] split = source.split(",");
                        pet.setName(split[0]);
                        pet.setAge(Integer.parseInt(split[1]));
                        return pet;
                    }
                    return null;
                }
            });
        }
    };
}

6、目标方法执行完成

将所有的数据都放在 ModelAndViewContainer;包含要去的页面地址View。还包含Model数据。

7、处理派发结果

processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
//InternalResourceView
@Override
protected void renderMergedOutputModel(
    Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

    // Expose the model object as request attributes.
    exposeModelAsRequestAttributes(model, request);

    // Expose helpers as request attributes, if any.
    exposeHelpers(request);

    // Determine the path for the request dispatcher.
    String dispatcherPath = prepareForRendering(request, response);

    // Obtain a RequestDispatcher for the target resource (typically a JSP).
    RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
    if (rd == null) {
        throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
                                   "]: Check that the corresponding file exists within your web application archive!");
    }

    // If already included or response already committed, perform include, else forward.
    if (useInclude(request, response)) {
        response.setContentType(getContentType());
        if (logger.isDebugEnabled()) {
            logger.debug("Including [" + getUrl() + "]");
        }
        rd.include(request, response);
    }

    else {
        // Note: The forwarded resource is supposed to determine the content type itself.
        if (logger.isDebugEnabled()) {
            logger.debug("Forwarding to [" + getUrl() + "]");
        }
        rd.forward(request, response);
    }
}

//暴露模型作为请求域属性
exposeModelAsRequestAttributes(model, request);

protected void exposeModelAsRequestAttributes(Map<String, Object> model,
                                              HttpServletRequest request) throws Exception {

    //model中的所有数据遍历挨个放在请求域中
    model.forEach((name, value) -> {
        if (value != null) {
            request.setAttribute(name, value);
        }
        else {
            request.removeAttribute(name);
        }
    });
}
0

评论区