SpringMVC

目录

一:SpringMVC简介和体验

1.1:介绍

1.2:主要作用

1.3:核心组件和调用流程理解

1.4:工作流程

1.5:快速体验

二:SpringMVC接收数据

2.1:访问路径设置

2.2:接收参数

        2.2.1:param和json参数比较

        2.2.2:param参数接受

        2.2.3:路径参数接收

        2.2.4:json参数接收

2.3:接收Cookie数据

2.4:接收请求头数据

2.5:原生API对象操作

2.6:共享域对象操作

        2.6.1:属性(共享)域作用回顾

        2.6.2:Request级别属性(共享)域

        2.6.3:Session级别属性(共享)域

        2.6.4:Application级别属性(共享)域 

三:SpringMVC响应数据

3.1:handler方法分析

3.2:页面跳转控制

        3.2.1:快速返回模版视图

        3.2.2:转发和重定向 

3.3:返回JSON数据

        3.3.1:前置准备

        3.3.2:@ResponseBody

        3.3.3:@RestController

3.4:返回静态资源处理

四:RESTFul风格设计和实战

4.1:RESTFul风格概述

        4.1.1:RESTFul风格简介

        4.1.2:RESTFul风格特点

        4.1.3:RESTFul风格设计规范

        4.1.4:RESTFul风格好处

4.2:RESTFul风格实战

        4.2.1:需求分析

        4.2.2:RESTFul风格接口设计

        4.2.3:后台接口实现

五:SpringMVC其它扩展

5.1:全局异常处理机制

        5.1.1:异常处理的两种方式

        5.1.2:基于注解异常声明和异常处理

5.2:拦截器使用

        5.2.1:拦截器概念

        5.2.2:拦截器使用

5.3:参数检验  

六:SpringMVC总结


一:SpringMVC简介和体验


1.1:介绍

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

        在控制层框架历经Strust、WebWork、Strust2等诸多产品的历代更迭之后,目前业界普遍选择了SpringMVC作为Java EE项目表述层开发的首选方案。之所以能做到这一点,是因为SpringMVC具备如下显著优势:

        - Spring 家族原生产品,与IOC容器等基础设施无缝对接
        - 表述层各细分领域需要解决的问题全方位覆盖,提供全面解决方案
        - 代码清新简洁,大幅度提升开发效率
        - 内部组件化程度高,可插拔式组件即插即用,想要什么功能配置相应组件即可
        - 性能卓著,尤其适合现代大型、超大型互联网项目要求

1、MVC是什么?

        MVC是一种软件架构思想,把软件按照模型、视图、控制器来划分

        Model:模型层,指工程中的JavaBean,用来处理数据

                JavaBean被分成两类:

                        一类称为实体类Bean:专门用来存储业务数据,比如Student、User

                        一类称为业务处理Bean:指Servlet或Dao对象,专门用来处理业务逻辑和数据访问

        View:视图层,指工程中的html、jsp等页面,作用是和用户进行交互,展示数据

        Controller:控制层,指工程中的Servlet,作用是接收请求和响应浏览器

2、流程:

        ①用户通过视图层发送请求到服务器,在服务器中请求被Controller接收

        ②Controller调用相应的Model层处理请求,处理完毕后结果返回到Controller

        ③Controller再根据请求处理的结果找到对应View视图,渲染数据后最终响应给浏览器

SpringMVC对这套MVC流程进行封装,帮助开发者屏蔽底层细节,并且开放出相关接口供开发者调用,让MVC开发更简单方便

1.2:主要作用

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

        总结:

  1. 简化前端参数接收(形参列表)
  2. 简化后端数据响应(返回值)
  3. ......                
1.3:核心组件和调用流程理解

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

        除了`DispatcherServlet,`SpringMVC还会提供其他特殊的组件协作完成请求处理和响应呈现。

SpringMVC涉及组件理解: 

        1. DispatcherServlet :  前置控制器,负责调度其它组件的执行,可以降低不同组件之间的耦合性,是这个SpringMVC的核心模块;SpringMVC提供,我们需要使用web.xml配置使其生效,它是整个流程处理的核心,所有请求都经过它的处理和分发!
        2. HandlerMapping :  DispatcherServlet是通过HandlerMapping把请求映射到不同的Handler;SpringMVC提供,我们需要进行IoC配置使其加入IoC容器方可生效,它内部缓存handler(controller方法)和handler访问路径数据,被DispatcherServlet调用,用于查找路径对应的handler!
        3. HandlerAdapter :处理器拦截器,是一个接口,如果我们需要进行一些拦截处理,可以通过实现该接口来完成; SpringMVC提供,我们需要进行IoC配置使其加入IoC容器方可生效,它可以处理请求参数和处理响应数据,每次DispatcherServlet都通handlerAdapter间接调用handler,他是handler和DispatcherServlet之间的适配器!
        4. Handler : handler又称处理器,完成具体的业务逻辑,相当于Servlet;它是Controller类内部的方法简称,是由我们自己定义,用来接收参数,向后调用业务,最终返回响应结果!
        5. ViewResovler :视图解析器,DispatcherServlet通过它把逻辑视图解析为物理视图,最终把渲染的结果响应给客户端; SpringMVC提供,我们需要进行IoC配置使其加入IoC容器方可生效!视图解析器主要作用简化模版视图页面查找,但是需要注意,前后端分离项目,后端只返回JSON数据,不返回页面,那就不需要视图解析器!所以,视图解析器,相对其他的组件不是必须的!

1.4:工作流程
  1. 客户端请求被DispatcherServlet接收

  2. 根据HandlerMapping映射到Handler
  3. 生成Handler和HandlerInterceptor
  4. Handler和HandlerInterceptor以HandlerExecutionChain的形式一并返回给DispatcherServlet
  5. DispatcherServlet通过HandlerAdapter调用Handler的方法完成业务逻辑处理
  6. 返回一个ModelAndView对象给DispatcherServlet
  7. DispatcherServlet把获取的ModelAndView对象传给ViewResolver视图解析器,把逻辑视图解析成物理视图
  8. ViewResolver返回一个View进行视图渲染(把模型填充到视图中)
  9. DispatcherServlet把渲染后的视图响应给客户端

1.5:快速体验

        ①体验场景需求

        ②配置分析

1. DispatcherServlet,设置处理所有请求!
2. HandlerMapping,HandlerAdapter,Handler需要加入到IoC容器,供DS调用!
3. Handler自己声明(Controller)需要配置到HandlerMapping中供DS查找!

        ③准备项目

                a:创建项目

                        springmvc-base-quick

                b:导入依赖        

<?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.jyf</groupId>
    <artifactId>ssm-springmvc-part</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>
    <modules>
        <module>springmvc-base-quick-01</module>
        <module>springmvc-base-input-02</module>
        <module>test</module>
    </modules>

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

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

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

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

        <dependency>
            <groupId>io.mateu</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.11.97</version>
        </dependency>

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

        ④Controller声明

package com.jyf.controller;

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

/**
 * @Author: 小贾
 * @CreateTime: 2024-03-11  12:27
 * @Description: TODO
 */
@Controller
public class HelloController {
    //handler -> springmvc/hello return "hello springmvc!"
    @RequestMapping("springmvc/hello")//对外访问的地址,到handlerMapping注册的注解
    @ResponseBody//直接返回字符串给前端,不需要找视图解析器
    public String hello(){
        System.out.println("HelloController.hello");
        //返回给前端
        return "hello springmvc";
    }
}

        ⑤SpringMVC核心组件配置类

package com.jyf.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

/**
 * @Author: 小贾
 * @CreateTime: 2024-03-11  12:26
 * @Description: 1、controller配置ioc容器
 *               2、handlerMapping handlerAdapter加入到ioc容器
 */
@Configuration
@ComponentScan("com.jyf.controller")
public class MvcConfig {
    @Bean
    public RequestMappingHandlerMapping requestMappingHandlerMapping(){
        return new RequestMappingHandlerMapping();
    }

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

         ⑥SpringMVC环境搭建

package com.jyf.config;

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

/**
 * @Author: 小贾
 * @CreateTime: 2024-03-11  12:26
 * @Description: 可以被web项目加载,会初始化ioc容器,会设置dispatcherServlet的地址
 */
public class SpringMvcInit extends AbstractAnnotationConfigDispatcherServletInitializer {
    //service mapper层ioc容器的配置
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[0];
    }

    //设置我们项目对应的配置
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{MvcConfig.class};
    }

    //配置springmvc内部自带servlet的访问地址
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}

        ⑦启动测试

二:SpringMVC接收数据


2.1:访问路径设置

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

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

        ①精准路径匹配

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

        ②模糊路径匹配

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

        ③类和方法的区别

        ④请求方式指定(限制)

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

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

        默认情况下:@RequestMapping("/logout") 任何请求方式都可以访问!

        如果需要特定指定:

package com.jyf.requestmapping;

import jakarta.ws.rs.POST;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

/**
 * @Author: 小贾
 * @CreateTime: 2024-03-11  16:23
 * @Description: TODO
 */
@Controller
@RequestMapping("user")
public class UserController {
    //handler -> handlerMapping 指定访问地址
    /*
    * @WebServlet("必须使用/开头")
    *
    * @RequestMapping("不一定使用/开头") user/login /user/login
    *   1、精确地址[一个 | 多个]
    *       一个:/user/login
    *       二个:{"地址一","地址二"}
    *   2、支持模糊 *:任意一层字符串 |**:任意两层字符串
    *       /user/*  -> user/a、user/aaaa 可以  user/a/b 不行
    *       /user/** -> user user/a user/a/a/a/a
    *
    *   3、类上和方法上添加@RequestMapping的区别
    *       类上:提取通用的访问地址
    *       方法上:是具体的handler地址
    *       访问:类地址 + 方法地址即可
    *   4、请求方式指定
    *       客户端 -> http(get | post | put | delete) -> ds -> handler
    *       默认情况:@RequestMapping(value = "login"):只要地址正确,任何请求方式都可以访问
    *       指定请求情况:method = {RequestMethod.GET,RequestMethod.POST}
    *       不符合请求方式:会出现405异常!
    *   5、注解进阶(只能使用在方法上)
    *       get @GetMapping == @RequestMapping(value = "login",method = RequestMethod.GET)
    *       post @GetMapping == @RequestMapping(value = "login",method = RequestMethod.POST)
    *       put @GetMapping == @RequestMapping(value = "login",method = RequestMethod.PUT)
    *       delete @GetMapping == @RequestMapping(value = "login",method = RequestMethod.DELETE)
    *
    */
    @RequestMapping(value = "login",method = RequestMethod.POST)//作用:注册地址,将handler注册到handlerMapping中
    @PostMapping //get请求地址
    public String login(){
        return null;
    }

    @RequestMapping(value = "register",method = {RequestMethod.GET,RequestMethod.POST})
    public String register() {
        return null;
    }
}
2.2:接收参数
        2.2.1:param和json参数比较

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

1. 参数编码:  

    param 类型的参数会被编码为 ASCII 码。例如,假设 `name=john doe`,则会被编码为 `name=john%20doe`。而 JSON 类型的参数会被编码为 UTF-8。
2. 参数顺序:  

    param 类型的参数没有顺序限制。但是,JSON 类型的参数是有序的。JSON 采用键值对的形式进行传递,其中键值对是有序排列的。
3. 数据类型:  

    param 类型的参数仅支持字符串类型、数值类型和布尔类型等简单数据类型。而 JSON 类型的参数则支持更复杂的数据类型,如数组、对象等。
4. 嵌套性:  

    param 类型的参数不支持嵌套。但是,JSON 类型的参数支持嵌套,可以传递更为复杂的数据结构。
5. 可读性:  

    param 类型的参数格式比 JSON 类型的参数更加简单、易读。但是,JSON 格式在传递嵌套数据结构时更加清晰易懂。

        总的来说,param 类型的参数适用于单一的数据传递,而 JSON 类型的参数则更适用于更复杂的数据结构传递。根据具体的业务需求,需要选择合适的参数类型。在实际开发中,常见的做法是:在 GET 请求中采用 param 类型的参数,而在 POST 请求中采用 JSON 类型的参数传递。

        2.2.2:param参数接受

①直接接值:只要形参参数名和类型与传递参数相同,即可自动接收!

②@RequestParam注解:

        可以使用 `@RequestParam` 注释将 Servlet 请求参数(即查询参数或表单数据)绑定到控制器中的方法参数。

        `@RequestParam`使用场景:
                  - 指定绑定的请求参数名
                  - 要求请求参数必须传递
                  - 为请求参数提供默认值

 ③特殊场景接值

        a:一名多值

        b:实体接收

        Spring MVC 是 Spring 框架提供的 Web 框架,它允许开发者使用实体对象来接收 HTTP 请求中的参数。通过这种方式,可以在方法内部直接使用对象的属性来访问请求参数,而不需要每个参数都写一遍。

package com.jyf.param;

import com.jyf.pojo.User;
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.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.List;

/**
 * @Author: 小贾
 * @CreateTime: 2024-03-11  16:48
 * @Description: 讲解如何接收前端的param参数
 */
@Controller
@RequestMapping("param")
public class ParamController {
    /*
    * 1、直接接收
    *   /param/data?name=张三&age=18
    *   形参列表,填写对应的名称参数即可!   请求参数名 = 形参参数名即可!
    *       1、名称相同
    *       2、可以不传递,不报错
    * */
    @RequestMapping("data")
    @ResponseBody
    public String data(String name,int age){
        System.out.println("name = " + name+ ", age = " + age);
        return "name = " + name+ ", age = " + age;
    }

    /*
    * 2、注解指定
    *       指定任意的请求参数名:
    *           要求必须传递
    *           要求不必须传递,给与一个默认值
    *       /param/data1?account=root&page=1
    *       account必须传递,page可以不必须传递,如果不传递默认值就是1
    *
    *       @RequestParam -> 形参列表:指定请求参数名
    *           或者是否必须传递
    *           或者非必须传递设置默认值
    *       用法:@RequestParam(value = "请求参数名,如果形参名和请求参数名一致,可以省略!"
    *                          required = true/false,前端是否必须传递此参数,默认是必须,不传报400异常!,
    *                          defaultValue = "默认值",当非必须传递false,可以设置默认值)
    * */
    @GetMapping("data1")
    @ResponseBody
    public String data1(@RequestParam(value = "account") String username,
                        @RequestParam(required = false,defaultValue = "1") int page){
        System.out.println("username= " + username + "), page = " + page);
        return "username= " + username + ", page = " + page;

    }

    /*
    * 3、特殊值
    *   一名多值 key=1&key=2
    *   param/data2?key=1&key=2
    *
    *   不加注解@RequestParam,将jyf对应的一个字符串直接赋值给集合,类型异常
    *   加了注解,就会将集合用add方法加入到对应的字符串中
    * */
    @GetMapping("data2")
    @ResponseBody
    public String data2(@RequestParam List<String> jyf){
        System.out.println("jyf = " + jyf);
        return "OK!";
    }

    /*
    * 4、使用实体对象接值
    *   param/data3?name=jyf&age=18 准备一个对应属性和get|set方法的实体类即可! -> 形参列表声明对象参数即可!
    * */
    @RequestMapping("data3")
    @ResponseBody
    public String data3(User user){
        System.out.println("user= "+ user);
        return user.toString();
    }
}
        2.2.3:路径参数接收

         路径传递参数是一种在 URL 路径中传递参数的方式。在 RESTful 的 Web 应用程序中,经常使用路径传递参数来表示资源的唯一标识符或更复杂的表示方式。而 Spring MVC 框架提供了 `@PathVariable` 注解来处理路径传递参数。

        `@PathVariable` 注解允许将 URL 中的占位符映射到控制器方法中的参数。

        例如,如果我们想将 `/user/{id}` 路径下的 `{id}` 映射到控制器方法的一个参数中,则可以使用 `@PathVariable` 注解来实现。

package com.jyf.path;

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

/**
 * @Author: 小贾
 * @CreateTime: 2024-03-11  18:17
 * @Description: 路径参数
 */
@Controller
@RequestMapping("/path")
@ResponseBody
public class PathController {
    /*
    * path/账号/密码
    * 动态路径设计:{key} = *
    *   {key}在形参列表获取传入的参数
    * 接收路径参数:String account,String password -> 接收param格式参数
    * 必须使用@PathVariable("key")
    * */
    @RequestMapping("{account}/{password}")
    public String path(@PathVariable("account") String account, @PathVariable("password") String password){
        System.out.println("username = " + account + " password = " + password);
        return "username = " + account + " password = " + password;
    }
}
        2.2.4:json参数接收

        前端传递 JSON 数据时,Spring MVC 框架可以使用 @RequestBody 注解来将 JSON 数据转换为 Java 对象。@RequestBody 注解表示当前方法参数的值应该从请求体中获取,并且需要指定 value 属性来指示请求体应该映射到哪个参数上

package com.jyf.json;

import com.jyf.pojo.Person;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * @Author: 小贾
 * @CreateTime: 2024-03-11  18:28
 * @Description: TODO
 */
@RequestMapping("json")
@Controller
@ResponseBody
public class JsonController {
    //data -> 请求体 post{name,age,gender}
    //前端 -> json -> 报415异常,不支持数据类型
    //原因:Java原生的api,只支持路径参数和param参数 request.getParameter() param 不支持json
    //json就是前端的格式
    //解决:1、导入json处理的依赖  2、handlerAdapter配置json转化器
    //SpringMVC支持json,将json数据直接封装到JavaBean对象中,要求JavaBean的属性名和json的key一致
    //@RequestBody:将请求体中的数据封装到一个JavaBean对象中,要求请求体中的json数据和JavaBean的属性名一致
    @RequestMapping("data")
    public String data(@RequestBody Person person){
        System.out.println("person = " + person);
        return person.toString();
    }
}
2.3:接收Cookie数据

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

package com.jyf.cookie;

import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * @Author: 小贾
 * @CreateTime: 2024-03-13  09:17
 * @Description: 接收Cookie
 */
@Controller
@RequestMapping("cookie")
@ResponseBody
public class CookieController {
    @RequestMapping("data")
    public String data(@CookieValue(value = "cookieName")String value){
        System.out.println("value = " + value);
        return value;
    }

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


2.4:接收请求头数据

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

package com.jyf.header;

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

/**
 * @Author: 小贾
 * @CreateTime: 2024-03-13  09:31
 * @Description: TODO
 */
@Controller
@RequestMapping("header")
@ResponseBody
public class HeaderController {
    @GetMapping("data")
    public String data(@RequestHeader("host") String host){
        System.out.println("host = " + host);
        return "host = " + host;
    }
}
2.5:原生API对象操作
package com.jyf.api;

import jakarta.servlet.ServletContext;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * @Author: 小贾
 * @CreateTime: 2024-03-13  09:34
 * @Description: TODO
 */
public class ApiController {
    @Autowired //ioc容器获取对应类型实体对象(组件)并自动装配
    private ServletContext servletContext;

    public void data(HttpServletResponse response,
                     HttpServletRequest request,
                     HttpSession session){
        //使用原生对象就可以
        //ServletContext [1、最大的配置文件 2、全局最大共享域 3、核心api getRealPath]
        //方案一:Request获取,session获取
        ServletContext servletContext1 = request.getServletContext();
        ServletContext servletContext2 = session.getServletContext();
        //方案二:ServletContext会自动装入到ioc容器!程序自动启动servletContext - ioc容器
        //直接全局注入就可以
    }
}
2.6:共享域对象操作
        2.6.1:属性(共享)域作用回顾

在 JavaWeb 中,共享域指的是在 Servlet 中存储数据,以便在同一 Web 应用程序的多个组件中进行共享和访问。常见的共享域有四种:`ServletContext`、`HttpSession`、`HttpServletRequest`、`PageContext`。

        1. `ServletContext` 共享域:`ServletContext` 对象可以在整个 Web 应用程序中共享数据,是最大的共享域。一般可以用于保存整个 Web 应用程序的全局配置信息,以及所有用户都共享的数据。在 `ServletContext` 中保存的数据是线程安全的。
        2. `HttpSession` 共享域:`HttpSession` 对象可以在同一用户发出的多个请求之间共享数据,但只能在同一个会话中使用。比如,可以将用户登录状态保存在 `HttpSession` 中,让用户在多个页面间保持登录状态。
        3. `HttpServletRequest` 共享域:`HttpServletRequest` 对象可以在同一个请求的多个处理器方法之间共享数据。比如,可以将请求的参数和属性存储在 `HttpServletRequest` 中,让处理器方法之间可以访问这些数据。
        4. `PageContext` 共享域:`PageContext` 对象是在 JSP 页面Servlet 创建时自动创建的。它可以在 JSP 的各个作用域中共享数据,包括`pageScope`、`requestScope`、`sessionScope`、`applicationScope` 等作用域。

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

        2.6.2:Request级别属性(共享)域
package com.jyf.share;

import jakarta.servlet.Servlet;
import jakarta.servlet.ServletContext;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;

import java.util.Map;

/**
 * @Author: 小贾
 * @CreateTime: 2024-03-13  09:44
 * @Description: 共享域对象获取
 */
@Controller
@RequestMapping("share")
@ResponseBody
public class ShareController {
    @Autowired
    private ServletContext servletContext;

    //原生api
    public void data1(HttpServletRequest request, HttpSession session){

    }

    //springmvc提供的方法:request提供了几种{限于了解}
    //model modelMap map modelAndView
    public void data1(Model model){
        model.addAttribute("key","value");
    }

    public void data1(ModelMap modelhMap){
        modelhMap.addAttribute("name","jyf");
    }

    public void data1(Map map){
        map.put("key","value");
    }

    public void data1(){
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("key","value");
        modelAndView.setViewName("视图名(页面名称)");
    }
}
        2.6.3:Session级别属性(共享)域
@RequestMapping("/attr/session")
@ResponseBody
public String testAttrSession(HttpSession session) {
    //直接对session对象操作,即对会话范围操作!
    return "target";
}
        2.6.4:Application级别属性(共享)域 
@Autowired
private ServletContext servletContext;

@RequestMapping("/attr/application")
@ResponseBody
public String attrApplication() {
    
    servletContext.setAttribute("appScopeMsg", "i am hungry...");
    
    return "target";
}

三:SpringMVC响应数据


3.1:handler方法分析
/**
 * TODO: 一个controller的方法是控制层的一个处理器,我们称为handler
 * TODO: handler需要使用@RequestMapping/@GetMapping系列,声明路径,在HandlerMapping中注册,供DS查找!
 * TODO: handler作用总结:
 *       1.接收请求参数(param,json,pathVariable,共享域等) 
 *       2.调用业务逻辑 
 *       3.响应前端数据(页面(不讲解模版页面跳转),json,转发和重定向等)
 * TODO: handler如何处理呢
 *       1.接收参数: handler(形参列表: 主要的作用就是用来接收参数)
 *       2.调用业务: { 方法体  可以向后调用业务方法 service.xx() }
 *       3.响应数据: return 返回结果,可以快速响应前端数据
 */
@GetMapping
public Object handler(简化请求参数接收){
    调用业务方法
    返回的结果 (页面跳转,返回数据(json))
    return 简化响应前端数据;
}

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

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

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

3.2:页面跳转控制
        3.2.1:快速返回模版视图

                ①开发模式

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

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

          指将前端的界面和后端的业务逻辑通过接口分离开发的一种方式。开发人员使用不同的技术栈和框架,前端开发人员主要负责页面的呈现和用户交互,后端开发人员主要负责业务逻辑和数据存储。前后端通信通过 API 接口完成,数据格式一般使用 JSON 或 XML。前后端分离模式可以提高开发效率,同时也有助于代码重用和维护。

混合开发模式:

          指将前端和后端的代码集成在同一个项目中,共享相同的技术栈和框架。这种模式在小型项目中比较常见,可以减少学习成本和部署难度。但是,在大型项目中,这种模式会导致代码耦合性很高,维护和升级难度较大。

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

                ②jsp技术

        JSP(JavaServer Pages)是一种动态网页开发技术,它是由 Sun 公司提出的一种基于 Java 技术的 Web 页面制作技术,可以在 HTML 文件中嵌入 Java 代码,使得生成动态内容的编写更加简单。

        JSP 最主要的作用是生成动态页面。它允许将 Java 代码嵌入到 HTML 页面中,以便使用 Java 进行数据库查询、处理表单数据和生成 HTML 等动态内容。另外,JSP 还可以与 Servlet 结合使用,实现更加复杂的 Web 应用程序开发。

        JSP 的主要特点包括:

                1. 简单:JSP 通过将 Java 代码嵌入到 HTML 页面中,使得生成动态内容的编写更加简单。
                2. 高效:JSP 首次运行时会被转换为 Servlet,然后编译为字节码,从而可以启用 Just-in-Time(JIT)编译器,实现更高效的运行。
                3. 多样化:JSP 支持多种标准标签库,包括 JSTL(JavaServer Pages 标准标签库)、EL(表达式语言)等,可以帮助开发人员更加方便的处理常见的 Web 开发需求。

        总之,JSP 是一种简单高效、多样化的动态网页开发技术,它可以方便地生成动态页面和与 Servlet 结合使用,是 Java Web 开发中常用的技术之一。

                ③准备jsp页面和依赖

 pom.xml依赖

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

jsp页面创建

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
    <head>
        <title>Title</title>
    </head>
    <body>
        <%--request.setAttribute("data","hello jsp!!")
            vue {{key}}
        --%>
    <font color="red">${data}</font>
    </body>
</html>

                ④快速响应模版页面

a:配置jsp视图解析器

package com.jyf.config;

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

/**
 * @Author: 小贾
 * @CreateTime: 2024-03-13  12:38
 * @Description: mvc组件的配置类
 */
@Configuration
@ComponentScan("com.jyf.json")
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {
    //handlerMapping handlerAdapter json转化器
    //视图解析器,指定前后缀
    public void configureViewResolvers(ViewResolverRegistry registry) {
        //registry可以快速添加前后缀
        registry.jsp("/WEB-INF/jsp/", ".jsp");
        //handler -> index
    }

    /*
    * 开启静态资源查找
    *   dispatcherServlet -> handlerMapping去找有没有对应的handler -》【没有 -》找对应的静态资源】
    * */
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer){
        configurer.enable();
    }
}

b:Handler返回视图

/**
 *  跳转到提交文件页面  /save/jump
 *  
 *  如果要返回jsp页面!
 *     1.方法返回值改成字符串类型
 *     2.返回逻辑视图名即可    
 *         <property name="prefix" value="/WEB-INF/views/"/>
 *            + 逻辑视图名 +
 *         <property name="suffix" value=".jsp"/>
 */
@GetMapping("jump")
public String jumpJsp(Model model){
    System.out.println("FileController.jumpJsp");
    model.addAttribute("msg","request data!!");
    return "home";
}

        3.2.2:转发和重定向 

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

package com.jyf.jsp;

import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * @Author: 小贾
 * @CreateTime: 2024-03-13  12:48
 * @Description: 快速返回一个jsp页面
 */
@Controller
@RequestMapping("com.jyf.jsp")
public class JspController {
    /*
    * TODO:快速查找视图
    *   1、方法的返回值就是字符串类型
    *   2、不能添加@ResponseBody,直接返回字符串给浏览器,不找视图,不走视图解析器
    *   3、返回值:对应中间的视图名称即可
    * */
    @GetMapping("index")
    public String index(HttpServletRequest request){
        request.setAttribute("data","hello jsp");
        System.out.println("JspController.index");
        return "index";
    }

    /*
    * 转发:只能是项目下的资源
    *   1、方法的返回值写成字符串
    *   2、不能添加@ResponseBody,
    *   3、返回的字符串前写forward:/转发地址
    * */
    @GetMapping("forward")
    public String forward(){
        System.out.println("JspController.forward");
        return "forward:/jsp/index";
    }

    /*
    * 重定向:
    *   1、方法的返回值写成字符串
    *   2、不能添加@ResponseBody
    *   3、返回值字符串前面写redirect:/重定向地址
    * */
    @GetMapping("redirect")
    public String redirect(){
        System.out.println("JspController.redirect");
        return "redirect:/jsp/index";
    }
}

总结:

        - 将方法的返回值,设置String类型
        - 转发使用forward关键字,重定向使用redirect关键字
        - 关键字: /路径
        - 注意:如果是项目下的资源,转发和重定向都一样都是项目下路径!都不需要添加项目根路径!

3.3:返回JSON数据
        3.3.1:前置准备

导入jackson依赖

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

添加json数据转化器

@EnableWebMvc

package com.jyf.config;

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

/**
 * @Author: 小贾
 * @CreateTime: 2024-03-13  12:38
 * @Description: mvc组件的配置类
 */
@Configuration
@ComponentScan("com.jyf.json")
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {
    //handlerMapping handlerAdapter json转化器
    //视图解析器,指定前后缀
    public void configureViewResolvers(ViewResolverRegistry registry) {
        //registry可以快速添加前后缀
        registry.jsp("/WEB-INF/jsp/", ".jsp");
        //handler -> index
    }

    /*
    * 开启静态资源查找
    *   dispatcherServlet -> handlerMapping去找有没有对应的handler -》【没有 -》找对应的静态资源】
    * */
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer){
        configurer.enable();
    }
}
        3.3.2:@ResponseBody

1:方法上使用@ResponseBody

        可以在方法上使用 @ResponseBody注解,用于将方法返回的对象序列化为 JSON 或 XML 格式的数据,并发送给客户端。在前后端分离的项目中使用!

        具体来说,`@ResponseBody` 注解可以用来标识方法或者方法返回值,表示方法的返回值是要直接返回给客户端的数据,而不是由视图解析器来解析并渲染生成响应体

package com.jyf.json;

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

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

/**
 * @Author: 小贾
 * @CreateTime: 2024-03-13  14:43
 * @Description: TODO
 */
//@Controller
@RequestMapping("json")
//@ResponseBody//返回json的注解,添加到类和方法上
@RestController//等于@Controller+@ResponseBody
public class JsonController {
    /*
    * TODO:@ResponseBody 数据直接放入响应体返回!不会再走视图解析器
    *  快速查找视图,转发和重定向都不生效了
    *
    * */
    @GetMapping("data")
    public User data(){
        /*
        * 对象 -》json -> {}
        * 集合 -》json -> []
        * */
        User user = new User();
        user.setName("xiao jia!");
        user.setAge(22);
        return user;//user -> handlerAdapter -> json -》@ResponseBody -> json直接返回【前后端分离模式】
    }

    @GetMapping("data2")
    public List<User> data1(){
        User user = new User();
        user.setName("ergouzi");
        user.setAge(3);

        List<User> users = new ArrayList<>();
        users.add(user);
        return users;
    }
}
        3.3.3:@RestController

        类上的 @ResponseBody 注解可以和 @Controller 注解合并为 @RestController 注解。所以使用了 @RestController 注解就相当于给类中的每个方法都加了 @ResponseBody 注解。

        RestController源码:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
 
  /**
   * The value may indicate a suggestion for a logical component name,
   * to be turned into a Spring bean in case of an autodetected component.
   * @return the suggested component name, if any (or empty String otherwise)
   * @since 4.0.1
   */
  @AliasFor(annotation = Controller.class)
  String value() default "";
 
}
3.4:返回静态资源处理

1. 静态资源概念

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

    - 纯HTML文件
    - 图片
    - CSS文件
    - JavaScript文件
    - ……

2.静态资源访问和问题解决

        web应用加入静态资源

        手动构建确保编译

        访问静态资源

        问题分析:

        - DispatcherServlet 的 url-pattern 配置的是“/”
        - url-pattern 配置“/”表示整个 Web 应用范围内所有请求都由 SpringMVC 来处理
        - 对 SpringMVC 来说,必须有对应的 @RequestMapping 才能找到处理请求的方法
        - 现在 images/mi.jpg 请求没有对应的 @RequestMapping 所以返回 404

        问题解决:

        在SpringMVC配置配置类

@EnableWebMvc  //json数据处理,必须使用此注解,因为他会加入json处理器
@Configuration
@ComponentScan(basePackages = "com.atguigu.controller") //TODO: 进行controller扫描
//WebMvcConfigurer springMvc进行组件配置的规范,配置组件,提供各种方法! 前期可以实现
public class SpringMvcConfig implements WebMvcConfigurer {

    //配置jsp对应的视图解析器
    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        //快速配置jsp模板语言对应的
        registry.jsp("/WEB-INF/views/",".jsp");
    }
    
    //开启静态资源处理 <mvc:default-servlet-handler/>
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }
}

四:RESTFul风格设计和实战


4.1:RESTFul风格概述
        4.1.1:RESTFul风格简介

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

        通过遵循 RESTful 架构的设计原则,可以构建出易于理解、可扩展、松耦合和可重用的 Web 服务。RESTful API 的特点是简单、清晰,并且易于使用和理解,它们使用标准的 HTTP 方法和状态码进行通信,不需要额外的协议和中间件。

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

        4.1.2:RESTFul风格特点

1. 每一个URI代表1种资源(URI 是名词);
2. 客户端使用GET、POST、PUT、DELETE 4个表示操作方式的动词对服务端资源进行操作:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源;
3. 资源的表现形式是XML或者**JSON**;
4. 客户端与服务端之间的交互在请求之间是无状态的,从客户端到服务端的每个请求都必须包含理解请求所必需的信息。

        4.1.3:RESTFul风格设计规范

 1:HTTP协议请求方式要求

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

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

2、URL路径风格要求

        REST风格下每个资源都应该有一个唯一的标识符,例如一个 URI(统一资源标识符)或者一个 URL(统一资源定位符)。资源的标识符应该能明确地说明该资源的信息,同时也应该是可被理解和解释的!

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

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

- 总结

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

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

        4.1.4:RESTFul风格好处

 1. 含蓄,安全

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

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

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

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

    过去做增删改查操作需要设计4个不同的URL,现在一个就够了。

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

6. 丰富的语义

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

4.2:RESTFul风格实战

        4.2.1:需求分析

- 数据结构: User {id 唯一标识,name 用户名,age 用户年龄}
- 功能分析
    - 用户数据分页展示功能(条件:page 页数 默认1,size 每页数量 默认 10)
    - 保存用户功能
    - 根据用户id查询用户详情功能
    - 根据用户id更新用户数据功能
    - 根据用户id删除用户数据功能
    - 多条件模糊查询用户功能(条件:keyword 模糊关键字,page 页数 默认1,size 每页数量 默认 10)

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

问题讨论

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

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

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

        - 对于查询用户详情,使用路径传递参数是因为这是一个单一资源的查询,即查询一条用户记录。使用路径参数可以明确指定所请求的资源,便于服务器定位并返回对应的资源,也符合 RESTful 风格的要求。
        - 而对于多条件模糊查询,使用请求参数传递参数是因为这是一个资源集合的查询,即查询多条用户记录。使用请求参数可以通过组合不同参数来限制查询结果,路径参数的组合和排列可能会很多,不如使用请求参数更加灵活和简洁。

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

                - 路径参数应该用于指定资源的唯一标识或者 ID,而请求参数应该用于指定查询条件或者操作参数。
                - 请求参数应该限制在 10 个以内,过多的请求参数可能导致接口难以维护和使用。
                - 对于敏感信息,最好使用 POST 和请求体来传递参数。

        4.2.3:后台接口实现

准备用户实体类:

package com.jyf.pojo;

/**
 * @Author: 小贾
 * @CreateTime: 2024-03-13  15:37
 * @Description: TODO
 */
public class User {
        private Integer id;
        private String name;

        private Integer age;

        public Integer getId() {
            return id;
        }

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

        public String getName() {
            return name;
        }

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

        public Integer getAge() {
            return age;
        }

        public void setAge(Integer age) {
            this.age = age;
        }

        @Override
        public String toString() {
            return "User{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
}

准备用户Controller:

package com.jyf.controller;

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

/**
 * projectName: com.atguigu.controller
 * description: 用户模块的控制器
 */
@RequestMapping("user")
@RestController
public class UserController {
    /**
     * 模拟分页查询业务接口
     */
    @GetMapping
    public Object queryPage(@RequestParam(name = "page",required = false,defaultValue = "1")int page,
                            @RequestParam(name = "size",required = false,defaultValue = "10")int size){
        System.out.println("page = " + page + ", size = " + size);
        System.out.println("分页查询业务!");
        return "{'status':'ok'}";
    }


    /**
     * 模拟用户保存业务接口
     */
    @PostMapping
    public Object saveUser(@RequestBody User user){
        System.out.println("user = " + user);
        System.out.println("用户保存业务!");
        return "{'status':'ok'}";
    }

    /**
     * 模拟用户详情业务接口
     */
    @PostMapping("/{id}")
    public Object detailUser(@PathVariable Integer id){
        System.out.println("id = " + id);
        System.out.println("用户详情业务!");
        return "{'status':'ok'}";
    }


    /**
     * 模拟用户更新业务接口
     */
    @PutMapping
    public Object updateUser(@RequestBody User user){
        System.out.println("user = " + user);
        System.out.println("用户更新业务!");
        return "{'status':'ok'}";
    }


    /**
     * 模拟条件分页查询业务接口
     */
    @GetMapping("search")
    public Object queryPage(@RequestParam(name = "page",required = false,defaultValue = "1")int page,
                            @RequestParam(name = "size",required = false,defaultValue = "10")int size,
                            @RequestParam(name = "keyword",required= false)String keyword){
        System.out.println("page = " + page + ", size = " + size + ", keyword = " + keyword);
        System.out.println("条件分页查询业务!");
        return "{'status':'ok'}";
    }
}

五:SpringMVC其它扩展


5.1:全局异常处理机制
        5.1.1:异常处理的两种方式

开发过程中是不可避免地会出现各种异常情况的,例如网络连接异常、数据格式异常、空指针异常等等。异常的出现可能导致程序的运行出现问题,甚至直接导致程序崩溃。因此,在开发过程中,合理处理异常、避免异常产生、以及对异常进行有效的调试是非常重要的。

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

        - 编程式异常处理:是指在代码中显式地编写处理异常的逻辑。它通常涉及到对异常类型的检测及其处理,例如使用 try-catch 块来捕获异常,然后在 catch 块中编写特定的处理代码,或者在 finally 块中执行一些清理操作。在编程式异常处理中,开发人员需要显式地进行异常处理,异常处理代码混杂在业务代码中,导致代码可读性较差。
        - 声明式异常处理:则是将异常处理的逻辑从具体的业务逻辑中分离出来,通过配置等方式进行统一的管理和处理。在声明式异常处理中,开发人员只需要为方法或类标注相应的注解(如 `@Throws` 或 `@ExceptionHandler`),就可以处理特定类型的异常。相较于编程式异常处理,声明式异常处理可以使代码更加简洁、易于维护和扩展。

站在宏观角度来看待声明式事务处理:

          整个项目从架构这个层面设计的异常处理的统一机制和规范。

          一个项目中会包含很多个模块,各个模块需要分工完成。如果张三负责的模块按照 A 方案处理异常,李四负责的模块按照 B 方案处理异常……各个模块处理异常的思路、代码、命名细节都不一样,那么就会让整个项目非常混乱。

          使用声明式异常处理,可以统一项目处理异常思路,项目更加清晰明了!

        5.1.2:基于注解异常声明和异常处理

1、声明异常处理控制器类

        异常处理控制类,统一定义异常处理handler方法!

/**
 * projectName: com.atguigu.execptionhandler
 * 
 * description: 全局异常处理器,内部可以定义异常处理Handler!
 */

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

  
}

2、声明异常处理handler方法

        异常处理handler方法和普通的handler方法参数接收和响应都一致!

        只不过异常处理handler方法要映射异常,发生对应的异常会调用!

        普通的handler方法要使用@RequestMapping注解映射路径,发生对应的路径调用!

/**
 * 异常处理handler 
 * @ExceptionHandler(HttpMessageNotReadableException.class) 
 * 该注解标记异常处理Handler,并且指定发生异常调用该方法!
 * 
 * 
 * @param e 获取异常对象!
 * @return 返回handler处理结果!
 */
@ExceptionHandler(HttpMessageNotReadableException.class)
public Object handlerJsonDateException(HttpMessageNotReadableException e){
    
    return null;
}

/**
 * 当发生空指针异常会触发此方法!
 * @param e
 * @return
 */
@ExceptionHandler(NullPointerException.class)
public Object handlerNullException(NullPointerException e){

    return null;
}

/**
 * 所有异常都会触发此方法!但是如果有具体的异常处理Handler! 
 * 具体异常处理Handler优先级更高!
 * 例如: 发生NullPointerException异常!
 *       会触发handlerNullException方法,不会触发handlerException方法!
 * @param e
 * @return
 */
@ExceptionHandler(Exception.class)
public Object handlerException(Exception e){

    return null;
}

3、配置文件扫描控制器类配置

        确保异常处理控制类被扫描

 <!-- 扫描controller对应的包,将handler加入到ioc-->
 @ComponentScan(basePackages = {"com.atguigu.controller",
 "com.atguigu.exceptionhandler"})
5.2:拦截器使用
        5.2.1:拦截器概念

拦截器 Springmvc VS 过滤器 javaWeb:

- 相似点
    - 拦截:必须先把请求拦住,才能执行后续操作
    - 过滤:拦截器或过滤器存在的意义就是对请求进行统一处理
    - 放行:对请求执行了必要操作后,放请求过去,让它访问原本想要访问的资源
- 不同点
    - 工作平台不同
        - 过滤器工作在 Servlet 容器中
        - 拦截器工作在 SpringMVC 的基础上
    - 拦截的范围
        - 过滤器:能够拦截到的最大范围是整个 Web 应用
        - 拦截器:能够拦截到的最大范围是整个 SpringMVC 负责的请求
    - IOC 容器支持
        - 过滤器:想得到 IOC 容器需要调用专门的工具方法,是间接的
        - 拦截器:它自己就在 IOC 容器中,所以可以直接从 IOC 容器中装配组件,也就是可以直接得到 IOC 容器的支持

选择:

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

 

        5.2.2:拦截器使用

1、创建拦截器类

public class Process01Interceptor implements HandlerInterceptor {

    // if( ! preHandler()){return;}
    // 在处理请求的目标 handler 方法前执行
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("request = " + request + ", response = " + response + ", handler = " + handler);
        System.out.println("Process01Interceptor.preHandle");
         
        // 返回true:放行
        // 返回false:不放行
        return true;
    }
 
    // 在目标 handler 方法之后,handler报错不执行!
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("request = " + request + ", response = " + response + ", handler = " + handler + ", modelAndView = " + modelAndView);
        System.out.println("Process01Interceptor.postHandle");
    }
 
    // 渲染视图之后执行(最后),一定执行!
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("request = " + request + ", response = " + response + ", handler = " + handler + ", ex = " + ex);
        System.out.println("Process01Interceptor.afterCompletion");
    }
}

2、修改配置类添加拦截器

@EnableWebMvc  //json数据处理,必须使用此注解,因为他会加入json处理器
@Configuration
@ComponentScan(basePackages = {"com.atguigu.controller","com.atguigu.exceptionhandler"}) //TODO: 进行controller扫描
//WebMvcConfigurer springMvc进行组件配置的规范,配置组件,提供各种方法! 前期可以实现
public class SpringMvcConfig implements WebMvcConfigurer {

    //配置jsp对应的视图解析器
    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        //快速配置jsp模板语言对应的
        registry.jsp("/WEB-INF/views/",".jsp");
    }

    //开启静态资源处理 <mvc:default-servlet-handler/>
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

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

3、配置详解

        a:默认拦截全部

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

        b:精准配置

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

        c:排除配置

//添加拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
    
    //将拦截器添加到Springmvc环境,默认拦截所有Springmvc分发的请求
    registry.addInterceptor(new Process01Interceptor());
    
    //精准匹配,设置拦截器处理指定请求 路径可以设置一个或者多个,为项目下路径即可
    //addPathPatterns("/common/request/one") 添加拦截路径
    registry.addInterceptor(new Process01Interceptor()).addPathPatterns("/common/request/one","/common/request/tow");
    
    
    //排除匹配,排除应该在匹配的范围内排除
    //addPathPatterns("/common/request/one") 添加拦截路径
    //excludePathPatterns("/common/request/tow"); 排除路径,排除应该在拦截的范围内
    registry.addInterceptor(new Process01Interceptor())
            .addPathPatterns("/common/request/one","/common/request/tow")
            .excludePathPatterns("/common/request/tow");
}

4、多个拦截器执行顺序

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

5.3:参数检验  

        在 Web 应用三层架构体系中,表述层负责接收浏览器提交的数据,业务逻辑层负责数据的处理。为了能够让业务逻辑层基于正确的数据进行处理,我们需要在表述层对数据进行检查,将错误的数据隔绝在业务逻辑层之外。

        ①检验概述

        ②操作演示

导入依赖:

<!-- 校验注解 -->
<dependency>
    <groupId>jakarta.platform</groupId>
    <artifactId>jakarta.jakartaee-web-api</artifactId>
    <version>9.1.0</version>
    <scope>provided</scope>
</dependency>
        
<!-- 校验注解实现-->        
<!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator -->
<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>8.0.0.Final</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator-annotation-processor -->
<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator-annotation-processor</artifactId>
    <version>8.0.0.Final</version>
</dependency>

应用校验注解:

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Min;
import org.hibernate.validator.constraints.Length;

/**
 * projectName: com.atguigu.pojo
 */
public class User {
    //age   1 <=  age < = 150
    @Min(10)
    private int age;

    //name 3 <= name.length <= 6
    @Length(min = 3,max = 10)
    private String name;

    //email 邮箱格式
    @Email
    private String email;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}

handler标记和绑定错误收集:

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

    /**
     * @Validated 代表应用校验注解! 必须添加!
     */
    @PostMapping("save")
    public Object save(@Validated @RequestBody User user,
                       //在实体类参数和 BindingResult 之间不能有任何其他参数, BindingResult可以接受错误信息,避免信息抛出!
                       BindingResult result){
       //判断是否有信息绑定错误! 有可以自行处理!
        if (result.hasErrors()){
            System.out.println("错误");
            String errorMsg = result.getFieldError().toString();
            return errorMsg;
        }
        //没有,正常处理业务即可
        System.out.println("正常");
        return user;
    }
}

③易混总结

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

1. @NotNull  (包装类型不为null)

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

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

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

        总之,这三种注解都是用于校验字段值是否为空的注解,但是其校验规则和用法有所不同。在进行数据校验时,需要根据具体情况选择合适的注解进行校验。

六:SpringMVC总结


核心点掌握目标
springmvc框架主要作用、核心组件、调用流程
简化参数接收路径设计、参数接收、请求头接收、cookie接收
简化数据响应模板页面、转发和重定向、JSON数据、静态资源
restful风格设计主要作用、具体规范、请求方式和请求参数选择
功能扩展全局异常处理、拦截器、参数校解
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值