SpringBoot之请求处理

简介: SpringBoot之请求处理



前言

包括请求映射、常用参数获取与基本注解、参数处理、POJO对象封装等。


一、请求映射

在学习SpringMVC的时候,@RequestMapping、@GetMapping、@PostMapping、@PutMapping、@DeleteMapping、Rest风格大家都一定学会了。这里我们就大致讲解一下在SpringBoot的使用,跟SpringMVC有些出入。

  • @xxxMapping;
  • @GetMapping
  • @PostMapping
  • @PutMapping
  • @DeleteMapping
  • Rest风格支持(使用HTTP请求方式动词来表示对资源的操作)
  • 以前:
  • /getUser 获取用户
  • /deleteUser 删除用户
  • /editUser 修改用户
  • /saveUser保存用户
  • 现在(SpringBoot): /user
  • GET-获取用户
  • DELETE-删除用户
  • PUT-修改用户
  • POST-保存用户
  • 核心Filter;HiddenHttpMethodFilter

需要讲解一下:SpringMVC中我们用到的Rest:

但是在SpringBoot在底层做了更加精简的操作,现在Rest风格就是上述/user就可以是get、post、delete、put请求任意一种。而SpringBoot如何区分呢,我们先回顾一下SpringMVC的解决办法如下:

大家如果对SpringMVC的Rest风格有点遗忘,可以阅读我前面的文章回顾一下SpringMVC之RESTful

SpringBoot同样是用的HiddenHttpMethodFilter,这一点是毋庸置疑的,但是SpringBoot的Rest功能默认是关闭的,所以我们需要手动开启。

  • 用法
  • 开启页面表单的Rest功能
  • 页面 form的属性method=post,隐藏域 _method=put、delete等(如果直接get或post,无需隐藏域)
  • 编写请求映射
spring:
  mvc:
    hiddenmethod:
      filter:
        enabled: true   #开启页面表单的Rest功能

1.实例

html页面:

<form action="/user" method="post">
    <input value="REST-POST提交" type="submit" />
</form>
<form action="/user" method="post">
    <input name="_method" type="hidden" value="DELETE"/>
    <input value="REST-DELETE 提交" type="submit"/>
</form>
<form action="/user" method="post">
    <input name="_method" type="hidden" value="PUT" />
    <input value="REST-PUT提交"type="submit" />
</form>

Controller层:

@GetMapping("/user")
//@RequestMapping(value = "/user",method = RequestMethod.GET)
    public String getUser(){
        return "GET-张三";
    }
    @PostMapping("/user")
//@RequestMapping(value = "/user",method = RequestMethod.POST)
    public String saveUser(){
        return "POST-张三";
    }
    @PutMapping("/user")
//@RequestMapping(value = "/user",method = RequestMethod.PUT)
    public String putUser(){
        return "PUT-张三";
    }
    @DeleteMapping("/user")
//@RequestMapping(value = "/user",method = RequestMethod.DELETE)
    public String deleteUser(){
        return "DELETE-张三";
    }

2.Rest原理

  • Rest原理(表单提交要使用REST的时候)
  • 表单提交会带上_method=PUT
  • 请求过来被HiddenHttpMethodFilter拦截
  • 请求是否正常,并且是POST
  • 获取到_method的值。
  • 兼容以下请求;PUT.DELETE.PATCH
  • 原生request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值。
  • 过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用requesWrapper的。

底层源码:

public class HiddenHttpMethodFilter extends OncePerRequestFilter {
  private static final List<String> ALLOWED_METHODS =
      Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(),
          HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));
  /** Default method parameter: {@code _method}. */
  public static final String DEFAULT_METHOD_PARAM = "_method";
  private String methodParam = DEFAULT_METHOD_PARAM;
  /**
   * Set the parameter name to look for HTTP methods.
   * @see #DEFAULT_METHOD_PARAM
   */
  public void setMethodParam(String methodParam) {
    Assert.hasText(methodParam, "'methodParam' must not be empty");
    this.methodParam = methodParam;
  }
  @Override
  protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
      throws ServletException, IOException {
    HttpServletRequest requestToUse = request;
    if ("POST".equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) {
      String paramValue = request.getParameter(this.methodParam);
      if (StringUtils.hasLength(paramValue)) {
        String method = paramValue.toUpperCase(Locale.ENGLISH);
        if (ALLOWED_METHODS.contains(method)) {
          requestToUse = new HttpMethodRequestWrapper(request, method);
        }
      }
    }
    filterChain.doFilter(requestToUse, response);
  }
  /**
   * Simple {@link HttpServletRequest} wrapper that returns the supplied method for
   * {@link HttpServletRequest#getMethod()}.
   */
  private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {
    private final String method;
    public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
      super(request);
      this.method = method;
    }
    @Override
    public String getMethod() {
      return this.method;
    }
  }
}

在底层WebMvcAutoConfiguration类中:

@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
    ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
    ...
    @Bean
    @ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
    @ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
    public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
        return new OrderedHiddenHttpMethodFilter();
    }
    ...
}

有个@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)意味着在没有HiddenHttpMethodFilter时,才执行hiddenHttpMethodFilter()。因此,我们可以自定义filter,改变默认的_method:

@Configuration(proxyBeanMethods = false)
public class WebConfig{
    @Bean
    public HiddenHttpMethodFilter hiddenHttpMethodFilter() {
        HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
        methodFilter.setMethodParam("_m");
        return methodFilter;
    }

原来的_method失效,应该用_m。

3.补充

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

  • SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping 。访问 /能访问到index.html;
  • SpringBoot自动配置了默认 的 RequestMappingHandlerMapping
  • 请求进来,挨个尝试所有的HandlerMapping看是否有请求信息。
  • 如果有就找到这个请求对应的handler
  • 如果没有就是下一个 HandlerMapping
  • 我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping。自定义 HandlerMapping

二、常用参数注解

注解:

  • @PathVariable 路径变量
  • @RequestHeader 获取请求头
  • @RequestParam 获取请求参数(指问号后的参数,url?a=1&b=2)
  • @CookieValue 获取Cookie值
  • @RequestAttribute 获取request域属性
  • @RequestBody 获取请求体[POST]
  • @MatrixVariable 矩阵变量
  • @ModelAttribute

1.@RequestAttribute

@RequestAttribute中value属性值是要在requset请求域中获取的参数;required = false代表该请求域中非必须有该参数,即使没有也不会报错。

@Controller
public class RequestController {
    @GetMapping("/goto")
    public String goToPage(HttpServletRequest request){
        request.setAttribute("msg","成功了...");
        request.setAttribute("code",200);
        return "forward:/success";  //转发到  /success请求
    }
    @ResponseBody
    @GetMapping("/success")
    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<>();
        map.put("reqMethod_msg",msg1);
        map.put("annotation_msg",msg);
        return map;
    }
}

2.@MatrixVariable与UrlPathHelper

  • 语法: 请求路径:/cars/sell;low=34;brand=byd,audi,yd
  • SpringBoot默认是禁用了矩阵变量的功能
  • 手动开启:原理。对于路径的处理。UrlPathHelper的removeSemicolonContent设置为false,让其支持矩阵变量的。
  • 矩阵变量必须有url路径变量才能被解析:


手动开启矩阵变量

  • 实现WebMvcConfigurer接口,创建返回WebMvcConfigurerBean:
@Configuration(proxyBeanMethods = false)
public class WebConfig{
    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer() {
           @Override
            public void configurePathMatch(PathMatchConfigurer configurer) {
                UrlPathHelper urlPathHelper = new UrlPathHelper();
                // 不移除;后面的内容。矩阵变量功能就可以生效
                urlPathHelper.setRemoveSemicolonContent(false);
                configurer.setUrlPathHelper(urlPathHelper);
            }
        }
    }
}

@MatrixVariable的用例

@RestController
public class ParameterTestController {
    ///cars/sell;low=34;brand=byd,audi,yd
    @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;
    }
}

html页面:

<a href="/cars/sell;low=34;brand=byd,audi,yd">测试MatrixVariable1</a>
<a href="/cars/sell;low=34;brand=byd;brand=audi;brand=yd">测试MatrixVariable2</a>
<a href="/boss/1;age=20/2;age=10">测试MatrixVariable3</a>

/boss/1;age=20/2;age=10对应的@MatrixVariable(value = "age",pathVar = "bossId") Integer bossAge, @MatrixVariable(value = "age",pathVar = "empId") Integer empAge的参数:首先路径请求是/1;age=20/2;age=10两部分,矩阵变量是根据分号进行分割,针对每一个Parh Variable绑定一个Matrix Variable,然后使用 value 和 pathVar属性就能找到该值。

3.其他注解

@PathVariable、@RequestHeader、@RequestParam、@CookieValue、@RequestBody

@RestController
public class ParameterTestController {
    //  car/2/owner/zhangsan
    @GetMapping("/car/{id}/owner")
    public Map<String,Object> getCar(@PathVariable("id") Integer id,
                                     @PathVariable Map<String,String> pv,
                                     @RequestHeader("User-Agent") String userAgent,
                                     @RequestHeader Map<String,String> header,
                                     @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("pv",pv);
        map.put("userAgent",userAgent);
        map.put("headers",header);
        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;
    }
}

html页面:

<ul>
    <a href="/car/3/owner?username=lisi">car/3/owner/lisi</a>
    <li>@PathVariable(路径变量)</li>
    <li>@RequestHeader(获取请求头)</li>
    <li>@RequestParam(获取请求参数)</li>
    <li>@CookieValue(获取cookie值)</li>
    <li>@RequestBody(获取请求题)</li>
</ul>
<form action="/save" method="post">
    测试@RequestBody</br>
    用户名:<input name="username" type="text"><br>
    邮箱:<input name="email" type="text"><br>
    <input type="submit" value="提交">
</form>

上面的@CookieValue("_ga") String _ga,@CookieValue("_ga") Cookie cookie前提是你浏览器有这个cookie对象。我这里就不再测试这俩。

4.感受model、map对象获取

能够直接在request请求域中直接获取

@Controller
public class RequestController {
    @GetMapping("/params")
    public String testParam(Map<String,Object> map,
                            Model model,
                            HttpServletRequest request,
                            HttpServletResponse response){
        map.put("hello","world666");
        model.addAttribute("world","hello666");
        request.setAttribute("message","HelloWorld");
        Cookie cookie = new Cookie("c1","v1");
        response.addCookie(cookie);
        return "forward:/success";
    }
    @ResponseBody
    @GetMapping("/success")
    public Map success(HttpServletRequest request,
                       @CookieValue("c1") String c1){
        Map<String,Object> map = new HashMap<>();
        Object hello = request.getAttribute("hello");
        Object world = request.getAttribute("world");
        Object message = request.getAttribute("message");
        map.put("hello",hello);
        map.put("world",world);
        map.put("message",message);
        map.put("cookie",c1);
        return map;
    }
}

补充

底层源码:

public class InternalResourceView extends AbstractUrlBasedView {
  @Override//该方法在AbstractView,AbstractUrlBasedView继承了AbstractView
  public void render(@Nullable Map<String, ?> model, HttpServletRequest request,
      HttpServletResponse response) throws Exception {
        ...
    Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
    prepareResponse(request, response);
        //看下一个方法实现
    renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
  }
    @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);
        ...
  }
    //该方法在AbstractView,AbstractUrlBasedView继承了AbstractView
    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);
      }
    });
  }
}

exposeModelAsRequestAttributes方法看出,Map<String,Object> map,Model model这两种类型数据可以给request域中放数据,用request.getAttribute()获取。

三、POJO自动封装(自定义Converter)

SpringBoot能够将POJO对象自动封装,其实我们在SpringMVC学习中就已经自动自动封装了。这里补充一下自定义参数绑定封装。

Controller层:

@RestController
public class ParameterTestController {
    /**
     * 数据绑定:页面提交的请求数据(GET、POST)都可以和对象属性进行绑定
     * @param person
     * @return
     */
    @PostMapping("/saveuser")
    public Person saveuser(Person person){
        return person;
    }
}

POJO:

@Data
public class Person {
    private String userName;
    private Integer age;
    private Date birth;
    private Pet pet;
}
@Data
public class Pet {
    private String name;
    private Integer age;
}

html页面:

<form action="/saveuser" method="post">
    姓名: <input name="userName" value="zhangsan"/> <br/>
    年龄: <input name="age" value="20"/> <br/>
    生日: <input name="birth" value="2003/11/22"/> <br/>
    宠物姓名:<input name="pet.name" value="阿毛"/><br/>
    宠物年龄:<input name="pet.age" value="5"/><br>
    <!-- 宠物:<input name="pet" value="阿毛,3"/>-->
    <input value="保存"type="submit" />
</form>

但如果是这种:

我们就要自己写一个Converter:

SpringBoot中WebDataBinder 利用它里面的 Converters 将请求数据转成指定的数据类型。再次封装到JavaBean中。在过程当中,用到GenericConversionService:在设置每一个值的时候,找它里面的所有converter那个可以将这个数据类型(request带来参数的字符串)转换到指定的类型。所以我们可以给WebDataBinder里面放自己的Converter。

@Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer() {
            @Override
            public void addFormatters(FormatterRegistry registry) {
                registry.addConverter(new Converter<String, Pet>() {
          //Converter<String, Pet>String是传入要转换值类型,Pet是要转换的类型
                    @Override
                    public Pet convert(String source) {//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;
                    }
                });
            }
        };
    }


总结

以上就是请求映射、POJO对象封装、常用参数注解的讲解。

相关文章
|
7月前
|
存储 JSON Java
SpringBoot集成AOP实现每个接口请求参数和返回参数并记录每个接口请求时间
SpringBoot集成AOP实现每个接口请求参数和返回参数并记录每个接口请求时间
452 2
|
3月前
|
Java 网络架构 Spring
springboot中restful风格请求的使用
本文介绍了在Spring Boot中如何使用RESTful风格的请求,包括创建HTML表单页面、在application.yaml配置文件中开启REST表单支持、编写Controller层及对应映射处理,并进行服务启动和访问测试。HTML表单默认只支持GET和POST请求,因此对于DELETE和PUT请求,需要使用隐藏域`_method`来支持。
springboot中restful风格请求的使用
|
2月前
|
SQL JSON Java
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
这篇文章介绍了如何在Spring Boot项目中整合MyBatis和PageHelper进行分页操作,并且集成Swagger2来生成API文档,同时定义了统一的数据返回格式和请求模块。
73 1
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
|
1月前
|
JavaScript 前端开发 Java
SpringBoot项目的html页面使用axios进行get post请求
SpringBoot项目的html页面使用axios进行get post请求
53 2
|
29天前
|
JavaScript 前端开发 Java
SpringBoot项目的html页面使用axios进行get post请求
SpringBoot项目的html页面使用axios进行get post请求
37 0
|
2月前
|
JSON NoSQL Java
springBoot:jwt&redis&文件操作&常见请求错误代码&参数注解 (九)
该文档涵盖JWT(JSON Web Token)的组成、依赖、工具类创建及拦截器配置,并介绍了Redis的依赖配置与文件操作相关功能,包括文件上传、下载、删除及批量删除的方法。同时,文档还列举了常见的HTTP请求错误代码及其含义,并详细解释了@RequestParam与@PathVariable等参数注解的区别与用法。
|
2月前
|
XML Java 应用服务中间件
【Spring】运行Spring Boot项目,请求响应流程分析以及404和500报错
【Spring】运行Spring Boot项目,请求响应流程分析以及404和500报错
221 2
|
2月前
|
前端开发 Java
学习SpringMVC,建立连接,请求,响应 SpringBoot初学,如何前后端交互(后端版)?最简单的能通过网址访问的后端服务器代码举例
文章介绍了如何使用SpringBoot创建简单的后端服务器来处理HTTP请求,包括建立连接、编写Controller处理请求,并返回响应给前端或网址。
60 0
学习SpringMVC,建立连接,请求,响应 SpringBoot初学,如何前后端交互(后端版)?最简单的能通过网址访问的后端服务器代码举例
|
3月前
|
JavaScript 前端开发 Java
SpringBoot项目的html页面使用axios进行get post请求
SpringBoot项目的html页面使用axios进行get post请求
53 6
|
2月前
|
SQL JSON 缓存
你了解 SpringBoot 在一次 http 请求中耗费了多少内存吗?
在工作中常需进行全链路压测并优化JVM参数。通过实验可精确计算特定并发下所需的堆内存,并结合JVM新生代大小估算GC频率,进而优化系统。实验基于SpringBoot应用,利用JMeter模拟并发请求,分析GC日志得出:单次HTTP请求平均消耗约34KB堆内存。复杂环境下,如公司线上环境,单次RPC请求内存消耗可达0.5MB至1MB,揭示了高并发场景下的内存管理挑战。