SpringMVC

简介

SpringMVC是隶属于Spring框架的一部分,主要是用来进行Web开发,是对Servlet进行了封装。

SpringMVC是处于Web层的框架,所以其主要的作用就是用来接收前端发过来的请求和数据然后经过处理并将处理的结果响应给前端。

SpringMVC是一种基于Java实现MVC模型的轻量级Web框架。

入门案例

代码实现

  1. 使用Maven创建Web模块spring_mvc_demo

  2. 添加依赖

    <dependencies>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.2.10.RELEASE</version>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <version>2.2</version>
                <configuration>
                    <port>8080</port>
                    <path>/spring_mvc_demo_war</path>
                </configuration>
            </plugin>
        </plugins>
    </build>
    

    servlet-api的坐标为什么需要添加<scope>provided</scope>

    • Tomcat中已经依赖了servlet-api,如果打包时包含了servlet-api则会与Tomcat中的servlet-api发生冲突

    SpringMVC是基于Spring的,在pom.xml导入了spring-webmvcjar包后,它会自动依赖spring相关坐标

  3. 创建SpringMVC配置类

    @Configuration
    @ComponentScan("com.fs.controller")
    public class SpringMvcConfig {
    }
    

    Spring整合SpringMVC时一般都只加载与其相关的bean。比如SpringMVC加载Controller,Spring加载Service等与其自身相关的bean。

  4. 创建Spring配置类

    @Configuration
    public class SpringConfig {
    }
    
  5. 使用配置类替换web.xml,作为 web 程序,必须有web3.0 的配置类。

    public class ServletConfig extends AbstractDispatcherServletInitializer {
        //创建Servlet容器时加载SpringMVC对应的bean,用来加载SpringMVC环境
        protected WebApplicationContext createServletApplicationContext() {
            //初始化WebApplicationContext对象
            AnnotationConfigWebApplicationContext annotationConfigWebApplicationContext = new AnnotationConfigWebApplicationContext();
    
            //加载指定配置类,放入WebApplicationContext对象范围中
            //WebApplicationContext的作用范围为ServletContext范围,即整个web容器范围
            annotationConfigWebApplicationContext.register(SpringMvcConfig.class);
            return annotationConfigWebApplicationContext;
        }
    
        //设置SpringMVC对应的请求映射路径,即SpringMVC拦截哪些请求
        protected String[] getServletMappings() {
            return new String[]{"/"};
        }
    
        //创建Servlet容器加载非SpringMVC对应的bean,用来加载Spring环境
        protected WebApplicationContext createRootApplicationContext() {
            AnnotationConfigWebApplicationContext annotationConfigWebApplicationContext = new AnnotationConfigWebApplicationContext();
            annotationConfigWebApplicationContext.register(SpringConfig.class);
            return annotationConfigWebApplicationContext;
        }
    }
    

    AbstractDispatcherServletInitializer类是SpringMVC提供的快速初始化Web3.0容器的抽象类。

    对于上述的配置方式,Spring还提供了一种更简单的配置方式,可以不用去创建AnnotationConfigWebApplicationContext对象,不用手动register对应的配置类:

    public class ServletConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
    
     protected Class<?>[] getRootConfigClasses() {
         return new Class[]{SpringConfig.class};
     }
    
     protected Class<?>[] getServletConfigClasses() {
         return new Class[]{SpringMvcConfig.class};
     }
    
     protected String[] getServletMappings() {
         return new String[]{"/"};
     }
    }
    
  6. 创建Controller类

    @Controller
    public class HelloController {
    
        @RequestMapping("/hello")
        @ResponseBody
        public String hello() {
            return "hello world";
        }
    }
    

    @RequestMapping注解

    • 写在SpringMVC控制器类或方法定义上方,用于设置当前控制器方法请求访问路径,如果类和方法上都添加了,则最终请求访问路径为类和方法两个访问路径进行拼接
    • @RequestMapping注解value属性前面加不加/都可以

    @ResponseBody注解

    • 写在SpringMVC控制器类或方法定义上方,用于设置控制器方法的返回值为响应内容,
      • 如果返回值类型是字符串,会将其作为文本内容直接响应给浏览器
      • 如果返回值类型是实体对象/集合,将会转换为JSON格式后再响应给浏览器
    • 如果没写@ResponseBody注解,则SpringMVC会把hello world当成页面的名称去查找,如果没有这个页面则会报错404。

功能测试

使用本地tomcat启动项目

image-20230807143655412

访问http://localhost:8080/spring_mvc_demo_war/hello

image-20230807144007337

工作流程

1630432494752

启动服务器初始化过程

  1. 服务器启动,执行ServletConfig配置类(替换web.xml配置文件),初始化web容器。

  2. 执行createServletApplicationContext方法,创建了WebApplicationContext对象,该方法加载SpringMVC的配置类SpringMvcConfig来初始化SpringMVC的容器。

  3. 加载SpringMvcConfig配置类,执行@ComponentScan加载对应的bean,扫描指定包及其子包下所有类上的注解,如Controller类上的@Controller注解。

  4. 加载HelloController,每个@RequestMapping配置的请求访问路径对应一个具体的方法,此时就建立了 /hello 和 hello方法的对应关系。

  5. 执行getServletMappings方法,设定SpringMVC拦截请求的路径规则,/代表所拦截请求的路径规则,只有被拦截后才能交给SpringMVC来处理请求。

单次请求过程

  1. 发送请求http://localhost:8080/spring_mvc_demo_war/hello
  2. web容器发现该请求满足SpringMVC拦截规则,将请求交给SpringMVC处理。
  3. 解析请求路径/hello。
  4. 由/hello匹配执行对应的方法hello(),服务器初始化时已经将请求访问路径和方法建立了对应关系,通过/hello就能找到对应的hello方法。
  5. 执行hello()
  6. 检测到有@ResponseBody直接将hello()方法的返回值作为响应体返回给请求方

Postman

Postman是一款网页调试与发送网页HTTP请求的Chrome插件。

作用:

  • 进行接口测试

image-20220826173003949

打开浏览器直接输入地址发送请求,发送的是GET请求

请求

简单参数

对于简单类型参数,不论是GET请求还是POST请求,只要请求参数名和Controller方法中的形参名保持一致,就可以获取到请求参数中的数据值。

GET请求

代码实现
  1. 创建UserController

    @Controller
    public class UserController {
    
        @RequestMapping("/user")
        @ResponseBody
        public String user(String id, String name, Integer age) {
            System.out.println(id);
            System.out.println(name);
            System.out.println(age);
            return "OK";
        }
    }
    
  2. 使用tomcat插件启动项目,并使用Postman测试

    http://localhost:8080/spring_mvc_demo_war/user?id=zhangsan&name=张三&age=18
    
解决请求参数中文乱码

修改pom.xml

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.tomcat.maven</groupId>
            <artifactId>tomcat7-maven-plugin</artifactId>
            <version>2.2</version>
            <configuration>
                <port>8080</port>
                <path>/spring_mvc_demo_war</path>
                <!--解决get请求请求参数中文乱码-->
                <uriEncoding>UTF-8</uriEncoding>
            </configuration>
        </plugin>
    </plugins>
</build>

POST请求

功能测试

使用tomcat插件启动项目,并使用Postman测试

http://localhost:8080/spring_mvc_demo_war/user

image-20230810211517477

解决请求参数中文乱码

修改ServletConfig

public class ServletConfig extends AbstractDispatcherServletInitializer {

    //解决post请求请求参数中文乱码
    @Override
    protected Filter[] getServletFilters() {
        CharacterEncodingFilter filter = new CharacterEncodingFilter();
        filter.setEncoding("UTF-8");
        return new Filter[]{filter};
    }
}

参数名不一致

对于简单参数,请求参数名和controller方法中的形参名不一致时,无法接收到请求数据。

代码实现
  1. 修改UserController

    @Controller
    public class UserController {
    
        @RequestMapping("/user")
        @ResponseBody
        public String user(String id, @RequestParam("name") String username, Integer age) {
            System.out.println(id);
            System.out.println(username);
            System.out.println(age);
            return "OK";
        }
    }
    

    写上@RequestParam注解后框架就不需要自己去解析注入,能提升框架处理性能

    @RequestParam注解

    • 写在SpringMVC控制器方法形参定义前面,用于绑定请求参数与处理器方法形参间的关系
    • required属性
      • 是否为必传参数
    • defaultValue属性
      • 参数默认值

POJO参数

对于POJO类型参数,不论是GET请求还是POST请求,只要请求参数名和Controller方法中的POJO类型形参的属性名保持一致,就可以获取到请求参数中的数据值。

简单POJO参数

代码实现
  1. 定义POJO实体类:

    public class User {
        private String id;
        private String name;
        private Integer age;
        
        //省略get、set、toString方法
    }
    
  2. 修改UserController

    @Controller
    public class UserController {
    
        @RequestMapping("/user")
        @ResponseBody
        public String user(User user) {
            System.out.println(user);
            return "OK";
        }
    }
    
  3. 使用tomcat插件启动项目,并使用Postman测试

    http://localhost:8080/spring_mvc_demo_war/user?id=zhangsan&name=张三&age=18
    

嵌套POJO参数

请求参数名和Controller方法中的POJO类型形参的属性名保持一致,按照对象层次结构关系即可接收嵌套POJO类属性参数。

代码实现
  1. 修改和创建POJO实体类:

    public class User {
        private String id;
        private String name;
        private Integer age;
        private Address address;
        
        //省略get、set、toString方法
    }
    
    public class Address {
        private String value;
        
        //省略get、set、toString方法
    }
    
  2. 使用tomcat插件启动项目,并使用Postman测试

    http://localhost:8080/spring_mvc_demo_war/user?id=zhangsan&name=张三&age=18&address.value=yueyang
    

数组集合参数

在HTML的表单中,有一个表单项是支持多选的(复选框),可以提交选择的多个值。

image-20221203164114083

后端程序接收上述多个值的方式有两种:

  1. 数组
  2. 集合

数组

对于数组类型参数,不论是GET请求还是POST请求,只要请求参数名与形参数组名称相同,定义数组类型形参即可接收参数。

代码实现
  1. 创建GirlFriendController

    @Controller
    public class GirlFriendController {
    
        @RequestMapping("/girlFriend")
        @ResponseBody
        public String girlFriend(String[] girls) {
            for (String girl : girls) {
                System.out.println(girl);
            }
            return "OK";
        }
    }
    
  2. 使用tomcat插件启动项目,并使用Postman测试

    http://localhost:8080/spring_mvc_demo_war/girlFriend?girls=xiaomei&girls=cuihua
    

集合

默认情况下,请求中参数名相同的多个值,需要使用数组来接收,如果使用集合则需要使用@RequestParam绑定参数关系

代码实现
  1. 修改GirlFriendController方法:

    @Controller
    public class GirlFriendController {
    
        @RequestMapping("/girlFriend")
        @ResponseBody
        public String girlFriend(@RequestParam List<String> girls) {
            for (String girl : girls) {
                System.out.println(girl);
            }
            return "OK";
        }
    }
    

JSON参数

JSON是开发中最常用的前后端数据交互方式。

常见的JSON数据类型:

  • JSON普通数组

    • ["value1","value2","value3",...]
      
  • JSON对象

    • {"key1":"value1","key2":"value2",...}
      
  • JSON对象数组

    • [{"key1":"value1",...},{"key2":"value2",...}]
      

JSON普通数组

代码实现
  1. SpringMVC默认使用的是jackson来处理JSON的转换,所以需要添加jackson依赖:

    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.9.0</version>
    </dependency>
    
  2. 在SpringMVC的配置类中开启SpringMVC的注解支持。

    @Configuration
    @ComponentScan("com.fs.controller")
    //开启JSON数据类型自动转换
    @EnableWebMvc
    public class SpringMvcConfig {
    }
    

    @EnableWebMvc注解

    • 写在SpringMVC配置类定义上方,用于开启SpringMVC多项辅助功能,这里面就包含了JSON和实体类对象之间相互转换的功能。
  3. 修改GirlFriendController方法:

    @Controller
    public class GirlFriendController {
    
        @RequestMapping("/girlFriend")
        @ResponseBody
        public String girlFriend(@RequestBody List<String> girls) {
            for (String girl : girls) {
                System.out.println(girl);
            }
            return "OK";
        }
    }
    

    @RequestBody注解

    • 写在SpringMVC控制器方法形参定义前面,用于解析JSON数据,将JSON数据映射到形参的实体类对象中,此注解一个处理器方法只能使用一次
  4. 使用tomcat插件启动项目,并使用Postman测试

    http://localhost:8080/spring_mvc_demo_war/girlFriend
    
    ["xiaomei","cuihua"]
    

    image-20230810213353681

JSON对象

代码实现
  1. 修改UserController方法:

    @Controller
    public class UserController {
    
        @RequestMapping("/user")
        @ResponseBody
        public String user(@RequestBody User user) {
            System.out.println(user);
            return "OK";
        }
    }
    
  2. 使用本地tomcat启动项目,并使用Postman测试

    http://localhost:8080/spring_mvc_demo_war/user
    
    {
        "id":"zhangsan",
        "name":"张三",
        "age":"18",
        "address":{
            "value":"yueyang"
        }
    }
    

    image-20230810214128362

    JSON中的key必须与形参对象属性名全字母小写相同

JSON对象数组

代码实现
  1. 修改UserController方法:

    @Controller
    public class UserController {
    
        @RequestMapping("/user")
        @ResponseBody
        public String user(@RequestBody List<User> users) {
            for (User user : users) {
                System.out.println(user);
            }
            return "OK";
        }
    }
    
  2. 使用tomcat插件启动项目,并使用Postman测试

    http://localhost:8080/spring_mvc_demo_war/user
    
    [{
        "id":"zhangsan",
        "name":"张三",
        "age":"18",
        "address":{
            "value":"yueyang"
        }
    },
    {
        "id":"lisi",
        "name":"李四",
        "age":"18",
        "address":{
            "value":"yueyang"
        }
    }]
    

    image-20230810214305671

日期参数

对于日期类型的参数在进行封装的时候,因为SpringMVC默认支持的字符串转日期的格式为yyyy/MM/dd,如果我们传递的日期数据不符合其默认格式,SpringMVC就无法进行格式转换,需要通过@DateTimeFormat注解来设置日期的格式。

后端controller方法中,需要使用Date类型或LocalDateTime类型,来封装传递的参数。

代码实现

  1. 修改GirlFriendController

    @Controller
    public class GirlFriendController {
    
        @RequestMapping("/girlFriend")
        @ResponseBody
        public String girlFriend(Date date, @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime localDateTime) {
            System.out.println(date);
            System.out.println(localDateTime);
            return "OK";
        }
    }
    

    @DateTimeFormat注解

    • 写在SpringMVC控制器方法形参前面,用于指定日期时间类型数据的格式
    • pattern属性
      • 指定日期时间格式字符串
  2. 使用tomcat插件启动项目,并使用Postman测试

    http://localhost:8080/spring_mvc_demo_war/girlFriend?date=2004/07/27&localDateTime=2004-07-27 12:34:56
    

实现原理

前后端交互时需要的数据类型有很多种,在数据的传递过程中存在很多类型的转换,谁来做这个类型转换?

在SpringMVC框架中提供了很多类型转换接口和实现类。

Converter接口
/**
*	S: the source type
*	T: the target type
*/
public interface Converter<S, T> {
    @Nullable
    //该方法就是将从页面上接收的数据(S)转换成我们想要的数据类型(T)返回
    T convert(S source);
}

Converter所属的包为org.springframework.core.convert.converter

Converter接口的实现类:

1630496385398

框架中提供了很多对应Converter接口的实现类,用来实现不同数据类型之间的转换。

HttpMessageConverter接口用于实现对象与JSON之间的转换

路径参数

前端:

  • 通过请求URL直接传递参数

后端:

  • 在@RequestMapping配置的请求访问路径中,使用 {key} 作为路径参数名来标识该路径参数,并使用@PathVariable获取路径参数

代码实现

  1. 修改GirlFriendController

    @Controller
    public class GirlFriendController {
    
        @RequestMapping("/girlFriend/{girl1}/{girl2}")
        @ResponseBody
        public String girlFriend(@PathVariable String girl1, @PathVariable String girl2) {
            System.out.println(girl1);
            System.out.println(girl2);
            return "OK";
        }
    }
    

    @PathVariable注解

    • 写在SpringMVC控制器方法形参定义前面,用于绑定路径参数与控制器方法形参之间的关系,要求路径参数名与控制器方法形参名相同
  2. 使用tomcat插件启动项目,并使用Postman测试

    http://localhost:8080/spring_mvc_demo_war/girlFriend/xiaomei/cuihua
    

响应

SpringMVC接收到请求和数据后,需要进行处理,处理完以后,需要将结果告知给用户,即 响应

对于响应,主要包含两部分内容:

  • 响应页面
  • 响应数据
    • 文本数据(入门案例中的HelloController就是响应文本数据)
    • JSON数据

响应页面

代码实现

  1. 在webapp下创建hello.jsp

    <h1>hello world</h1>
    
  2. 修改HelloController

    @Controller
    public class HelloController {
    
        @RequestMapping("/hello")
        public String hello() {
            return "hello.jsp";
        }
    }
    

    此处不能添加@ResponseBody注解,如果加了会直接将hello.html当字符串返回前端。

    方法的返回值类型需要为String类型。

  3. 使用本地tomcat启动项目,访问http://localhost:8080/spring_mvc_demo_war/hello

    image-20230810222253765

静态资源

默认情况下SpringMVC会拦截对静态资源的请求。

SpringMVC为什么会拦截静态资源呢?

因为当前在ServletConfig中配置的SpringMVC拦截规则为/,表示拦截任意请求,这个拦截规则最终会作用于SpringMVC的中央控制器(DispatcherServlet)上,但是Tomcat中有一个DefaultServlet,DefaultServlet是Tomcat中用来处理对静态资源的请求的,当其他Servlet配置的urlPattern都匹配不上时都会走这个Servlet。

如果SpringMVC中配置了 / 则会覆盖掉Tomcat中的DefaultServlet,就会引发请求静态资源的时候没有走默认的DefaultServlet而是走了自定义的Servlet类,即SpringMVC的中央控制器,但是SpringMVC会将其当作Controller处理,如果没有找对应的Controller方法,则会报404的错误,最终导致静态资源无法访问。

//设置SpringMVC对应的请求映射路径,即SpringMVC拦截哪些请求
protected String[] getServletMappings() {
    return new String[]{"/"};
}

代码实现

  1. 在webapp下创建index.html

    <html>
    <body>
    <h2>Hello World!</h2>
    </body>
    </html>
    
  2. 配置SpringMVC放行静态资源

    @Configuration
    public class SpringMvcSupport extends WebMvcConfigurationSupport {
        //设置静态资源访问过滤,当前类需要设置为配置类,并被扫描加载
        @Override
        protected void addResourceHandlers(ResourceHandlerRegistry registry) {
            //当访问/index.html的时候,从/目录下查找内容
            registry.addResourceHandler("/index.html").addResourceLocations("/");
        }
    }
    
  3. 配置SpringMVC包扫描

    @Configuration
    @ComponentScan({"com.fs.controller","com.fs.config.mvc"})
    //开启JSON数据类型自动转换
    @EnableWebMvc
    public class SpringMvcConfig {
    }
    
  4. 使用本地tomcat启动项目,访问http://localhost:8080/spring_mvc_demo_war/index.html

    image-20230811181858928

响应JSON数据

响应POJO对象

代码实现
  1. 修改UserController:

    @Controller
    public class UserController {
    
        @RequestMapping("/user")
        @ResponseBody
        public User user(User user) {
            System.out.println(user);
            return user;
        }
    }
    
  2. 使用本地tomcat启动项目,并使用Postman测试

    http://localhost:8080/spring_mvc_demo_war/user?id=zhangsan&name=张三&age=18&address.value=yueyang
    

    image-20230810223728349

响应POJO集合对象

代码实现
  1. 修改UserController:

    @Controller
    public class UserController {
    
        @RequestMapping("/user")
        @ResponseBody
        public List<User> user() {
            List<User> list = new ArrayList<>();
    
            Address address = new Address();
            address.setValue("yueyang");
    
            User zhangsan = new User();
            zhangsan.setId("zhangsan");
            zhangsan.setName("张三");
            zhangsan.setAge(18);
            zhangsan.setAddress(address);
    
            User lisi = new User();
            lisi.setId("lisi");
            lisi.setName("李四");
            lisi.setAge(18);
            lisi.setAddress(address);
    
            list.add(zhangsan);
            list.add(lisi);
    
            return list;
        }
    }
    
  2. 使用本地tomcat启动项目,并使用Postman测试

    http://localhost:8080/spring_mvc_demo_war/user
    

    image-20230810225118038

REST风格

什么是REST风格呢?

  • REST(Representational State Transfer),表述性状态转换,是一种软件架构风格。
  • 可以降低开发的复杂性,提高系统的可伸缩性

当我们想表示一个网络资源的时候,可以使用两种方式:

  • 传统风格资源描述形式
    • http://localhost/user/getById?id=1 查询id为1的用户信息
    • http://localhost/user/saveUser 保存用户信息
  • REST风格描述形式
    • http://localhost/user/1
    • http://localhost/user

原始的传统URL,定义比较复杂,而且将资源的访问行为对外暴露出来了。REST风格通过URL定位要操作的资源,通过HTTP动词(请求方式)来描述具体的操作。

REST风格的作用

  • 隐藏资源的访问行为,无法通过地址得知对资源是何种操作
  • 书写简化

按照REST风格访问资源时使用行为动作区分对资源进行了何种操作

  • http://localhost/users 查询全部用户信息 GET(查询)
  • http://localhost/users/1 查询指定用户信息 GET(查询)
  • http://localhost/users 添加用户信息 POST(新增/保存)
  • http://localhost/users 修改用户信息 PUT(修改/更新)
  • http://localhost/users/1 删除用户信息 DELETE(删除)

在REST风格的URL中,通过四种请求方式,来操作数据的增删改查:

  • GET : 查询
  • POST :新增
  • PUT :修改
  • DELETE :删除

基于REST风格,定义URL,URL将会更加简洁、更加规范。

REST是约定方式,约定不是规定,可以打破,所以称REST风格,而不是REST规范。

描述模块的功能通常使用复数,表示此类资源,而非单个资源如:users、emps、books…

RESTful,什么是RESTful呢?

  • 根据REST风格对资源进行访问称为,RESTful。

遵从REST风格来访问后台资源的项目,就是基于RESTful来进行开发的。

代码实现

  1. 创建POJO类

    public class Book {
        private Integer id;
        private String name;
        private double price;
        
        //省略get、set、toString方法
    }
    
  2. 创建BookController

    @Controller
    public class BookController {
    
        @RequestMapping(value = "/books", method = RequestMethod.POST)
        @ResponseBody
        public String save(@RequestBody Book book) {
            System.out.println(book);
            return "OK";
        }
    
        @RequestMapping(value = "/books/{id}", method = RequestMethod.DELETE)
        @ResponseBody
        public String delete(@PathVariable Integer id) {
            System.out.println(id);
            return "OK";
        }
    
        @RequestMapping(value = "/books", method = RequestMethod.PUT)
        @ResponseBody
        public String update(@RequestBody Book book) {
            System.out.println(book);
            return "OK";
        }
    
        @RequestMapping(value = "/books/{id}", method = RequestMethod.GET)
        @ResponseBody
        public Book getById(@PathVariable Integer id) {
    
            Book book = new Book();
            book.setId(id);
            book.setName("book1");
            book.setPrice(9.9);
    
            return book;
        }
    
        @RequestMapping(value = "/books", method = RequestMethod.GET)
        @ResponseBody
        public List<Book> getAll() {
    
            List<Book> list = new ArrayList<>();
    
            Book book1 = new Book();
            book1.setId(1);
            book1.setName("book1");
            book1.setPrice(9.9);
    
            Book book2 = new Book();
            book2.setId(2);
            book2.setName("book2");
            book2.setPrice(19.9);
    
            list.add(book1);
            list.add(book2);
    
            return list;
        }
    
    }
    

功能测试

使用本地tomcat启动项目,并使用Postman测试

  1. 查询所有

    http://localhost:8080/spring_mvc_demo_war/books
    

    image-20230811095635135

  2. 根据id查询

    http://localhost:8080/spring_mvc_demo_war/books/1
    

    image-20230811095721417

  3. 新增

    http://localhost:8080/spring_mvc_demo_war/books
    
    {
        "id":"1",
        "name":"book1",
        "price":"9.9"
    }
    

    image-20230811095942646

  4. 删除

    http://localhost:8080/spring_mvc_demo_war/books/1
    

    image-20230811100128662

  5. 修改

    http://localhost:8080/spring_mvc_demo_war/books
    
    {
        "id":"1",
        "name":"book1",
        "price":"9.9"
    }
    

    image-20230811100204811

RESTful快速开发

目前存在的问题:

  • 每个方法的@RequestMapping注解中都定义了访问路径/books,重复性太高。
  • 每个方法的@RequestMapping注解中都要使用method属性定义请求方式,重复性太高。
  • 每个方法响应json都需要加上@ResponseBody注解,重复性太高。

解决方案:

  • 使用@RestController注解替换@Controller与@ResponseBody注解,简化书写

  • 使用@GetMapping、@PostMapping、@PutMapping 、@DeleteMapping,代替@RequestMapping注解的method属性定义请求方式。

代码实现

  1. 修改BookController

    @RestController
    @RequestMapping("/books")
    public class BookController {
    
        @PostMapping
        public String save(@RequestBody Book book) {
            System.out.println(book);
            return "OK";
        }
    
        @DeleteMapping("/{id}")
        public String delete(@PathVariable Integer id) {
            System.out.println(id);
            return "OK";
        }
    
        @PutMapping
        public String update(@RequestBody Book book) {
            System.out.println(book);
            return "OK";
        }
    
        @GetMapping("/{id}")
        public Book getById(@PathVariable Integer id) {
    
            Book book = new Book();
            book.setId(id);
            book.setName("book1");
            book.setPrice(9.9);
    
            return book;
        }
    
        @GetMapping
        public List<Book> getAll() {
    
            List<Book> list = new ArrayList<>();
    
            Book book1 = new Book();
            book1.setId(1);
            book1.setName("book1");
            book1.setPrice(9.9);
    
            Book book2 = new Book();
            book2.setId(2);
            book2.setName("book2");
            book2.setPrice(19.9);
    
            list.add(book1);
            list.add(book2);
    
            return list;
        }
    
    }
    

    @RestController注解

    • 写在基于SpringMVC的RESTful开发控制器类定义上方,用于设置当前控制器类为RESTful风格,等同于@Controller与@ResponseBody两个注解组合功能

    @GetMapping、@PostMapping、@PutMapping、@DeleteMapping注解

    • 写在基于SpringMVC的RESTful开发控制器方法定义上方,用于设置当前控制器方法请求访问路径与请求动作,每种注解对应一个请求动作。
    • value属性
      • 请求访问路径

全局异常处理器

三层架构处理异常的方案:

  • Mapper接口在操作数据库的时候出错了,此时异常会往上抛(谁调用Mapper就抛给谁),会抛给service。
  • service 中也存在异常了,会抛给controller。
  • controller中,也没有做任何的异常处理,最终异常会再往上抛。
  • 最终抛给框架之后,框架会返回一个JSON格式的数据,里面封装的是错误的信息,但是框架返回的JSON格式的数据并不符合我们的开发规范。

image-20230107121909087

那么在三层构架项目中,出现了异常,该如何处理?

方案一:在所有Controller的所有方法中进行try…catch处理

  • 缺点:代码臃肿(不推荐)

方案二:全局异常处理器

  • 好处:简单、优雅(推荐)

image-20230107122904214

代码实现

  1. 修改Code

    public class CodeConstant {
        public static final Integer SAVE_OK = 20011;
        public static final Integer DELETE_OK = 20021;
        public static final Integer UPDATE_OK = 20031;
        public static final Integer GET_OK = 20041;
    
        public static final Integer SAVE_ERR = 20010;
        public static final Integer DELETE_ERR = 20020;
        public static final Integer UPDATE_ERR = 20030;
        public static final Integer GET_ERR = 20040;
    
        public static final Integer EXCEPTION = 10000;
    }
    
  2. 创建异常处理器类

    @RestControllerAdvice
    public class GlobalExceptionHandler {
    
        //处理异常
        @ExceptionHandler(Exception.class) //指定能够处理的异常类型
        public Result ex(Exception e) {
            e.printStackTrace();//打印堆栈中的异常信息
    
            //捕获到异常之后,响应一个标准的Result
            return Result.error(Code.EXCEPTION, "有妖气~~");
        }
    }
    

    @RestControllerAdvice注解

    • 写在Rest风格开发的控制器增强类定义上方,用于为Rest风格开发的控制器类做增强,表示当前类为全局异常处理器,等同于@ControllerAdvice与@ResponseBody两个注解组合功能

    @ExceptionHandler注解

    • 写在专用于异常处理的控制器方法上方,用于指定可以捕获哪种类型的异常进行处理
  3. 配置Spring包扫描

    @Configuration
    @ComponentScan("com.fs.handler")
    public class SpringConfig {
    }
    
  4. 修改HelloController

    @Controller
    public class HelloController {
    
        @RequestMapping("/hello")
        public String hello() {
    
            //制造异常
            int i = 1 / 0;
    
            return "hello.jsp";
        }
    }
    
  5. 使用本地tomcat启动项目,并使用Postman测试

    http://localhost:8080/spring_mvc_demo_war/hello
    

    image-20230811145031214

拦截器

什么是拦截器(Interceptor)?

  • 是一种动态拦截方法调用的机制,类似于过滤器。
  • 在SpringMVC中动态拦截控制器方法的执行。

拦截器的作用:

  • 在指定方法调用前后,根据业务需要执行预先设定的代码。
  • 拦截请求,阻止原始方法的执行。

代码实现

  1. 自定义拦截器,实现HandlerInterceptor接口,并重写其所有方法

    @Component
    public class BookInterceptor implements HandlerInterceptor {
        //目标资源方法执行前执行。
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            System.out.println("preHandle .... ");
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            System.out.println("调用方法:" + handlerMethod.getMethod().getName());
    
            return true; //true表示放行
        }
    
        //目标资源方法执行后执行
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
            System.out.println("postHandle ... ");
        }
    
        //视图渲染完毕后执行,最后执行
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            System.out.println("afterCompletion .... ");
        }
    }
    

    拦截器bean一般加载到SpringMVC中。

    handler参数

    • 表示被调用的Controller对象,本质上是一个方法对象,对反射中的Method对象进行了再包装

    ex参数

    • 表示被调用的Controller对象抛出的异常对象
  2. 配置拦截器

    @Configuration
    public class SpringMvcSupport extends WebMvcConfigurationSupport {
    
        //自定义的拦截器对象
        @Autowired
        private BookInterceptor bookInterceptor;
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            //配置自定义拦截器对象
            registry.addInterceptor(bookInterceptor).addPathPatterns("/books/**");
        }
    }
    

    addPathPatterns("要拦截路径")方法,就可以指定要拦截哪些资源。

    excludePathPatterns("不拦截路径")方法,指定哪些资源不需要拦截。

  3. 配置SpringMVC包扫描

    @Configuration
    @ComponentScan({"com.fs.controller","com.fs.config.mvc","com.fs.interceptor"})
    //开启JSON数据类型自动转换
    @EnableWebMvc
    public class SpringMvcConfig {
    }
    

执行流程

image-20230107112136151

  1. 打开浏览器访问部署在web服务器当中的web应用时,过滤器会拦截到这次请求。
  2. 过滤器放行之后进入到了spring的环境当中
  3. Tomcat并不识别所编写的Controller程序,它只识别Servlet程序,所以在Spring的Web环境中提供了一个核心的Servlet:DispatcherServlet(中央处理器),所有请求都会先进行到DispatcherServlet(属于SpringMVC),SpringMVC会根据配置的请求映射规则进行拦截,再将拦截到的请求转发给Controller。
  4. 拦截器会在执行Controller的方法之前将请求拦截住,执行preHandle()方法,这个方法执行完成后需要返回一个布尔类型的值,如果返回true,就表示放行本次操作,才会继续访问controller中的方法;如果返回false,则不会放行(controller中的方法也不会执行)。
  5. 在controller当中的方法执行完毕之后,再回过来执行postHandle()这个方法以及afterCompletion() 方法,然后再返回给DispatcherServlet,最终再来执行过滤器当中放行后的这一部分逻辑的逻辑。执行完毕之后,最终给浏览器响应数据。

过滤器和拦截器之间的区别:

  • 接口规范不同:过滤器需要实现Filter接口,而拦截器需要实现HandlerInterceptor接口。
  • 拦截范围不同:过滤器Filter会拦截所有的资源,而Interceptor只会拦截Spring环境中的资源。

FastJSON

Fastjson 是阿里巴巴提供的一个Java语言编写的高性能功能完善的 JSON 库,是目前Java语言中最快的 JSON 库,可以实现 Java 对象和 JSON 字符串的相互转换(序列化和反序列化)。

序列化API:

JSON.toJSONString()

反序列化API:

JSON.parseObject

SerializerFeature枚举

该枚举支持序列化的一些特性数据定义

  1. 引入依赖

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.76</version>
    </dependency>
    
  2. 修改Book

    public class Book {
        private Integer id;
        private String name;
        private Double price;
        private Boolean aBoolean;
        private LocalDateTime localDateTime;
    
        //省略get、set、toString方法
    }
    
  3. 创建FastJSONTest

    public class FastJSONTest {
        public static void main(String[] args) {
    
            Book book = new Book();
            book.setId(1);
            book.setName(null);
            book.setPrice(null);
            book.setaBoolean(null);
            book.setLocalDateTime(LocalDateTime.now());
    
            //FastJSON默认情况下不会序列化为值为null的字段
            System.out.println(JSON.toJSONString(book));
    
            //枚举常量 WriteMapNullValue 序列化为null的字段
            System.out.println(JSON.toJSONString(book, SerializerFeature.WriteMapNullValue));
    
            //枚举常量 WriteNullStringAsEmpty String类型的字段为null时序列化为""
            System.out.println(JSON.toJSONString(book, SerializerFeature.WriteNullStringAsEmpty));
    
            //枚举常量 WriteNullNumberAsZero Number类型的字段为null时序列化为0
            System.out.println(JSON.toJSONString(book, SerializerFeature.WriteNullNumberAsZero));
    
            //枚举常量 WriteNullBooleanAsFalse Boolean类型的字段为null时序列化为false
            System.out.println(JSON.toJSONString(book, SerializerFeature.WriteNullBooleanAsFalse));
    
            //枚举常量 WriteDateUseDateFormat  格式化日期格式
            System.out.println(JSON.toJSONString(book, SerializerFeature.WriteDateUseDateFormat));
    
            //枚举常量 PrettyFormat 格式化输出
            System.out.println(JSON.toJSONString(book, SerializerFeature.PrettyFormat));
        }
    }
    
  4. 运行程序

    image-20230821230633486

@JSonField注解

该注解作用于方法上,字段上和参数上.可在序列化和反序列化时进行特性功能定制

  • name属性

    • 序列化后的名字
  • ordinal属性

    • 序列化后的顺序,取值越大顺序越靠后
  • format属性

    • 序列化后的格式
  • serialize属性

    • 是否序列化该字段
  • deserialize属性

    • 是否反序列化该字段
  • serialzeFeatures属性

    • 序列化时的特性定义(SerializerFeature枚举)

@ JSonType注解

该注解作用于类上,对该类的字段进行序列化和反序列化时的特性功能定制.

  • includes属性
    • 要被序列化的字段.
  • orders属性
    • 序列化后的顺序.
  • serialzeFeatures属性
    • 序列化时的特性定义(SerializerFeature枚举)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值