SpringMVC基础入门

文章目录


1、SpringMVC概述

  • SpringMVC是隶属于Spring框架的一部分,主要是用来进行Web开发,是对Servlet进行了封装。
    在这里插入图片描述
    在这里插入图片描述
  • 浏览器发送一个请求给后端服务器,后端服务器现在是使用Servlet来接收请求和数据

  • 如果所有的处理都交给Servlet来处理的话,所有的东西都耦合在一起,对后期的维护和扩展极为不利

  • 将后端服务器Servlet拆分成三层,分别是webservicedao

    • web层主要由servlet来处理,负责页面请求和数据的收集以及响应结果给前端
    • service层主要负责业务逻辑的处理
    • dao层主要负责数据的增删改查操作
  • servlet处理请求和数据的时候,存在的问题是一个servlet只能处理一个请求

  • 针对web层进行了优化,采用了MVC设计模式,将其设计为controllerviewModel

    • controller负责请求和数据的接收,接收后将其转发给service进行业务处理
    • service根据需要会调用dao对数据进行增删改查
    • dao把数据处理完后将结果交给service,service再交给controller
    • controller根据需求组装成Model和View,Model和View组合起来生成页面转发给前端浏览器
    • 这样做的好处就是controller可以处理多个请求,并对请求进行分发,执行不同的业务操作。

随着互联网的发展,上面的模式因为是同步调用,性能慢慢的跟不是需求,所以异步调用慢慢的走到了前台,是现在比较流行的一种处理方式。
在这里插入图片描述

  • 因为是异步调用,所以后端不需要返回view视图,将其去除
  • 前端如果通过异步调用的方式进行交互,后台就需要将返回的数据转换成json格式进行返回
  • SpringMVC主要负责的就是
    • controller如何接收请求和数据
    • 如何将请求和数据转发给业务层
    • 如何将响应数据转换成json发回到前端

介绍了这么多,对SpringMVC进行一个定义

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

  • 优点

    • 使用简单、开发便捷(相比于Servlet)
    • 灵活性强

2、SpringMVC入门案例

因为SpringMVC是一个Web框架,将来是要替换Servlet,所以先来回顾下以前Servlet是如何进行开发的?

1.创建web工程(Maven结构)

2.设置tomcat服务器,加载web工程(tomcat插件)

3.导入坐标(Servlet)

4.定义处理请求的功能类(UserServlet)

5.设置请求映射(配置映射关系)

SpringMVC的制作过程和上述流程几乎是一致的,具体的实现流程是什么?

1.创建web工程(Maven结构)

2.设置tomcat服务器,加载web工程(tomcat插件)

3.导入坐标(SpringMVC+Servlet)

4.定义处理请求的功能类(UserController)

5.设置请求映射(配置映射关系)

6.将SpringMVC设定加载到Tomcat容器中

2.1、案例制作

步骤一:创建Maven项目

在这里插入图片描述

在这里插入图片描述

步骤二:导入坐标

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>springmvc_quickstart</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>
    <dependencies>
        <!--导入SpringMVC与servlet-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.2.10.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>


    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <version>2.2</version>
                <configuration>
                    <port>80</port>
                    <path>/</path>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

说明: servlet的坐标为什么需要添加<scope>provided</scope>?

  • scope是maven中jar包依赖作用范围的描述,

  • 如果不设置默认是compile在在编译、运行、测试时均有效

  • 如果运行有效的话就会和tomcat中的servlet-api包发生冲突,导致启动报错

  • provided代表的是该包只在编译和测试的时候用,运行的时候无效直接使用tomcat中的,就避免冲突

步骤三:创建配置类

package com.chuhe.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

//创建springmvc的配置文件,加载controller对应的bean
@Configuration
@ComponentScan("com.chuhe.controller")
public class SpringMvcConfig {
}

步骤四:创建Controller类

package com.chuhe.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

//定义controller,使用@Controller定义bean
@Controller
public class UserController {
    //设置当前操作的访问路径
    @RequestMapping("/save")
    //设置当前操作的返回值类型
    @ResponseBody
    public String save(){
        System.out.println("use save...");
        return "{'module':'springmvc'}";
    }
}

步骤五:使用配置类替换web.xml

将web.xml删除,换成ServletContainersInitConfig

package com.chuhe.config;

import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.support.AbstractDispatcherServletInitializer;

//定义一个servlet容器启动配置类,在里面加载spring的配置
public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer {
    //加载SprivgMVC配置
    @Override
    protected WebApplicationContext createServletApplicationContext() {
        AnnotationConfigWebApplicationContext ctx=new AnnotationConfigWebApplicationContext();
        ctx.register(SpringMvcConfig.class);
        return ctx;
    }
    //设置哪些请求归属springMVC处理
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};//所有请求归SpringMVC管理
    }
    //加载spring容器配置
    @Override
    protected WebApplicationContext createRootApplicationContext() {
        return null;
    }
}

步骤六:启动tomcat,浏览器输入http://localhost/save进行访问

2.2、工作流程解析

在这里插入图片描述

2.2.1、启动服务器初始化过程

  1. 服务器启动,执行ServletContainersInitConfig类,初始化web容器

    ♠ 功能类似于以前的web.xml

  2. 执行createServletApplicationContext方法,创建了WebApplicationContext对象

    ♠ 该方法加载SpringMVC的配置类SpringMvcConfig来初始化SpringMVC的容器

  3. 加载SpringMvcConfig配置类

    //创建springmvc的配置文件,加载controller对应的bean
    @Configuration
    @ComponentScan("com.chuhe.controller")
    public class SpringMvcConfig {
    }
    
  4. 执行@ComponentScan加载对应的bean

    ♠ 扫描指定包及其子包下所有类上的注解,如Controller类上的@Controller注解

  5. 加载UserController,每个@RequestMapping的名称对应一个具体的方法

    //定义controller,使用@Controller定义bean
    @Controller
    public class UserController {
        //设置当前操作的访问路径
        @RequestMapping("/save")
        //设置当前操作的返回值类型
        @ResponseBody
        public String save(){
            System.out.println("use save...");
            return "{'module':'springmvc'}";
        }
    }
    

    ♠ 此时就建立了 /save 和 save方法的对应关系

  6. 执行getServletMappings方法,设定SpringMVC拦截请求的路径规则

        protected String[] getServletMappings() {
            return new String[]{"/"};//所有请求归SpringMVC管理
        }
    

    /代表所拦截请求的路径规则,只有被拦截后才能交给SpringMVC来处理请求

2.2.2、单次请求过程

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

2.3、bean加载控制

  • 入门案例中我们创建过一个SpringMvcConfig的配置类。
  • 学习Spring的时候也创建过一个配置类SpringConfig
  • 这两个配置类都需要加载资源,那么它们分别都需要加载哪些内容?

项目目录结构:
在这里插入图片描述

  • config目录存入的是配置类,写过的配置类有:
    ♠ ServletContainersInitConfig
    ♠ SpringConfig
    ♠ SpringMvcConfig
    ♠ JdbcConfig
    ♠ MybatisConfig
  • controller目录存放的是SpringMVC的controller类
  • service目录存放的是service接口和实现类
  • dao目录存放的是dao/Mapper接口

controller、service和dao这些类都需要被容器管理成bean对象,那么到底是该让SpringMVC加载还是让Spring加载呢?

  • SpringMVC加载其相关bean(表现层bean),也就是controller包下的类
  • Spring控制的bean
    ♠ 业务bean(Service)
    ♠ 功能bean(DataSource,SqlSessionFactoryBean,MapperScannerConfigurer等)

清楚谁该管哪些bean以后,接下来要解决的问题是如何让Spring和SpringMVC分开加载各自的内容。

  • 在SpringMVC的配置类SpringMvcConfig中使用注解@ComponentScan,我们只需要将其扫描范围设置到controller即可,如

    //创建springmvc的配置文件,加载controller对应的bean
    @Configuration
    @ComponentScan("com.chuhe.controller")
    public class SpringMvcConfig {
    }
    
  • 在Spring的配置类SpringConfig中使用注解@ComponentScan,当时扫描的范围中其实是已经包含了controller,如:

    @Configuration
    @ComponentScan("com.chuhe")
    public class SpringConfig {
    }
    

    Spring已经多把SpringMVC的controller类也给扫描到,如何避免Spring错误加载到SpringMVC的bean?

2.3.1、避免Spring错误加载SpringMVC的bean

针对上面的问题,解决方式就是:加载Spring控制的bean的时候排除掉SpringMVC控制的bean。

具体排除方法:

  • 方式一:Spring加载的bean设定扫描范围为精准范围,例如service包、dao包等
  • 方式二:Spring加载的bean设定扫描范围为com.itheima,排除掉controller包中的bean
  • 方式三:不区分Spring与SpringMVC的环境,加载到同一个环境中[了解即可]

2.3.2、设置bean加载控制

  • 方式一:Spring加载的bean设定扫描范围为精准范围

    @Configuration
    @ComponentScan({"com.chuhe.service","chuhe.dao"})
    public class SpringConfig {
    }
    

    说明:

    上述只是通过例子说明可以精确指定让Spring扫描对应的包结构,真正在做开发的时候,因为Dao最终是交给MapperScannerConfigurer对象来进行扫描处理的,我们只需要将其扫描到service包即可。

  • 方式二:修改Spring配置类,设定扫描范围为com.itheima,排除掉controller包中的bean

    @Configuration
    @ComponentScan(value="com.chuhe",
        excludeFilters=@ComponentScan.Filter(
        	type = FilterType.ANNOTATION,//设置排除规则,这里是按注解排除
            classes = Controller.class
        )
    )
    public class SpringConfig {
    }
    
    • excludeFilters属性:设置扫描加载bean时,排除的过滤规则

    • type属性:设置排除规则,当前使用按照bean定义时的注解类型进行排除

      ANNOTATION:按照注解排除
      ASSIGNABLE_TYPE:按照指定的类型过滤
      ASPECTJ:按照Aspectj表达式排除,基本上不会用
      REGEX:按照正则表达式排除
      CUSTOM:按照自定义规则排除

      大家只需要知道第一种ANNOTATION即可

    • classes属性:设置排除的具体注解类,当前设置排除@Controller定义的bean
      UserController.java

出现的问题

  • Spring配置类扫描的包是com.chuhe
  • SpringMVC的配置类,SpringMvcConfig上有一个@Configuration注解,也会被Spring扫描到。
  • SpringMvcConfig上又有一个@ComponentScan,把controller类又给扫描进来了。
  • 所以如果不把@ComponentScan注释掉,Spring配置类将Controller排除,但是因为扫描到SpringMVC的配置类,又将其加载回来。
  • 解决方案,也简单,把SpringMVC的配置类移出Spring配置类的扫描范围即可。比如放在其他目录下。

2.3.3、修改ServletContainersInitConfig加载Spring的配置类

修改ServletContainersInitConfig在tomcat服务器启动时加载Spring的配置类

//定义一个servlet容器启动配置类,在里面加载spring的配置
public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer {
    //加载SprivgMVC配置
    @Override
    protected WebApplicationContext createServletApplicationContext() {
        AnnotationConfigWebApplicationContext ctx=new AnnotationConfigWebApplicationContext();
        ctx.register(SpringMvcConfig.class);
        return ctx;
    }
    //设置哪些请求归属springMVC处理
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};//所有请求归SpringMVC管理
    }
    //加载spring容器配置
    @Override
    protected WebApplicationContext createRootApplicationContext() {
        AnnotationConfigWebApplicationContext ctx=new AnnotationConfigWebApplicationContext();
        ctx.register(SpringConfig.class);
        return ctx;
    }
}

2.3.4、ServletContainersInitConfig的简化方式

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

public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
	//加载spring容器配置
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{SpringConfig.class};
    }
	//加载SprivgMVC配置
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{SpringMvcConfig.class};
    }
	//设置哪些请求归属springMVC处理
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}

3、请求与响应

3.1、请求映射路径

3.1.1、环境准备

  • 步骤一:创建一个Web的Maven项目
    在这里插入图片描述

  • 步骤二:pom.xml添加依赖

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>org.example</groupId>
        <artifactId>springmvc_request</artifactId>
        <version>1.0-SNAPSHOT</version>
        <packaging>war</packaging>
    
        <properties>
            <maven.compiler.source>11</maven.compiler.source>
            <maven.compiler.target>11</maven.compiler.target>
        </properties>
        <dependencies>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-webmvc</artifactId>
                <version>5.2.10.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>javax.servlet-api</artifactId>
                <version>3.1.0</version>
                <scope>provided</scope>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.tomcat.maven</groupId>
                    <artifactId>tomcat7-maven-plugin</artifactId>
                    <version>2.1</version>
                    <configuration>
                        <port>80</port>
                        <path>/</path>
                    </configuration>
                </plugin>
            </plugins>
        </build>	
    </project>
    
  • 步骤三:创建对应的配置类

    package com.chuhe.config;
    
    import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
    
    public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
        @Override
        protected Class<?>[] getRootConfigClasses() {
            return new Class[0];
        }
    
        @Override
        protected Class<?>[] getServletConfigClasses() {
            return new Class[]{SpringMvcConfig.class};
        }
    
        @Override
        protected String[] getServletMappings() {
            return new String[]{"/"};
        }
    }
    
    
    package com.chuhe.config;
    
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    @ComponentScan("com.chuhe.controller")
    public class SpringMvcConfig {
    }
    
    
  • 步骤四:编写BookController和UserController

    package com.chuhe.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    @Controller
    //请求路径前缀
    @RequestMapping("/user")
    public class UserController {
        @RequestMapping("/save")
        @ResponseBody
        public String save(){
            System.out.println("user save...");
            return "{'module':'use Save'}";
        }
        @RequestMapping("/delete")
        @ResponseBody
        public String delete(){
            System.out.println("user delete...");
            return "{'module':'user delete'}";
        }
    }
    
    
    package com.chuhe.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    @Controller
    @RequestMapping("/book")
    public class BookController {
        @RequestMapping("/save")
        @ResponseBody
        public String save(){
            System.out.println("book save...");
            return "{'module':'book save'}";
        }
    
    }	
    

@RequestMapping
设置当前控制器方法请求访问路径,如果设置在类上统一设置当前控制器方法请求访问路径前缀。

  • 步骤五:运行
    浏览器访问:http://localhost/book/save
    浏览器访问:http://localhost/user/save

3.2、请求参数

3.2.1、环境准备

  • 步骤一:创建一个Maven项目
    在这里插入图片描述

  • 步骤二:添加依赖坐标

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>org.example</groupId>
        <artifactId>springmvc_request_param</artifactId>
        <version>1.0-SNAPSHOT</version>
        <packaging>war</packaging>
    
    
        <properties>
            <maven.compiler.source>11</maven.compiler.source>
            <maven.compiler.target>11</maven.compiler.target>
        </properties>
        <dependencies>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-webmvc</artifactId>
                <version>5.2.10.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>javax.servlet-api</artifactId>
                <version>3.1.0</version>
                <scope>provided</scope>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.tomcat.maven</groupId>
                    <artifactId>tomcat7-maven-plugin</artifactId>
                    <version>2.2</version>
                    <configuration>
                        <port>80</port>
                        <path>/</path>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    
    
    </project>
    
  • 步骤三:创建对应的配置类

    package com.chuhe.config;
    
    import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
    
    public class SevletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
        @Override
        protected Class<?>[] getRootConfigClasses() {
            return new Class[0];
        }
    
        @Override
        protected Class<?>[] getServletConfigClasses() {
            return new Class[]{SpringMvcConfig.class};
        }
    
        @Override
        protected String[] getServletMappings() {
            return new String[]{"/"};
        }
    }
    
    
    package com.chuhe.config;
    
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    @ComponentScan("com.chuhe.controller")
    public class SpringMvcConfig {
    }
    
    
  • 步骤四:编写UserController

    @Controller
    public class UserController {
        @RequestMapping("/commonParam")
        @ResponseBody
        public String commonParam(){
            return "{'module':'user commonParam'}";
        }
    }
    
  • 步骤五:编写模型类,User和Address

    public class Address {
        private String province;
        private String city;
        //setter...getter...略
    }
    
    	public class User {
    	    private String name;
    	    private int age;
    	    //setter...getter...略
    	}
    

3.2.2、参数传递

3.2.2.1、GET发送单个参数
  • 发送请求
    在这里插入图片描述

  • 接收参数

    @Controller
    public class UserController {
        @RequestMapping("/commonParam")
        @ResponseBody
        public String commonParam(String name){
            System.out.println("普通参数传递:"+name);
            return "{'module':'user commonParam'}";
        }
    }
    
3.2.2.2、GET发送多个参数
  • 请求参数
    在这里插入图片描述

  • 接收参数

    @Controller
    public class UserController {
        @RequestMapping("/commonParam")
        @ResponseBody
        public String commonParam(String name,int age){
            System.out.println("普通参数传递:"+name);
            System.out.println("普通参数传递:"+age);
            return "{'module':'user commonParam'}";
        }
    }
    
3.2.2.3、GET请求中文乱码
  • 发送请求http://localhost:80/commonParam?name=锄禾&age=18

  • 后台接收乱码
    在这里插入图片描述

  • 解决方法
    Tomcat8.5以后的版本已经处理了中文乱码的问题,但是IDEA中的Tomcat插件目前只到Tomcat7,所以需要修改pom.xml来解决GET请求中文乱码问题。

        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.tomcat.maven</groupId>
                    <artifactId>tomcat7-maven-plugin</artifactId>
                    <version>2.2</version>
                    <configuration>
                        <port>80</port><!--tomcat端口号-->
                        <path>/</path><!--虚拟目录-->
                        <uriEncoding>UTF-8</uriEncoding><!--访问路径编解码字符集-->
                    </configuration>
                </plugin>
            </plugins>
        </build>
    
3.2.2.4、POST发送参数
  • 发送请求
    在这里插入图片描述
  • 接收参数:
    和GET一致,不用做任何修改
3.2.2.5、POST请求中文乱码

GET请求中文乱码的方案,解决不了POST请求中文乱码。

  • 解决方案:配置过滤器
    Ctrl+O,添加getServletFilters方法
    在这里插入图片描述

    package com.chuhe.config;
    
    import org.springframework.web.filter.CharacterEncodingFilter;
    import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
    
    import javax.servlet.Filter;
    
    public class SevletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
        @Override
        protected Class<?>[] getRootConfigClasses() {
            return new Class[0];
        }
    
        @Override
        protected Class<?>[] getServletConfigClasses() {
            return new Class[]{SpringMvcConfig.class};
        }
    
        @Override
        protected String[] getServletMappings() {
            return new String[]{"/"};
        }
    
        //POST请求乱码处理
        @Override
        protected Filter[] getServletFilters() {
            CharacterEncodingFilter filter=new CharacterEncodingFilter();
            filter.setEncoding("UTF-8");
            return new Filter[]{filter};
        }
    }
    
    

3.2.3、五种类型参数传递

GET或POST来发送请求和数据,比较复杂的参数传递,常见的参数种类有:

  • 普通参数
  • POJO类型参数
  • 嵌套POJO类型参数
  • 数组类型参数
  • 集合类型参数
3.2.3.1、普通参数

普通参数: url地址传参,地址参数名与形参变量名相同,定义形参即可接收参数。

  • 请求参数与接收参数名称相同
    在这里插入图片描述

  • 请求参数与接收参数名称不一致:
    解决方案:使用@RequestParam注解

    请求参数
    在这里插入图片描述

    接收参数

    @Controller
    public class UserController {
        @RequestMapping("/commonParam")
        @ResponseBody
        public String commonParam(@RequestParam("username")String name, int age){
            System.out.println("普通参数传递:"+name);
            System.out.println("普通参数传递:"+age);
            return "{'module':'user commonParam'}";
        }
    }
    

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

3.2.3.2、POJO数据类型

简单数据类型一般处理的是参数个数比较少的请求,如果参数比较多,那么后台接收参数的时候就比较复杂,这个时候可以考虑使用POJO数据类型。

  • POJO参数:请求参数名与形参对象属性名相同,定义POJO类型形参即可接收参数。

  • 请求参数
    在这里插入图片描述

  • 接收参数

    //POJO参数:请求参数与形参对象中的属性对应即可完成参数传递
    @RequestMapping("/pojoParam")
    @ResponseBody
    public String pojoParam(User user){
        System.out.println(user.getName());
        System.out.println(user.getAge());
        return "{'module':'user pojoParam'}";
    }
    

注意:

  • POJO参数接收,前端GET和POST发送请求数据的方式不变。
  • 请求参数key的名称要和POJO中属性的名称一致,否则无法封装。
3.2.3.3、 嵌套POJO类型参数

嵌套POJO参数: 请求参数名与形参对象属性名相同,按照对象层次结构关系即可接收嵌套POJO属性参数。

  • 请求参数:
    在这里插入图片描述

  • 处理参数:

    @RequestMapping("/pojoParam")
    @ResponseBody
    public String pojoParam(User user){
        System.out.println(user.getName());
        System.out.println(user.getAge());
        Address address = user.getAddress();
        System.out.println(address.getProvince());
        System.out.println(address.getCity());
        return "{'module':'user pojoParam'}";
    }
    

注意:

请求参数key的名称要和POJO中属性的名称一致,否则无法封装。

3.2.3.4、数组类型参数

数组参数: 请求参数名与形参对象属性名相同且请求参数为多个,定义数组类型即可接收参数

  • 请求参数
    在这里插入图片描述

  • 接收参数

    @RequestMapping("/arrParam")
    @ResponseBody
    public String arrParam(String[] likes){
        System.out.println(Arrays.toString(likes));
        return "{'module':'user arrParam'}";
    }
    
3.2.3.5、集合类型参数
  • 请求参数
    在这里插入图片描述

  • 接收参数

    @RequestMapping("/listParam")
    @ResponseBody
    public String listParam( List<String> likes){
        System.out.println(likes);
        return "{'module':'user listParam'}";
    

接收请求时,服务器报错信息如下:在这里插入图片描述
错误的原因是:SpringMVC将List看做是一个POJO对象来处理,将其创建一个对象并准备把前端的数据封装到对象中,但是List是一个接口无法创建对象,所以报错。

解决方案是:使用@RequestParam注解

@RequestMapping("/listParam")
@ResponseBody
public String listParam(@RequestParam List<String> likes){
    System.out.println(likes);
    return "{'module':'user listParam'}";
}
  • 集合保存普通参数:请求参数名与形参集合对象名相同且请求参数为多个,@RequestParam绑定参数关系
  • 对于简单数据类型使用数组会比集合更简单些。

知识点1:@RequestParam

名称@RequestParam
类型形参注解
位置SpringMVC控制器方法形参定义前面
作用绑定请求参数与处理器方法形参间的关系
相关参数required:是否为必传参数
defaultValue:参数默认值

3.3、响应json数据

现在比较流行的开发方式为异步调用。前后台以异步方式进行交换,传输的数据使用的是JSON

对于JSON数据类型,我们常见的有三种:

  • json普通数组:["value1","value2","value3",...]
  • json对象:{key1:value1,key2:value2,...}
  • json对象数组:[{key1:value1,...},{key2:value2,...}]

3.3.1、JSON普通数组

  • 步骤一:SpringMVC默认使用的是jackson来处理json的转换,所以需要在pom.xml添加jackson依赖。

    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.9.0</version>
    </dependency>
    
  • 步骤二:开启SpringMVC注解支持
    在SpringMVC的配置类中开启SpringMVC的注解支持,这里面就包含了将JSON转换成对象的功能。

    @Configuration
    @ComponentScan("com.chuhe.controller")
    //开启json数据类型自动转换
    @EnableWebMvc
    public class SpringMvcConfig {
    }
    
  • 步骤四:请求参数
    在这里插入图片描述

  • 步骤四:接收参数

        @RequestMapping("/listParamJson")
        @ResponseBody
        //使用@RequestBody注解将外部传递的json数组数据映射到形参的集合对象中作为数据
        public String listParamJson(@RequestBody List<String> likes){
            System.out.println(likes);
            return "{'module':'list common for json param'}";
        }
    
    

3.3.2、JSON对象数据

一、POJO对象数据
请求数据:

{
	"name":"chuhe",
	"age":15
}
  • 请求数据

在这里插入图片描述

  • 接收数据

        @RequestMapping("/pojoParamJson")
        @ResponseBody
        public String pojoParamJson(@RequestBody User user){
            System.out.println(user.getName());
            System.out.println(user.getAge());
            return "{'module':'pojo for json param'}";
    
        }
    

二、嵌套POJO
请求参数

{
    "name":"chuhe",
    "age":18,
    "address":{
        "province":"江苏",
        "city":"徐州"
    }
}

在这里插入图片描述

3.3.3、JSON对象数组

  • 请求参数
    在这里插入图片描述
  • 接收参数
        @RequestMapping("/listPojoParamJson")
        @ResponseBody
        public String listPojoParamJson(@RequestBody List<User> list){
            System.out.println(list);
            return "{'module':'list pojo for json param'}";
        }
    
@RequestBody与@RequestParam区别

区别

  • @RequestParam用于接收url地址传参,表单传参【application/x-www-form-urlencoded】 *
    @RequestBody用于接收json数据【application/json】

应用

  • 后期开发中,发送json格式数据为主,@RequestBody应用较广
  • 如果发送非json格式数据,选用@RequestParam接收请求参数

3.4、日期类型参数传递

  • 请求参数
    在这里插入图片描述

  • 接收参数

    @RequestMapping("/dateParam")
    @ResponseBody
    public String dateParam(Date d1,
                            @DateTimeFormat(pattern="yyy-MM-dd")Date d2,
                            @DateTimeFormat(pattern="yyyy年MM月dd日 HH:mm:ss")Date d3){
        System.out.println(d1);
        System.out.println(d2);
        System.out.println(d3);
        return "{'module':'date param'}";
    }
    

注意:SpringMVC的配置类把@EnableWebMvc当做标配配置上去,不要省略

3.5、响应

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

  • 响应页面
  • 响应数据
    • 文本数据
    • json数据

3.5.1、环境准备

  • 步骤一:创建一个Web的Maven项目。
    在这里插入图片描述

  • 步骤二:pom.xm添加依赖

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>org.example</groupId>
        <artifactId>springmvc_response</artifactId>
        <version>1.0-SNAPSHOT</version>
        <packaging>war</packaging>
    
        <properties>
            <maven.compiler.source>11</maven.compiler.source>
            <maven.compiler.target>11</maven.compiler.target>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-webmvc</artifactId>
                <version>5.2.10.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>javax.servlet-api</artifactId>
                <version>3.1.0</version>
                <scope>provided</scope>
            </dependency>
            <dependency>
                <groupId>com.fasterxml.jackson.core</groupId>
                <artifactId>jackson-databind</artifactId>
                <version>2.9.0</version>
            </dependency>
        </dependencies>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.tomcat.maven</groupId>
                    <artifactId>tomcat7-maven-plugin</artifactId>
                    <version>2.2</version>
                    <configuration>
                        <port>80</port>
                        <path>/</path>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    </project>
    
  • 步骤三:创建对应的配置类

    package com.chuhe.config;
    
    import org.springframework.web.filter.CharacterEncodingFilter;
    import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
    
    import javax.servlet.Filter;
    
    public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
        @Override
        protected Class<?>[] getRootConfigClasses() {
            return new Class[0];
        }
    
        @Override
        protected Class<?>[] getServletConfigClasses() {
            return new Class[]{SpringMvcConfig.class};
        }
    
        @Override
        protected String[] getServletMappings() {
            return new String[]{"/"};
        }
        //POST请求乱码处理
        @Override
        protected Filter[] getServletFilters() {
            CharacterEncodingFilter filter=new CharacterEncodingFilter();
            filter.setEncoding("UTF-8");
            return new Filter[]{filter};
        }
    }
    
    
    package com.chuhe.config;
    
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.EnableWebMvc;
    
    @Configuration
    @ComponentScan("com.chuhe.controller")
    //开启json数据类型自动转换
    @EnableWebMvc
    public class SpringMvcConfig {
    }
    
    
  • 步骤四:编写模型类User

    public class User {
        private String name;
        private int age;
        //getter...setter...toString省略
    }
    
  • 步骤五:webapp下创建page.jsp

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>Title</title>
    </head>
    <body>
        <h1>Hello SpringMvc!</h1>
    </body>
    </html>
    
  • 步骤六:编写UserController

    @Controller
    public class UserController {
    
        
    }
    

3.5.2、响应页面

@Controller
public class UserController {
    @RequestMapping("/toJumpPage")
    //注意
	//1.此处不能添加@ResponseBody,如果加了该注入,会直接将page.jsp当字符串返回前端
	//2.方法需要返回String
    public String toJumpPage(){
        System.out.println("跳转页面");
        return "page.jsp";
    }
}

浏览器访问:http://localhost/toJumpPage
在这里插入图片描述

3.5.3、响应文本数据

@Controller
public class UserController {

    @RequestMapping("/toText")
    //注意此处该注解就不能省略,如果省略了,会把response text当前页面名称去查找,如果没有回报404错误
    @ResponseBody
    public String toText(){
        System.out.println("返回纯文本数据");
        return "response text";
    }
}

浏览器访问:http://localhost/toText
在这里插入图片描述

3.5.4、响应JSON数据

3.5.4.1、响应POJO对象
@Controller
public class UserController {

    @RequestMapping("/toJsonPojo")
    @ResponseBody
    public User toJsonPojo(){
        System.out.println("返回json对象数据");
        User user=new User();
        user.setName("锄禾");
        user.setAge(18);
        return user;
    }
}

浏览器访问:http://localhost/toJsonPojo
在这里插入图片描述

3.5.4.2、响应POJO集合对象
@Controller
public class UserController {

    
    @RequestMapping("/toJsonList")
    @ResponseBody
    public List<User> toJsonList(){
        System.out.println("返回json集合数据");
        User user1=new User();
        user1.setName("唐青枫");
        user1.setAge(20);

        User user2=new User();
        user2.setName("曲无忆");
        user2.setAge(19);

        List<User> list=new ArrayList<User>();
        list.add(user1);
        list.add(user2);
        return list;
    }

}

浏览器访问:http://localhost/toJsonList
在这里插入图片描述

4、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对应一种操作,这样做不仅麻烦,也不安全,因为会程序的人读取了你的请求url地址,就大概知道该url实现的是一个什么样的操作。

        查看REST风格的描述,你会发现请求地址变的简单了,并且光看请求URL并不是很能猜出来该URL的具体功能。

        一个相同的url地址即可以是新增也可以是修改或者查询,那么到底我们该如何区分该请求到底是什么操作呢?

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

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

        请求的方式比较多,但是比较常用的就4种,分别是GET,POST,PUT,DELETE

按照不同的请求方式代表不同的操作类型。

  • 发送GET请求是用来做查询
  • 发送POST请求是用来做新增
  • 发送PUT请求是用来做修改
  • 发送DELETE请求是用来做删除

注意:

  • 上述行为是约定方式,约定不是规范,可以打破,所以称REST风格,而不是REST规范
  • REST提供了对应的架构方式,按照这种架构设计项目可以降低开发的复杂性,提高系统的可伸缩性
  • REST中规定GET/POST/PUT/DELETE针对的是查询/新增/修改/删除,但是我们如果非要用GET请求做删除,这点在程序上运行是可以实现的。
  • 但是如果绝大多数人都遵循这种风格,你写的代码让别人读起来就有点莫名其妙了。
  • 描述模块的名称通常使用复数,也就是加s的格式描述,表示此类资源,而非单个资源,例如:users、books、accounts…

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

在进行开发的过程中,大多是都是遵从REST风格来访问我们的后台服务,所以可以说以后都是基于RESTful来进行开发的。

4.1、新增POST

在这里插入图片描述

@Controller
public class UserController {
    //新增
    @RequestMapping(value = "/users",
                    method = RequestMethod.POST,
                    produces = "application/json;charset=utf-8")
    @ResponseBody
    public String save(@RequestBody User user){
        System.out.println(user);
        return"{'module':'user新增'}";
    }

}

4.2、修改

在这里插入图片描述

@Controller
public class UserController {

    //修改
    @RequestMapping(value = "/users",
                    method = RequestMethod.PUT,
                    produces ="application/json;charset=utf-8")
    @ResponseBody
    public String update(@RequestBody User user){
        System.out.println(user);
        return"{'module':'user修改'}";
    }

}

4.3、删除

在这里插入图片描述

@Controller
public class UserController {

    //删除
    @RequestMapping(value = "/users/{id}",
                    method=RequestMethod.DELETE,
                    produces = "application/json;charset=utf-8")
    @ResponseBody
    public String delete(@PathVariable Integer id){
        System.out.println(id);
        return"{'module':'user删除'}";
    }

}

传递路径参数

前端发送请求的时候使用:http://localhost/users/1,路径中的1就是我们想要传递的参数。
后端获取参数,需要做如下修改:

  • 修改@RequestMapping的value属性,将其中修改为/users/{id},目的是和路径匹配
  • 在方法的形参前添加@PathVariable注解

方法形参的名称和路径{}中的值不一致

@Controller
public class UserController {

    //删除
    @RequestMapping(value = "/users/{id}",
                    method=RequestMethod.DELETE,
                    produces = "application/json;charset=utf-8")
    @ResponseBody
    public String delete(@PathVariable("id") Integer userId){
        System.out.println(userId);
        return"{'module':'user删除'}";
    }

}

前端请求传两个参数

        前端发送请求的时候使用:http://localhost/users/1/tom,路径中的1tom就是我们想要传递的两个参数。

@Controller
public class UserController {

    //删除
    @RequestMapping(value = "/users/{id}/{name}",
                    method=RequestMethod.DELETE,
                    produces = "application/json;charset=utf-8")
    @ResponseBody
    public String delete(@PathVariable Integer id,@PathVariable String name){
        System.out.println(id);
        System.out.println(name);
        return"{'module':'user删除'}";
    }

}

4.4、根据ID查询

在这里插入图片描述

@Controller
public class UserController {
    //根据ID查询
    @RequestMapping(value = "/users/{id}",
                    method = RequestMethod.GET,
                    produces = "application/json;charset=utf-8")
    @ResponseBody
    public String getById(@PathVariable Integer id){
        System.out.println(id);
        return "{'module':'user根据ID查询'}";
    }  
}

4.5、查询全部

在这里插入图片描述

@Controller
public class UserController {
   
    //查询所有
    @RequestMapping(value = "/users",
            method = RequestMethod.GET,
            produces = "application/json;charset=utf-8")
    @ResponseBody
    public String getAll(){

        return "{'module':'user查询所有'}";
    }
}

4.6、@RequestBody@RequestParam@PathVariable,这三个注解之间的区别和应用

  • 区别
    • @RequestParam用于接收url地址传参或表单传参
    • @RequestBody用于接收json数据
    • @PathVariable用于接收路径参数,使用{参数名称}描述路径参数
  • 应用
    • 后期开发中,发送请求参数超过1个时,以json格式为主,@RequestBody应用较广
    • 如果发送非json格式数据,选用@RequestParam接收请求参数
    • 采用RESTful进行开发,当参数数量较少时,例如1个,可以采用@PathVariable接收请求路径变量,通常用于传递id值

4.7、RESTful快速开发

上述Rest风格的开发,存在三个问题:

  • 问题1:每个方法的@RequestMapping注解中都定义了访问路径/users,重复性太高。

  • 问题2:每个方法的@RequestMapping注解中都要使用method属性定义请求方式,重复性太高。

  • 问题3:每个方法响应json都需要加上@ResponseBody注解,重复性太高。


//@Controller
//@ResponseBody
@RestController//相当于上面两句
@RequestMapping(value = "/users",produces = "application/json;charset=utf-8")

public class UserController {
    //新增
	//@RequestMapping(method = RequestMethod.POST)
    @PostMapping
    public String save(@RequestBody User user){
        System.out.println(user);
        return"{'module':'user新增'}";
    }
    //修改
	//@RequestMapping(method = RequestMethod.PUT)
    @PutMapping
    public String update(@RequestBody User user){
        System.out.println(user);
        return"{'module':'user修改'}";
    }
    //删除
	//@RequestMapping(value = "/{id}/{name}",method=RequestMethod.DELETE)
    @DeleteMapping("/{id}/{name}")
    public String delete(@PathVariable Integer id,@PathVariable String name){
        System.out.println(id);
        System.out.println(name);
        return"{'module':'user删除'}";
    }
    //根据ID查询
	//@RequestMapping(value = "/{id}",method = RequestMethod.GET)
    @GetMapping("/{id}")
    public String getById(@PathVariable Integer id){
        System.out.println(id);
        return "{'module':'user根据ID查询'}";
    }
    //查询所有
	//@RequestMapping(method = RequestMethod.GET)
    @GetMapping
    public String getAll(){

        return "{'module':'user查询所有'}";
    }
}

4.8、RESTful案例

4.8.1、环境准备

  • 步骤一:创建一个Maven项目
    在这里插入图片描述

  • 步骤二:pom.xml添加依赖坐标

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>org.example</groupId>
        <artifactId>springmvc_rest_cast</artifactId>
        <version>1.0-SNAPSHOT</version>
        <packaging>war</packaging>
    
    
        <properties>
            <maven.compiler.source>11</maven.compiler.source>
            <maven.compiler.target>11</maven.compiler.target>
        </properties>
        <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>
            <dependency>
                <groupId>com.fasterxml.jackson.core</groupId>
                <artifactId>jackson-databind</artifactId>
                <version>2.9.0</version>
            </dependency>
        </dependencies>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.tomcat.maven</groupId>
                    <artifactId>tomcat7-maven-plugin</artifactId>
                    <version>2.2</version>
                    <configuration>
                        <port>80</port>
                        <path>/</path>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    
    </project>
    
  • 步骤三:创建对应的配置类

    package com.chuhe.config;
    
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.EnableWebMvc;
    
    @Configuration
    @ComponentScan("com.chuhe.controller")
    @EnableWebMvc
    public class SpringMvcConfig {
    }
    
    
    package com.chuhe.config;
    
    import org.springframework.web.filter.CharacterEncodingFilter;
    import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
    
    import javax.servlet.Filter;
    
    public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
        @Override
        protected Class<?>[] getRootConfigClasses() {
            return new Class[0];
        }
    
        @Override
        protected Class<?>[] getServletConfigClasses() {
            return new Class[]{SpringMvcConfig.class};
        }
    
        @Override
        protected String[] getServletMappings() {
            return new String[]{"/"};
        }
    
        //POST请求乱码
        @Override
        protected Filter[] getServletFilters() {
            CharacterEncodingFilter filter=new CharacterEncodingFilter();
            filter.setEncoding("UTF-8");
    
            return new Filter[]{filter};
        }
    }
    
    
  • 步骤四:编写模型类Book

    public class Book {
        private Integer id;
        private String type;
        private String name;
        private String description;
        //setter...getter...toString略
    }
    
  • 步骤五:编写BookController

    //@Controller
    //@ResponseBody
    @RestController
    @RequestMapping(value = "/books",produces = "application/json;charset=utf-8")
    public class BookController {
        //新增
        @PostMapping
        public String save(@RequestBody Book book){
            System.out.println(book);
            return "{'module':'新增'}";
        }
        //查询所有
        @GetMapping
        public List<Book> getAll(){
            List<Book> list=new ArrayList<Book>();
    
            Book b1=new Book();
            b1.setId(1);
            b1.setName("Java");
            b1.setType("编程");
            b1.setDescription("Java从入门到放弃");
    
            Book b2=new Book();
            b2.setId(2);
            b2.setName("Spring");
            b2.setType("编程");
            b2.setDescription("Spring从入门到放弃");
    
            list.add(b1);
            list.add(b2);
    
            return list;
        }
    
    }
    

4.8.2、页面访问处理

  • 步骤一:添加静态页面
    在这里插入图片描述

  • 步骤二:访问pages目录下的books.html
    打开浏览器输入http://localhost/pages/books.html
    在这里插入图片描述

报错原因:
服务器提示提示信息如下:
在这里插入图片描述
SpringMVC拦截了静态资源,根据/pages/books.html去controller找对应的方法,找不到所以会报404的错误。

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

在这里插入图片描述

SpringMVC放行静态资源

SpringMVC需要将静态资源进行放行。

  • 创建SpringMvcSupport配置文件

    @Configuration
    public class SpringMvcSupport extends WebMvcConfigurationSupport {
        //设置静态资源访问过滤,当前类需要设置为配置类,并被扫描加载
        @Override
        protected void addResourceHandlers(ResourceHandlerRegistry registry) {
            System.out.println("加载SpringMvcSupport...");
            //当访问/pages/????时候,从/pages目录下查找内容
            registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
            registry.addResourceHandler("/js/**").addResourceLocations("/js/");
            registry.addResourceHandler("/css/**").addResourceLocations("/css/");
            registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/");
    
        }
    }
    
  • 该配置类是在config目录下,SpringMVC扫描的是controller包,所以该配置类还未生效,要想生效需要将SpringMvcConfig配置类进行修改。

    @Configuration
    @ComponentScan({"com.chuhe.controller","com.chuhe.config"})
    //@ComponentScan("com.chuhe")//或者增加扫描范围
    @EnableWebMvc
    public class SpringMvcConfig {
    }
    
  • 步骤三:修改books.html

    <!DOCTYPE html>
    
    <html>
        <head>
            <!-- 页面meta -->
            <meta charset="utf-8">
            <title>SpringMVC案例</title>
            <!-- 引入样式 -->
            <link rel="stylesheet" href="../plugins/elementui/index.css">
            <link rel="stylesheet" href="../plugins/font-awesome/css/font-awesome.min.css">
            <link rel="stylesheet" href="../css/style.css">
        </head>
    
        <body class="hold-transition">
    
            <div id="app">
    
                <div class="content-header">
                    <h1>图书管理</h1>
                </div>
    
                <div class="app-container">
                    <div class="box">
                        <div class="filter-container">
                            <el-input placeholder="图书名称" style="width: 200px;" class="filter-item"></el-input>
                            <el-button class="dalfBut">查询</el-button>
                            <el-button type="primary" class="butT" @click="openSave()">新建</el-button>
                        </div>
    
                        <el-table size="small" current-row-key="id" :data="dataList" stripe highlight-current-row>
                            <el-table-column type="index" align="center" label="序号"></el-table-column>
                            <el-table-column prop="type" label="图书类别" align="center"></el-table-column>
                            <el-table-column prop="name" label="图书名称" align="center"></el-table-column>
                            <el-table-column prop="description" label="描述" align="center"></el-table-column>
                            <el-table-column label="操作" align="center">
                                <template slot-scope="scope">
                                    <el-button type="primary" size="mini">编辑</el-button>
                                    <el-button size="mini" type="danger">删除</el-button>
                                </template>
                            </el-table-column>
                        </el-table>
    
                        <div class="pagination-container">
                            <el-pagination
                                class="pagiantion"
                                @current-change="handleCurrentChange"
                                :current-page="pagination.currentPage"
                                :page-size="pagination.pageSize"
                                layout="total, prev, pager, next, jumper"
                                :total="pagination.total">
                            </el-pagination>
                        </div>
    
                        <!-- 新增标签弹层 -->
                        <div class="add-form">
                            <el-dialog title="新增图书" :visible.sync="dialogFormVisible">
                                <el-form ref="dataAddForm" :model="formData" :rules="rules" label-position="right" label-width="100px">
                                    <el-row>
                                        <el-col :span="12">
                                            <el-form-item label="图书类别" prop="type">
                                                <el-input v-model="formData.type"/>
                                            </el-form-item>
                                        </el-col>
                                        <el-col :span="12">
                                            <el-form-item label="图书名称" prop="name">
                                                <el-input v-model="formData.name"/>
                                            </el-form-item>
                                        </el-col>
                                    </el-row>
                                    <el-row>
                                        <el-col :span="24">
                                            <el-form-item label="描述">
                                                <el-input v-model="formData.description" type="textarea"></el-input>
                                            </el-form-item>
                                        </el-col>
                                    </el-row>
                                </el-form>
                                <div slot="footer" class="dialog-footer">
                                    <el-button @click="dialogFormVisible = false">取消</el-button>
                                    <el-button type="primary" @click="saveBook()">确定</el-button>
                                </div>
                            </el-dialog>
                        </div>
    
                    </div>
                </div>
            </div>
        </body>
    
        <!-- 引入组件库 -->
        <script src="../js/vue.js"></script>
        <script src="../plugins/elementui/index.js"></script>
        <script type="text/javascript" src="../js/jquery.min.js"></script>
        <script src="../js/axios-0.18.0.js"></script>
    
        <script>
            var vue = new Vue({
    
                el: '#app',
    
                data:{
    				dataList: [],//当前页要展示的分页列表数据
                    formData: {},//表单数据
                    dialogFormVisible: false,//增加表单是否可见
                    dialogFormVisible4Edit:false,//编辑表单是否可见
                    pagination: {},//分页模型数据,暂时弃用
                },
    
                //钩子函数,VUE对象初始化完成后自动执行
                created() {
                    this.getAll();
                },
    
                methods: {
                    // 重置表单
                    resetForm() {
                        //清空输入框
                        this.formData = {};
                    },
    
                    // 弹出添加窗口
                    openSave() {
                        this.dialogFormVisible = true;
                        this.resetForm();
                    },
    
                    //添加
                    saveBook () {
                        axios.post("/books",this.formData).then((res)=>{
    
                        });
                    },
    
                    //主页列表查询
                    getAll() {
                        axios.get("/books").then((res)=>{
                            this.dataList = res.data;
                        });
                    },
    
                }
            })
        </script>
    </html>
    

5、SSM整合

5.1、流程分析

  • 步骤一:创建Maven工程
  • 步骤二:pom.xml添加SSM依赖坐标
  • 步骤三:编写Web项目的入口配置类,实现AbstractAnnotationConfigDispatcherServletInitalizer重写以下方法:
        ♠ getRootConfigClasses() :返回Spring的配置类->需要SpringConfig配置类
        ♠ getServletConfigClasses() :返回SpringMVC的配置类->需要SpringMvcConfig配置类
        ♠ getServletMappings() : 设置SpringMVC请求拦截路径规则
        ♠ getServletFilters() :设置过滤器,解决POST请求中文乱码问题
  • 步骤四:SSM整合
    SpringConfig
      ♠ 标识该类为配置类 @Configuration
      ♠ 扫描Service所在的包 @ComponentScan
      ♠ 在Service层要管理事务 @EnableTransactionManagement
      ♠ 读取外部的properties配置文件 @PropertySource
      ♠ 整合Mybatis需要引入Mybatis相关配置类 @Import
      ♠ 第三方数据源配置类 JdbcConfig
        ✱构建DataSource数据源,DruidDataSouroce,需要注入数据库连接四要素, @Bean @Value
        ✱构建平台事务管理器,DataSourceTransactionManager,@Bean
      ♠ Mybatis配置类 MybatisConfig。
        ✱ 构建SqlSessionFactoryBean并设置别名扫描与数据源,@Bean
        ✱构建MapperScannerConfigurer并设置DAO层的包扫描。
    SpringMvcConfig
      ♠ 标识该类为配置类 @Configuration
      ♠ 扫描Controller所在的包 @ComponentScan
      ♠ 开启SpringMVC注解支持 @EnableWebMvc
    功能模块
      ♠ 创建数据库表
      ♠ 根据数据库表创建对应的模型类
      ♠ 通过Dao层完成数据库表的增删改查(接口+自动代理)
      ♠ 编写Service层[Service接口+实现类]
        ✱@Service
        ✱@Transactional
        ✱整合Junit对业务层进行单元测试
          ★ @RunWith
          ★ @ContextConfiguration
          ★ @Test
      ♠ 编写Controller层
        ✱ 接收请求 @RequestMapping @GetMapping @PostMapping @PutMapping @DeleteMapping
        ✱接收数据 简单、POJO、嵌套POJO、集合、数组、JSON数据类型
          ★ @RequestParam
          ★ @PathVariable
          ★ @RequestBody
        ✱ 转发业务层
          ★ @Autowired
        ✱ 响应结果
          ★ @ResponseBody

5.2、整合配置

✈步骤一: 创建Maven项目

在这里插入图片描述

✈步骤二:添加依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.chuhe</groupId>
    <artifactId>springmvc_ssm</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.10.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.2.10.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.2.10.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.2.10.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.5</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>1.3.0</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.46</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.12</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.9.0</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <version>2.2</version>
                <configuration>
                    <port>80</port>
                    <path>/</path>
                    <uriEncoding>utf-8</uriEncoding>
                </configuration>
            </plugin>
        </plugins>
    </build>


</project>

✈步骤三: 创建项目包结构

  • config目录存放的是相关的配置类
  • controller编写的是Controller类
  • dao存放的是Dao接口,因为使用的是Mapper接口代理方式,所以没有实现类包
  • service存的是Service接口,impl存放的是Service实现类
  • resources:存入的是配置文件,如Jdbc.properties
  • webapp:目录可以存放静态资源
  • test/java:存放的是测试类

✈步骤四: 创建SpringConfig配置类

@Configuration//标识该类为配置类
@ComponentScan("com.chuhe.service")//扫描Service所在的包
@EnableTransactionManagement//在Service层要管理事务
@PropertySource("classpath:jdbc.properties")//读取外部的properties配置文件
@Import({JdbcConfig.class,MybatisConfig.class})//整合Mybatis需要引入Mybatis相关配置类
public class SpringConfig {
}

✈步骤五:创建JdbcConfig配置类

package com.chuhe.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;

public class JdbcConfig {
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String  username;
    @Value("${jdbc.password}")
    private String  password;
    @Bean
    public DataSource dataSource(){
        DruidDataSource dataSource=new DruidDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }
    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource){
        DataSourceTransactionManager ds=new DataSourceTransactionManager();
        ds.setDataSource(dataSource);
        return ds;
    }
}

✈步骤六:创建MybatisConfig配置类

package com.chuhe.config;

import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.context.annotation.Bean;

import javax.sql.DataSource;

public class MybatisConfig {
    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){
        SqlSessionFactoryBean factoryBean=new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource);
        factoryBean.setTypeAliasesPackage("com.chuhe.domain");
        return factoryBean;
    }

    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer(){
        MapperScannerConfigurer msc=new MapperScannerConfigurer();
        msc.setBasePackage("com.chuhe.dao");
        return msc;
    }
}

✈步骤七:创建jdbc.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ssm_db
jdbc.username=root
jdbc.password=1234

✈步骤八: 创建SpringMVC配置类

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

✈步骤九: 创建Web项目入口配置类

package com.chuhe.config;

import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

import javax.servlet.Filter;

public class ServletConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
   //加载Spring配置类
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{SpringConfig.class};
    }
    //加载SpringMVC配置类
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{SpringMvcConfig.class};
    }
    //设置SpringMVC请求地址拦截规则
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
    //设置POST请求编码
    @Override
    protected Filter[] getServletFilters() {
        CharacterEncodingFilter filter=new CharacterEncodingFilter();
        filter.setEncoding("UTF-8");
        return new Filter[]{filter};
    }
}

5.3、功能模块开发

✈步骤一:创建数据库及表

create database ssm_db character set utf8;
use ssm_db;
create table tbl_book(
  id int primary key auto_increment,
  type varchar(20),
  name varchar(50),
  description varchar(255)
);

insert  into `tbl_book`(`id`,`type`,`name`,`description`) values (1,'计算机理论','Spring实战 第五版','Spring入门经典教程,深入理解Spring原理技术内幕'),(2,'计算机理论','Spring 5核心原理与30个类手写实践','十年沉淀之作,手写Spring精华思想'),(3,'计算机理论','Spring 5设计模式','深入Spring源码刨析Spring源码中蕴含的10大设计模式'),(4,'计算机理论','Spring MVC+Mybatis开发从入门到项目实战','全方位解析面向Web应用的轻量级框架,带你成为Spring MVC开发高手'),(5,'计算机理论','轻量级Java Web企业应用实战','源码级刨析Spring框架,适合已掌握Java基础的读者'),(6,'计算机理论','Java核心技术 卷Ⅰ 基础知识(原书第11版)','Core Java第11版,Jolt大奖获奖作品,针对Java SE9、10、11全面更新'),(7,'计算机理论','深入理解Java虚拟机','5个纬度全面刨析JVM,大厂面试知识点全覆盖'),(8,'计算机理论','Java编程思想(第4版)','Java学习必读经典,殿堂级著作!赢得了全球程序员的广泛赞誉'),(9,'计算机理论','零基础学Java(全彩版)','零基础自学编程的入门图书,由浅入深,详解Java语言的编程思想和核心技术'),(10,'市场营销','直播就这么做:主播高效沟通实战指南','李子柒、李佳奇、薇娅成长为网红的秘密都在书中'),(11,'市场营销','直播销讲实战一本通','和秋叶一起学系列网络营销书籍'),(12,'市场营销','直播带货:淘宝、天猫直播从新手到高手','一本教你如何玩转直播的书,10堂课轻松实现带货月入3W+');

✈步骤二: 编写模型类

public class Book {
    private Integer id;
    private String type;
    private String name;
    private String description;
    //getter...setter...toString省略
}

✈步骤三:编写Dao接口

package com.chuhe.dao;

import com.chuhe.domain.Book;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;

import java.util.List;

public interface BookDao {
    //新增
    @Insert("Insert into tbl_book(id,type,name,description)values(null,#{type},#{name},#{description})")
    public void save(Book book);

    //修改
    @Update("update tbl_book set type=#{type},name=#{name},description=#{description} where id=#{id}}")
    public void update(Book book);

    //删除
    @Delete("delete from tbl_book where id=#{id}")
    public void delete(Integer id);

    //根据id查询
    @Select("select * from tbl_book where id=#{id}")
    public Book getById(Integer id);

    //查询全部
    @Select("select * from tbl_book")
    public List<Book> getAll();

}

✈步骤四:编写Service接口和实现类

package com.chuhe.service;

import com.chuhe.domain.Book;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
@Transactional
public interface BookService {
    /**
     * 新增
     * @param book
     * @return
     */
    public boolean save(Book book);

    /**
     * 修改
     * @param book
     * @return
     */
    public boolean update(Book book);

    /**
     * 根据id删除
     * @param id
     * @return
     */
    public boolean delete(Integer id);

    /**
     * 根据id查询
     * @param id
     * @return
     */
    public Book getById(Integer id);

    /**
     * 查询全部
     * @return
     */
    public List<Book> getAll();

}

package com.chuhe.service.impl;

import com.chuhe.dao.BookDao;
import com.chuhe.domain.Book;
import com.chuhe.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
@Service
public class BookServiceImpl implements BookService {
    @Autowired
    private BookDao bookDao;
    @Override
    public boolean save(Book book) {
        bookDao.save(book);
        return true;
    }

    @Override
    public boolean update(Book book) {
        bookDao.update(book);
        return true;
    }

    @Override
    public boolean delete(Integer id) {
        bookDao.delete(id);
        return true;
    }

    @Override
    public Book getById(Integer id) {
        return bookDao.getById(id);
    }

    @Override
    public List<Book> getAll() {
        return bookDao.getAll();
    }
}

在这里插入图片描述

说明:

  • bookDao在Service中注入的会提示一个红线提示,为什么呢?

    • BookDao是一个接口,没有实现类,接口是不能创建对象的,所以最终注入的应该是代理对象 。

    • 代理对象是由Spring的IOC容器来创建管理的 。

    • IOC容器又是在Web服务器启动的时候才会创建 。

    • IDEA在检测依赖关系的时候,没有找到适合的类注入,所以会提示错误提示 。

    • 但是程序运行的时候,代理对象就会被创建,框架会使用DI进行注入,所以程序运行无影响。

如何解决上述问题?

  • 可以不用理会,因为运行是正常的
  • 设置错误提示级别在这里插入图片描述

✈步骤五:编写Controller类

package com.chuhe.controller;

import com.chuhe.domain.Book;
import com.chuhe.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.*;
import java.util.List;

//@Configuration
//@ResponseBody
@RestController//等价于上面注释的两句
@RequestMapping("/books")

public class BookController {
    @Autowired
    private BookService bookService;
    //新增
    @PostMapping
    public boolean save(@RequestBody Book book){

        return bookService.save(book);
    }
    //修改
    @PutMapping
    public boolean update(@RequestBody Book book){
        return bookService.update(book);
    }
    //删除
    @DeleteMapping("/{id}")
    public boolean delete(@PathVariable Integer id){
        return bookService.delete(id);
    }
    //根据id查询
    @GetMapping("/{id}")
    public Book getById(@PathVariable Integer id){
       return bookService.getById(id);
    }
    //查询全部
    @GetMapping
    public List<Book> getAll(){
        System.out.println("BookController running...");
        return bookService.getAll();
    }
}

5.4、单元测试

✈步骤一:新建测试类
✈步骤二:注入Service类
✈步骤三:编写测试方法

package com.chuhe;

import com.chuhe.config.SpringConfig;
import com.chuhe.domain.Book;
import com.chuhe.service.BookService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.swing.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes= SpringConfig.class)
public class BookServiceTest {
    @Autowired
    private BookService bookService;
    @Test
    public void testGetById(){
        Book book=bookService.getById(1);
        System.out.println(book);
    }
}

6、统一结果封装

6.1、表现层与前端数据传输协议定义

在这里插入图片描述

随着业务的增长,我们需要返回的数据类型会越来越多。将返回结果的数据进行统一,具体如何来做,大体的思路为:

  • 为了封装返回的结果数据:创建结果模型类,封装数据到data属性中
  • 为了封装返回的数据是何种操作及是否操作成功:封装操作结果到code属性中
  • 操作失败后为了封装返回的错误信息:封装特殊消息到message(msg)属性中

在这里插入图片描述

可以设置统一数据返回结果类:

public class Result{
	private Object data;
	private Integer code;
	private String msg;
}

注意: Result类名及类中的字段并不是固定的,可以根据需要自行增减提供若干个构造方法,方便操作。

6.2、表现层与前端数据传输协议实现

对于结果封装,应该是在表现层进行处理,所以把结果类放在controller包,当然也可以放在domain包。
✈步骤一: 创建Result类

package com.chuhe.controller;

public class Result {
    //描述统一格式中的数据
    private Object data;
    private Integer code;
    private String msg;
    //构造方法是方便对象的创建
    public Result(){}
    //构造方法是方便对象的创建
    public Result(Integer code,Object data) {
        this.data = data;
        this.code = code;
    }
    //构造方法是方便对象的创建
    public Result(Object data, Integer code, String msg) {
        this.data = data;
        this.code = code;
        this.msg = msg;
    }

//setter...getter...省略

✈步骤二:定义返回码Code类

package com.chuhe.controller;

//定义状态码
public class Code {
    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;
}

注意: code类中的常量设计可以根据需要自行增减,例如将查询再进行细分为GET_OK,GET_ALL_OK,GET_PAGE_OK等。

✈步骤三:修改Controller类的返回值

package com.chuhe.controller;

import com.chuhe.domain.Book;
import com.chuhe.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.web.bind.annotation.*;
import java.util.List;

//@Configuration
//@ResponseBody
@RestController//等价于上面注释的两句
@RequestMapping("/books")

public class BookController {
    @Autowired
    private BookService bookService;
    //新增
    @PostMapping
    public Result save(@RequestBody Book book){
        boolean flag=bookService.save(book);
        return new Result(flag?Code.SAVE_OK:Code.SAVE_ERR,flag);
    }
    //修改
    @PutMapping
    public Result update(@RequestBody Book book){
        boolean flag=bookService.update(book);

        return new Result(flag?Code.UPDATE_OK:Code.UPDATE_ERR,flag);

    }
    //删除
    @DeleteMapping("/{id}")
    public Result delete(@PathVariable Integer id){
        boolean flag=bookService.delete(id);
        return new Result(flag?Code.DELETE_OK:Code.DELETE_ERR);
    }
    //根据id查询
    @GetMapping("/{id}")
    public Result getById(@PathVariable Integer id){
        Book book=bookService.getById(id);
        Integer code=book!=null?Code.GET_OK:Code.GET_ERR;
        String msg=book!=null?"查询成功":"数据查询失败,请重试";
        return new Result(code,book,msg);
    }
    //查询全部
    @GetMapping
    public Result getAll(){
        List<Book> list=bookService.getAll();
        String msg=list!=null?"查询成功":"数据查询失败,请重试";
        Integer code=list!=null?Code.GET_OK:Code.GET_ERR;
        return new Result(code,list,msg);

    }
}

至此,我们的返回结果就已经能以一种统一的格式返回给前端。前端根据返回的结果,先从中获取code,根据code判断,如果成功则取data属性的值,如果失败,则取msg中的值做提示。

7、统一异常处理

7.1、异常的种类及出现异常原因

  • 框架内部抛出的异常:因使用不合规导致。
  • 数据层抛出的异常:因外部服务器故障导致(例如:服务器访问超时)。
  • 业务层抛出的异常:因业务逻辑书写错误导致(例如:遍历业务书写操作,导致索引异常等)。
  • 表现层抛出的异常:因数据收集、校验等规则导致(例如:不匹配的数据类型间导致异常)。
  • 工具类抛出的异常:因工具类书写不严谨不够健壮导致(例如:必要释放的连接长期未释放等)。

✈各个层级均出现异常,异常处理代码书写在哪一层?

  • 所有的异常均抛出到表现层进行处理

✈异常的种类很多,表现层如何将所有的异常都处理到呢?

  • 异常分类

✈表现层处理异常,每个方法中单独书写,代码书写量巨大且意义不强,如何解决?

  • AOP

7.2、异常处理器的使用

7.2.1、环境准备

✈步骤一: 创建一个Web的Maven项目
✈步骤二: pom.xml添加SSM整合所需jar包
✈步骤三: 创建对应的配置类
✈步骤四: 编写Controller、Service接口、Service实现类、Dao接口和模型类
✈步骤五: resources下提供jdbc.properties配置文件

7.2.2、使用步骤

✈步骤一:创建异常处理器类
package com.chuhe.controller;


import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

//@ControllerAdvice
@RestControllerAdvice//声明该类用来做异常处理,Rest风格开发用这个
public class ProjectExceptionAdvice {
    //声明这个函数用来处理异常,括号内是要处理的异常类型
    @ExceptionHandler(Exception.class)//除了自定义的异常处理器,保留对Exception类型的异常处理,用于处理非预期的异常
    public Result doException(Exception ex){
        System.out.println("出异常了.........");
        return new Result(666,null,"程序异常");
    }
}


注意:要确保SpringMvcConfig能够扫描到异常处理器类

  • SpringMvcConfig.java下
    @ComponentScan("com.chuhe.controller")可以扫描到该类。
✈步骤二:让程序抛出异常

在这里插入图片描述

在这里插入图片描述

知识点:@RestControllerAdvice

名称@RestControllerAdvice
类型类注解
位置Rest风格开发的控制器增强类定义上方
作用为Rest风格开发的控制器类做增强

说明: 此注解自带@ResponseBody注解与@Component注解,具备对应的功能 。在这里插入图片描述

知识点:@ExceptionHandler

名称@ExceptionHandler
类型方法注解
位置专用于异常处理的控制器方法上方
作用设置指定异常的处理方案,功能等同于控制器方法,
出现异常后终止原始控制器执行,并转入当前方法执行

说明: 此类方法可以根据处理的异常不同,制作多个方法分别处理对应的异常。

7.3、项目异常处理方案

7.3.1、异常分类

异常处理器我们已经能够使用了,那么在项目中该如何来处理异常呢?
因为异常的种类有很多,如果每一个异常都对应一个@ExceptionHandler,那得写多少个方法来处理各自的异常,所以我们在处理异常之前,需要对异常进行一个分类:
✈业务异常(BusinessException)

  • 规范的用户行为产生的异常
    ✱如,用户在页面输入内容的时候未按照指定格式进行数据填写,如在年龄框输入的是字符串。

  • 不规范的用户行为操作产生的异常
    ✱如,用户故意传递错误数据
    在这里插入图片描述

✈系统异常(SystemException)

  • 项目运行过程中可预计但无法避免的异常
    ✱比如数据库或服务器宕机

✈其他异常(Exception)

  • 编程人员未预期到的异常,如:用到的文件不存在
    在这里插入图片描述

将异常分类以后,针对不同类型的异常,要提供具体的解决方案。

7.3.2、异常解决方案

✈业务异常(BusinessException)

  • 发送对应消息传递给用户,提醒规范操作
      ✱大家常见的就是提示用户名已存在或密码格式不正确等。

✈系统异常(SystemException)

  • 发送固定消息传递给用户,安抚用户
      ✱系统繁忙,请稍后再试
      ✱系统正在维护升级,请稍后再试
      ✱系统出问题,请联系系统管理员等
  • 发送特定消息给运维人员,提醒维护
      ✱可以发送短信、邮箱或者是公司内部通信软件
  • 记录日志
      ✱发消息和记录日志对用户来说是不可见的,属于后台程序

✈ 其他异常(Exception)

  • 发送固定消息传递给用户,安抚用户
  • 发送特定消息给编程人员,提醒维护(纳入预期范围内)
      ✱一般是程序没有考虑全,比如未做非空校验等
  • 记录日志

7.3.3、异常解决方案的具体实现

✈步骤一:自定义异常类
  • exception包下创建SystemException
    package com.chuhe.exception;
    //自定义系统异常处理器
    //让自定义异常类继承`RuntimeException`的好处是,后期在抛出这两个异常的时候,就不用在try...catch...或throws了
    public class SystemException extends RuntimeException{
        private Integer code;
    
        public Integer getCode() {
            return code;
        }
    
        public void setCode(Integer code) {
            this.code = code;
        }
    
        public SystemException(Integer code, String message) {
            super(message);
            this.code = code;
        }
    
        public SystemException(Integer code,String message, Throwable cause) {
            super(message, cause);
            this.code = code;
        }
    }
    
  • exception包下创建BusinessException
    package com.chuhe.exception;
    //自定义系统异常处理器
    //让自定义异常类继承`RuntimeException`的好处是,后期在抛出这两个异常的时候,就不用在try...catch...或throws了
    public class BusinessException extends RuntimeException{
        private Integer code;
    
        public Integer getCode() {
            return code;
        }
    
        public void setCode(Integer code) {
            this.code = code;
        }
    
        public BusinessException(Integer code, String message) {
            super(message);
            this.code = code;
        }
    
        public BusinessException(Integer code, String message, Throwable cause) {
            super(message, cause);
            this.code = code;
        }	
    }
    
    

说明:

  • 让自定义异常类继承RuntimeException的好处是,后期在抛出这两个异常的时候,就不用在try…catch…或throws了
  • 自定义异常类中添加code属性的原因是为了更好的区分异常是来自哪个业务的
✈步骤二:将需要处理的异常抛出

假如在BookServiceImpl 类中,getById方法中,接收到前端发过来的数据,如果数据不符合要求就可以抛异常。

package com.chuhe.service.impl;

import com.chuhe.controller.Code;
import com.chuhe.dao.BookDao;
import com.chuhe.domain.Book;
import com.chuhe.exception.BusinessException;
import com.chuhe.exception.SystemException;
import com.chuhe.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class BookServiceImpl implements BookService {
    @Autowired
    private BookDao bookDao;

    @Override
    public Book getById(Integer id) {
        //模拟业务异常,包装成自定义异常
        if (id == 1) {
            throw new BusinessException(Code.BUSINESS_ERR, "id不允许为1");
        }

        //模拟系统异常,将可能出现的异常进行包装,转换成自定义异常
        try {
            int i=1/0;
        } catch (Exception e) {
            throw new SystemException(Code.SYSTEM_TIMEOUT_ERR,"系统超时,请稍后再试");
        }
        return bookDao.getById(id);
    }

	//其它方法略... ...

}

具体的包装方式有:

  • 方式一:try{}catch(){}在catch中重新throw我们自定义异常即可。
  • 方式二:直接throw自定义异常即可

BUSINESS_ERRSYSTEM_TIMEOUT_ERR这些异常代码都是在Code类中定义好的。

✈步骤三:处理器类中处理异常
package com.chuhe.controller;

import com.chuhe.exception.BusinessException;
import com.chuhe.exception.SystemException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

//@ControllerAdvice
@RestControllerAdvice//声明该类用来做异常处理,Rest风格开发用这个
public class ProjectExceptionAdvice {
    //处理系统异常
    //声明这个函数用来处理异常,括号内是要处理的异常类型
    @ExceptionHandler(SystemException.class)//处理SystemException异常
    public Result doSystemException(SystemException ex){
        //记录日志
        //发送消息给运维
        //发送邮件给开发人员,ex对象发送给开发人员
        return new Result(ex.getCode(),null,ex.getMessage());
    }

    //处理业务异常
    @ExceptionHandler(BusinessException.class)
    public Result doBusinessException(BusinessException ex){
        return new Result(ex.getCode(),null,ex.getMessage());
    }

    //除了自定义的异常处理,保留对Exception类型的异常处理,用于处理非预期的异常
    @ExceptionHandler(Exception.class)
    public Result doException(Exception ex){
        //记录日志
        //发送消息给运维
        //发送邮件给开发人员,ex对象发送给开发人员
        return new Result(Code.SYSTEM_UNKNOW_ERR,null,"系统繁忙,请稍后再试");
    }
}

7.3.4、小结

对于异常,不管后台哪一层抛出异常,都会以我们与前端约定好的方式进行返回,前端只需要把信息获取到,根据返回的正确与否来展示不同的内容即可。

项目中的异常处理方式为:
在这里插入图片描述

8、拦截器

在这里插入图片描述

  1. 浏览器发送一个请求会先到Tomcat的web服务器。

  2. Tomcat服务器接收到请求以后,会去判断请求的是静态资源还是动态资源。

  3. 如果是静态资源,会直接到Tomcat的项目部署目录下去直接访问。

  4. 如果是动态资源,就需要交给项目的后台代码进行处理。

  5. 在找到具体的方法之前,我们可以去配置过滤器(可以配置多个),按照顺序进行执行。

  6. 然后进入到到中央处理器(SpringMVC中的内容),SpringMVC会根据配置的规则进行拦截。

  7. 如果满足规则,则进行处理,找到其对应的controller类中的方法进行执行,完成后返回结果。

  8. 如果不满足规则,则不进行处理。

  9. 这个时候,如果我们需要在每个Controller方法执行的前后添加业务,这个就是拦截器要做的事。

8.1、拦截器概念

拦截器(Interceptor):是一种动态拦截方法调用的机制,在SpringMVC中动态拦截控制器方法的执行。

作用:

  • 在指定的方法调用前后执行预先设定的代码
  • 阻止原始方法的执行
  • 总结:拦截器就是用来做增强

拦截器和过滤器之间的区别是什么?

  • 归属不同:Filter属于Servlet技术,Interceptor属于SpringMVC技术。
  • 拦截内容不同:Filter对所有访问进行增强,Interceptor仅针对SpringMVC的访问进行增强。

在这里插入图片描述

8.2、拦截器入门

8.2.1、拦截器开发

✈步骤一:创建拦截器类

让类实现HandlerInterceptor接口,重写接口中的三个方法。

package com.chuhe.controller.interceptor;

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
//定义拦截器类,实现HandlerInterceptor接口
//注意当前类必须受Spring容器控制
public class ProjectInterceptor implements HandlerInterceptor {
    //原始方法调用前执行的内容
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle...");
        return false;//false为拦截,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");
    }
}

注意:

  • 拦截器类要被SpringMVC容器扫描到。
  • preHandle方法返回true时,拦截器不拦截
  • preHandle方法返回false时,拦截器会拦截,原始方法会被拦截
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object >handler) throws Exception {
   System.out.println("preHandle...");
   return true;
}
  • preHandle 方法:该方法在控制器的处理请求方法前执行,其返回值表示是否中断后续操作,返回 true 表示继续向下执行,返回> false 表示中断后续操作。
  • postHandle 方法:该方法在控制器的处理请求方法调用之后、解析视图之前执行,可以通过此方法对请求域中的模型和视图做进一步的修改。
  • afterCompletion 方法:该方法在控制器的处理请求方法执行完成后执行,即视图渲染结束后执行,可以通过此方法实现一些资源清理、记录日志信息等工作。
✈步骤二:配置拦截器类
@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
    @Autowired
    private ProjectInterceptor projectInterceptor;
    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        //SpringMvc放行态资源
        registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
    }

    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        //配置拦截器
        //"/books/"最后面的“/”不能省略
        registry.addInterceptor(projectInterceptor).addPathPatterns("/books/");
    }
}
✈步骤三:SpringMVC添加SpringMvcSupport包扫描
@Configuration
@ComponentScan({"com.chuhe.controller","com.chuhe.config"})
@EnableWebMvc

public class SpringMvcConfig {
}
✈步骤四:运行测试

在这里插入图片描述

  • preHandle方法返回true时,拦截器不拦截

    在这里插入图片描述
  • preHandle方法返回false时,拦截器会拦截,原始方法会被拦截
    在这里插入图片描述
✈步骤五:修改拦截器拦截规则
  • 访问http://localhost/books/,拦截器生效,并可根据preHandle方法返回值决定是否拦截。
  • 访问http://localhost/books/1,发现拦截器并未触发,无论preHandle方法返回何值。
  • 原因是拦截器的addPathPatterns方法配置的拦截路径是/books/,我们现在发送的是/books/1,所以没有匹配上,因此没有拦截,拦截器就不会执行。

修改拦截器拦截规则:

@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
    @Autowired
    private ProjectInterceptor projectInterceptor;
    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        //SpringMvc放行静态资源
        registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
    }

    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        //配置拦截器
        registry.addInterceptor(projectInterceptor).addPathPatterns("/books","/books/*");
    }
}

再次访问http://localhost/books/1,已成功触发拦截器。

踩过的坑:
SpringMvcSupport 类中addInterceptors方法

@Override
protected void addInterceptors(InterceptorRegistry registry) {
   //配置拦截器
   registry.addInterceptor(projectInterceptor).addPathPatterns("/books");
}
  • 配置成/books,访问http://localhost/books/无法触发拦截器,但访问http://localhost/books可触发。
  • 如果要使http://localhost/books/http://localhost/books均可触发拦截器,可配置成addPathPatterns("/books","/books/")
  • 如要使http://localhost/books/1也可以触发拦截器,可配置成("/books","/books/*"),但是http://localhost/books/1/不会触发拦截器。解决办法:配置成("/books","/books/*","/books/*/")
  • 如果不加/books/*/,则http://localhost/books/1/可绕过拦截器,正常执行执行对应的业务处理。
✈步骤六:SpringMvcConfig中配置拦截器
@Configuration
@ComponentScan("com.chuhe.controller")//不需要再扫描"com.chuhe.config"
@EnableWebMvc

//继承WebMvcConfigurer
public class SpringMvcConfig implements WebMvcConfigurer {
    @Autowired
    private ProjectInterceptor projectInterceptor;

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        //配置SPringMvc放行静态资源
        registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //配置拦截器
        registry.addInterceptor(projectInterceptor).addPathPatterns("/books","/books/*","/books/*/");
    }

}

注意:SpringMvcConfig中配置了拦截器,就不用再写SpringMvcSupport类了。

8.3、拦截器的执行流程

在这里插入图片描述

8.4、拦截器参数

8.4.1、前置处理方法

原始方法之前运行preHandle

public boolean preHandle(HttpServletRequest request,
                         HttpServletResponse response,
                         Object handler) throws Exception {
    System.out.println("preHandle");
    return true;
}
  • request:请求对象
  • response:响应对象
  • handler:被调用的处理器对象,本质上是一个方法对象,对反射中的Method对象进行了再包装。

✈使用request对象可以获取请求数据中的内容,如获取请求头的Content-Type
✈使用handler参数,可以获取方法的相关信息。

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String host = request.getHeader("Host");//获取请求host
        System.out.println(host);
        HandlerMethod hm=(HandlerMethod)handler;
        System.out.println(hm.getMethod().getName());//获取的方法名,比如save
        return false;
    }
  • 这三个方法中,最常用的是preHandle。
  • 在这个方法中可以通过返回值来决定是否要进行放行。
  • 可以把业务逻辑放在该方法中,如果满足业务则返回true放行,不满足则返回false拦截。

8.4.2、后置处理方法

原始方法运行后运行,如果原始方法被拦截,则不执行

public void postHandle(HttpServletRequest request,
                       HttpServletResponse response,
                       Object handler,
                       ModelAndView modelAndView) throws Exception {
    System.out.println("postHandle");
}

前三个参数和上面的是一致的。

modelAndView:如果处理器执行完成具有返回结果,可以读取到对应数据与页面信息,并进行调整。现在都是返回json数据,所以该参数的使用率不高。

8.4.3、完成处理方法

拦截器最后执行的方法,无论原始方法是否执行

public void afterCompletion(HttpServletRequest request,
                            HttpServletResponse response,
                            Object handler,
                            Exception ex) throws Exception {
    System.out.println("afterCompletion");
}

前三个参数与上面的是一致的。

ex:如果处理器执行过程中出现异常对象,可以针对异常情况进行单独处理 。因为现在已经有全局异常处理器类,所以该参数的使用率也不高。

8.5、拦截器链配置

8.5.1、配置多个拦截器

✈步骤一:创建拦截器类
@Component
//定义拦截器类,实现HandlerInterceptor接口
//注意当前类必须受Spring容器控制
public class ProjectInterceptor2 implements HandlerInterceptor {
    //原始方法调用前执行的内容
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle222...");
        return true;
    }

    //原始方法调用后执行的内容
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle2...");
    }

    //原始方法调用完成后执行的内容
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion2...");
    }
}
✈步骤二:配置拦截器类
@Configuration
@ComponentScan("com.chuhe.controller")//不需要再扫描"com.chuhe.config"
@EnableWebMvc

//继承WebMvcConfigurer
public class SpringMvcConfig implements WebMvcConfigurer {
    @Autowired
    private ProjectInterceptor projectInterceptor;
    @Autowired
    private ProjectInterceptor2 projectInterceptor2;

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        //配置静态资源
        registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //配置拦截器
        registry.addInterceptor(projectInterceptor).addPathPatterns("/books","/books/*","/books/*/");
        registry.addInterceptor(projectInterceptor2).addPathPatterns("/books","/books/*","/books/*/");
    }

}

注意:

  • 拦截器执行的顺序是和配置顺序有关。
  • 当配置多个拦截器时,形成拦截器链。
  • 拦截器链的运行顺序参照拦截器添加顺序为准
  • 当拦截器中出现对原始处理器的拦截,后面的拦截器均终止运行。
  • 当拦截器运行中断,仅运行配置在前面的拦截器的afterCompletion操作。
    在这里插入图片描述

—————————————————————————
✈步骤一:
✈步骤二:
✈步骤三:
✈步骤四:
✈步骤五:
✈步骤六:

一、XXXXXXXXX

  • 步骤一:
    ✱XXXXXXXXXXX
    ✱XXXXXXXXXXX
      ★XXXXXXXXXXX
        ♠ XXXXXXXXXXXX
    —————————————————————————
  • 步骤一:
  • 步骤二:
  • 步骤三:
  • 步骤四:
  • 步骤五:
  • 步骤六:
    —————————————————————————
      全角空格
    ♠ 黑桃
    ★ 五角星
    ✱ 六芒星
    ✈ 飞机

—————————————————————————

✈步骤一:
✈步骤二:
✈步骤三:
✈步骤四:
✈步骤五:
✈步骤六:

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值