SpringMVC快速入门

01. 概述

1. 简介

=== SpringMVC 是 Spring 框架针对 Web 提供的技术支持模块、并不是独立的框架

2. MVC 模式

MVC 模式全称为 Model-View-Controller(模型-视图-控制器)模式,它是一种 软件架构模式,其目标是将软件的用户界面(即前台页面)和业务逻辑分离,使代码具有更高的可扩展性、可复用性、可维护性以及灵活性。

通常情况下,一个完整的 Java Web 应用程序,其结构如下图所示:

image.png

MVC 模式将应用程序划分成==模型==(Model)、==视图==(View)、==控制器==(Controller)等三层,如下图所示:

image.png

Model(模型):

是应用程序的主体部分,主要由以下三部分组成:

  • 实体类 Bean:专门用来存储业务数据的对象,它们通常与数据库中的某个表对应,例如 User、Student 等。
  • 业务处理 Bean:指 Service 对象,专门用于处理业务逻辑。
  • 数据处理 Bean:指 Dao 或 Mapper 对象,专门用于处理数据库访问逻辑。

View(视图):

指在应用程序中专门用来与浏览器进行交互,展示数据的资源。在 Web 应用中,View 就是我们常说的前台页面,通常由 HTML、JSP、CSS、JavaScript 等组成。

Controller(控制器):

通常指的是,应用程序的 Servlet。它负责将用户的请求交给模型(Model)层进行处理,并将 Model 层处理完成的数据,返回给视图(View)渲染并展示给用户。

3. MVC 工作流程

  1. 用户发送请求到服务器。
  2. 在服务器中,请求被控制层(Controller)接收。
  3. Controller 调用相应的 Model 层处理请求。
  4. Model 层处理完毕将结果返回到 Controller。
  5. Controller 再根据 Model 返回的请求处理结果,找到相应的 View 视图。
  6. View 视图渲染数据后最终响应给浏览器。

4. 相关组件

组件提供者描述
DispatcherServlet框架提供前端控制器,它是整个 Spring MVC 流程控制中心,负责统一处理请求和响应,调用其他组件对用户请求进行处理。
HandlerMapping框架提供处理器映射器,根据请求的 url、method 等信息查找相应的 Handler。
Handler开发人员提供处理器,通常被称为 Controller(控制器)。它可以在 DispatcherServlet 的控制下,对具体的用户请求进行处理。
HandlerAdapter框架提供处理器适配器,负责调用具体的控制器方法,对用户发来的请求来进行处理。
ViewResolver框架提供视图解析器,其职责是对视图进行解析,得到相应的视图对象。常见的视图解析器有 ThymeleafViewResolver、InternalResourceViewResolver 等。
View开发人员提供视图,它作用是将模型(Model)数据通过页面展示给用户。

02. 机制说明

 整理了这份面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题

需要全套面试笔记【点击此处】即可免费获取

1. 环境

Spring 对 web 技术提供了两个模块:

  • spring-web -- 完成对 web 技术基础抽象和基础实现 (spring-context、spring-aop、spring-aspectj...)
  • spring-webmvc -- 完成对 servlet-api 规范的封装支持
  • pom.xml
 

xml

复制代码

<dependency>    <groupId>org.springframework</groupId>    <artifactId>spring-webmvc</artifactId>    <version>6.1.2</version> </dependency>

  • maven

image.png

image.png

2. DispatcherServlet

=== DispatcherServlet 是 SpringMVC 抽象的 Servlet 实现

  1. DispatcherServlet 通常 URL 配置为 /* 用于拦截整个项目中的请求
  2. DispatcherServlet 拦截 URL 后、会将 URL 匹配 Controller 类中的 URL 注解方法
  3. 匹配 URL 方法完成后、SpringMVC 会提取 URL 中的参数、再对方法进行反射
  4. 并在解析 URL 参数过程中、预留了可插拔的参数校验手段
  5. 反射 URL 方法执行的过程中、预留了可插拔的拦截器手段、用于控制该执行过程
  6. URL 方法执行拿到结果后、还会检查 URL 方法配置、再决定以什么方式返回给客户端
  7. 返回方式有 视图页面跳转、请求转发、重定向、返回 JSON 数据 等
  8. 在整个拦截 URL 执行的过程中 被全局 try 捕获异常、异常还提供全局异常处理

3. JSON

=== SpringMVC 若配置 URL 方法返回值为 JSON 时、默认会使用 Jackson

  • 特别的
  • SpringMVC 是直接使用 Jackson 基础常用 Api、将 URL 方法返回值处理返回
  • 一般不用过多考虑 SpringMVC & Jackson 版本对应问题
  • pom.xml
 

xml

复制代码

<dependency>    <groupId>com.fasterxml.jackson.core</groupId>    <artifactId>jackson-databind</artifactId>    <version>2.16.1</version> </dependency> ​ <!--       <dependency>--> <!--           <groupId>com.fasterxml.jackson.core</groupId>--> <!--           <artifactId>jackson-annotations</artifactId>--> <!--           <version>2.16.1</version>--> <!--       </dependency>--> ​ <!--       <dependency>--> <!--           <groupId>com.fasterxml.jackson.core</groupId>--> <!--           <artifactId>jackson-core</artifactId>--> <!--           <version>2.16.1</version>--> <!--       </dependency>--> ​ <!-- 选择 jackson 对 java8 time 类型支持 -->     <dependency>    <groupId>com.fasterxml.jackson.datatype</groupId>    <artifactId>jackson-datatype-jsr310</artifactId>    <version>2.16.1</version> </dependency> ​ <!-- 选择 jackson 对 java8 stream 等 类型支持 -->   <dependency>    <groupId>com.fasterxml.jackson.datatype</groupId>    <artifactId>jackson-datatype-jdk8</artifactId>    <version>2.16.1</version> </dependency> ​ <!-- 选择 jackson 对 类构造方法参数名 方法参数名 反射支持 -->     <dependency>    <groupId>com.fasterxml.jackson.module</groupId>    <artifactId>jackson-module-parameter-names</artifactId>    <version>2.16.1</version> </dependency>

4. 上下文入口

=== AbstractAnnotationConfigDispatcherServletInitializer 提供三个方法

  • getRootConfigClasses 读取公共的 ROOT 上下文配置 [通常不需要]
  • getServletConfigClasses 从 DispatcherServlet 开始建立上下文
  • getServletMappings 为 DispatcherServlet 绑定 URL
  • AbstractAnnotationConfigDispatcherServletInitializer.java
 

java

复制代码

public class XyzWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { ​ @Override protected Class<?>[] getRootConfigClasses() { return new Class<?>[] { RootConfig.class }; } ​ @Override protected Class<?>[] getServletConfigClasses() { return new Class<?>[] { App1Config.class }; } ​ @Override protected String[] getServletMappings() { return new String[] { "/*" }; }   }

03. 整合

1. 环境

  • pom.xml
 

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 https://maven.apache.org/xsd/maven-4.0.0.xsd">    <modelVersion>4.0.0</modelVersion> ​    <groupId>com.zpark</groupId>    <artifactId>spring-mvc</artifactId>    <version>1.0-SNAPSHOT</version>    <name>spring-mvc</name>    <packaging>war</packaging> ​    <properties>        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>        <maven.compiler.target>17</maven.compiler.target>        <maven.compiler.source>17</maven.compiler.source>        <junit.version>5.9.2</junit.version>    </properties> ​    <dependencies>        <!--servlet-->        <dependency>            <groupId>jakarta.servlet</groupId>            <artifactId>jakarta.servlet-api</artifactId>            <version>6.0.0</version>            <scope>provided</scope>        </dependency>        <!--spring mvc-->        <dependency>            <groupId>org.springframework</groupId>            <artifactId>spring-webmvc</artifactId>            <version>6.1.2</version>        </dependency> ​        <!--jackson-->        <dependency>            <groupId>com.fasterxml.jackson.core</groupId>            <artifactId>jackson-databind</artifactId>            <version>2.16.1</version>        </dependency>        <dependency>            <groupId>com.fasterxml.jackson.datatype</groupId>            <artifactId>jackson-datatype-jsr310</artifactId>            <version>2.16.1</version>        </dependency>        <dependency>            <groupId>com.fasterxml.jackson.datatype</groupId>            <artifactId>jackson-datatype-jdk8</artifactId>            <version>2.16.1</version>        </dependency>        <dependency>            <groupId>com.fasterxml.jackson.module</groupId>            <artifactId>jackson-module-parameter-names</artifactId>            <version>2.16.1</version>        </dependency> ​    </dependencies> ​    <build>        <plugins>                        <plugin>                <groupId>org.apache.maven.plugins</groupId>                <artifactId>maven-compiler-plugin</artifactId>                <version>3.1</version>                <configuration>                    <compilerArgs>                        <compilerArg>-parameters</compilerArg>                    </compilerArgs>                </configuration>            </plugin>                        <plugin>                <groupId>org.apache.maven.plugins</groupId>                <artifactId>maven-war-plugin</artifactId>                <version>3.3.2</version>            </plugin>            <plugin>                <groupId>org.codehaus.cargo</groupId>                <artifactId>cargo-maven3-plugin</artifactId>                <version>1.10.10</version>                <configuration>                    <container>                        <containerId>tomcat10x</containerId>                        <type>embedded</type>                    </container>                    <configuration>                        <properties>                            <cargo.servlet.port>8080</cargo.servlet.port>                        </properties>                    </configuration>                    <deployables>                        <deployable>                            <type>war</type>                            <location>                               ${project.build.directory}/${project.build.finalName}.war                            </location>                            <properties>                                <context>/springmvc</context>                            </properties>                        </deployable>                    </deployables>                </configuration>            </plugin> ​        </plugins>    </build> </project>

2. 配置

  • SpringMvcServletInitializer.java
 

java

复制代码

package com.zpark.springmvc.conf; ​ import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; ​ public class SpringMvcServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {    @Override    protected Class<?>[] getRootConfigClasses() {        return new Class[0];   } ​    // 读取 Spring WEB 容器 配置类    @Override    protected Class<?>[] getServletConfigClasses() {        return new Class[]{SpringWebContextConfiguration.class};   } ​    // 注册 DispatcherServlet 配置 URL /*    @Override    protected String[] getServletMappings() {        return new String[]{"/*"};   } ​ }

  • SpringWebContextConfiguration.java
 

java

复制代码

package com.zpark.springmvc.conf; ​ import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.EnableWebMvc; ​ @Configuration @ComponentScan(basePackages = "com.zpark.springmvc") // 1. 识别 Web 相关注解 // 2. 为 DispatcherServlet 注册相关组件到容器中 @EnableWebMvc public class SpringWebContextConfiguration { }

3. 响应 JSON

  • UserController.java
 

java

复制代码

package com.zpark.springmvc.controller; ​ import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ResponseBody; ​ import java.util.Collections; import java.util.Map; ​ @Controller public class WelcomeController { ​    //返回普通文本    @GetMapping(value = "/welcome", produces = "text/plain;charset=utf-8")    @ResponseBody    public String welcome(){        return "你好 SpringMVC";   } ​    //返回json    @GetMapping("/welcomeJson")    @ResponseBody    public Map<String, String> welcomeJson(){        return Collections.singletonMap("msg", "你好 SpringMVC");   } ​ }

4. 响应视图

=== SpringMVC 处理静态资源有两种方式: DispatcherServlet 处理 | 交由 DefaultServlet

  • 配置静态资源处理
  • 方式一(DispatcherServlet 处理):视图解析器 InternalResourceViewResolver
  • 方式二(DefaultServlet 处理):静态资源(webapp直接放行)
  • SpringWebContextConfiguration.java
 

java

复制代码

package com.zpark.springmvc.conf; ​ import org.springframework.context.annotation.Bean; 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.WebMvcConfigurer; import org.springframework.web.servlet.view.InternalResourceViewResolver; ​ @Configuration @ComponentScan(basePackages = "com.zpark.springmvc") @EnableWebMvc public class SpringWebContextConfiguration implements WebMvcConfigurer {     ​    @Override    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {        configurer.enable();   }   ​    @Bean    public InternalResourceViewResolver viewResolver(){        System.out.println("viewResolver...");        return new InternalResourceViewResolver("/view/", ".html");   } ​ }

  • 视图层
  • WelcomeController.java
 

java

复制代码

package com.zpark.springmvc.controller; ​ import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ResponseBody; ​ import java.util.Collections; import java.util.Map; ​ @Controller public class WelcomeController {        //... ... ​    //返回页面    @GetMapping("/welcomeView")    public String welcomeView(){        return "welcome";   } } ​

  • 页面

  • src/main/webapp/view/welcome.html

04. URL 注解

=== SpringMVC 主要提供以下注解用于绑定 URL 映射方法关系

  • @Controller:声明在类上,被注解的类会被 SpringMVC 识别为控制器用于接收、响应请求(单例)(响应页面视图
  • @ResponseBody:可声明在 类/方法 上 (响应 json 数据
  • @RestController:声明在类上,= @Controller + @ResponseBody(响应 json 数据
  • @RequestMapping -- 类上为总 URL | 方法上为 方法次 URL 默认接收 GET 请求
  • @GetMapping -- 只能在方法上 只接收 GET 请求
  • @PostMapping -- 只能在方法上 只接收 POST 请求
  • @PutMapping -- 接收客户端 PUT 请求方式的 URL
  • @DeleteMapping -- 接收客户端 DELETE 请求方式的 URL

==示例==

  • UsageController.java
 

java

复制代码

package com.zpark.springmvc.controller; ​ import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; ​ // @Controller @RestController // @RequestMapping("/usage") public class UsageController { ​    // @RequestMapping("/select")    @GetMapping("/select")    @ResponseBody    public String select(){        return "select... ";   } ​    @PostMapping("/update")    @ResponseBody    public String update(){        return "update...";   } ​    @PutMapping("/add")    @ResponseBody    public String add(){        return "add...";   } ​    @DeleteMapping("/delete")    @ResponseBody    public String delete(){        return "delete...";   } ​ }

05. 参数处理

=== SpringMVC 将 URL 参数解析后可将其指定传递给当前 对应 URL 方法的参数

  • SpringMVC 要求 URL 参数名称对应 URL 方法参数名称

  • SpringMVC 支持 @RequestParam("URL 参数名") 指定注入给注解参数变量

  • SpringMVC 支持对参数进行基础格式化和自定义格式化 @DateTimeFormat 可格式日期时间

    • 参数接收格式化 和 URL 方法返回值格式化没半毛钱关系 各是各
    • 如参数日期格式化是 SpringMVC | 方法返回值对象中的日期是 Jackson API
  • SpringMVC 支持以对象成员方式接收参数

  • SpringMVC 解析参数时可插拔参数校验、支持 Bean validation 规范

  • SpringMVC 默认只接收表单格式数据、如果参数是 JSON 必须使用 @RequestBody 注解方法参数

==示例==

  • ParamController.java
 

java

复制代码

package com.zpark.springmvc.controller; ​ import com.fasterxml.jackson.annotation.JsonFormat; import com.zpark.springmvc.entity.User; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.web.bind.annotation.*; ​ import java.time.LocalDateTime; import java.util.Map; ​ @RestController @RequestMapping("/param") public class ParamController { ​    @PostMapping("/url1")    public Map<String, Object> url1(String username,                                    @RequestParam("sex") int gender,                                    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")                                    LocalDateTime dateTime){        return Map.of("用户名", username, "性别", gender, "日期", dateTime);   } ​    @PostMapping("/url2")    public Map<String, Object> url2(User user){        System.out.println("user = " + user);        return Map.of("user", user);   } ​    // 参数为 json 格式    @PostMapping("/url3")    public Map<String, Object> url3(@RequestBody User user){        System.out.println("user = " + user);        return Map.of("user", user);   } ​ }

  • User.java
 

java

复制代码

package com.zpark.springmvc.entity; ​ import com.fasterxml.jackson.annotation.JsonFormat; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.Accessors; import org.springframework.format.annotation.DateTimeFormat; ​ import java.time.LocalDateTime; ​ @Data @NoArgsConstructor @AllArgsConstructor @Accessors(chain = true) public class User {    private Integer id;    private String username;    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")    private LocalDateTime dateTime; }

06. 占位参数

=== 占位参数是对 restful 风格 URL 的参数支持

1. RestFul

=== REST,即 Representational State Transfer 的缩写,对这个词组的翻译是表现层状态转化。

RESTful 是一种软件设计风格,就是目前最流行的一种互联网软件架构。它结构清晰、符合标准、易于理解、扩展方便,所以正得到越来越多网站的采用。

REST(Representational State Transfer)表述性状态转移是一组架构约束条件和原则。满足这些约束条件和原则的应用程序或设计就是RESTful。需要注意的是,REST 是设计风格而不是标准。REST 通常基于使用 HTTP,URI,和 XML(标准通用标记语言下的一个子集)以及 HTML(标准通用标记语言下的一个应用)这些现有的广泛流行的协议和标准。

==示例==

 

markdown

复制代码

//查询所有人员(传统) localhost:8088/user/findAll 请求方式:GET //查询所有人员(RESTful) localhost:8088/users    请求方式:GET ​ //修改人员(传统) localhost:8088/user/update 请求方式:POST //修改人员(RESTful) localhost:8088/users    请求方式:POST ​ //添加人员(传统) localhost:8088/user/add    请求方式:PUT //添加人员(RESTful) localhost:8088/users    请求方式:PUT ​ //删除人员(传统) localhost:8088/user/delete    请求方式:DELETE //删除人员(RESTful) localhost:8088/users    请求方式:DELETE

我们通常称地址栏中输入的地址为 URI(Uniform Resource Identifier),翻译成中文就是统一资源标识符

资源:我们在浏览器页面上看到的东西都可以称之为资源,比如图片,文字,语音等等。

URI:就是用于定位这些资源的位置的,RESTful 风格的接口中只出现了表示资源的名词,关于这个资源的操作,通过HTTP内置的几种请求类型来区分。同一个路径/users,因为请求方式的不同,而去找寻不同的接口,完成对资源状态的转变。

2. 设计规范

URI = /path/[?query][#fragment]

==URL 命名规范==

1:不用大写字母,所有单词使用英文且小写。

2:连字符用中杠"-“而不用下杠”_"

3:正确使用 "/"表示层级关系,URL的层级不要过深,并且越靠前的层级应该相对越稳定

4:结尾不要包含正斜杠分隔符"/"

5:资源表示用复数不要用单数

6:不要使用文件扩展名

3. 接口传参

接口传参使用 ==@PathVariable==、或者==@RequestBody==。

@PathVariable("占位参数名称") 映射URL绑定的占位符。

URL = http://IP:port/path/{参数名}

  • url: get/user?username=admin | restful: /get/user/admin
  • springmvc restful url: /get/user/{username}
  • @PathVariable("占位参数名称")
  • PathVariableController.java
 

java

复制代码

package com.zpark.springmvc.controller; ​ ​ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; ​ import java.util.Map; ​ @RestController @RequestMapping("/param") public class ParamController { ​    //http://localhost:8080/springmvc/param/url4/admin    @GetMapping("/url4/{username}")    public Map<String, Object> url4(@PathVariable("username") String username){        return Map.of("username", username);   } ​ }

07. 统一视图

=== 统一视图 使得全站总是返回统一的数据结构、更有利于系统维护

==结果封装:示例==

  • JsonHelper.java
 

java

复制代码

package com.zpark.springmvc.utils; ​ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.fasterxml.jackson.module.paramnames.ParameterNamesModule; import jakarta.servlet.http.HttpServletResponse; ​ import java.io.IOException; import java.io.PrintWriter; import java.util.Objects; ​ public final class JsonHelper { ​    private JsonHelper(){} ​    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); ​    static {        OBJECT_MAPPER.registerModule(new JavaTimeModule())               .registerModule(new Jdk8Module())               .registerModule(new ParameterNamesModule());   } ​ ​    public static String toJSON(Object object){        Objects.requireNonNull(object, "object parameter is null");        try {            return OBJECT_MAPPER.writeValueAsString(object);       } catch (JsonProcessingException e) {            throw new RuntimeException(e);       }   } ​ ​    public static void responseToClient(HttpServletResponse response, Object object){        response.setContentType("application/json");        response.setCharacterEncoding("utf-8");        try {            PrintWriter writer = response.getWriter();            writer.println(toJSON(object));            writer.close();       } catch (IOException e) {            throw new RuntimeException(e);       }   } ​ }

  • JsonBody.java
 

java

复制代码

package com.zpark.springmvc.utils; ​ import jakarta.servlet.http.HttpServletResponse; ​ import java.util.HashMap; import java.util.Map; import java.util.Objects; ​ public class JsonBody { ​        private int code;        private boolean success;        private String msg;        private Map<String, Object> data; ​    public int getCode() {        return code;   } ​    public boolean isSuccess() {        return success;   } ​    public String getMsg() {        return msg;   } ​    public Map<String, Object> getData() {        return data;   } ​    private JsonBody(int code, boolean success, String msg) {        this.code = code;        this.success = success;        this.msg = msg;   } ​        public static JsonBody success(int code, String msg){        return new JsonBody(code, true, msg);   } ​        public static JsonBody failed(int code, String msg){        return new JsonBody(code, false, msg);   } ​        public void toClient(HttpServletResponse response){        JsonHelper.responseToClient(response, this);   } ​      public JsonBody add(String key, Object value) {        if (data == null) {            data = new HashMap<>();       }        data.put(key, value); ​        return this;   } }

==使用示例==

  • UserService.java
 

java

复制代码

package com.zpark.springmvc.service; ​ import com.zpark.springmvc.entity.User; import org.springframework.stereotype.Service; ​ import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; ​ @Service public class UserService { ​    private static List<User> userList = new ArrayList(); ​    static {        userList.add(new User(1, "张三", LocalDateTime.now()));        userList.add(new User(2, "李四", LocalDateTime.now()));        userList.add(new User(3, "王五", LocalDateTime.now()));        userList.add(new User(4, "赵六", LocalDateTime.now()));   } ​    // 查询所有用户    public List<User> findAll(){        return userList;   } ​    //根据 id 查询单个用户    public User findById(Integer id){        List<User> collect = userList.stream()               .filter(t -> id.equals(t.getId()))               .toList(); ​        return collect.isEmpty() ? null : collect.get(0);   } ​    //新增一个用户    public Boolean addUser(User user){        return userList.add(user);   } ​    //修改一个用户    public Boolean updateUser(User user){        User oldUser = findById(user.getId());        oldUser.setUsername(user.getUsername()).setDateTime(user.getDateTime());        return true;   } ​    //删除一个用户    public Boolean removeUser(Integer id){        return userList.remove(findById(id));   } }

  • UserController.java
 

java

复制代码

package com.zpark.springmvc.controller; ​ import com.zpark.springmvc.entity.User; import com.zpark.springmvc.service.UserService; import com.zpark.springmvc.utils.JsonBody; import org.springframework.web.bind.annotation.*; ​ import java.util.Objects; ​ @RestController @RequestMapping("/users") public record UserController(UserService userService) { ​    //查询所有用户    @GetMapping    public JsonBody findAll(){        return JsonBody.success(200, "查询成功").add("userList",  userService.findAll());   } ​    //根据id查询一条用户 /users/1    @GetMapping("{id}")    public JsonBody findById(@PathVariable Integer id){        User user = userService.findById(id);        return Objects.nonNull(user) ?                JsonBody.success(200, "查询成功").add("user", user) :                JsonBody.failed(404, "查询不到该用户");   } ​    //新增一个用户    @PutMapping    public JsonBody addUser(User user){        return userService.addUser(user) ?                JsonBody.success(200, "新增成功").add("user", user) :                JsonBody.failed(404, "新增失败");   } ​    //修改一条数据    @PostMapping    public JsonBody updateUser(@RequestBody User user){        return userService.updateUser(user) ?                JsonBody.success(200, "修改成功").add("user", user) :                JsonBody.failed(404, "修改失败");   } ​    //删除一条数据    @DeleteMapping("{id}")    public JsonBody removeUser(@PathVariable Integer id){        return userService.removeUser(id) ?                JsonBody.success(200, "删除成功") :                JsonBody.failed(404, "修改失败");   } ​ }

[!WARNING]

=== 浏览器表单提交 method 方式只有 get | post

=== 表单若想使用 put、delete 等提交方式,需开启 springmvc 表单隐藏域参数支持

08. 全局异常

=== Spring MVC 反射执行 Controller 中的方法时预留了异常处理机制

=== 全局异常是因为 DispatcherServlet 拦截了当次请求,对于一次 request 请求后续发生的所有异常都能被 DispatcherServlet 捕获

  • @RestControllerAdvice、@ControllerAdvice 表示发生异常所执行的 Controller
  • @ExceptionHandler(异常类.class) 用于处理指定异常
  • @ExceptionHandler(Exception.class) 用于处理通用异常

==示例==

 

java

复制代码

package com.zpark.springmvc.exception; ​ import com.zpark.springmvc.utils.JsonBody; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; ​ @ControllerAdvice public class GlobalExceptionController { ​    //异常类型匹配越细,优先级越高    @ExceptionHandler(Exception.class)    @ResponseBody    public JsonBody handleExceptionToJson(Exception exception) {        System.out.println("全局异常错误信息:" + exception.getMessage());        return JsonBody.failed(500, "服务器错误,请联系管理员:" + exception.getMessage());   } ​    @ExceptionHandler(ArithmeticException.class)    @ResponseBody    public JsonBody handleArithmeticExceptionToJson(Exception exception) {        System.out.println("算术异常错误信息:" + exception.getMessage());        return JsonBody.failed(500, "算术异常:" + exception.getMessage());   } ​ }

09. 数据校验

=== 数据校验总是校验外部来源数据

=== Jakarta EE 提供了一套校验数据的方式和机制被称为 Bean Validation 规范

=== Hibernate Validator 对该规范做了实现

  • springMVC 提供内置校验手段 & 支持 Bean 校验规范
  • Spring 容器可配置 LocalValidatorFactoryBean 来指定校验接口
  • LocalValidatorFactoryBean 能够自动找到 Bean 规范实现 [只要你有 Hibernate Validator 环境即可]
  • Bean Validation 支持对象 | 方法参数 | 分组 等复杂校验 请参考规范 [与 spring 无关]

1. 环境

 

xml

复制代码

<dependency>    <groupId>org.hibernate.validator</groupId>    <artifactId>hibernate-validator</artifactId>    <version>8.0.1.Final</version> </dependency>

  • dependency

image.png

2. 配置

=== LocalValidatorFactoryBean

  • LocalValidatorFactoryBean.java
 

java

复制代码

package com.zpark.springmvc.conf; ​ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; ​ @Configuration public class HibernateValidatorConfiguration { ​    @Bean    public LocalValidatorFactoryBean validator() {        return new LocalValidatorFactoryBean();   } ​ }

3. 运用

=== 校验实体类

  • Person.java
 

java

复制代码

package com.zpark.springmvc.entity; ​ import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Size; import lombok.Data; import org.hibernate.validator.constraints.Length; ​ import java.io.Serializable; ​ @Data public class Person implements Serializable { ​ ​    @NotNull(message = "账号不能为空")    @Length(min = 1, max = 12, message = "账号不符合格式")    private String account; ​    @NotNull(message = "密码不能为空")    @Length(min = 6, max = 16, message = "密码格式不正确")    private String password; ​    @NotNull(message = "手机号不能为空")    @Pattern(regexp = "1[3456789]\d{9}", message = "手机号不符合格式")    private String phone;     }

=== Spring MVC 校验 需使用 @Validated 注解通知 Spring 校验

  • 若请求方法上没有 BindingResult 参数时、校验不通过时 SpringMVC 会走到异常机制
  • 若有 BindingResult 参数,则 BindingResult 参数必须写在校验参数的后面
  • BindingResult 参数用于获取校验不通过的信息且有参数时、不会抛出异常

==示例==

  • PersonController.java
 

java

复制代码

package com.zpark.springmvc.controller; ​ import com.zpark.springmvc.entity.Person; import com.zpark.springmvc.utils.JsonBody; import org.springframework.validation.BindingResult; import org.springframework.validation.FieldError; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; ​ import java.util.List; ​ @RestController @RequestMapping("/persons") public class PersonController { ​    @PostMapping("/register")    public JsonBody register(@Validated Person person, BindingResult bindingResult){ ​        System.out.println("person = " + person); ​        //异常被自己处理了 (很明显不应该自己处理)        if (bindingResult.hasErrors()){            JsonBody jsonBody = JsonBody.failed(500, "参数错误");            //获取参数错误的相关属性列表            List<FieldError> fieldErrors = bindingResult.getFieldErrors();            fieldErrors.forEach(f -> {                String field = f.getField();                String msg = f.getDefaultMessage();                jsonBody.add(field, msg);           }); ​            return jsonBody;       } ​        return JsonBody.success(200, "参数正确").add("person", person); ​   } ​ }

==测试==

image.png

=== 异常应当交由 springmvc 统一处理,抛出的异常为 org.springframework.validation.BindException

  • GlobalExceptionController.java
 

java

复制代码

@ExceptionHandler(BindException.class) @ResponseBody public JsonBody handleBindException(BindException bindException){ ​    JsonBody jsonBody = JsonBody.failed(500, "参数错误");    //获取参数错误的相关属性列表    List<FieldError> fieldErrors = bindException.getFieldErrors();    fieldErrors.forEach(f -> {        String field = f.getField();        String msg = f.getDefaultMessage();        jsonBody.add(field, msg);   }); ​    return jsonBody; }

  • PersonController.java
 

java

复制代码

@PostMapping("/register") public JsonBody register(@Validated Person person){ ​    System.out.println("person = " + person); ​    return JsonBody.success(200, "参数正确").add("person", person); ​ }

4. 散参数校验

=== @Validated 需要注解在 Controller 类上 & 抛出规范异常 ConstraintViolationException

==示例==

  • HibernateValidatorConfiguration.java
 

java

复制代码

package com.zpark.springmvc.conf; ​ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; import org.springframework.validation.beanvalidation.MethodValidationPostProcessor; ​ @Configuration public class HibernateValidatorConfiguration { ​    @Bean    public LocalValidatorFactoryBean validator() {        return new LocalValidatorFactoryBean();   } ​    //散参数校验支持    @Bean    public MethodValidationPostProcessor validationPostProcessor() {        MethodValidationPostProcessor processor = new MethodValidationPostProcessor();        // 将 ConstraintViolationException 转为 非法状态异常 没用 [拿不到校验信息]        // processor.setAdaptConstraintViolations(true);        return processor;   } ​ }

  • PersonController.java
 

java

复制代码

package com.zpark.springmvc.controller; ​ import com.zpark.springmvc.entity.Person; import com.zpark.springmvc.utils.JsonBody; ​ import jakarta.validation.constraints.Max; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; ​ ​ @RestController @RequestMapping("/persons") @Validated public class PersonController { ​    @PostMapping("/info")    public JsonBody info(@Max(value = 200, message = "宽度不能大于200px") int weight,                         @Max(value = 200, message = "高度不能大于200px")int height){        return JsonBody.success(200, "校验通过")               .add("weight", weight)               .add("height", height);   } ​ }

  • GlobalExceptionHandler.java
 

java

复制代码

package com.zpark.springmvc.exception; ​ import com.zpark.springmvc.utils.JsonBody; import jakarta.validation.ConstraintViolation; import jakarta.validation.ConstraintViolationException; import jakarta.validation.Path; import org.springframework.validation.BindException; import org.springframework.validation.FieldError; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; ​ import java.util.Iterator; import java.util.List; import java.util.Set; ​ @ControllerAdvice public class GlobalExceptionController { ​    //... ​    // 散参数校验异常    @ExceptionHandler(ConstraintViolationException.class)    @ResponseBody    public JsonBody handleConstraintViolationException(ConstraintViolationException cve) { ​        JsonBody body = JsonBody.failed(500, "散参数校验不通过"); ​        Set<ConstraintViolation<?>> violations = cve.getConstraintViolations(); ​        // for (ConstraintViolation<?> v : violations) {        //     String filed = v.getPropertyPath().toString();        //     String msg = v.getMessageTemplate();        //     body.add(filed, msg);        // } ​        violations.forEach(v -> {            // String filed = v.getPropertyPath().toString(); //不好使,返回的是“方法名.属性名”            String filed = v.getPropertyPath().toString().split("\.")[1];            String msg = v.getMessageTemplate();            body.add(filed, msg);       }); ​        return body;   } ​ }

5. 分组校验

=== 默认 Bean Validation 会校验实体类中所有的注解

  • 分组校验是指:指定校验实体类中的部分注解
  • 分组校验只对 javabean 有效
  • 具备分组的属性不会被默认校验

==示例==:登录校验账户名、密码;注册校验账户名、密码、手机号

  • Person.java
 

java

复制代码

package com.zpark.springmvc.entity; ​ import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Size; import lombok.Data; import org.hibernate.validator.constraints.Length; ​ import java.io.Serializable; ​ @Data public class Person implements Serializable { ​ ​    @NotNull(message = "账号不能为空", groups = {PersonRegister.class, PersonLogin.class})    @Length(min = 1, max = 12, message = "账号不符合格式" , groups = {PersonRegister.class, PersonLogin.class})    private String account; ​    @NotNull(message = "密码不能为空" , groups = {PersonRegister.class, PersonLogin.class})    @Length(min = 6, max = 16, message = "密码格式不正确" , groups = {PersonRegister.class, PersonLogin.class})    private String password; ​    @NotNull(message = "手机号不能为空" , groups = {PersonRegister.class})    @Pattern(regexp = "1[3456789]\d{9}", message = "手机号不符合格式" , groups = {PersonRegister.class})    private String phone; ​ ​    public static interface PersonLogin { ​   } ​    public static interface PersonRegister { ​   } }

  • PersonController.java
  • @Validated(value = { 指定分组类型1.class, 指定分组类型2.class })
 

java

复制代码

package com.zpark.springmvc.controller; ​ import com.zpark.springmvc.entity.Person; import com.zpark.springmvc.utils.JsonBody; ​ import jakarta.validation.constraints.Max; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; ​ ​ @RestController @RequestMapping("/persons") @Validated public class PersonController { ​    @PostMapping("/register")    public JsonBody register(@Validated(Person.PersonRegister.class) Person person){        return JsonBody.success(200, "参数正确").add("person", person); ​   } ​    @PostMapping("/login")    public JsonBody login(@Validated(Person.PersonLogin.class) Person person){        return JsonBody.success(200, "参数正确").add("person", person); ​   } ​ }

10. Easy-Captcha

=== Easy-Captcha 是用于生成验证码图片的 API、与 Servlet API 无关

=== 官网:gitee.com/ele-admin/E…

  • 验证码

image.png

  • pom.xml
 

xml

复制代码

<dependency> <groupId>com.github.whvcse</groupId> <artifactId>easy-captcha</artifactId> <version>1.6.2</version> </dependency>

  • http response header
 

java

复制代码

response.setContentType("image/gif"); response.setHeader("Pragma", "No-cache"); response.setHeader("Cache-Control", "no-cache"); response.setDateHeader("Expires", 0);

  • CaptchaController.java
 

java

复制代码

package com.zpark.springmvc.controller; import com.wf.captcha.ChineseCaptcha; import com.wf.captcha.ChineseGifCaptcha; import com.wf.captcha.GifCaptcha; import com.wf.captcha.utils.CaptchaUtil; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.io.IOException; import java.util.Objects; @RestController @RequestMapping("/captcha") public class CaptchaController { //建议加参数校验 @GetMapping public void captcha(Integer width, Integer height, HttpServletRequest request, HttpServletResponse response) throws IOException { // 设置请求头为输出图片类型 response.setContentType("image/gif"); response.setHeader("Pragma", "No-cache"); response.setHeader("Cache-Control", "no-cache"); response.setDateHeader("Expires", 0); // 三个参数分别为宽、高、验证码位数 // 中文类型 width = Objects.nonNull(width) ? width : 130; height = Objects.nonNull(height) ? height : 48; ChineseCaptcha captcha = new ChineseCaptcha(width, height, 4); // gif类型 // GifCaptcha captcha = new GifCaptcha(width, height, 4); // 中文gif类型 // ChineseGifCaptcha captcha = new ChineseGifCaptcha(width, height, 4); String code = captcha.text(); System.out.println("code = " + code); request.getSession().setAttribute("code", code); try { captcha.out(response.getOutputStream()); } catch (IOException e) { throw new RuntimeException(e); } } }

  • login.html

11. Filter

=== Filter 是 JAVA WEB 三大组件之一、执行优先级比 SpringMVC 高 [若是拦截到]

  • 尽量避免在 SpringMVC 环境去使用 Filter、可使用 SpringMVC 拦截实现相同功能

1. 注册方式

=== Filter 相比 Servlet 有 四种 注册方式

  • Filter 注册方式: web.xml | 注解 | API | 指定注册到 Servlet
  • web.xml
 

xml

复制代码

<filter> <filter-name>filterName</filter-name> <filter-class>x.y.z.filterName</filter-class> </filter> <filter-mapping> <filter-name>filterName</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>

  • @WebFilter
 

java

复制代码

package com.zpark.springmvc.filter; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebFilter; import jakarta.servlet.http.HttpFilter; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; @WebFilter("/*") public class CustomFilter extends HttpFilter { @Override protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { super.doFilter(request, response, chain); } }

  • api
 

java

复制代码

ServletContext servletContext = request.getServletContext(); FilterRegistration.Dynamic registration = servletContext.addFilter("filterName", "x.y.z.FilterName"); registration.addMappingForUrlPatterns(拦截请求类型, true, "url");

  • api [拦截指定名称的某个或多个 servlet]
 

java

复制代码

ServletContext servletContext = request.getServletContext(); FilterRegistration.Dynamic registration = servletContext.addFilter("filterName", "x.y.z.FilterName"); registration.addMappingForServletNames(拦截请求类型, true, "指定拦截的servlet名称");

2. 自定义注册

=== 业务指定 Filter 自行注册即可 | SpringMVC 提供的 Filter [编码|跨域]

  • CustomFilter.java [自定义推荐]
 

java

复制代码

package com.zpark.springmvc.filter; import jakarta.servlet.FilterChain; import jakarta.servlet.FilterRegistration; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebFilter; import jakarta.servlet.http.HttpFilter; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; @WebFilter("/*") public class CustomFilter extends HttpFilter { @Override protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println("do something..."); super.doFilter(request, response, chain); } }

  • SpringMvcServletInitializer.java [springMVC 提供的 filter 由以下方式注册]
 

java

复制代码

package com.zpark.springmvc.conf; import jakarta.servlet.Filter; import org.springframework.web.filter.CharacterEncodingFilter; import org.springframework.web.filter.HiddenHttpMethodFilter; import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; public class SpringMvcServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasses() { return new Class[0]; } // 读取 Spring WEB 容器 配置类 @Override protected Class<?>[] getServletConfigClasses() { return new Class[]{SpringWebContextConfiguration.class}; } // 注册 DispatcherServlet 配置 URL /* @Override protected String[] getServletMappings() { return new String[]{"/*"}; } // 注册 Filter // CharacterEncodingFilter Spring 提供全栈编码设置 默认 URL 为 /* @Override protected Filter[] getServletFilters() { //tomcat9 已经是 utf-8 [↓可省略] CharacterEncodingFilter encodingFilter = new CharacterEncodingFilter("UTF-8", true, true); return new Filter[]{encodingFilter}; } }

  • 不好使 spring 不会帮你注册给 servlet 容器
 

java

复制代码

@Bean public CustomFilter customFilter() { System.out.println("======================================++"); return new CustomFilter(); }

12. 拦截器

=== SpringMVC 拦截器是 DispatcherServlet 执行过程中的拦截机制 不是 Filter

filter("/aa") -> DispatcherServlet ("/*") -> springmvc拦截器 -> controller

1. 机制

  • HandlerInterceptor -- 接口用于实现拦截功能

    • preHandle -- URL 方法执行之前 [前置拦截]
    • postHandle -- URL 方法反射执行之后、但未取得返回值做响应
    • afterCompletion -- 响应结束之后
  • Interceptor 允许拦截指定URL、同时也可指定排除 URL 相比 Filter 更好用
  • Interceptor 只允许拦截 Controller URL
  • 开发者尽量避免使用过多拦截器导致 DispatcherServlet 执行过程过于复杂
  • 不要让拦截器有交叉逻辑、若是有交叉逻辑、请合并拦截器设计
  • 官网

image.png

2. 应用

==声明拦截器==

  • GlobalInterceptor.java
 

java

复制代码

package com.zpark.springmvc.interceptor; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; /** * 拦截器行为 */ public class GlobalInterceptor implements HandlerInterceptor { //前置拦截 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("前置拦截..."); String status = request.getParameter("status"); return "true".equals(status); } // Controller 方法执行完成、但未响应 @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("方法执行完成、但未响应..."); } // 请求响应结束后 @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("请求响应结束后..."); } }

==注册拦截器==

  • SpringWebContextConfiguration.java
 

java

复制代码

package com.zpark.springmvc.conf; import com.zpark.springmvc.interceptor.GlobalInterceptor; import org.springframework.context.annotation.Bean; 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.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.view.InternalResourceViewResolver; @Configuration @ComponentScan(basePackages = "com.zpark.springmvc") @EnableWebMvc public class SpringWebContextConfiguration implements WebMvcConfigurer { /** * 拦截器拦截规则 * @param registry */ @Override public void addInterceptors(InterceptorRegistry registry) { System.out.println("注册拦截器..."); registry.addInterceptor(new GlobalInterceptor()) .addPathPatterns("/**") .excludePathPatterns("/users/**", "/captcha"); } }

13. 文件下载上传

=== SpringMVC 中下载上传都可使用 Servlet 完成、也可使用 SpringMVC 封装机制完成

=== springMVC 提供 HTTP 协议处理相关的类封装、同时提供文件下载上传的支持

  • org.springframework.http.HttpHeaders 封装了 HTTP 响应头
  • org.springframework.http.MediaType 封装了 MIME 类型
  • org.springframework.http.HttpStatus 封装了 HTTP 状态码
  • springMVC 文件下载可直接使用 servlet-api 或者 ResponseEntity 类
  • springMVC 文件上传可直接使用 servlet-api 或者 MultipartFile 类型接收文件

1. 文件下载

=== 此处演示两种方式、及 ResponseEntity 类说明

  • FileController.java
 

java

复制代码

package com.zpark.springmvc.controller; import com.zpark.springmvc.utils.JsonBody; import jakarta.servlet.http.HttpServletResponse; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.io.File; import java.io.IOException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @RestController @RequestMapping("/file") public class FileController { /** * servlet 方式下载 * @param response * @throws IOException */ @GetMapping("/download/servlet") public void downloadByServlet(HttpServletResponse response) throws IOException { //要下载的文件地址 File file = new File("C:\Users\mofang\Pictures\Saved Pictures\Fleet.png"); String fileName = URLEncoder.encode(file.getName(), StandardCharsets.UTF_8); byte[] bytes = Files.readAllBytes(file.toPath()); response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); response.addHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + fileName); response.getOutputStream().write(bytes); } /** * springmvc 方式下载 * @return * @throws IOException */ @GetMapping("/download/springmvc") public ResponseEntity<byte[]> downloadBySpringMvc() throws IOException { //要下载的文件地址 File file = new File("C:\Users\mofang\Pictures\Saved Pictures\中文.png"); String fileName = URLEncoder.encode(file.getName(), StandardCharsets.UTF_8); byte[] bytes = Files.readAllBytes(file.toPath()); HttpHeaders headers = new HttpHeaders(); headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM_VALUE); headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + fileName); return new ResponseEntity<>(bytes, headers, HttpStatus.OK); } }

2. 文件上传

=== 由于 servlet-api 安全性设计、只有配置文件上传的 servlet 才能够完成文件上传

  • 配置: 需配置单个上传文件的大小、多个文件的总大小

=== servlet 文件上传配置方式有三种:xml | 注解 | API

  • servlet xml配置方式:web.xml
 

xml

复制代码

<servlet> <servlet-name>NameServlet</servlet-name> <servlet-class>x.y.z.NameServlet</servlet-class> <multipart-config> <location>绝对路径</location> <file-size-threshold>缓存区临时大小(文件读取多大时写入一次)</file-size-threshold> <max-file-size>单个文件最大值</max-file-size> <max-request-size>多个文件总值</max-request-size> </multipart-config> </servlet> <servlet-mapping> <servlet-name>NameServlet</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping>

  • servlet注解方式:@MultipartConfig
 

java

复制代码

package com.zpark.springmvc.servlet; import jakarta.servlet.annotation.MultipartConfig; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; @WebServlet(urlPatterns = "/name") @MultipartConfig( location = "", fileSizeThreshold = 0, maxFileSize = 2 * 1024 * 1024, maxRequestSize = 4 * 1024 * 1024 ) public class NameServlet extends HttpServlet { }

  • servlet Api:方式
 

java

复制代码

package com.zpark.springmvc.servlet; import jakarta.servlet.MultipartConfigElement; import jakarta.servlet.ServletContext; import jakarta.servlet.ServletException; import jakarta.servlet.ServletRegistration; import jakarta.servlet.annotation.MultipartConfig; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; public class NameServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ServletContext servletContext = request.getServletContext(); ServletRegistration.Dynamic dynamic = servletContext.addServlet("nameServlet", new NameServlet()); dynamic.addMapping("/*"); MultipartConfigElement multipartConfigElement = new MultipartConfigElement("", 2 * 1024, 2 * 1024, 0); dynamic.setMultipartConfig(multipartConfigElement); } }

=== springmvc 文件上传方式配置:

  • springmvc DispatcherServlet 文件上传方式:

    ==配置==

 

java

复制代码

package com.zpark.springmvc.conf; import jakarta.servlet.Filter; import jakarta.servlet.MultipartConfigElement; import jakarta.servlet.ServletRegistration; import org.springframework.web.filter.CharacterEncodingFilter; import org.springframework.web.filter.HiddenHttpMethodFilter; import org.springframework.web.servlet.DispatcherServlet; import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; public class SpringMvcServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { // ... ... @Override protected void customizeRegistration(ServletRegistration.Dynamic registration) { MultipartConfigElement multipartConfig = new MultipartConfigElement("", 2 * 1024 * 1024, 2 * 1024 * 1024, 0); registration.setMultipartConfig(multipartConfig); } }

  • FileUploadController.java
 

java

复制代码

package com.zpark.springmvc.controller; import com.zpark.springmvc.utils.JsonBody; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.Part; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.UUID; @RestController @RequestMapping("/file") public class FileController { @PostMapping("/upload/servlet") public JsonBody uploadByServlet(HttpServletRequest request) throws ServletException, IOException { //获取文件,参数为前台的name Part part = request.getPart("file"); String fileName = part.getSubmittedFileName(); System.out.println("fileName = " + fileName); InputStream in = part.getInputStream(); byte[] bytes = new byte[in.available()]; in.read(bytes); Files.write(Paths.get("d:\" + UUID.randomUUID() + ".png"), bytes); return JsonBody.success(200, "上传成功").add("fileName", fileName); } @PostMapping("/upload/springmvc") public JsonBody fileUploadBySpringMvc(@RequestParam("file") MultipartFile file) throws IOException { //获取文件名称 String originalFilename = file.getOriginalFilename(); System.out.println("originalFilename = " + originalFilename); byte[] bytes = file.getBytes(); Files.write(Paths.get("d:\" + UUID.randomUUID() + "-" + originalFilename), bytes); return JsonBody.success(200, "上传成功").add("fileName", originalFilename); } }

  • /webapp/view/upload.html

14. 跨域

=== 概念: 请求的出发域和请求域不一致就叫跨域 [协议:ip:port]

  • 浏览器才限制跨域、客户端 API 不限制跨域【apache-httpclient、okhttp、restTemplate】
  • 解决逻辑: HTTP 响应跨域响应头、通知浏览器是否允许跨域

1. 响应头

  • org.springframework.http.HttpHeaders.java

image.png

2. 配置

=== SpringMVC 解决跨域就是读取开发者的配置之后 加上这些响应头通知浏览器

==配置方式:==

  • 注解
  • WebMvcConfigurer 接口配置[推荐]
  • CorsFilter
  • 自行手动 response 添加头

=== 注解方式:@CrossOrigin

  • CorsController.java
 

java

复制代码

package com.zpark.springmvc.controller; import com.zpark.springmvc.utils.JsonBody; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/cors") // @CrossOrigin("http://127.0.0.1:8081") //允许此站点请求 @CrossOrigin("*") //允许所有站点请求 打在类上表示整个类的方法都支持跨域 public class CorsController { // @CrossOrigin("*") //打在类上表示此方法支持跨域 @GetMapping public JsonBody cors(){ return JsonBody.success(200, "跨域访问"); } }

=== WebMvcConfigurer 接口配置方式

  • WebMvcConfigurer.java [推荐]
 

java

复制代码

package com.zpark.springmvc.conf; import com.zpark.springmvc.interceptor.GlobalInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.*; import org.springframework.web.servlet.view.InternalResourceViewResolver; @Configuration @ComponentScan(basePackages = "com.zpark.springmvc") @EnableWebMvc public class SpringWebContextConfiguration implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") //哪些资源支持跨域 .allowedOrigins("*") //允许哪些站点跨域访问系统资源 .allowedHeaders("*") //CorsConfiguration.ALL = * .allowedMethods("*") //CorsConfiguration.ALL = * // .exposedHeaders("") .allowCredentials(false); //是否支持携带cookie, 如果为true,则allowedOrigins不能写* } }

=== Filter 配置方式 CorsConfiguration.java

  • SpringMvcServletInitializer.java
 

java

复制代码

package com.zpark.springmvc.conf; import jakarta.servlet.Filter; import jakarta.servlet.MultipartConfigElement; import jakarta.servlet.ServletRegistration; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CharacterEncodingFilter; import org.springframework.web.filter.CorsFilter; import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; import java.util.List; public class SpringMvcServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Filter[] getServletFilters() { CharacterEncodingFilter encodingFilter = new CharacterEncodingFilter("UTF-8", true, true); CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowCredentials(true); configuration.addAllowedHeader("*"); configuration.addAllowedMethod(CorsConfiguration.ALL); configuration.setAllowedOrigins(List.of("http://localhost:63342")); //允许谁跨域访问我 UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", configuration); //项目中哪些资源允许跨域 CorsFilter corsFilter = new CorsFilter(source); return new Filter[]{encodingFilter, corsFilter}; } }

=== 参考链接:docs.spring.io/spring-fram…

image.png

==测试==

  • welcome.html
 

html

复制代码

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>welcome</title> </head> <body> <button onclick="handleClick()">点击发送请求</button> <script> function handleClick(){ const url = 'http://127.0.0.1:8080/springmvc/users' fetch(url) } </script> </body> </html>

15. Web 对象域

=== SpringMVC Web 对象域对应 WebApplicationContext 接口中的声明

  • WebApplicationContext.java
 

java

复制代码

package org.springframework.web.context; import jakarta.servlet.ServletContext; import org.springframework.context.ApplicationContext; import org.springframework.lang.Nullable; public interface WebApplicationContext extends ApplicationContext { String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT"; String SCOPE_REQUEST = "request"; String SCOPE_SESSION = "session"; String SCOPE_APPLICATION = "application"; String SERVLET_CONTEXT_BEAN_NAME = "servletContext"; String CONTEXT_PARAMETERS_BEAN_NAME = "contextParameters"; String CONTEXT_ATTRIBUTES_BEAN_NAME = "contextAttributes"; @Nullable ServletContext getServletContext(); }

1. 规范说明

  • spring 上下文对象域对应规范支持为 逻辑兼容 jakarta.inject-api 不兼容 cdi-api
  • cdi-api 规范中才包含 @RequestScope | @SessiontScope
  • @RequestScope | @SessiontScope 必须使用 Spring 提供的
  • org.springframework.web.context.annotation 包中声明了对应 cdi-api 注解

2. 运用

  • RequestEntity.java [request]
 

java

复制代码

package com.zpark.springmvc.entity; import org.springframework.stereotype.Component; import org.springframework.web.context.annotation.RequestScope; @Component // @Scope(WebApplicationContext.SCOPE_REQUEST) @RequestScope //每次请求获取新的对象 public class RequestEntity { public RequestEntity() { System.out.println("RequestEntity..."); } }

  • Person.java [session 强制开启会话]
 

java

复制代码

package com.zpark.springmvc.entity; import org.springframework.stereotype.Component; import org.springframework.web.context.annotation.SessionScope; @Component @SessionScope //对象存在于一次会话中 public class SessionEntity { public SessionEntity() { System.out.println("SessionEntity..."); } }

  • ScopeController.java
 

java

复制代码

package com.zpark.springmvc.controller; import com.zpark.springmvc.entity.RequestEntity; import com.zpark.springmvc.entity.SessionEntity; import jakarta.servlet.http.HttpSession; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.Enumeration; @RestController @RequestMapping("scope") public class ScopeController { private RequestEntity requestEntity; private SessionEntity sessionEntity; @Autowired public void setRequestEntity(RequestEntity requestEntity) { this.requestEntity = requestEntity; } @Autowired public void setSessionEntity(SessionEntity sessionEntity) { this.sessionEntity = sessionEntity; } @GetMapping public void get(HttpSession session){ System.out.println("requestEntity = " + requestEntity); System.out.println("sessionEntity = " + sessionEntity); //sessionEntity 对象是存放在 session中的,不是在spring中 Enumeration<String> attributeNames = session.getAttributeNames(); while (attributeNames.hasMoreElements()){ String element = attributeNames.nextElement(); System.out.println("element = " + element); } System.out.println("========================================="); } }

=== 若要使用 Jakarta ee 规范 api,请导入依赖

  • pom.xml
 

xml

复制代码

<!-- x spring --> <dependency> <groupId>jakarta.inject</groupId> <artifactId>jakarta.inject-api</artifactId> <version>2.0.0</version> </dependency> <!--规范--> <dependency> <groupId>jakarta.enterprise</groupId> <artifactId>jakarta.enterprise.cdi-api</artifactId> <version>4.0.1</version> </dependency> //@RequestScoped //@SessionScoped

3. @EnableWebMvc机制

=== @EnableWebMvc 实际使用 DelegatingWebMvcConfiguration 完成 SpringMVC 装配逻辑

  • DelegatingWebMvcConfiguration 类提供 DispatcherServlet 执行流程所需关键对象
  • WebContextConfiguration.java
 

java

复制代码

package com.zpark.springmvc.conf; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration; import org.springframework.web.servlet.config.annotation.EnableWebMvc; // @EnableWebMvc @Configuration @ComponentScan(basePackages = "com.zpark.springmvc") // 1. SpringBoot 自动装配 spring-webmvc 使用这个类 DelegatingWebMvcConfiguration // 2. SpringBoot 中不能使用 @EnableWebMvc 不然会覆盖 DelegatingWebMvcConfiguration public class WebContextConfiguration extends DelegatingWebMvcConfiguration { }

16. Webjars

=== Webjars 是后端静态资源工程化的机制和手段、SpringMVC 提供了封装支持

  • 演示: 前端工程化 [node.js [前端独立运行环境] | npm [类似 maven 构建管理工具]]

1. 机制

=== 官网: www.webjars.org/

image.png

  • pom.xml
 

xml

复制代码

<!-- https://mvnrepository.com/artifact/org.webjars/bootstrap --> <dependency> <groupId>org.webjars</groupId> <artifactId>bootstrap</artifactId> <version>5.3.3</version> </dependency>

2. SpringMVC配置

=== SpringMVC 只需将 webjar 的资源映射到 springMVC 静态资源处理上即可

  • SpringWebContextConfiguration.java
 

java

复制代码

package com.zpark.springmvc.conf; import com.zpark.springmvc.interceptor.GlobalInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.*; import org.springframework.web.servlet.view.InternalResourceViewResolver; @Configuration @ComponentScan(basePackages = "com.zpark.springmvc") @EnableWebMvc public class SpringWebContextConfiguration implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { // ip:8080/springmvc/webjars/css/bootstrap.min.css registry.addResourceHandler("/webjars/**") .addResourceLocations("classpath:/META-INF/resources/webjars/"); } // ... ... }

  • webapp/view/webjars.html
 

html

复制代码

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="../webjars/bootstrap/5.3.3/css/bootstrap.css"> </head> <body> <div class="container py-5 bg-warning"> <h1 class="text-center text-white">后端静态资源工程化</h1> </div> </body> </html>

17. Websocket

=== SpringMVC 提供对 Websocket 的实现、以及兼容规范 Websocket-Api

  • 推荐使用 SpringMVC-WebSocket 富应用、提供对文本、字节流处理
  • Websocket 是独立的双端通讯协议、通常基于 HTTP 协议之上使用、协议名: WS://ip:port

1. Spring-Websocket

=== spring-websocket 提供文本、二进制数据类型处理

  • TextWebSocketHandler | BinaryWebSocketHandler 对应数据类型处理器
  • @EnableWebSocket 开启 spring-websocket 功能处理
  • WebSocketConfigurer 配置 XxWebSocketHandler 处理 URL 机制等
  • spring-websocket 依然要处理浏览器跨域问题【无需考虑 API 客户端跨域】
  • spring-websocket 还提供对会话拦截等功能处理
  • pom.xml
 

xml

复制代码

<dependency> <groupId>org.springframework</groupId> <artifactId>spring-websocket</artifactId> <version>6.1.3</version> </dependency>

=== 运用

  • TextWebSocketHandlerServer.java
 

java

复制代码

package com.zpark.springmvc.websocket; import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.handler.TextWebSocketHandler; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List; /** * 处理文本类型 */ public class TextWebSocketHandlerServer extends TextWebSocketHandler { //群发、分组 // static List<WebSocketSession> socketSessionList = new ArrayList<>(); // 客户端和服务端建立链接时触发 @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { // socketSessionList.add(session); System.out.println("建立连接了: " + session.getId()); } // 客户端发送消息到服务器时触发 @Override protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { System.out.println("客户端发送了消息是: " + message.getPayload()); // 通常是某种业务逻辑 String dateTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); session.sendMessage(new TextMessage("message from server: " + dateTime)); } // 客户端和服务端断开链接时触发 @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { // socketSessionList.remove(session); System.out.println("关闭连接了: " + session.getId()); } }

  • WebSocketConfigurer.java
 

java

复制代码

package com.zpark.springmvc.conf; import com.zpark.springmvc.websocket.TextWebSocketHandlerServer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.config.annotation.EnableWebSocket; import org.springframework.web.socket.config.annotation.WebSocketConfigurer; import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; @Configuration @EnableWebSocket public class WebSocketServerConfiguration implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(textWebSocketHandlerServer(), "/ws-server"); } @Bean public TextWebSocketHandlerServer textWebSocketHandlerServer(){ return new TextWebSocketHandlerServer(); } }

  • websocket.html
 

html

复制代码

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>WebSocket</title> <link rel="stylesheet" href="/springmvc/css/bootstrap.min.css"> </head> <body> <button class="btn btn-primary" onclick="send()">点我发送消息</button> <script> const url = "ws://localhost:8080/springmvc/ws-server" const websocket = new WebSocket(url) //监听连接打开 websocket.addEventListener('open', function (evt){ console.log('client linked...') }) //客户端收到消息 websocket.addEventListener('message', function (evt){ console.log('服务器消息:' + evt.data) }) //监听连接关闭 websocket.addEventListener('close', function (evt){ console.log('client closed...') }) function send(){ websocket.send('你好,服务器...') } </script> </body> </html>

2. Websocket-Api

=== 仅包含会话抽象、且部署 websocket-server 端点及部署机制受到策略限制

=== 参考文档:jakarta.ee/learn/docs/…

  • jakarta ee 10 管理 websocket-api 规范版本是 2.1
  • websocket-api 可在 spring-mvc 环境下直接使用、无需任何配置
  • springboot 中可使用 spring-websocket-api 也可使用规范 [无需拉取规范]
  • springboot 针对嵌入式 servlet [内置支持 websocket 规范] 容器自动整合 websocket-api

==环境==

  • pom.xml
 

xml

复制代码

<dependency> <groupId>jakarta.websocket</groupId> <artifactId>jakarta.websocket-api</artifactId> <version>2.1.1</version> <scope>provided</scope> </dependency> <dependency> <groupId>jakarta.websocket</groupId> <artifactId>jakarta.websocket-client-api</artifactId> <version>2.1.1</version> <scope>provided</scope> </dependency>

//删掉 springmvc 配置的 websocket:TextWebSocketHandlerServer.java、WebSocketServerConfiguration.java

  • JakartaWebsocketServer.java
 

java

复制代码

package com.zpark.springmvc.websocket; import jakarta.websocket.OnClose; import jakarta.websocket.OnMessage; import jakarta.websocket.OnOpen; import jakarta.websocket.Session; import jakarta.websocket.server.ServerEndpoint; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; @ServerEndpoint("/ws-socket") public class JakartaWebsocketServer { @OnOpen public void onOpen(Session session) { System.out.println("连接打开了: " + session.getId()); } @OnClose public void openClose(Session session) { System.out.println("连接关闭了: " + session.getId()); } @OnMessage public void onMessage(Session session, String message) { System.out.println("客户端发送过来的消息: " + message); String dateTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); session.getAsyncRemote().sendText("server: " + dateTime); } }

18. HttpClient

 

markdown

复制代码

HttpClient 是 Apache Jakarta Common 下的子项目,可以用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本和建议。

=== spring6 需要使用 JDK 17 & JDK 11 内置 HttpClient

=== 参考文档:docs.spring.io/spring-fram…

  • SpringMVC 提供 RestClient | RestTemplate | HTTP Interface 方式

image.png

  • 第三方

image.png

1. HttpClient [v11]

  • HttpClientController.java
 

java

复制代码

package com.zpark.springmvc.controller; import com.zpark.springmvc.entity.User; import com.zpark.springmvc.utils.JsonBody; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; @RestController public class HttpClientController { //get请求 @GetMapping("/get") public JsonBody get(String username, String password){ return JsonBody.success(200, "127.0.0.1:8080/springmvc/get") .add("username", username) .add("password", password); } //post 表单参数请求 @PostMapping("/post1") public JsonBody post1(String username, String password){ return JsonBody.success(200, "127.0.0.1:8080/springmvc/post") .add("username", username) .add("password", password); } //post json参数请求 @PostMapping("/post2") public JsonBody post2(@RequestBody User user){ return JsonBody.success(200, "127.0.0.1:8080/springmvc/post") .add("user", user); } }

  • 新建 maven 项目 :: http-client,调用上文接口
  • HttpClientUsage.java
 

java

复制代码

package com.zpark.http; import java.io.IOException; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; public class HttpClientUsage { public static void main(String[] args) throws Exception { // getHttp(); // post1Http(); post2Http(); } private static void getHttp() throws IOException, InterruptedException { String url = "http://127.0.0.1:8080/springmvc/get?username=张三&password=123"; HttpClient client = HttpClient.newHttpClient(); //定义请求 HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(url)) .GET() .build(); //发送请求 HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); //获取响应结果 System.out.println(response.body()); } private static void post1Http() throws IOException, InterruptedException { String url = "http://127.0.0.1:8080/springmvc/post1"; HttpClient client = HttpClient.newHttpClient(); //定义请求 HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(url)) .header("Content-Type", "application/x-www-form-urlencoded") //表单提交 .POST(HttpRequest.BodyPublishers.ofString("username=张三&password=123")) .build(); //发送请求 HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); //获取响应结果 System.out.println(response.body()); } private static void post2Http() throws IOException, InterruptedException { String url = "http://127.0.0.1:8080/springmvc/post2"; HttpClient client = HttpClient.newHttpClient(); String json = """ { "id": 1, "username": "张三", "dateTime": "2024-03-22 12:22:53" } """; //定义请求 HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(url)) .header("Content-Type", "application/json") //json提交 .POST(HttpRequest.BodyPublishers.ofString(json)) .build(); //发送请求 HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); //获取响应结果 System.out.println(response.body()); } }

2. RestClient

=== spring 6.1 新增 依赖: [spring-web & jackson] 模块

  • http-client 客户端项目拉取依赖
 

xml

复制代码

<dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>6.1.3</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.datatype</groupId> <artifactId>jackson-datatype-jdk8</artifactId> <version>2.16.1</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.datatype</groupId> <artifactId>jackson-datatype-jsr310</artifactId> <version>2.16.1</version> </dependency>

  • RestClientUsage.java
 

java

复制代码

@Data public class JsonBody { private int code; private String msg; private Map<String, Object> data; } @Data @AllArgsConstructor public class User { private int id; private String username; private LocalDateTime dateTime; } package com.zpark.http; import org.springframework.http.MediaType; import org.springframework.web.client.RestClient; import java.time.LocalDateTime; public class RestClientUsage { public static void main(String[] args) { post2(); } private static void get(){ RestClient client = RestClient.create(); //可带模板{} String url = "http://127.0.0.1:8080/springmvc/get?username={username}&password={password}"; String body = client.get().uri(url, "张三", "123456") .retrieve() //发起请求 .body(String.class); //.body(JsonBody.class); System.out.println("body = " + body); } private static void post1(){ RestClient client = RestClient.create(); String url = "http://127.0.0.1:8080/springmvc/post1?username={username}&password={password}"; JsonBody body = client .post() .uri(url, "李四", "123") .retrieve() .body(JsonBody.class); System.out.println("body = " + body); } private static void post2(){ RestClient client = RestClient.create(); String url = "http://127.0.0.1:8080/springmvc/post2"; JsonBody body = client .post() .uri(url) .contentType(MediaType.APPLICATION_JSON) //请求携带数据为json .body(new User(1, "王五", LocalDateTime.now())) //请求体 .retrieve() //发起请求 .body(JsonBody.class); //响应类型 System.out.println("body = " + body); } }

3. RestTemplate

=== RestTemplate 的方法与 RestClient 等效

=== 点击查看参考文档

==示例==

  • RestTemplateUsage.java
 

java

复制代码

package com.zpark.http; import org.springframework.http.MediaType; import org.springframework.web.client.RestClient; import org.springframework.web.client.RestTemplate; import java.time.LocalDateTime; public class RestTemplateUsage { public static void main(String[] args) { } private static void get() { String url = "http://127.0.0.1:8080/springmvc/get?username={username}&password={password}"; RestTemplate restTemplate = new RestTemplate(); String object = restTemplate.getForObject(url, String.class, "张三", "123"); System.out.println("object = " + object); } private static void post1(){ String url = "http://127.0.0.1:8080/springmvc/post1?username={username}&password={password}"; RestTemplate restTemplate = new RestTemplate(); JsonBody body = restTemplate.postForObject(url, null, JsonBody.class, "李四", "123"); System.out.println("body = " + body); } private static void post2() { String url = "http://127.0.0.1:8080/springmvc/post2"; RestTemplate restTemplate = new RestTemplate(); JsonBody body = restTemplate.postForObject(url, new User(1, "赵六", LocalDateTime.now()), JsonBody.class); System.out.println("body = " + body); } }

4. HTTP Interface

=== HTTP Interface 是 Spring 6.1 提供 HTTP 请求接口代理(类似于 openfeign)

  • HttpService.java
 

java

复制代码

package com.zpark.http; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.service.annotation.GetExchange; import org.springframework.web.service.annotation.HttpExchange; import org.springframework.web.service.annotation.PostExchange; //需aop代理,导入 spring-context 依赖 @HttpExchange("http://127.0.0.1:8080/springmvc") //请求项目路径 public interface HttpService { @GetExchange("/get") //请求资源路径 JsonBody get(@RequestParam("username") String username, @RequestParam("password")String password); @PostExchange("/post1") JsonBody post1(@RequestParam("username") String username, @RequestParam("password")String password); @PostExchange("/post2") JsonBody post2(@RequestBody User user); }

  • Api.java 完成接口绑定
 

java

复制代码

package com.zpark.http; import org.springframework.web.client.RestClient; import org.springframework.web.client.support.RestClientAdapter; import org.springframework.web.service.invoker.HttpServiceProxyFactory; import java.time.LocalDateTime; public class HttpInterfaceProxyUsage { public static void main(String[] args) { RestClient client = RestClient.create(); //spring环境时此对象放容器 //适配器 RestClientAdapter adapter = RestClientAdapter.create(client); //代理工厂 HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build(); //拿到代理对象 HttpService service = factory.createClient(HttpService.class);//spring环境时此对象放容器 // JsonBody body = service.get("张三", "123"); // System.out.println("body = " + body); // JsonBody body = service.post1("李四", "123"); // System.out.println("body = " + body); //JsonBody body = service.post2(new User(1, "王五", LocalDateTime.now())); //System.out.println("body = " + body); } }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值