老冯课堂笔记SpringMVC

发布时间 2023-04-09 00:34:51作者: lkjlwq

1.SpringMVC简介

SpringMVC是一种基于Java实现MVC模型的轻量级Web框架,SpringMVC已经成为了目前最主流的MVC框架之一,它通过一套注解,让一个简单的Java类成为处理请求的控制器,而无需实现任何接口,同时它还支持RESTful编程风格的请求。

Spring框架是什么?

​ SpringMVC就是一个mvc的框架,基于一套注解可以让普通类变成控制器

作用是什么?

​ 简化开发,提升开发效率

2.入门案例

1.创建maven工程,安装javatoweb插件,然后转换为web模块

image-20230310092604256

image-20230310092646234

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.darksnow</groupId>
    <artifactId>SpringMVC-Demo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <dependencies>

        <!-- servlet依赖,因为springmvc底层还是使用servlet -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>

        <!-- SpringMVC的依赖 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.2.3.RELEASE</version>
        </dependency>
    </dependencies>
</project>

3.定义处理请求的功能类

package com.darksnow.controller;

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


//SpringMVC特点:可以让普通类使用@Controller注解就能马上变成一个控制器
@Controller
public class UserController {

    /**
        @RequestMapping 该注解就是用于设置方法的访问路径
        注意:如果SpringMVC的方法需要直接返回一个字符串或者一个json对象,那么都需要在方法上添加@ResponseBody注解
     */
    @ResponseBody
    @RequestMapping("/save")
    public String save(){
        System.out.println("save方法被调用了......");
        return "Hi SpringMVC~";
    }
}

注意事项:

对于SpringMVC而言,Controller方法返回值默认表示要跳转的页面,没有对应的页面就会报错,如果不想跳转页面而是响应数据,那么就需要在方法上使用@ResponseBody注解

4.编写SpringMVC配置类,加载处理请求的Bean

package com.darksnow.config;

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

/**
 * SpringMVC配置类,本质上还是一个Spring配置类
 */
@Configuration
@ComponentScan("com.darksnow.controller")
public class SpringMvcConfig {
}

5.加载SpringMVC配置,并设置SpringMVC请求拦截的路径

package com.darksnow.config;

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

/*
    tomcat在启动的时候,其实默认是会加载web.xml文件,只不过我们目前都是使用了注解开发,
    所以web.xml文件目前没有任何内容,那么我们就需要定义一个类去取代web.xml文件,
    让tomcat在启动的时候就加载该配置类

    注意:如果一个类需要去取代web.xml文件,那么该类必须要继承AbstractDispatcherServletInitializer
 */
public class WebConfig extends AbstractDispatcherServletInitializer {


    /**
     * 创建SpringMVC容器,加载SpringMVC的配置文件
     */
    @Override
    protected WebApplicationContext createServletApplicationContext() {
        //创建容器
        AnnotationConfigWebApplicationContext webApplicationContext = new AnnotationConfigWebApplicationContext();
        webApplicationContext.register(SpringMvcConfig.class);
        return webApplicationContext;
    }

    /**
     * 配置SpringMVC能够处理的路径
     */
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

    @Override
    protected WebApplicationContext createRootApplicationContext() {
        return null;
    }
}

6.运行结果

image-20230310100133183

3.入门案例工作流程分析

3.1 启动服务器初始化过程

1.tomcat服务器启动,执行自己编写的WebConfig类,初始化web容器

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

3.加载自己编写的SpringMvcConfig配置类

4.执行@ComponentSca加载对应的bean

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

6.执行WebConfig类中的getServletMappings方法,定义所有请求都通过SpringMVC

3.2 单次请求过程

1.发送请求http://localhost:8080/save

2.web容器(tomcat)发现所有请求都经过SpringMVC,讲请求交给SpringMVC处理

3.解析请求路径/save

4.由/save匹配执行对应的save()

5.执行save()

6.检测到有@ResponseBody直接将save()方法的返回值作为响应体返回给请求方

4.Controller加载控制与业务bean加载控制

例子

package com.darksnow.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;

/**
 * SpringMVC配置类,本质上还是一个Spring配置类
 */
@Configuration
//@ComponentScan("com.darksnow.controller")

/*
    excludeFilters:
        排除扫描路径中加载的bean,需要指定类别(type)与具体项(classes)
        type默认取值就是注解类型,@ComponentScan.Filter是注解内部的注解
    includeFilters:
        加载指定的bean,需要指定type,与classes
 */
@ComponentScan(value = "com.darksnow",
        excludeFilters = @ComponentScan.Filter(
            type = FilterType.ANNOTATION,
            classes = Controller.class
        )
)
public class SpringMvcConfig {
}

5.分开加载

SpringMVC配置类

package com.darksnow.config;

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

/**
 * SpringMVC配置类,本质上还是一个Spring配置类
 */
@Configuration
@ComponentScan("com.darksnow.controller")
public class SpringMvcConfig {
}

Spring配置类

package com.darksnow.config;

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

/**
 * Spring的配置类
 */
@Configuration
@ComponentScan("com.darksnow.service")
public class SpringConfig {
}

我们可以将SpringMVC与Spring分开写配置类,但却同时加载两个配置类

package com.darksnow.config;

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

/*
    tomcat在启动的时候,其实默认是会加载web.xml文件,只不过我们目前都是使用了注解开发,
    所以web.xml文件目前没有任何内容,那么我们就需要定义一个类去取代web.xml文件,
    让tomcat在启动的时候就加载该配置类

    注意:如果一个类需要去取代web.xml文件,那么该类必须要继承AbstractDispatcherServletInitializer
 */
public class WebConfig extends AbstractDispatcherServletInitializer {


    /**
     * 创建SpringMVC容器,加载SpringMVC的配置文件
     */
    @Override
    protected WebApplicationContext createServletApplicationContext() {
        //创建容器
        AnnotationConfigWebApplicationContext webApplicationContext = new AnnotationConfigWebApplicationContext();
        webApplicationContext.register(SpringMvcConfig.class);
        return webApplicationContext;
    }

    /**
     * 配置SpringMVC能够处理的路径
     */
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

    /**
     * 加载Spring的配置
     */
    @Override
    protected WebApplicationContext createRootApplicationContext() {
        AnnotationConfigWebApplicationContext webApplicationContext = new AnnotationConfigWebApplicationContext();
        webApplicationContext.register(SpringConfig.class);
        return webApplicationContext;
    }
}

service层代码

package com.darksnow.service;

public interface UserService {
    void save();
}

------------------------------------------------------
    
package com.darksnow.service.impl;

import com.darksnow.service.UserService;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService {
    @Override
    public void save() {
        System.out.println("业务侧:保存用户");
    }
}    

UserController

package com.darksnow.controller;

import com.darksnow.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;


//SpringMVC特点:可以让普通类使用@Controller注解就能马上变成一个控制器
@Controller
public class UserController {

    @Autowired
    private UserService userService;

    @ResponseBody
    @RequestMapping(value = "/save",produces = "application/json;charset=utf-8")
    public String save(){
        System.out.println("控制器:调用保存");
        userService.save();
        return "添加成功~";
    }
}

6.Servlet容器初始化的简写格式

Spring3.2开始引入一个简易的WebApplicationInitializer实现类,AbstractAnnotationConfigDispatcherServletInitializer

package com.darksnow.config;

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

/**
 * Servlet容器初始化的配置类
 * AbstractAnnotationConfigDispatcherServletInitializer 专门针对注解的配置编写的抽象类
 */
public class WebConfig extends AbstractAnnotationConfigDispatcherServletInitializer {

    /**
     * 读取Spring的配置类
     */
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[] {SpringConfig.class};
    }

    /**
     * 读取SpringMVC的配置类
     */
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[] {SpringMvcConfig.class};
    }

    /**
     * 指定拦截的访问地址
     */
    @Override
    protected String[] getServletMappings() {
        return new String[] {"/"};
    }
}

7.PostMan

7.1 PostMan介绍

PostMan是一款强大的网页调试与发送网页HTTP请求的工具,它的作用是常用于进行接口的测试。

7.2 PostMan使用

创建集合

image-20230310113452641

发送请求获取json数据

image-20230310113639412

保存当前请求

image-20230310113911769

8.请求

8.1 请求映射路径

package com.darksnow.controller;

import com.darksnow.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

/*
    @RequestMapping注解是用于设置请求路径的
    @RequestMapping注解可以用于类上与方法上

    一个Controller层的方法的路径 = http://localhost:8080/类上的路径/方法上的路径

    类上一般是否会设置访问路径?
    一般都会设置,因为代表了模块名字
 */
@Controller
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @ResponseBody
    @RequestMapping(value = "/save",produces = "application/json;charset=utf-8")
    public String save(){
        System.out.println("控制器:调用保存");
        userService.save();
        return "添加成功~";
    }
}

8.2 请求参数(GET/POST)

GET请求传递普通参数

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

image-20230313090252872

package com.darksnow.controller;

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

@Controller
public class ParamController {

    /*
        @GetMapping 这个注解专门用于get请求方式

        使用SpringMVC获取参数的时候不需要再使用getParameter方法了,以后接收
        请求参数,只需要在方法上声明形参即可。SpringMVC会自动帮你封装。

        注意:响应数据如果出现中文,需要通知浏览器使用指定编码的码表去解码。
     */
    @RequestMapping(value = "/commonParam",produces = "application/json;charset=utf-8",method = RequestMethod.GET)
    @ResponseBody
    public String commonParam(String name,int age){
        System.out.println("姓名:" + name);
        System.out.println("年龄:" + age);
        return "数据接收完毕!";
    }
}

POST请求传递普通参数

普通参数:form表单post请求传参,表单参数名与形参变量名相同,定义形参即可接收参数

image-20230313092807299

package com.darksnow.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

@Controller
public class ParamController {

    /*
        @PostMapping 这个注解专门用于post请求方式

        使用SpringMVC获取参数的时候不需要再使用getParameter方法了,以后接收
        请求参数,只需要在方法上声明形参即可。SpringMVC会自动帮你封装。

        注意:响应数据如果出现中文,需要通知浏览器使用指定编码的码表去解码。
     */
    @RequestMapping(value = "/commonParam",produces = "application/json;charset=utf-8",method = RequestMethod.POST)
    @ResponseBody
    public String commonParam(String name,int age){
        System.out.println("姓名:" + name);
        System.out.println("年龄:" + age);
        return "数据接收完毕!";
    }
}

问题:我们发现,POST请求传递的参数如果包含中文那么就会出现中文乱码问题

POST请求中文乱码处理

在加载SpringMVC配置的配置类中可以指定字符过滤器

package com.darksnow.config;

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

import javax.servlet.Filter;

/**
 * Servlet容器初始化的配置类
 * AbstractAnnotationConfigDispatcherServletInitializer 专门针对注解的配置编写的抽象类
 */
public class WebConfig extends AbstractAnnotationConfigDispatcherServletInitializer {

    /**
     * 读取Spring的配置类
     */
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[] {SpringConfig.class};
    }

    /**
     * 读取SpringMVC的配置类
     */
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[] {SpringMvcConfig.class};
    }

    /**
     * 指定拦截的访问地址
     */
    @Override
    protected String[] getServletMappings() {
        return new String[] {"/"};
    }

    /**
     * 解决全局乱码问题,需要重写getServletFilters方法,
     * SpringMVC已经帮你写好了一个全局乱码过滤器的类CharacterEncodingFilter
     */
    @Override
    protected Filter[] getServletFilters() {
        CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
        characterEncodingFilter.setEncoding("utf-8");
        return new Filter[]{characterEncodingFilter};
    }
}

8.3 五种类型参数传递

普通参数

普通参数:当请求参数名与形参变量名不同,使用@RequestParam绑定参数关系

image-20230313095607755

package com.darksnow.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

@Controller
public class ParamController {
    /*
        注意:SpringMVC默认情况只会把url请求参数名与方法形参名相同的数据进行封装,如果不同名是不能够封装的

        如果方法形参名与url请求参数名不对应,那么解决方案为:使用@RequestParam注解来解决

        注意:如果方法的形参个数与传递过来的参数个数不匹配,没有传递过来的参数会有一个默认值,null

        @RequestParam注解常用的属性:
            name:指定参数的名字
            defaultValue:指定默认值,如果浏览器不传递该参数,则使用默认值
            required:指定该参数是否必须要传递参数,如果当required=true时,且没有defaultValue时,不传递参数会报错!
     */
    @RequestMapping(value = "/diffName",produces = "application/json;charset=utf-8",method = RequestMethod.GET)
    @ResponseBody
    public String diffName(@RequestParam(name = "username") String name,@RequestParam(defaultValue = "20",required = true) Integer age) {
        System.out.println("姓名:" + name);
        System.out.println("年龄:" + age);
        return "数据接收完毕!";
    }
}

POJO类型参数

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

image-20230313103007761

package com.darksnow.pojo;

public class User {
    private  String name;
    private Integer age;

    //省略getter,setter,toString
}
package com.darksnow.controller;

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

@Controller
public class ParamController {
    /*
        如果参数的名字与对象的属性名相同,那么就可以直接使用对象去接收,属性会一一对应。
     */
    @RequestMapping(value = "/commonPojo",produces = "application/json;charset=utf-8",method = RequestMethod.GET)
    @ResponseBody
    public String commonPojo(User user){
        System.out.println("对象的数据:" + user);
        return "数据接收完毕!";
    }
}

注意事项:请求参数key的名称要和POJO中的属性名称一致,否则无法封装

嵌套POJO类型参数

POJO对象中包含POJO对象

package com.darksnow.pojo;

public class User {
    private  String name;
    private Integer age;
    private Address address;

    //省略getter,setter,toString
}
package com.darksnow.pojo;

public class Address {
    private String province;
    private String city;

    //省略getter,setter,toString
}

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

image-20230313104233844

package com.darksnow.controller;

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

@Controller
public class ParamController {
    /*
        形参类型是POJO,SpringMVC如果形参是一个实体类对象会自动把属性给封装到对象
        要求:形参名字必须与实体类的属性的名字一致,SpringMVC底层帮你们设置请求参数到对象的时候依赖的是setter方法
     */
    @RequestMapping(value = "/pojoContainPojoParam",produces = "application/json;charset=utf-8",method = RequestMethod.GET)
    @ResponseBody
    public String pojoContainPojoParam(User user) {
        System.out.println("接收的请求参数,姓名:" + user.getName() + " 年龄:" + user.getAge() + " 地址:" + user.getAddress());
        return "success";
    }
}

数组类型参数

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

image-20230313105300340

package com.darksnow.controller;

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

import java.util.Arrays;

@Controller
public class ParamController {
    @RequestMapping(value = "/arrayParam",produces = "application/json;charset=utf-8",method = RequestMethod.GET)
    @ResponseBody
    public String arrayParam(String[] likes){
        System.out.println("数组的数据:" + Arrays.toString(likes));
        return "success";
    }
}

集合类型参数

集合保存普通参数:请求参数名与形参集合对象名相同且请求参数为多个,@RequestParam绑定参数关系

image-20230313110029862

package com.darksnow.controller;

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

import java.util.Arrays;
import java.util.List;

@Controller
public class ParamController {
    /*
        传递的参数是复选框,一个名字有多个值的情况下就可以使用集合或者数组类型接收
        注意:如果你使用List集合接收请求参数,必须使用@RequestParam注解
     */
    @RequestMapping(value = "/listParam",produces = "application/json;charset=utf-8",method = RequestMethod.GET)
    @ResponseBody
    public String listParam(@RequestParam List<String> likes){
        System.out.println("兴趣爱好为:" + likes);
        return "success";
    }
}

8.4 JSON数据参数传递

1.JSON数据参数介绍

  • json普通数组:["","","",...]
  • json对象:
  • json对象数组:[{key:value,...},{key:value,...}]

2.传递json普通数组

添加json数据转换相关坐标

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

设置发送json数据(请求body中添加json数据)

image-20230313113457004

开启自动转换json数据的支持

package com.darksnow.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

/**
 * SpringMVC配置类,本质上还是一个Spring配置类
 */
@Configuration
@ComponentScan("com.darksnow.controller")
@EnableWebMvc  //开启json数据类型进行自动转换
public class SpringMvcConfig {
}

注意事项:

@EnableWebMvc注解功能强大,该注解整合了多个功能,此处仅使用其中一部分功能,即json数据类型进行自动转换

在Controller中编写方法接收json参数

package com.darksnow.controller;

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

import java.util.Arrays;
import java.util.List;

@Controller
public class ParamController {
    /*
        请求参数是json数组
        注意:如果请求参数是json格式,需要在请求参数的前面添加@RequestBody
     */
    @RequestMapping(value = "/listParamForJson",produces = "application/json;charset=utf-8",method = RequestMethod.GET)
    @ResponseBody
    public String listParamForJson(@RequestBody String[] likes){
        System.out.println("数组的数据:" + Arrays.toString(likes));
        return "success";
    }
}

3.@RequestBody注解介绍

  • 名称:@RequestBody
  • 类型:形参注解
  • 位置:SpringMVC控制器方法形参定义的前面
  • 作用:将请求中请求体所包含的数据传递给请求参数,此注解一个处理器方法只能使用一次

4.传递json对象

  • pojo参数:json数据与形参对象属性名相同,定义pojo类型形参即可接收参数

image-20230313114856617

package com.darksnow.controller;

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

import java.util.Arrays;
import java.util.List;

@Controller
public class ParamController {
    @RequestMapping(value = "/pojoParamForJson",produces = "application/json;charset=utf-8",method = RequestMethod.GET)
    @ResponseBody
    public String pojoParamForJson(@RequestBody User user){
        System.out.println("json对象的数据:" + user);
        return "success";
    }
}

5.传递json对象数组

package com.darksnow.controller;

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

import java.util.Arrays;
import java.util.List;

@Controller
public class ParamController {
    @RequestMapping(value = "/pojoObjArrForJson",produces = "application/json;charset=utf-8",method = RequestMethod.GET)
    @ResponseBody
    public String pojoObjArrForJson(@RequestBody User[] users){
        System.out.println("json对象数组的数据:" + Arrays.toString(users));
        return "success";
    }
}

image-20230314143319780

8.5 日期类型参数传递

image-20230314144627403

package com.darksnow.controller;

import com.darksnow.pojo.User;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import java.util.Arrays;
import java.util.Date;
import java.util.List;

@Controller
public class ParamController {
    /**
     *  SpringMVC默认请求是可以将字符串转换为Date类型数据,但是默认只会把
     *  yyyy/MM/dd 这种格式的字符串进行类型转换
     *
     *  因为前端传递字符串日期的时候,格式不是一定的,那么我们可以通过下面的注解来解决格式不一致问题
     *  @DateTimeFormat 该注解可以设置日期类型的格式,默认是yyyy/MM/dd
     *
     */
    @RequestMapping(value = "/dateParam",produces = "application/json;charset=utf-8",method = RequestMethod.GET)
    @ResponseBody
    public String dateParam(Date date,@DateTimeFormat(pattern = "yyyy-MM-dd") Date date1){
        System.out.println("小雅的生日:" + date);
        System.out.println("小兮的生日:" + date1);
        return "success";
    }
}

@DateTimeFormat注解介绍

  • 类型:形参注解
  • 位置:SpringMVC控制器方法形参前面
  • 作用:设置日期时间型数据格式
  • 属性:pattern,指定日期时间格式字符串

注意事项

传递日期类型参数必须在配置类上使用@EnableWebMvc注解。其功能之一:根据类型匹配对应的类型转换器

9.响应

9.1 响应页面

package com.darksnow.controller;

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

@Controller
public class ResponseController {
    /*
        如果一个方法不添加@ResponseBody注解,那么该方法的返回值就是一个跳转页面名称。
     */
    @RequestMapping("/jsp")
    public String toJsp() {
        return "Hello.jsp";
    }
}
<%--
 	该页面在webapp这个目录下,名为Hello.jsp
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <h1>Hi,JSP页面.......</h1>
</body>
</html>

9.2 文本数据

package com.darksnow.controller;

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

@Controller
public class ResponseController {
    @RequestMapping(value = "/text",produces = "application/json;charset=utf-8")
    @ResponseBody
    public String responseText() {
        return "呵呵~";
    }
}

9.3 json数据

package com.darksnow.controller;

import com.darksnow.pojo.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class ResponseController {
    /*
        如果你需要返回一个json数据,那么直接返回一个java对象即可
        SpringMVC会自动帮你把java对象转为json
     */
    @RequestMapping("/json")
    @ResponseBody
    public User toJson() {
        User user = new User();
        user.setName("小雅");
        user.setAge(18);
        return user;
    }
}

注意:需要添加jackson-databind依赖以及在SpringMvcConfig配置类上添加@EnableWebMvc注解

10.RESTFul风格

10.1 RESTFul介绍

作用:简化url的书写格式,同一个url处理不同的请求

  • REST(Representational State Transfer),表现形式状态转换
  • 优点:
    • 隐藏资源的访问行为,无法通过地址得知对资源是何种操作
    • 简化书写

既然它描述的是个设计风格,那什么样的接口规范才算符合它的风格呢?就需要看Restful其中的两个特点:

​ 1.每一个URI代表1种资源:http://localhost/user

​ 2.客户端使用GET,POST,PUT,DELETE四个表示操作方式的动词对服务器资源进行操作

​ (1)GET用来获取资源

​ (2)POST用来新建资源

​ (3)PUT用来更新资源

​ (4)DELETE用来删除资源

10.2 快速入门

package com.darksnow.controller;

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

@Controller
@RequestMapping(produces = "application/json;charset=utf-8")
public class UserController {

    /*
        添加请求方式:post
        用method = RequestMethod.POST来限制请求方式

        你可以不加@RequestBody,但是在postman得选择Body里面的x-www-form-urlencoded
        如果加了这个注解,你要选择raw,数据要为json的形式
     */
    @RequestMapping(value = "/user",method = RequestMethod.POST)
    @ResponseBody
    public String add(@RequestBody User user){
        System.out.println("添加操作..." + user);
        return "添加";
    }

    /*
        修改请求方式为:PUT
        在postman测试的时候也得用PUT
     */
    @RequestMapping(value = "/user",method = RequestMethod.PUT)
    @ResponseBody
    public String update(@RequestBody User user){
        System.out.println("更新操作..." + user);
        return "更新";
    }

    /*
        删除请求方式为:DELETE
        postman测试的时候,用DELETE方式,地址为:http://localhost:8080/user/1
        {id} 参数占位符
        @PathVariable:该注解的作用就是获取访问路径上的数据给到变量,占位符的名字与变量名一致,则注解可以省略名字不写
     */
    @RequestMapping(value = "/user/{id}",method = RequestMethod.DELETE)
    @ResponseBody
    public String delete(@PathVariable("id") Integer id){
        System.out.println("删除操作..." + id);
        return "删除";
    }

    /*
        查询的请求方式:get
        postman测试的时候,用GET方式,地址为:http://localhost:8080/user/1
     */
    @RequestMapping(value = "/user/{id}",method = RequestMethod.GET)
    @ResponseBody
    public String findById(@PathVariable("id") Integer id){
        System.out.println("查询操作..." + id);
        return "根据一个id查询";
    }

    /*
        查询的请求方式:get
        postman测试的时候,用GET方式,地址为:http://localhost:8080/user
     */
    @RequestMapping(value = "/user",method = RequestMethod.GET)
    @ResponseBody
    public String findAll(){
        System.out.println("查询全部...");
        return "查询全部";
    }
}

10.3 @PathVariable介绍

  • 类型:形参注解
  • 位置:SpringMVC控制器方法形参定义前面
  • 作用:绑定路径参数与处理器方法形参间的关系,要求路径参数名与形参名一一对应

10.4 @RequestBody,@RequestParam,@PathVariable区别和应用

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

10.5 快速入门中的问题

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

​ 解决:在Controller类上使用@RequestMapping定义共同的访问路径

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

​ 解决:用具体的Mapping来替代RequestMapping

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

​ 解决:在类上使用@RestController,因为它是一个组合注解,@RestController = @ResponseBody+@Controller

package com.darksnow.controller;

import com.darksnow.pojo.User;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping(value = "/user",produces = "application/json;charset=utf-8")
public class UserController {

    /*
        添加请求方式:post
        用method = RequestMethod.POST来限制请求方式

        你可以不加@RequestBody,但是在postman得选择Body里面的x-www-form-urlencoded
        如果加了这个注解,你要选择raw,数据要为json的形式
     */
    @PostMapping
    public String add(@RequestBody User user){
        System.out.println("添加操作..." + user);
        return "添加";
    }

    /*
        修改请求方式为:PUT
        在postman测试的时候也得用PUT
     */
    @PutMapping
    public String update(@RequestBody User user){
        System.out.println("更新操作..." + user);
        return "更新";
    }

    /*
        删除请求方式为:DELETE
        postman测试的时候,用DELETE方式,地址为:http://localhost:8080/user/1
        {id} 参数占位符
        @PathVariable:该注解的作用就是获取访问路径上的数据给到变量,占位符的名字与变量名一致,则注解可以省略名字不写
     */
    @DeleteMapping("/{id}")
    public String delete(@PathVariable("id") Integer id){
        System.out.println("删除操作..." + id);
        return "删除";
    }

    /*
        查询的请求方式:get
        postman测试的时候,用GET方式,地址为:http://localhost:8080/user/1
     */
    @GetMapping("/{id}")
    public String findById(@PathVariable("id") Integer id){
        System.out.println("查询操作..." + id);
        return "根据一个id查询";
    }

    /*
        查询的请求方式:get
        postman测试的时候,用GET方式,地址为:http://localhost:8080/user
     */
    @GetMapping
    public String findAll(){
        System.out.println("查询全部...");
        return "查询全部";
    }
}

11.RESTFul案例

实体类

package com.darksnow.pojo;

public class Book {
    private Integer id;
    private String type;
    private String name;
    private String description;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    @Override
    public String toString() {
        return "Book{" +
                "id=" + id +
                ", type='" + type + '\'' +
                ", name='" + name + '\'' +
                ", description='" + description + '\'' +
                '}';
    }
}

Controller层代码

package com.darksnow.controller;

import com.darksnow.pojo.Book;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.List;

@RestController
@RequestMapping("/books")
public class BookController {

    @PostMapping
    public String save(@RequestBody Book book){
        System.out.println("save book is ---> " + book);
        return "success";
    }

    @GetMapping
    public List<Book> getAll() {
        List<Book> bookList = new ArrayList<>();

        Book book1 = new Book();
        book1.setId(1);
        book1.setType("计算机");
        book1.setName("疯狂Java讲义");
        book1.setDescription("很好的一本Java入门书籍~");
        bookList.add(book1);

        Book book2 = new Book();
        book2.setId(2);
        book2.setType("计算机");
        book2.setName("Java编程思想");
        book2.setDescription("非常推荐的一本好书~");
        bookList.add(book2);

        Book book3 = new Book();
        book3.setId(3);
        book3.setType("生活");
        book3.setName("小雅的故事");
        book3.setDescription("小雅的爱情故事~");
        bookList.add(book3);

        return bookList;
    }
}

复制页面到webapp目录下

image-20230315085904251

对静态资源放行

编写类继承于WebMvcConfigurationSupport,重写addResourceHandlers方法,在类上加上@Configuration注解

package com.darksnow.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {

    //设置静态资源访问过滤的,当前类需要设置为配置类,并被扫描加载
    //要注意这个类需要被@ComponentScan扫描到
    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        //当访问/pages/xxx的时候,会从/pages目录下去查找资源
        registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
        registry.addResourceHandler("/js/**") .addResourceLocations("/js/");
        registry.addResourceHandler("/css/**").addResourceLocations("/css/");
        registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/");
    }
}

12.SSM框架整合(纯注解)

12.1 jdbc.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///darksnow?CharacterEncoding=utf-8
jdbc.username=root
jdbc.password=root

12.2 配置类

package com.darksnow.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Controller;
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;
    }

    /*
        事务管理器的顶层接口:PlatformTransactionManager
     */
    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dataSource);
        return dataSourceTransactionManager;
    }
}
package com.darksnow.config;

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

import javax.sql.DataSource;


public class MyBatisConfig {

    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        //扫描实体类,让实体类可以使用别名
        sqlSessionFactoryBean.setTypeAliasesPackage("com.darksnow.entity");
        sqlSessionFactoryBean.setDataSource(dataSource);

        return sqlSessionFactoryBean;
    }

    /**
     * 生成代理对象过程:
     *      mybatis会使用MapperScannerConfigurer去扫描dao包,每扫到一个接口,就会使用sqlSessionFactoryBean
     *      去生成一个接口的代理对象,代理对象会存储到Spring容器中
     */
    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer() {
        MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
        //扫描dao
        mapperScannerConfigurer.setBasePackage("com.darksnow.dao");
        return mapperScannerConfigurer;
    }
}
package com.darksnow.config;

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

@Configuration
@ComponentScan(basePackages = "com.darksnow.service")
@PropertySource("classpath:jdbc.properties")
@Import(value = {MyBatisConfig.class,JdbcConfig.class})
public class SpringConfig {
}
package com.darksnow.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

@Configuration
@ComponentScan("com.darksnow.controller")
@EnableWebMvc
public class SpringMvcConfig {
}
package com.darksnow.config;

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

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[]{"/"};
    }
}

12.3 实体类

package com.darksnow.entity;

public class Book {
    private Integer id;
    private String type;
    private String name;
    private String description;

    //省略getter,setter,toString
}
package com.darksnow.entity;

/**
 * 状态类,里面全是常量
 * 当然你可以考虑用枚举的形式来完成
 */
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;
}
package com.darksnow.entity;


public class Result {

    private int status;
    private Object obj;
    private String msg;

    public Result(int status, Object obj, String msg) {
        this.status = status;
        this.obj = obj;
        this.msg = msg;
    }

    public Result(int status, String msg) {
        this.status = status;
        this.msg = msg;
    }

   //省略getter,setter,toString 
}

12.4 dao层代码

package com.darksnow.dao;

import com.darksnow.entity.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 tb_book values(null,#{type},#{name},#{description})")
    int save(Book book);

    @Delete("delete from tb_book where id = #{id}")
    int delete(Integer id);

    @Update("update tb_book set type=#{type},name=#{name},description=#{description} where id = #{id}")
    int update(Book book);

    @Select("select * from tb_book")
    List<Book> findAll();
}

12.5 service层代码

package com.darksnow.service;

import com.darksnow.entity.Book;

import java.util.List;

public interface BookService {

    boolean save(Book book);

    boolean delete(Integer id);

    boolean update(Book book);

    List<Book> findAll();
}
package com.darksnow.service.impl;

import com.darksnow.dao.BookDao;
import com.darksnow.entity.Book;
import com.darksnow.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) {
        return bookDao.save(book) > 0;
    }

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

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

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

12.6 controller层代码

package com.darksnow.controller;

import com.darksnow.entity.Book;
import com.darksnow.entity.Code;
import com.darksnow.service.BookService;
import com.darksnow.entity.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * 下面的方法统一返回值结果是为什么?
 * 前后端分离开发的情况下,前端人员不清楚后端人员controller的返回值是什么,会给前端人员造成困扰
 * 所以我们就统一返回结果。让前后端开发人员具有默契。
 */
@RestController
@RequestMapping("/books")
public class BookController {

    @Autowired
    private BookService bookService;

    @PostMapping
    public Result save(@RequestBody Book book) {
        return bookService.save(book) ? new Result(Code.SAVE_OK,"添加成功") : new Result(Code.SAVE_ERR,"添加失败");
    }

    @DeleteMapping("/{id}")
    public Result delete(@PathVariable("id") Integer id) {
        return bookService.delete(id) ? new Result(Code.DELETE_OK,"删除成功") : new Result(Code.DELETE_ERR,"删除失败");
    }

    @PutMapping
    public Result update(@RequestBody Book book) {
        return bookService.update(book) ? new Result(Code.UPDATE_OK,"修改成功") : new Result(Code.UPDATE_ERR,"修改失败");
    }

    @GetMapping
    public Result findAll() {
        List<Book> bookList = bookService.findAll();
        if (bookList != null && bookList.size() > 0) {
            return new Result(Code.GET_OK,bookList,"查询成功");
        }else {
            return new Result(Code.GET_ERR,bookList,"查询失败");
        }
    }
}

12.7 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>com.darksnow</groupId>  
  <artifactId>SSM-Demo</artifactId>  
  <version>1.0-SNAPSHOT</version>  
  <packaging>war</packaging>
  <properties> 
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>  
    <maven.compiler.source>1.8</maven.compiler.source>  
    <maven.compiler.target>1.8</maven.compiler.target> 
  </properties>  
  <dependencies> 
    <!-- SpringMVC的依赖 -->  
    <dependency> 
      <groupId>org.springframework</groupId>  
      <artifactId>spring-webmvc</artifactId>  
      <version>5.2.3.RELEASE</version> 
    </dependency>  
    <dependency> 
      <groupId>org.springframework</groupId>  
      <artifactId>spring-jdbc</artifactId>  
      <version>5.2.3.RELEASE</version> 
    </dependency>  
    <dependency> 
      <groupId>org.springframework</groupId>  
      <artifactId>spring-test</artifactId>  
      <version>5.2.3.RELEASE</version> 
    </dependency>  
    <dependency> 
      <groupId>org.mybatis</groupId>  
      <artifactId>mybatis</artifactId>  
      <version>3.5.0</version> 
    </dependency>  
    <!--    spring整合mybatis依赖    -->  
    <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.18</version> 
    </dependency>  
    <dependency> 
      <groupId>junit</groupId>  
      <artifactId>junit</artifactId>  
      <version>4.12</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.8</version> 
    </dependency>  
    <!--  druid连接池      -->  
    <dependency> 
      <groupId>com.alibaba</groupId>  
      <artifactId>druid</artifactId>  
      <version>1.1.16</version> 
    </dependency> 
  </dependencies> 
</project>

13.异常处理

问题:项目各个层级均可能出现异常,异常处理代码写在哪一层?

程序开发过程中不可避免的会遇到异常现象,我们不能够让用户看到这样的页面数据

image-20230316152122699

13.1 编写异常处理器

异常处理器的底层原理,就是AOP

@RestControllerAdvice是对Controller进行增强的,可以全局捕获SpringMVC抛出的异常,并匹配相应的@ExceptionHandler中指定的异常类型,重新封装异常信息,将统一格式返回给前端。

这个类所在的包要能够被SpringMVC扫描到

package com.darksnow.controller;

import com.darksnow.entity.Result;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;


/**
 * 统一异常处理器:
 *      作用:一旦发现controller抛出异常,就会去处理
 */
//@ControllerAdvice //异常处理,该注解自带@Component注解
@RestControllerAdvice //异常处理,返回值默认是json,自带@ResponseBody和@Component注解
public class ProjectExceptionHandler {

    @ExceptionHandler(Exception.class) //处理的是哪种类型的异常
    public Result handlerException(Exception e) {
        e.printStackTrace();
        return new Result(4444,"您的网络异常,请稍后!");
    }
}

image-20230316153615746

13.2 项目异常处理方案

问题:请说出项目当前异常的分类以及对应类型异常该如何处理?

项目异常分类

  • 业务异常(BusinessException)

    • 规范的用户行为产生的异常
    • 不规范的用户行为操作产生的异常
  • 系统异常(SystemException)

    • 项目运行过程中可预计且无法避免的异常
  • 其他异常(Exception)

    • 编程人员未预期到的异常

项目异常处理方案

  • 业务异常(BusinessException)
    • 发送对应消息传递给用户,提醒规范操作
  • 系统异常(SystemException)
    • 发送固定消息传递给用户,安抚用户
    • 发送特定消息给运维人员,提醒维护
    • 记录日志
  • 其他异常(Exception)
    • 发送固定消息传递给用户,安抚用户
    • 发送特定消息给编程人员,提醒维护
    • 记录日志

13.3 项目异常处理代码实现

自定义系统异常

package com.darksnow.exceptions;

/**
 * 自定义系统异常
 */
public class SystemException extends RuntimeException{

    private Integer code; //表示是什么类型的异常

    public SystemException(Integer code) {
        this.code = code;
    }

    public SystemException(Integer code,String message) {
        super(message);
        this.code = code;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }
}

自定义业务异常

package com.darksnow.exceptions;

/**
 * 自定义业务异常
 */
public class BusinessException extends RuntimeException{
    private Integer code; //表示是什么类型的异常

    public BusinessException(Integer code) {
        this.code = code;
    }

    public BusinessException(Integer code, String message) {
        super(message);
        this.code = code;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }
}

自定义异常编码

package com.darksnow.entity;

/**
 * 状态类,里面全是常量
 * 当然你可以考虑用枚举的形式来完成
 */
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;

    //异常的状态码(新增)
    //接口中的变量默认被public static final修饰,所以它们都是全局常量
    public static final Integer SYSTEM_ERR = 50001;
    public static final Integer SYSTEM_TIMEOUT_ERR = 50002;
    public static final Integer SYSTEM_UNKOWN_ERR = 59999;
    public static final Integer BUSINESS_ERR = 60002;
}

出发自定义异常

package com.darksnow.controller;

import com.darksnow.entity.Book;
import com.darksnow.entity.Code;
import com.darksnow.exceptions.BusinessException;
import com.darksnow.service.BookService;
import com.darksnow.entity.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * 下面的方法统一返回值结果是为什么?
 * 前后端分离开发的情况下,前端人员不清楚后端人员controller的返回值是什么,会给前端人员造成困扰
 * 所以我们就统一返回结果。让前后端开发人员具有默契。
 */
@RestController
@RequestMapping("/books")
public class BookController {

    @Autowired
    private BookService bookService;

    @DeleteMapping("/{id}")
    public Result delete(@PathVariable("id") Integer id) {
        if (id < 0) {
            throw new BusinessException(Code.BUSINESS_ERR,"数据有误!!!");
        }
        return bookService.delete(id) ? new Result(Code.DELETE_OK,"删除成功") : new Result(Code.DELETE_ERR,"删除失败");
    }

}

在异常通知类中拦截并处理异常

package com.darksnow.controller;

import com.darksnow.entity.Result;
import com.darksnow.exceptions.BusinessException;
import com.darksnow.exceptions.SystemException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;


/**
 * 统一异常处理器:
 *      作用:一旦发现controller抛出异常,就会去处理
 */
//@ControllerAdvice //异常处理,该注解自带@Component注解
@RestControllerAdvice //异常处理,返回值默认是json,自带@ResponseBody和@Component注解
public class ProjectExceptionHandler {

    @ExceptionHandler(SystemException.class) //处理的是哪种类型的异常
    public Result handlerSystemException(SystemException e) {
        e.printStackTrace();
        return new Result(e.getCode(), e.getMessage());
    }

    @ExceptionHandler(BusinessException.class) //处理的是哪种类型的异常
    public Result handlerBusinessException(BusinessException e) {
        e.printStackTrace();
        return new Result(e.getCode(), e.getMessage());
    }

    @ExceptionHandler(Exception.class) //处理的是哪种类型的异常
    public Result handlerException(Exception e) {
        e.printStackTrace();
        return new Result(4444,"您的网络异常,请稍后!");
    }
}

image-20230316164608527

14.案例开发

14.1 准备工作

将页面导入到webapp下

package com.darksnow.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {

    //设置静态资源访问过滤的,当前类需要设置为配置类,并被扫描加载
    //要注意这个类需要被@ComponentScan扫描到
    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        //当访问/pages/xxx的时候,会从/pages目录下去查找资源
        registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
        registry.addResourceHandler("/js/**") .addResourceLocations("/js/");
        registry.addResourceHandler("/css/**").addResourceLocations("/css/");
        registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/");
    }
}
package com.darksnow.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

@Configuration
@ComponentScan(basePackages = {"com.darksnow.controller","com.darksnow.config"})
@EnableWebMvc
public class SpringMvcConfig {
}

14.2 查询,删除,新建,编辑功能

<!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" @click="handlerUpdate(scope.row)">编辑</el-button>
                                <el-button size="mini" type="danger" @click="handlerDelete(scope.row)">删除</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="dialogFormVisible4Edit">
                            <el-form ref="dataEditForm" :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="dialogFormVisible4Edit = false">取消</el-button>
                                <el-button type="primary" @click="handlerEdit()">确定</el-button>
                            </div>
                        </el-dialog>
                    </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)=>{
                        if (res.data.status == 20011) {
                            //成功
                            this.$message.success(res.data.msg);
                            //关闭窗口
                            this.dialogFormVisible = false;
                            //重新查询全部数据
                            this.getAll();
                        }else {
                            //失败
                            this.$message.error(res.data.msg);
                        }
                    });
                },

                //主页列表查询
                getAll() {
                    axios.get("/books").then((res)=>{
                        this.dataList = res.data.obj;
                    });
                },

                handlerDelete(row) {
                    axios.delete("/books/"+row.id).then((res) => {
                     var result = res.data;
                     if(result.status == 20021) {
                         this.$message.success(result.msg);
                         //重新查询全部数据
                         this.getAll();
                     }else if(result.status == 20020) {
                         //失败
                         this.$message.error(result.msg);
                     }else {
                         //异常
                         this.$message.error(result.msg);
                     }
                    });
                },

                handlerUpdate(row) {
                    //显示编辑的弹出层
                    this.dialogFormVisible4Edit = true;
                    //数据回显
                    //this.formData = row;

                    axios.get("/books/" + row.id).then(res => {
                        this.formData = res.data.obj;
                    })
                },

                handlerEdit() {
                    axios.put("/books",this.formData).then(res => {
                        var result = res.data;
                        if (result.status == 20031) {
                            //关闭窗口
                            this.dialogFormVisible4Edit = false;
                            //成功提示信息
                            this.$message.success(result.msg);
                            //重新查询全部
                            this.getAll();
                        }else {
                            //失败提示
                            this.$message.error(result.msg);
                        }
                    })
                }
            }
        })
    </script>
</html>

15.拦截器

15.1 拦截器简介

拦截器概念与作用

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

  • 作用:

    • 在指定的方法调用前后执行预先设定的代码
    • 阻止原方法的执行
    • 总结一下:增强
  • 核心原理:AOP思想

拦截器与过滤器的区别

  • 归属不同:Filter属于Servlet技术,Interceptor属于SpringMVC技术
  • 拦截内容不同:Filter对所有访问(静态资源与动态资源的访问)进行拦截,Interceptor仅针对SpringMVC(只会拦截Controller的请求,对静态资源是不拦截的)的访问进行增强。

15.2 入门案例

自定义拦截器

package com.darksnow.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  //注意这个类所在的包要被扫描到,否则该类对象无法被容器管理
public class DemoInterceptor implements HandlerInterceptor {
    /**
     * 执行目标方法之前执行的方法,方法的返回值代表是否放行目标方法
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle执行了[前置通知]");
        return true;
    }

    /**
     * 目标方法执行完毕之后,就会执行该方法
     * 该方法执行的前提
     *      1.preHandle方法必须要放行
     *      2.目标方法不要出现异常
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle......");
    }

    /**
        最终执行方法,方法执行前提:
            1.preHandle方法先放行
            2.目标方法出现异常,这个方法是可以执行的
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("最终通知......afterCompletion");
    }
}

配置拦截器的拦截路径

方法1:

package com.darksnow.config;

import com.darksnow.interceptor.DemoInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

/**
    该类存在的作用:对静态资源进行放行,放行给tomcat去处理
 */
@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {

    @Autowired
    private DemoInterceptor demoInterceptor;

    //设置静态资源访问过滤的,当前类需要设置为配置类,并被扫描加载
    //要注意这个类需要被@ComponentScan扫描到
    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        //当访问/pages/xxx的时候,会从/pages目录下去查找资源
        registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
        registry.addResourceHandler("/js/**") .addResourceLocations("/js/");
        registry.addResourceHandler("/css/**").addResourceLocations("/css/");
        registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/");
    }

    /**
     * 给拦截器定义拦截路径
     */
    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(demoInterceptor).addPathPatterns("/books","/books/","/books/*");
    }
}

方式2:

注意:与方式1两者只能选择一种,不然会有冲突,如果方式1起作用会导致方式2的拦截器不起作用。

package com.darksnow.config;

import com.darksnow.interceptor.DemoInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
@ComponentScan(basePackages = {"com.darksnow.controller","com.darksnow.config","com.darksnow.interceptor"})
@EnableWebMvc
public class SpringMvcConfig implements WebMvcConfigurer {

    @Autowired
    private DemoInterceptor demoInterceptor;

    /**
     * 静态资源放行
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        //当访问/pages/xxx的时候,会从/pages目录下去查找资源
        registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
        registry.addResourceHandler("/js/**") .addResourceLocations("/js/");
        registry.addResourceHandler("/css/**").addResourceLocations("/css/");
        registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/");
    }

    /**
     * 给拦截器定义拦截路径
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        System.out.println("拦截器:" + demoInterceptor);
        registry.addInterceptor(demoInterceptor).addPathPatterns("/books","/books/","/books/*");
    }
}

15.3 拦截器流程分析

image-20230317174213253

15.4 拦截器参数

问题:postHandle()和afterCompletion()方法都是处理器方法执行之后执行,有什么区别?

package com.darksnow.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
public class DemoInterceptor implements HandlerInterceptor {

    /**
     * 执行目标方法之前执行的方法,方法的返回值代表是否放行目标方法,true为放行,false为被拦截的处理器将不执行
     * @param request       请求对象
     * @param response      响应对象
     * @param handler       被调用的处理器对象,本质上是一个方法对象,对反射技术中的Method对象进行了包装
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle执行了[前置通知]");
        return false;
    }

    /**
     * 目标方法执行完毕之后,就会执行该方法
     * 该方法执行的前提
     *      1.preHandle方法必须要放行
     *      2.目标方法不要出现异常
     * @param request       请求对象
     * @param response      响应对象
     * @param handler       被调用的处理器对象,本质上是一个方法对象,对反射技术中的Method对象进行了包装
     * @param modelAndView  如果处理器执行完成具有返回结果,可以读取到对应数据与页面信息,并进行跳转
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle......");
    }

    /**
     * 最终执行方法,方法执行前提:
     *      1.preHandle方法先放行
     *      2.目标方法出现异常,这个方法是可以执行的
     *  Exception ex参数:如果处理器执行过程中出现异常对象,可以针对异常情况进行单独处理
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("最终通知......afterCompletion");
    }
}

15.5 拦截器链配置

15.6 拦截器总结

16.SSM框架整合(xml配置)

17.文件上传