SSM框架学习(四、SpringMVC实战:构建高效表述层框架)

news2024/9/23 5:38:54

目录

一、SpringMVC简介和体验

1.介绍

2.主要作用 

 3.核心组件和调用流程理解

4.快速体验

二、SpringMVC接收数据

1.访问路径设置

(1)精准路径匹配

(2)模糊路径匹配

(3)类和方法上添加 @RequestMapping 的区别

(4)请求方式指定 

(5)注解进阶

2.接收参数

(1)param 和 json参数比较

(2)param参数接收

Ⅰ. 直接接值

Ⅱ. 注解指定

Ⅲ. 特殊场景接值

(3) 路径参数接收

(4)json 参数接收

3.接收 Cookie 数据

4.接收请求头数据

5.原生 Api 对象操作

6.共享域对象操作

 (1)属性(共享)域作用回顾

(2)Request级别属性(共享)域

三、SpringMVC响应数据

1.handler方法分析

2.页面跳转控制 

(1)快速返回模板视图

(2)转发和重定向 

3.返回 JSON 数据 

(1)前置准备

(2)@ResponseBody

4.返回静态资源处理

四、RESTFul风格设计和实战 

1.RESTFul风格概述

(1)RESTFul风格简介

(2)RESTFul风格特点

(3)RESTFul风格设计规范

(4)RESTFul风格好处

2.RESTFul风格实战

(1)需求分析

(2)RESTFul风格接口设计

(3)后台接口实现 

五、SpringMVC其他扩展

1.全局异常处理机制

(1)异常处理两种方式

(2)基于注解异常声明异常处理

 2.拦截器使用

(1)拦截器概念

(2)拦截器使用

 (3)拦截器配置细节

3.参数校验

(1)校验概述

(2)易混总结

(3)操作演示


一、SpringMVC简介和体验

1.介绍

spring Web MVC是基于Servlet API构建的原始Web框架,从一开始就包含在Spring Framework中。
正式名称“Spring Web MVC”来自其源模块的名称( spring-webmvc ),但它通常被称为“Spring MVC”。

SpringMVC作为Java EE项目表述层开发的首选方案。之所以能做到这一点,是因为SpringMVC具备如下显著优势:

① Spring 家族原生产品,与IOC容器等基础设施无缝对接

表述层各细分领域需要解决的问题全方位覆盖,提供全面解决方案

代码清新简洁,大幅度提升开发效率

④ 内部组件化程度高,可插拔式组件即插即用,想要什么功能配置相应组件即可

性能卓著,尤其适合现代大型、超大型互联网项目要求


原生Servlet API开发代码片段:

protected void doGet(HttpServletRequest request, HttpServletResponse response) 
                                                        throws ServletException, IOException {  
    String userName = request.getParameter("userName");
    
    System.out.println("userName="+userName);
}

基于SpringMVC开发代码片段:

@RequestMapping("/user/login")
public String login(@RequestParam("userName") String userName,Sting password){
    
    log.debug("userName="+userName);
    //调用业务即可
    
    return "result";
}

2.主要作用 

SSM框架构建起单体项目的技术栈需求,其中的SpringMVC 负责表述层(控制层)实现简化:

① 简化前端参数接收( 形参列表 )

② 简化后端数据响应(返回值)

 3.核心组件和调用流程理解

Spring MVC与许多其他Web框架一样,是围绕前端控制器模式设计的,其中中央 Servlet DispatcherServlet 做整体请求处理调度。

SpringMVC涉及组件理解:

DispatcherServlet : SpringMVC 提供,我们需要使用 web.xml 配置使其生效,它是整个流程处理的核心,所有请求都经过它的处理和分发[ CEO ]

HandlerMapping : SpringMVC 提供,我们需要进行 IoC 配置使其加入 IoC 容器方可生效,它内部缓存 handler (controller 方法)和 handler 访问路径数据,被 DispatcherServlet 调用,用于查找路径对应的 handler[秘书]

HandlerAdapter : SpringMVC 提供,我们需要进行 IoC 配置使其加入 IoC 容器方可生效,它可以处理请求参数和处理响应数据数据,每次 DispatcherServlet 都是通过 handlerAdapter 间接调用handler,他是 handler 和 DispatcherServlet 之间的适配器。[经理]

Handler : handler又称处理器,他是 Controller 类内部的方法简称,是由我们自己定义,用来接收参数,向后调用业务,最终返回响应结果。[打工人]

ViewResovler : SpringMVC提供,我们需要进行 IoC 配置使其加入 IoC 容器方可生效!视图解析器主要作用简化模版视图页面查找的,但是需要注意,前后端分离项目,后端只返回JSON数据,不返回页面,那就不需要视图解析器!所以,视图解析器,相对其他的组件不是必须的![财务]

4.快速体验

 需求:

配置分析:

① DispatcherServlet,设置处理所有请求。

② HandlerMapping,HandlerAdapter,Handler 需要加入到IoC容器,供DS调用。

③ Handler 自己声明(Controller),需要配置到 HandlerMapping 中供DS查找。

步骤:

① 准备项目

 a.创建项目(springmvc-base-quick-01,注意转成 maven/web 项目)

 b.导入依赖

<properties>
    <spring.version>6.0.6</spring.version>
    <servlet.api>9.1.0</servlet.api>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
    <!-- springioc相关依赖  -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <!-- Servlet 相关依赖  -->
    <!-- 在 pom.xml 中引入 Jakarta EE Web API 的依赖 -->
    <!--
        在 Spring Web MVC 6 中,Servlet API 迁移到了 Jakarta EE API,因此在配置 DispatcherServlet 时需要使用
         Jakarta EE 提供的相应类库和命名空间。错误信息 “‘org.springframework.web.servlet.DispatcherServlet’
         is not assignable to ‘javax.servlet.Servlet,jakarta.servlet.Servlet’” 表明你使用了旧版本的
         Servlet API,没有更新到 Jakarta EE 规范。
    -->
    <dependency>
        <groupId>jakarta.platform</groupId>
        <artifactId>jakarta.jakartaee-web-api</artifactId>
        <version>${servlet.api}</version>
        <scope>provided</scope>
    </dependency>

    <!-- springwebmvc相关依赖  -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>${spring.version}</version>
    </dependency>

</dependencies>

② Controller声明

@Controller
public class HelloController {
    @RequestMapping("springmvc/hello")  //对外访问的地址,在handerMapping中注册
    @ResponseBody   //直接返回字符串给前端,不去找视图解析器
    public String hello(){
        //返回给前端
        return "hello springmvc";
    }
}

③ Spring MVC 配置类(声明 springmvc 涉及组件信息的配置类)

/*
* 1.controller配置到ioc容器
* 2.handerMapping,handerAdapter加入到ioc容器
* */
@Configurable
@ComponentScan(basePackages = "com.mihoyo.controller")
public class MvcConfig {
    @Bean
    public RequestMappingHandlerMapping handlerMapping(){
        return new RequestMappingHandlerMapping();
    }

    @Bean
    public RequestMappingHandlerAdapter handlerAdapter(){
        return new RequestMappingHandlerAdapter();
    }
}

④ SpringMVC环境搭建

//可以被web项目加载,会初始化ioc容器,会设置dispatchServlet的地址
public class SpringMvcInit extends AbstractAnnotationConfigDispatcherServletInitializer {
    //service、mapper层的ioc容器的配置
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[0];
    }

    //设置项目对应的配置类(springmvc、controller层ioc容器的配置)
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{MvcConfig.class};
    }

    //设置dispatcherServlet的处理路径(一般情况下为 / 代表处理所有请求)
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}

⑤ 启动测试

注意: tomcat应该是10+版本,方可支持 Jakarta EE API。

二、SpringMVC接收数据

1.访问路径设置

@RequestMapping注解的作用就是将请求的 URL 地址和处理请求的方式(handler方法)关联起来,建立映射关系。

SpringMVC 接收到指定的请求,就会来找到在映射关系中对应的方法来处理这个请求。

(1)精准路径匹配

在@RequestMapping注解指定 URL 地址时,不使用任何通配符,按照请求地址进行精确匹配。

@Controller
public class UserController {

    /**
     * 精准设置访问地址 /user/login
     */
    @RequestMapping(value = {"/user/login"})
    @ResponseBody
    public String login(){
        System.out.println("UserController.login");
        return "login success!!";
    }

    /**
     * 精准设置访问地址 /user/register
     */
    @RequestMapping(value = {"/user/register"})
    @ResponseBody
    public String register(){
        System.out.println("UserController.register");
        return "register success!!";
    }
    
}

 细节:

① 当要指定多个URL地址时,格式为:@RequestMapping(value = {"地址1","地址2"})

② @RequestMapping 与 @WebServlet 的区别:

        @WebServlet() 中的URL地址必须使用 / 开头

        @RequestMapping() 开头的 / 可以省略 

③ 多个 handler 方法映射了同一个地址,导致 SpringMVC 在接收到这个地址的请求时不知道该找哪个 handler 方法处理,就会触发下列异常:

There is already 'demo03MappingMethodHandler' bean method com.mihoyo.mvc.handler.Demo03MappingMethodHandler#empGet() mapped.

(2)模糊路径匹配

在@RequestMapping注解指定 URL 地址时,通过使用通配符,匹配多个类似的地址。

@Controller
public class ProductController {

    /**
     *  路径设置为 /product/*  
     *    /* 为单层任意字符串  /product/a  /product/aaa 可以访问此handler  
     *    /product/a/a 不可以
     *  路径设置为 /product/** 
     *   /** 为任意层任意字符串  /product/a  /product/aaa 可以访问此handler  
     *   /product/a/a 也可以访问
     */
    @RequestMapping("/product/*")
    @ResponseBody
    public String show(){
        System.out.println("ProductController.show");
        return "product show!";
    }
}

单层匹配和多层匹配:

/*:只能匹配URL地址中的一层,如果想准确匹配两层,那么就写 “/*/*” 以此类推。

/**:可以匹配URL地址中的多层。

其中所谓的一层或多层是指一个URL地址字符串被 “/” 划分出来的各个层次。

这个知识点虽然对于@RequestMapping注解来说实用性不大,但是将来配置拦截器的时候也遵循这个规则。 

(3)类和方法上添加 @RequestMapping 的区别

方法上是具体的 hander 地址,类上可以提取公共的访问地址,访问地址:类地址 + 方法地址。

a. 标记到 hander 方法上:

@Controller
public class UserController {

    @RequestMapping(value = {"/user/login"})
    @ResponseBody
    public String login(){
        System.out.println("UserController.login");
        return "login success!!";
    }

    @RequestMapping(value = {"/user/register"})
    @ResponseBody
    public String register(){
        System.out.println("UserController.register");
        return "register success!!";
    }
    
}

b. 加到类上进行优化:

@Controller
@RequestMapping("user")
public class UserController {

    @RequestMapping("login")
    @ResponseBody
    public String login(){
        System.out.println("UserController.login");
        return "login success!!";
    }

    @RequestMapping("register")
    @ResponseBody
    public String register(){
        System.out.println("UserController.register");
        return "register success!!";
    }

    //    /user 的首页,@RequestMapping不可省略
    @RequestMapping
    @ResponseBody
    public String index(){
        System.out.println("UserController.index");
        return "index success!!";
    }
}

 细节:

① 类上的 @RequestMapping 是对公共地址的提取,是非必需的(可以不加 / 不提取)。

    而方法上的 @RequestMapping 是将hander 方法注册到 handerMapping 中,是必需的,不加则不认为该方法能被外部访问!

② 如果类上添加了地址 "user",而存在一个方法的地址就是该地址 "/user",则该方法直接加上 @RequestMapping 注解即可,无需写地址。但 @RequestMapping 不能省略!

(4)请求方式指定 

客户端 -> http协议 -> ds ( DispatcherServlet ) -> hander

HTTP 协议定义了八种请求方式,在 SpringMVC 中封装到了下面这个枚举类:

public enum RequestMethod {
  GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE
}

默认情况下:@RequestMapping("/login") 只要地址正确,任何请求方式都可以访问。

指定请求方式:method = {RequestMethod.POST,RequestMethod.GET}

@Controller
public class UserController {

    /**
     * 精准设置访问地址 /user/login
     * method = RequestMethod.POST 可以指定单个或者多个请求方式!
     * 注意:违背请求方式会出现405异常!
     */
    @RequestMapping(value = {"/user/login"} , method = RequestMethod.POST)
    @ResponseBody
    public String login(){
        System.out.println("UserController.login");
        return "login success!!";
    }

    /**
     * 精准设置访问地址 /user/register
     */
    @RequestMapping(value = {"/user/register"},method = {RequestMethod.POST,RequestMethod.GET})
    @ResponseBody
    public String register(){
        System.out.println("UserController.register");
        return "register success!!";
    }

}

注意:不符合指定的请求方式,会触发 405 异常

(5)注解进阶

@RequestMapping 的 HTTP 方法特定快捷方式变体:

GET:@RequestMapping("login",method=RequestMethod.GET) == @GetMapping("login")

POST:@RequestMapping("login",method=RequestMethod.POST) == @PostMapping("login")

PUT:@RequestMapping("login",method=RequestMethod.PUT) == @PutMapping("login")

DELETE:@RequestMapping("login",method=RequestMethod.DELETE) == @DeleteMapping("login")

注意:

只有方法上才能使用这些注解,类上不能使用(类上使用 @RequestMapping 只是为了提取公共的地址,而不是提取公共的请求方式!)

2.接收参数

(1)param 和 json参数比较

在 HTTP 请求中,我们可以选择不同的参数类型,如 param 类型和 JSON 类型。下面对这两种参数类型进行区别和对比:

① 参数编码:

param 类型的参数会被编码为 ASCII 码。例如,假设 name=john doe,则会被编码为 name=john%20doe。

JSON 类型的参数会被编码为 UTF-8

② 参数顺序:

param 类型的参数没有顺序限制

但是,JSON 类型的参数是有序的。JSON 采用键值对的形式进行传递,其中键值对是有序排列的。

③ 数据类型:

param 类型的参数仅支持字符串类型、数值类型和布尔类型等简单数据类型

JSON 类型的参数则支持更复杂的数据类型,如数组、对象等。

④ 嵌套性:

param 类型的参数不支持嵌套

但是,JSON 类型的参数支持嵌套,可以传递更为复杂的数据结构。

⑤ 可读性:

param 类型的参数格式比 JSON 类型的参数更加简单、易读。

但是,JSON 格式在传递嵌套数据结构时更加清晰易懂。

(2)param参数接收

客户端请求:

Ⅰ. 直接接值
@Controller
@RequestMapping("param")
public class ParamController {

    //1.直接接收
    @RequestMapping("data")
    @ResponseBody
    public String data(String name, int age) {
        System.out.println("name = " + name + ",age = " + age);
        return "name = " + name + ",age = " + age;
    }

}

形参列表直接填写对应名称的参数即可,但注意

① 名称必须相同:请求传递的参数名 = 形参参数名

② 使用该方式,请求可以不传递参数,并且不会报错。

Ⅱ. 注解指定
@Controller
@RequestMapping("param")
public class ParamController {

    //2.注解指定(name必须传递,age可以不传,默认值为1)
    @RequestMapping("data")
    @ResponseBody
    public String data(@RequestParam(value = "name") String username,
                       @RequestParam(required = false, defaultValue = "1") int age) {
        System.out.println("name = " + username + ",age = " + age);
        return "name = " + username + ",age = " + age;
    }

}

@RequestParam 的用法:

①  value = "指定的请求参数名",如果形参名和请求参数名相同,可以省略

② required = true / false:前端是否必须传递此参数,默认是必须传递,不传触发 400 异常

③ defaultValue = "默认值":当该参数是非必须传递时,可以设置一个默认值

Ⅲ. 特殊场景接值

a.一名多值

key=1&key=2:直接使用集合接值即可

@Controller
@RequestMapping("param")
public class ParamController {

    //3.1一名多值
    @RequestMapping("data")
    @ResponseBody
    public String data(@RequestParam List<String> hbs) {
        System.out.println("hbs = " + hbs);
        return "hbs = " + hbs;
    }
}

运行结果: 

注意:

该场景下,形参上必须加上 @RequestParam 注解

如果不加,HandlerAdapter 会将 hbs 所对应的字符串直接赋值给集合,从而产生类型异常!

如果加上,HandlerAdapter 会将 hbs 所对应的字符串一个个通过 add 方法加入到集合中。

b. 实体接收 

用户注册(用户的信息)-> 对应的实体类 -> 插入到数据库中(表)

准备一个对应属性和有 get、set 方法的实体类:

@Data
public class User {
    private String name;
    private int age;
}

在控制器中,使用实体对象接收:

@Controller
@RequestMapping("param")
public class ParamController {

    //3.2 实体接值
    @RequestMapping("data")
    @ResponseBody
    public String data(User user) {
        System.out.println("user = " + user);
        return "user = " + user;
    }

}

运行结果:

注意:该方式下,实体类的属性名必须等于请求参数名! 

(3) 路径参数接收

路径传递参数是一种在 URL 路径中传递参数的方式。

原本的:user/login?name=root&password=123456

路径传参:user/root/123456

该方式可以使得路径变得更短。从而使得解析效率更高,性能越好!

由于路径是动态的,所以我们需要学会:① 设置动态路径,② 接收动态路径参数

@Controller
@RequestMapping("path")
@ResponseBody
public class PathController {
    
    //设置动态路径:{key} 作用等价于通配符*,但可以通过key在形参列表获取传入的参数
    @RequestMapping("{name}/{password}")
    //接收路径参数:@PathVariable
    public String login(@PathVariable(value = "name") String username, @PathVariable String password) {
        System.out.println("username = " + username + ",password = " + password);
        return "username = " + username + ",password = " + password;
    }
}

注意:

该方式下,形参必须加上 @PathVariable 注解。因为如果不加,默认认为是通过 param 格式来接收参数。

运行结果:

(4)json 参数接收

前端传递 JSON 数据时,Spring MVC 框架可以使用 @RequestBody 注解来将 JSON 数据转换为 Java 对象。

@RequestBody 注解表示当前方法参数的值应该从请求体中获取,并且需要指定 value 属性来指示请求体应该映射到哪个参数上。其使用方式和示例代码如下:

a. 前端发送 JSON 数据的示例:(使用postman测试)

{
  "name": "张三",
  "age": 18,
  "gender": "男"
}

b. 定义一个用于接收 JSON 数据的 Java 类:

@Data
public class Person {
    private String name;
    private int age;
    private String gender;
}

 c. 在控制器中,使用 @RequestBody 注解来接收 JSON 数据,并将其转换为 Java 对象

@Controller
@RequestMapping("json")
@ResponseBody
public class JsonController {

    @PostMapping("data")
    public String data(@RequestBody Person person) {
        System.out.println("person = " + person);
        return "person = " + person;
    }
}

d. 完善配置

问题:415 不支持数据类型

原因:Java 原生的 API,只支持路径参数和 param 参数,不支持 json(json 是前端的格式)

解决办法:

① pom.xml 加入 jackson 依赖( json 处理的依赖)

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.15.0</version>
</dependency>

② handerAdapter 配置 json 转化器

@Configuration
@EnableWebMvc   //给handlerAdapter配置了json转化器
@ComponentScan(basePackages = "com.mihoyo")
public class MvcConfig {
    @Bean
    public RequestMappingHandlerMapping handlerMapping(){
        return new RequestMappingHandlerMapping();
    }

    @Bean
    public RequestMappingHandlerAdapter handlerAdapter(){
        return new RequestMappingHandlerAdapter();
    }
}

e. @EnableWebMvc注解说明

@EnableWebMvc 注解效果等同于在 XML 配置中,可以使用 <mvc:annotation-driven> 元素.

我们来解析<mvc:annotation-driven>对应的解析工作:

① 查看标签最终对应解析的Java类:

② 查看解析类中具体的动作

解析器会主动调用对应的 parse 方法: 

class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {

  public static final String HANDLER_MAPPING_BEAN_NAME = RequestMappingHandlerMapping.class.getName();

  public static final String HANDLER_ADAPTER_BEAN_NAME = RequestMappingHandlerAdapter.class.getName();

  static {
    ClassLoader classLoader = AnnotationDrivenBeanDefinitionParser.class.getClassLoader();
    javaxValidationPresent = ClassUtils.isPresent("jakarta.validation.Validator", classLoader);
    romePresent = ClassUtils.isPresent("com.rometools.rome.feed.WireFeed", classLoader);
    jaxb2Present = ClassUtils.isPresent("jakarta.xml.bind.Binder", classLoader);
    jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) &&
            ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
    jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
    jackson2SmilePresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", classLoader);
    jackson2CborPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.cbor.CBORFactory", classLoader);
    gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);
  }


  @Override
  @Nullable
  public BeanDefinition parse(Element element, ParserContext context) {
    //handlerMapping加入到ioc容器
    readerContext.getRegistry().registerBeanDefinition(HANDLER_MAPPING_BEAN_NAME, handlerMappingDef);

    //添加jackson转化器
    addRequestBodyAdvice(handlerAdapterDef);
    addResponseBodyAdvice(handlerAdapterDef);

    //handlerAdapter加入到ioc容器
    readerContext.getRegistry().registerBeanDefinition(HANDLER_ADAPTER_BEAN_NAME, handlerAdapterDef);
    return null;
  }

  //具体添加jackson转化对象方法
  protected void addRequestBodyAdvice(RootBeanDefinition beanDef) {
    if (jackson2Present) {
      beanDef.getPropertyValues().add("requestBodyAdvice",
          new RootBeanDefinition(JsonViewRequestBodyAdvice.class));
    }
  }

  protected void addResponseBodyAdvice(RootBeanDefinition beanDef) {
    if (jackson2Present) {
      beanDef.getPropertyValues().add("responseBodyAdvice",
          new RootBeanDefinition(JsonViewResponseBodyAdvice.class));
    }
  }

总结:

添加 @EnableWebMvc 注解后,就会自动帮我们在 ioc 容器中添加 handlerMapping 和 handlerAdapter,并给 handlerAdapter 添加 jackson 的 json 处理器

此时,配置类就可简写为:

@Configuration
@EnableWebMvc   //给handlerAdapter配置了json转化器
@ComponentScan(basePackages = "com.mihoyo")
public class MvcConfig {

}

3.接收 Cookie 数据

@Controller
@RequestMapping("cookie")
@ResponseBody
public class CookieController {

    //存cookie
    @RequestMapping("save")
    public String save(HttpServletResponse response){
        Cookie cookie=new Cookie("cookieName","root");
        response.addCookie(cookie);
        return "ok";
    }
}

可以使用 @CookieValue 注解 将 HTTP Cookie 的值绑定到控制器中的方法参数。

@Controller
@RequestMapping("cookie")
@ResponseBody
public class CookieController {

    //取cookie
    @RequestMapping("data")
    public String data(@CookieValue(value = "cookieName") String value) {
        System.out.println("value = " + value);
        return "value = " + value;
    }
}

运行结果:

4.接收请求头数据

可以使用 @RequestHeader 注解将请求标头绑定到控制器中的方法参数。

请考虑以下带有标头的请求:

@Controller
@RequestMapping("header")
@ResponseBody
public class HeaderController {

    @RequestMapping("data")
    public String data(@RequestHeader("Host") String host){
        System.out.println("host = " + host);
        return "host = " + host;
    }
}

 运行结果:

5.原生 Api 对象操作

下表描述了常用的支持的控制器方法参数:

Controller method argument 控制器方法参数Description
jakarta.servlet.ServletRequest, jakarta.servlet.ServletResponse请求/响应对象
jakarta.servlet.http.HttpSession强制存在会话。因此,这样的参数永远不会为 null
java.io.InputStream, java.io.Reader用于访问由 Servlet API 公开的原始请求正文。
java.io.OutputStream, java.io.Writer用于访问由 Servlet API 公开的原始响应正文。
@PathVariable接收路径参数注解
@RequestParam用于访问 Servlet 请求参数,包括多部分文件。参数值将转换为声明的方法参数类型。
@RequestHeader用于访问请求标头。标头值将转换为声明的方法参数类型。
@CookieValue用于访问Cookie。Cookie 值将转换为声明的方法参数类型。
@RequestBody用于访问 HTTP 请求正文。正文内容通过使用 HttpMessageConverter 实现转换为声明的方法参数类型。
java.util.Map, org.springframework.ui.Model, org.springframework.ui.ModelMap共享域对象,并在视图呈现过程中向模板公开。
Errors, BindingResult验证和数据绑定中的错误信息获取对象!

获取原生对象示例:

/**
 * 如果想要获取请求或者响应对象,或者会话等,可以直接在形参列表传入,并且不分先后顺序!
 * 注意: 接收原生对象,并不影响参数接收!
 */
@GetMapping("api")
@ResponseBody
public String api(HttpSession session , HttpServletRequest request,
                  HttpServletResponse response){
    String method = request.getMethod();
    System.out.println("method = " + method);
    return "api";
}

 扩展:

servletContext 对象:

① 是最大的配置文件,可以读取全局最大配置

② 是全局最大共享域,有 getAttribute 和 setAttribute 方法

③ 拥有核心 api (如:getRealPath)

Test:如何获取 servletContext 对象?

方案一: 通过 request 或者 session 获取

    @RequestMapping("data")
    public String data(HttpServletRequest request, HttpSession session){

        ServletContext servletContext = request.getServletContext();
        ServletContext servletContext1 = session.getServletContext();
        ...
    }

方案二:通过 @AutoWired 注解 自动从 ioc 容器中获取组件进行装配

@Controller
@RequestMapping("api")
@ResponseBody
public class ApiController {
    @Autowired
    private ServletContext servletContext;
    ...
}

6.共享域对象操作

 (1)属性(共享)域作用回顾

在 JavaWeb 中,共享域指的是在 Servlet 中存储数据,以便在同一 Web 应用程序的多个组件中进行共享和访问。

常见的共享域有四种:ServletContext、HttpSession、HttpServletRequest、PageContext。

ServletContext 共享域:ServletContext 对象可以在整个 Web 应用程序中共享数据,是最大的共享域。一般可以用于保存整个 Web 应用程序的全局配置信息,以及所有用户都共享的数据。在 ServletContext 中保存的数据是线程安全的。

HttpSession 共享域:HttpSession 对象可以在同一用户发出的多个请求之间共享数据,但只能在同一个会话中使用。比如,可以将用户登录状态保存在 HttpSession 中,让用户在多个页面间保持登录状态。

HttpServletRequest 共享域:HttpServletRequest 对象可以在同一个请求的多个处理器方法之间共享数据。比如,可以将请求的参数和属性存储在 HttpServletRequest 中,让处理器方法之间可以访问这些数据。

PageContext 共享域:PageContext 对象是在 JSP 页面Servlet 创建时自动创建的。它可以在 JSP 的各个作用域中共享数据,包括pageScope、requestScope、sessionScope、applicationScope 等作用域。

共享域的作用是提供了方便实用的方式在同一 Web 应用程序的多个组件之间传递数据,并且可以将数据保存在不同的共享域中,根据需要进行选择和使用。

(2)Request级别属性(共享)域

① 使用 Model 类型的形参

@RequestMapping("/attr/request/model")
@ResponseBody
@Controller
public class ShareController(
    
    //在形参位置声明Model类型变量,用于存储模型数据
    public void data(Model model){
        //我们将数据存入模型,SpringMVC 会帮我们把模型数据存入请求域
        model.addAttribute("key","value");
    }

}

② 使用 ModelMap 类型的形参

@RequestMapping("/attr/request/modelMap")
@ResponseBody
@Controller
public class ShareController(
    
    //在形参位置声明ModelMap类型变量,用于存储模型数据
    public void data(ModelMap modelMap){
        //我们将数据存入模型,SpringMVC 会帮我们把模型数据存入请求域
        modelMap.addAttribute("key","value");
    }

}

③ 使用 Map 类型的形参

@RequestMapping("/attr/request/map")
@ResponseBody
@Controller
public class ShareController(

    //在形参位置声明Map类型变量,用于存储模型数据
    public void data(Map<String, Object> map){
        map.put("key","value");
    }

}

④ 使用 ModelAndView 对象

@RequestMapping("/attr/request/mav")
@ResponseBody
@Controller
public class ShareController(

    public ModelAndView data(){
        // 1.创建ModelAndView对象
        ModelAndView modelAndView = new ModelAndView();
        // 2.存入模型数据
        modelAndView.addObject("key","value");
        // 3.设置视图名称
        modelAndView.setViewName("index");
    
        return modelAndView;
    }

    
}

⑤ 使用原生 request 对象

@RequestMapping("/attr/request/original")
@ResponseBody
@Controller
public class ShareController(
    
    // 拿到原生对象,就可以调用原生方法执行各种操作
    public void data(HttpServletRequest request){
        request.setAttribute("key","value");
    }
}

总结:前四种了解即可,掌握原生 api 方式就行

三、SpringMVC响应数据

1.handler方法分析

理解handler方法的作用和组成:

@GetMapping
public Object handler(简化请求参数接收){
    调用业务方法
    返回的结果 (页面跳转,返回数据(json))
    return 简化响应前端数据;
}

总结:

请求数据接收,我们都是通过 handler 的形参列表。

前端数据响应,我们都是通过 handler 的 return 关键字快速处理。

springmvc 简化了参数接收和响应!

2.页面跳转控制 

在 Web 开发中,有两种主要的开发模式:前后端分离和混合开发。

混合开发模式:

指将前端和后端的代码集成在同一个项目中,共享相同的技术栈和框架。

这种模式在小型项目中比较常见,可以减少学习成本和部署难度。但是,在大型项目中,这种模式会导致代码耦合性很高,维护和升级难度较大。

对于混合开发,我们就需要使用动态页面技术,动态展示Java的共享域数据。

前后端分离模式[重点]:

指将前端的界面和后端的业务逻辑通过接口分离开发的一种方式。

开发人员使用不同的技术栈和框架,前端开发人员主要负责页面的呈现和用户交互,后端开发人员主要负责业务逻辑和数据存储。

前后端通信通过 API 接口完成,数据格式一般使用 JSON 或 XML。前后端分离模式可以提高开发效率,同时也有助于代码重用和维护。

(1)快速返回模板视图

a. 准备jsp页面和依赖

pom.xml依赖:

<!-- jsp需要依赖! jstl-->
<dependency>
    <groupId>jakarta.servlet.jsp.jstl</groupId>
    <artifactId>jakarta.servlet.jsp.jstl-api</artifactId>
    <version>3.0.0</version>
</dependency>

 jsp页面创建(建议位置:/WEB-INF/下,避免外部直接访问)

位置:/WEB-INF/views/index.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <%--可以获取共享域的数据,动态展示! jsp== 后台vue --%>
    <font color="red">${data}</font>
</body>
</html>

b. 快速响应模版页面

配置 jsp 视图解析器: 

@Configuration
@ComponentScan(basePackages = "com.mihoyo")
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {
    //配置视图解析器,指定前后缀
    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        //registry可以快速添加前后缀
        registry.jsp("/WEB-INF/views/",".jsp");
    }
}

 handler 返回视图:

@Controller
@RequestMapping("jsp")
public class JspController {
    // 快速查找视图
    @GetMapping("index")
    public String data(HttpServletRequest request){
        request.setAttribute("data","hello jsp!!!");
        return "index";
    }
}

注意:

方法的返回值必须是 String 类型

不能给方法添加 @ResponseBody(@ResponseBody 会直接返回字符串给浏览器,不走视图解析器)

③ 返回值:填写对应中间的视图名称即可

(2)转发和重定向 

在 Spring MVC 中,Handler 方法返回值来实现快速转发,可以使用 redirect 或者 forward 关键字来实现重定向。

@Controller
@RequestMapping("jsp")
public class JspController {
    
    // 转发
    @GetMapping("forward")
    public String forward(){
        return "forward:/jsp/index";
    }

    // 重定向到项目下的其他地址
    @GetMapping("redirect")
    public String redirect(){
        return "redirect:/jsp/index";
    }

    // 重定向到外部网站
    @GetMapping("redirect/baidu")
    public String redirectBaidu(){
        return "redirect:http://www.baidu.com";
    }

    // 快速查找视图
    @GetMapping("index")
    public String data(HttpServletRequest request){
        request.setAttribute("data","hello jsp!!!");
        return "index";
    }
}

细节:

① 方法返回值都为 String 类型

② 不能添加 @ResponseBody 注解

③ 转发返回字符串 = forward:转发地址        重定向返回字符串 = redirct:重定向的地址

路径细节:

a.一般情况:

转发是项目下的资源跳转路径:项目下的地址(jsp/index        忽略 Application context)

重定向可以是项目下的资源,也可以是项目外的地址重定向属于外部请求,路径:全地址(/spingmvc/jsp/index        不忽略 Application context)

b. 使用springmvc 语法:

springmvc 底层做了优化,无论是转发还是重定向,路径都是项目下的地址,不需要写全地址了。

3.返回 JSON 数据 

(1)前置准备

导入 jackson 依赖:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.15.0</version>
</dependency>

添加json数据转化器:@EnableWebMvc

@Configuration
@ComponentScan(basePackages = "com.mihoyo")
@EnableWebMvc
public class MvcConfig{

}

准备与JSON 数据对应的实体类:

@Data
public class Person {
    private String name;
    private int age;
    private String gender;
}

(2)@ResponseBody

@Controller
@RequestMapping("json")
public class JsonController {

    //返回一个对象(对象->json->{})
    @GetMapping("data")
    @ResponseBody
    public User data() {
        User user = new User();
        user.setName("tom");
        user.setAge(18);

        return user;
    }

    //返回一个集合(集合->json->[])
    @GetMapping("data/list")
    @ResponseBody
    public List<User> dataList() {
        User user = new User();
        user.setName("tom");
        user.setAge(18);

        List<User> list = new ArrayList<>();
        list.add(user);

        return list;
    }
}

可以在方法上使用 @ResponseBody注解,用于将方法返回的对象序列化为 JSON 或 XML 格式的数据,放入响应体并直接发送给客户端,不走视图解析器

如果类中每个方法上都标记了 @ResponseBody 注解,那么这些注解就可以提取到类上,如下:

@Controller
@RequestMapping("json")
@ResponseBody
public class JsonController {

    //返回一个对象(对象->json->{})
    @GetMapping("data")
    public User data() {
        ...
    }

    //返回一个集合(集合->json->[])
    @GetMapping("data/list")
    public List<User> dataList() {
        ...
    }
}

扩展:

类上的 @ResponseBody 注解可以和 @Controller 注解合并为 @RestController 注解。

@RequestMapping("json")
@RestController 
public class JsonController {

    //返回一个对象(对象->json->{})
    @GetMapping("data")
    public User data() {
        ...
    }

    //返回一个集合(集合->json->[])
    @GetMapping("data/list")
    public List<User> dataList() {
        ...
    }
}

4.返回静态资源处理

静态资源概念:资源本身已经是可以直接拿到浏览器上使用的程度了,不需要在服务器端做任何运算、处理。

典型的静态资源包括:

        纯HTML文件

        图片

        CSS文件

        JavaScript文件......

web应用加入静态资源:

编译:

访问静态资源:

问题分析:

对 SpringMVC 来说,必须有对应的 @RequestMapping 才能找到处理请求的 handler 方法

现在 images/dog.jpg 请求没有对应的 @RequestMapping 所以返回 404


解决办法:在 SpringMVC 配置配置类

@Configuration
@ComponentScan(basePackages = "com.mihoyo")
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {

    //开启静态资源查找
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }
}

开启后,ds 会先去 handlerMapping(大秘书)中找有没有对应的 handler 方法。

如果没有,交给 DefaultServletHandler(二秘书)进行转发,去查找项目下的(静态)资源。

运行结果:

四、RESTFul风格设计和实战 

1.RESTFul风格概述

(1)RESTFul风格简介

RESTful(Representational State Transfer)是一种软件架构风格,用于设计网络应用程序和服务之间的通信。它是一种基于标准 HTTP 方法的简单和轻量级的通信协议,广泛应用于现代的Web服务开发。

RESTful 是一种基于 HTTP 和标准化的设计原则的软件架构风格,用于设计和实现可靠、可扩展和易于集成的 Web 服务和应用程序。 

学习RESTful 设计原则可以帮助我们更好去设计 HTTP 协议的 API 接口。

简单来说:

① 教我们如何设计路径

② 教我们如何选择参数传递

③ 教我们如何选择请求方式

(2)RESTFul风格特点

① 每一个 URI 代表1种资源(URI 是名词地址);

② 客户端使用GET、POST、PUT、DELETE 4个表示操作方式的动词对服务端资源进行操作

GET用来获取资源
POST用来新建资源(也可以用于更新资源)
PUT用来更新资源
DELETE用来删除资源

③ 资源的表现形式是XML 或者 JSON

④ 客户端与服务端之间的交互在请求之间是无状态的,从客户端到服务端的每个请求都必须包含理解请求所必需的信息。 

(3)RESTFul风格设计规范

① HTTP 协议请求方式要求

REST 风格主张在项目设计、开发过程中,具体的操作符合 HTTP 协议定义的请求方式的语义

操作请求方式
查询操作GET
保存操作POST
删除操作DELETE
更新操作PUT

 ② URL路径风格要求

REST风格下每个资源都应该有一个唯一的标识符,例如一个 URI(统一资源标识符)或者一个 URL(统一资源定位符)。

资源的标识符应该能明确地说明该资源的信息,同时也应该是可被理解和解释的。

使用 URL+请求方式 确定具体的动作,它也是一种标准的HTTP协议请求。

操作传统风格REST 风格
保存/CRUD/saveEmpURL 地址:/CRUD/emp 请求方式:POST
删除/CRUD/removeEmp?empId=2URL 地址:/CRUD/emp/2 请求方式:DELETE
更新/CRUD/updateEmpURL 地址:/CRUD/emp 请求方式:PUT
查询/CRUD/editEmp?empId=2URL 地址:/CRUD/emp/2 请求方式:GET

总结:

① 根据接口的具体动作,选择具体的 HTTP 协议请求方式;

② 路径设计从原来携带动作标识,改成名词,对应资源的唯一标识即可。

(4)RESTFul风格好处

① 含蓄,安全:

使用问号键值对的方式给服务器传递数据太明显,容易被人利用来对系统进行破坏。使用 REST 风格携带数据不再需要明显的暴露数据的名称

② 风格统一:

URL 地址整体格式统一,从前到后始终都使用斜杠划分各个单词,用简单一致的格式表达语义。

③ 无状态:

在调用一个接口(访问、操作资源)的时候,可以不用考虑上下文,不用考虑当前状态,极大的降低了系统设计的复杂度。

④ 严谨,规范:

严格按照 HTTP1.1 协议中定义的请求方式本身的语义进行操作。

⑤ 简洁,优雅:

过去做增删改查操作需要设计4个不同的URL,现在一个就够了(URL 可以重复利用)。

操作传统风格REST 风格
保存/CRUD/saveEmpURL 地址:/CRUD/emp 请求方式:POST
删除/CRUD/removeEmp?empId=2URL 地址:/CRUD/emp/2 请求方式:DELETE
更新/CRUD/updateEmpURL 地址:/CRUD/emp 请求方式:PUT
查询/CRUD/editEmp?empId=2URL 地址:/CRUD/emp/2 请求方式:GET

注意:URL地址 + 请求方式都相同,才叫重复 ,仅仅 URL 重复不要紧

 ⑥ 丰富的语义:

通过 URL 地址就可以知道资源之间的关系。它能够把一句话中的很多单词用斜杠连起来,反过来说就是可以在 URL 地址中用一句话来充分表达语义。

http://localhost:8080/shop

http://localhost:8080/shop/product

http://localhost:8080/shop/product/cellPhone

http://localhost:8080/shop/product/cellPhone/iPhone

2.RESTFul风格实战

(1)需求分析

数据结构: User {id 唯一标识,name 用户名,age 用户年龄}

功能分析:

        用户数据分页展示功能(条件:page 页数 默认1,size 每页数量 默认 10)

        保存用户功能

        根据用户id查询用户详情功能

        根据用户id更新用户数据功能

        根据用户id删除用户数据功能

        多条件模糊查询用户功能(条件:keyword 模糊关键字,page 页数 默认1,size 每页数量         默认 10)

(2)RESTFul风格接口设计

接口设计:

功能接口和请求方式请求参数返回值
分页查询GET /userpage=1&size=10{ 响应数据 }
用户添加POST /user{ user 数据 }{响应数据}
用户详情GET /user/1路径参数{响应数据}
用户更新PUT /user{ user 更新数据}{响应数据}
用户删除DELETE /user/1路径参数{响应数据}
条件模糊GET /user/searchpage=1&size=10&keyword=关键字{响应数据}

Test1: Restful 风格要求URI是名词,为什么多条件模糊查询的 URL 中含有动作(search)?

因为分页查询已经 GET 请求了,URL 是 /user。

如果条件模糊也是 GET /user,那两者就重复了。所以,为了避免冲突,不得不加!

Restful 风格并不是无脑遵循的,需要根据实际,灵活掌握!


Test2:为什么查询用户详情,就使用路径传递参数;而多条件模糊查询,就使用请求参数传递? 

误区:restful风格下,不是所有请求参数都是路径传递!可以使用其他方式传递! 

在 RESTful API 的设计中,路径和请求参数和请求体都是用来向服务器传递信息的方式。

对于查询用户详情:使用路径传递参数是因为这是一个单一资源的查询,即查询一条用户记录。使用路径参数可以明确指定所请求的资源,便于服务器定位并返回对应的资源,也符合 RESTful 风格的要求。 

对于多条件模糊查询:使用请求参数传递是因为这是一个资源集合的查询,即查询多条用户记录。使用请求参数可以通过组合不同参数来限制查询结果,路径参数的组合和排列可能会很多,不如使用请求参数更加灵活和简洁。

此外,还有一些通用的原则可以遵循:

① 路径参数应该用于指定资源的唯一标识或者 ID,而请求参数应该用于指定查询条件或者操作参数。

② 请求参数应该限制在 10 个以内,过多的请求参数可能导致接口难以维护和使用。

③ 对于敏感信息,最好使用 POST 和请求体来传递参数。

(3)后台接口实现 

准备用户实体类:

@Data
public class User {
    private Integer id;
    private String name;
    private Integer age;
}

准备用户Controller:(我们只关心接口怎么设计,暂不考虑具体业务实现)

package com.mihoyo.controller;

import com.mihoyo.pojo.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@Controller
@RequestMapping("user")
public class UserController {

    @GetMapping
    public List<User> page(@RequestParam(required = false, defaultValue = "1") int page,
                           @RequestParam(required = false, defaultValue = "10") int size) {
        System.out.println("page = " + page + ", size = " + size);
        return null;
    }

    @PostMapping
    public User save(@RequestBody User user) {
        return user;
    }

    @GetMapping("{id}")
    public User detail(@PathVariable Integer id) {
        return null;
    }

    @PutMapping
    public User update(@RequestBody User user) {
        return user;
    }

    @DeleteMapping("{id}")
    public User delete(@PathVariable Integer id) {
        return null;
    }

    @GetMapping("search")
    public List<User> search(String keyword,
                             @RequestParam(required = false, defaultValue = "1") int page,
                             @RequestParam(required = false, defaultValue = "10") int size) {
        return null;
    }

}

五、SpringMVC其他扩展

1.全局异常处理机制

(1)异常处理两种方式

开发过程中是不可避免地会出现各种异常情况的,例如网络连接异常、数据格式异常、空指针异常等等。

异常的出现可能导致程序的运行出现问题,甚至直接导致程序崩溃。因此,在开发过程中,合理处理异常、避免异常产生、以及对异常进行有效的调试是非常重要的。

对于异常的处理,一般分为两种方式:

编程式异常处理:是指在代码中显式地编写处理异常的逻辑。它通常涉及到对异常类型的检测及其处理,例如使用 try-catch 块来捕获异常,然后在 catch 块中编写特定的处理代码,或者在 finally 块中执行一些清理操作。在编程式异常处理中,开发人员需要显式地进行异常处理,异常处理代码混杂在业务代码中,导致代码可读性较差。

声明式异常处理:则是将异常处理的逻辑从具体的业务逻辑中分离出来,通过配置等方式进行统一的管理和处理。在声明式异常处理中,开发人员只需要为方法或类标注相应的注解(如 @Throws 或 @ExceptionHandler),就可以处理特定类型的异常。相较于编程式异常处理,声明式异常处理可以使代码更加简洁、易于维护和扩展。

(2)基于注解异常声明异常处理

a. 声明异常处理控制器类

/**
 * @RestControllerAdvice = @ControllerAdvice + @ResponseBody
 * @ControllerAdvice 代表当前类的异常处理controller! 
 */
@RestControllerAdvice
public class GlobalExceptionHandler {

  
}

b. 在异常处理控制器类中,统一定义异常处理 hander 方法

@RestControllerAdvice
public class GlobalExceptionHandler {

    //当发生空指针异常会触发此方法
    @ExceptionHandler(NullPointerException.class)
    //形参中可以获取异常对象,以便进行处理
    public Object handlerNullException(NullPointerException e){
        String message = e.getMessage();
        return message;
    }

    //所有异常都会触发此方法,但是如果有具体的异常处理Handler
    @ExceptionHandler(Exception.class)
    public Object handlerException(Exception e){
        String message = e.getMessage();
        return message;
    }
  
}

细节:

① 需要在方法上加 @ExceptionHandler来指定异常。指定的异常,可以精确指定,也可以指定其父类异常。当两者都存在时,走精确指定的异常处理方法。当没有精确指定,走父类异常处理方法。

② 可以在异常处理方法的形参中获取异常对象,在方法中进行处理。但形参异常类型和指定的异常类型一定要相同

③ 必须配置文件扫描控制器类配置,确保异常处理控制类被扫描

 <!-- 扫描controller对应的包,将handler加入到ioc-->
 @ComponentScan(basePackages = {"com.mihoyo.controller",
 "com.mihoyo.exceptionhandler"})

 2.拦截器使用

(1)拦截器概念

在程序中,使用拦截器在请求到达具体 handler 方法前,统一执行检测。

拦截器(Springmvc) VS 过滤器(javaWeb): 

相似点: 

        拦截:必须先把请求拦住,才能执行后续操作

        过滤:拦截器或过滤器存在的意义就是对请求进行统一处理

        放行:对请求执行了必要操作后,放请求过去,让它访问原本想要访问的资源

不同点:

        a. 工作平台不同

                过滤器工作在 Servlet 容器中

                拦截器工作在 SpringMVC 的基础上

        b. 拦截的范围

                过滤器:能够拦截到的最大范围是整个 Web 应用

                拦截器:能够拦截到的最大范围是整个 SpringMVC 负责的请求内部

        c. IOC 容器支持

                过滤器:想得到 IOC 容器需要调用专门的工具方法,是间接的

                拦截器:它自己就在 IOC 容器中,所以可以直接从 IOC 容器中装配组件,也就是可以直
                                接得到 IOC 容器的支持

选择:功能需要如果用 SpringMVC 的拦截器能够实现,就不使用过滤器。

(2)拦截器使用

拦截器方法拦截位置:

a. 创建拦截器类:

public class MyInterceptor implements HandlerInterceptor {

    // 执行handler方法之前,调用的拦截方法(编码格式设置,登录保护,权限处理...)
    /* 参数:
    *   request:请求对象
    *   response:响应对象
    *   handler:就是我们要调用的方法
    * */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("request = " + request + ", response = " + response + ", handler = " + handler);
        System.out.println("MyInterceptor.preHandle");
         
        /* return:
        *   true:放行
        *   false:不放行
        * */
        return true;
    }
 
    // 当handler方法执行之后,触发的方法,所以没有拦截机制(返回值void)
    // 此方法只有preHandle方法放行(return true)后,才会触发(对结果处理,敏感词检查...)
    // 参数:modelAndView:返回的视图和共享域对象
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("request = " + request + ", response = " + response + ", handler = " + handler + ", modelAndView = " + modelAndView);
        System.out.println("MyInterceptor.postHandle");
    }
 
    // 渲染视图之后执行(最后 = 整体都处理完毕了),一定执行!
    // 参数:ex:handle方法报错了,产生的异常对象
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("request = " + request + ", response = " + response + ", handler = " + handler + ", ex = " + ex);
        System.out.println("MyInterceptor.afterCompletion");
    }
}

 b. 修改配置类添加拦截器

@Configuration
@ComponentScan(basePackages = "com.mihoyo")
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {

    //配置拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry){
        ///将拦截器添加到Springmvc环境,默认拦截所有Springmvc分发的请求
        registry.addInterceptor(new MyInterceptor());
    }
}

 (3)拦截器配置细节

① 默认拦截全部

@Override
public void addInterceptors(InterceptorRegistry registry) {
    //将拦截器添加到Springmvc环境,默认拦截所有Springmvc分发的请求
    registry.addInterceptor(new MyInterceptor());
}

② 精准配置

@Override
public void addInterceptors(InterceptorRegistry registry) {
    
    //精准匹配,设置拦截器处理指定请求 路径可以设置一个或者多个,为项目下路径即可
    //也支持 /* 和 /** 模糊路径。 * 任意一层字符串 ** 任意层 任意字符串
    registry.addInterceptor(new MyInterceptor())
            .addPathPatterns("/common/request/one","/common/request/tow");
            // 或者addPathPatterns("/common/request/*")
}

 ③ 排除配置

//添加拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
    
    //排除匹配,排除应该在匹配的范围内排除
    registry.addInterceptor(new Process01Interceptor())
            .addPathPatterns("/common/request/one","/common/request/tow")
            .excludePathPatterns("/common/request/tow");
}

注意:排除路径,排除的地址应该在拦截的范围内 

④ 多个拦截器执行顺序

@Override
public void addInterceptors(InterceptorRegistry registry) {

    //添加多个拦截器
    registry.addInterceptor(new MyInterceptor1());
    registry.addInterceptor(new MyInterceptor2());
}

preHandle() 方法:SpringMVC 会把所有拦截器收集到一起,然后按照配置顺序调用各个 preHandle() 方法。

postHandle() 方法:SpringMVC 会把所有拦截器收集到一起,然后按照配置相反的顺序调用各个 postHandle() 方法。

afterCompletion() 方法:SpringMVC 会把所有拦截器收集到一起,然后按照配置相反的顺序调用各个 afterCompletion() 方法。

3.参数校验

在 Web 应用三层架构体系中,表述层负责接收浏览器提交的数据,业务逻辑层负责数据的处理。

为了能够让业务逻辑层基于正确的数据进行处理,我们需要在表述层对数据进行检查,将错误的数据隔绝在业务逻辑层之外。

当方法越来越多,参数越来越多,重复的校验工作就会越来越麻烦! 

(1)校验概述

JSR 303 是 Java 为 Bean 数据合法性校验提供的标准框架,它已经包含在 JavaEE 6.0 标准中。

JSR 303 通过在 Bean 属性上标注类似于 @NotNull、@Max 等标准的注解指定校验规则,并通过标准的验证接口对Bean进行验证。

注解规则
@Null标注值必须为 null
@NotNull标注值不可为 null
@AssertTrue标注值必须为 true
@AssertFalse标注值必须为 false
@Min(value)标注值必须大于或等于 value
@Max(value)标注值必须小于或等于 value
@DecimalMin(value)标注值必须大于或等于 value
@DecimalMax(value)标注值必须小于或等于 value
@Size(max,min)标注值大小必须在 max 和 min 限定的范围内
@Digits(integer,fratction)标注值值必须是一个数字,且必须在可接受的范围内
@Past标注值只能用于日期型,且必须是过去的日期
@Future标注值只能用于日期型,且必须是将来的日期
@Pattern(value)标注值必须符合指定的正则表达式

JSR 303 只是一套标准,需要提供其实现才可以使用。

Hibernate Validator 是 JSR 303 的一个参考实现,除支持所有标准的校验注解外,它还支持以下的扩展注解: 

注解规则
@Email标注值必须是格式正确的 Email 地址
@Length标注值字符串大小必须在指定的范围内
@NotEmpty标注值字符串不能是空字符串
@Range标注值必须在指定的范围内

Spring 4.0 版本已经拥有自己独立的数据校验框架,同时支持 JSR 303 标准的校验框架。Spring 在进行数据绑定时,可同时调用校验框架完成数据校验工作。在SpringMVC 中,可直接通过注解驱动 @EnableWebMvc 的方式进行数据校验。

Spring 的 LocalValidatorFactoryBean 既实现了 Spring 的 Validator 接口,也实现了 JSR 303 的 Validator 接口。只要在Spring容器中定义了一个LocalValidatorFactoryBean,即可将其注入到需要数据校验的 Bean中。Spring本身并没有提供JSR 303的实现,所以必须将JSR 303的实现者的jar包放到类路径下。 

配置 @EnableWebMvc后,SpringMVC 会默认装配好一个 LocalValidatorFactoryBean,通过在处理方法的入参上标注 @Validated 注解即可让 SpringMVC 在完成数据绑定后执行数据校验的工作。 

(2)易混总结

@NotNull、@NotEmpty、@NotBlank 都是用于在数据校验中检查字段值是否为空的注解,但是它们的用法和校验规则有所不同。

① @NotNull (包装类型不为null)

@NotNull 注解是 JSR 303 规范中定义的注解,当被标注的字段值为 null 时,会认为校验失败而抛出异常。该注解不能用于字符串类型的校验,若要对字符串进行校验,应该使用 @NotBlank 或 @NotEmpty 注解。

② @NotEmpty (集合类型长度大于0)

@NotEmpty 注解同样是 JSR 303 规范中定义的注解,对于 CharSequence、Collection、Map 或者数组对象类型的属性进行校验,校验时会检查该属性是否为 Null 或者 size()==0,如果是的话就会校验失败。但是对于其他类型的属性,该注解无效。需要注意的是只校验空格前后的字符串,如果该字符串中间只有空格,不会被认为是空字符串,校验不会失败。

③ @NotBlank (字符串类型,不为null,且不为" "字符串)

@NotBlank 注解是 Hibernate Validator 附加的注解,对于字符串类型的属性进行校验,校验时会检查该属性是否为 Null 或 “” 或者只包含空格,如果是的话就会校验失败。需要注意的是,@NotBlank 注解只能用于字符串类型的校验。

(3)操作演示

① 导入依赖

<!-- 校验注解实现-->        
<!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator -->
<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>8.0.0.Final</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator-annotation-processor -->
<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator-annotation-processor</artifactId>
    <version>8.0.0.Final</version>
</dependency>

② 实体类添加校验注解

/*
* 要求:
*   1.name不为null和空字符串
*   2.password长度大于6
*   3.age>=1
*   4.email为邮箱格式的字符串
*   5.birthday必须是过去的时间
* */
@Data
public class User {
    @NotBlank
    private String name;

    @Length(min=6)
    private String password;

    @Min(1)
    private Integer age;

    @Email
    private String email;

    @Past
    private Date birthday;
}

③ handler 方法形参使用实体类对象接值,并加上 @Validated 注解

@RestController
@RequestMapping("user")
public class UserController {

    @PostMapping("register")
    public User register(@Validated @RequestBody User user){
        System.out.println("user = " + user);
        return user;
    }
}

注意:

如果是 param 参数,加 @Validated 注解即可;

如果是 json 参数,加 @Validated 和 @RequestBody 注解。

运行结果:

如果不符合校验规则,就直接向前端抛出异常。

显然,这是不规范的!

正常情况下,我们应当接收错误信息,向前端返回自定义的错误结果(状态码) 。

@RestController
@RequestMapping("user")
public class UserController {

    @PostMapping("register")
    public Object register(@Validated @RequestBody User user, BindingResult result){
        if(result.hasErrors()){
            //有绑定错误,就不直接返回了,由我们自己定义
            Map data=new HashMap();
            data.put("code",400);
            data.put("msg","参数校验异常");
            return data;
        }
        System.out.println("user = " + user);
        return user;
    }
}

注意:

BindingResult 用于获取错误,来进行绑定错误。

形参中,BindingResult 必须紧挨着 校验对象(User),否则相当于没加 BindingResult。

运行结果:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2156722.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CSP-J 2024 入门组初赛第一轮初赛试题及答案解析

CSP-J 2024 入门组初赛第一轮初赛试题及答案解析 一、 单项选择题&#xff08;共15题&#xff0c;每题2分&#xff0c;共计30分&#xff1a;每题有且仅有一个正确选项&#xff09; 1 32 位 int 类型的存储范围是&#xff08; &#xff09; A -2147483647 ~ 2147483647 B -21…

【QGIS入门实战精品教程】6.1:QGIS根据属性条件查询数据(SQL表达式)

文章目录 一、字段过滤二、高级过滤(表达式)一、字段过滤 对于单个字段的查询,可以采用字段过滤,例如,从县区数据中,根据NAME字段,查找出县级市玉门市。操作为:右键县区→打开属性表: 点击左下角,选择name字段。 输入玉门市,回车,选择查找除的属性表记录,此时图斑…

汽车总线之----FlexRay总线

Introduction 随着汽车智能化发展&#xff0c;车辆开发的ECU数量不断增加&#xff0c;人们对汽车系统的各个性能方面提出了更高的需求&#xff0c;比如更多的数据交互&#xff0c;更高的传输带宽等。现如今人们广泛接受电子功能来提高驾驶安全性&#xff0c;像ABS防抱死系统&a…

计算机毕业设计之:基于深度学习的路面检测系统(源码+部署文档+讲解)

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…

【redis-02】深入理解redis中RBD和AOF的持久化

redis系列整体栏目 内容链接地址【一】redis基本数据类型和使用场景https://zhenghuisheng.blog.csdn.net/article/details/142406325【二】redis的持久化机制和原理https://zhenghuisheng.blog.csdn.net/article/details/142441756 如需转载&#xff0c;请输入&#xff1a;htt…

2025校招内推-招联金融

【投递方式】 直接扫下方二维码&#xff0c;或点击内推官网https://wecruit.hotjob.cn/SU61025e262f9d247b98e0a2c2/mc/position/campus&#xff0c;使用内推码 igcefb 投递&#xff09; 【招聘岗位】 后台开发 前端开发 数据开发 数据运营 算法开发 技术运维 软件测试 产品策…

CentOS Stream 9部署docker,并开启API

1、安装docker &#xff08;1&#xff09;安装Docker的依赖项和存储库 sudo dnf install -y dnf-plugins-core sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo &#xff08;2&#xff09;安装Docker sudo dnf install -y docke…

0基础学习PyTorch——最小Demo

大纲 环境准备安装依赖 训练和推理训练生成数据加载数据TensorDatasetDataLoader 定义神经网络定义损失函数和优化器训练模型 推理 参考代码 PyTorch以其简洁直观的API、动态计算图和强大的社区支持&#xff0c;在学术界和工业界都享有极高的声誉&#xff0c;成为许多深度学习爱…

yum 集中式安装 LNMP

目录 安装 nginx 安装 mysql 安装 php 配置lnmp 配置 nginx 支持 PHP 解析 安装 nginx 修改yum源 将原本的yum源备份 vim /etc/yum.repos.d/nginx.repo [nginx-stable] namenginx stable repo baseurlhttp://nginx.org/packages/centos/7/$basearch/ gpgcheck0 enable…

黎巴嫩BP机爆炸事件启示录:我国应加快供应链安全立法

据报道&#xff0c;当地时间9月17日下午&#xff0c;黎巴嫩首都贝鲁特以及黎巴嫩东南部和东北部多地都发生了BP机爆炸事件。当时的统计数据显示&#xff0c;爆炸造成9人死亡&#xff0c;约2800人受伤。9月18日&#xff0c;死亡人数上升到11人&#xff0c;受伤人数超过4000。 目…

14年数据结构

第一题 解析&#xff1a; 求时间复杂度就是看程序执行了多少次。 假设最外层执行了k次&#xff0c;我们看终止条件是kn&#xff0c;则&#xff1a; 有, 内层是一个j1到jn的循环&#xff0c;显然执行了n次。 总的时间复杂度是内层外层 答案选C。 第二题 解析&#xff1a; 一步一…

车辆行人转向意图状态检测系统源码分享

车辆行人转向意图状态检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of …

【Python】Maya:为人类打造的 Python 日期时间库

不知道少了什么&#xff0c;总感觉没有以前快乐。 在编程中处理日期和时间总是一个挑战&#xff0c;尤其是当涉及到时间和时区的转换时。Maya 是一个由 Kenneth Reitz 开发的 Python 库&#xff0c;旨在简化日期时间的处理&#xff0c;使其对人类开发者更加友好。本文将介绍 M…

如何在jupyter notebook中使用虚拟环境

一&#xff1a;在cmd中打开已经创建好的虚拟环境 二&#xff1a;安装ipykernel conda install ipykernel 三&#xff1a;安装牛逼conda conda install -c conda-forge nb_conda 四&#xff1a;运行jupyter notebook,选择虚拟环境

linux强制关闭再启动后zookeeper无法启动

1、若开启了zkserver就先关闭zkserver 查看zkserver是否启动 sh zkServer.sh status关闭zkServer sh zkServer.sh stop2、更改conf/zoo.cfg 将这里的启动端口改为2183 3、启动zkServer sh zkServer.sh start4、以2183端口启动zkCli zkCli.sh -server 127.0.0.1:2183这样启…

传知代码-基于多尺度动态卷积的图像分类

代码以及视频讲解 本文所涉及所有资源均在传知代码平台可获取 概述 在计算机视觉领域&#xff0c;图像分类是非常重要的任务之一。近年来&#xff0c;深度学习的兴起极大提升了图像分类的精度和效率。本文将介绍一种基于动态卷积网络&#xff08;Dynamic Convolutional Netw…

机器人机构、制造

简单整理一下&#xff0c;在学习了一些运动学和动力学之类的东西&#xff0c;简单的整合了一些常用的机械结构和图片。 1.电机&#xff1a; 市面上的电机有&#xff1a;直流电机&#xff0c;交流电机&#xff0c;舵机&#xff0c;步进电机&#xff0c;电缸&#xff0c;无刷电…

【无人机设计与控制】 基于matlab的蚁群算法优化无人机uav巡检

摘要 本文使用蚁群算法&#xff08;ACO&#xff09;优化无人机&#xff08;UAV&#xff09;巡检路径。无人机巡检任务要求高效覆盖特定区域&#xff0c;以最小化能源消耗和时间。本研究提出的算法通过仿生蚁群算法优化巡检路径&#xff0c;在全局搜索和局部搜索中平衡探索与开…

【软件工程】成本效益分析

一、成本分析目的 二、成本估算方法 三、成本效益分析方法 课堂小结 例题 选择题

深度之眼(三十)——pytorch(一)--深入浅出pytorch(附安装流程)

文章目录 一、前言一、pytoch二、六个部分三、如何学习四、学习路径&#xff08;重要)五、安装pytorch5.1 坑15.2 坑2 一、前言 我看了下目录 第一章和第二章都是本科学的数字图像处理。 也就是这一专栏&#xff1a;数字图像实验。 所以就不准备学习前两章了&#xff0c;直接…