01. 概述
1. 简介
=== SpringMVC 是 Spring 框架针对 Web 提供的技术支持模块、并不是独立的框架
2. MVC 模式
MVC 模式全称为 Model-View-Controller(模型-视图-控制器)模式,它是一种 软件架构模式
,其目标是将软件的用户界面(即前台页面)和业务逻辑分离,使代码具有更高的可扩展性、可复用性、可维护性以及灵活性。
通常情况下,一个完整的 Java Web 应用程序,其结构如下图所示:
MVC 模式将应用程序划分成==模型==(Model)、==视图==(View)、==控制器==(Controller)等三层,如下图所示:
Model(模型):
是应用程序的主体部分,主要由以下三部分组成:
- 实体类 Bean:专门用来存储业务数据的对象,它们通常与数据库中的某个表对应,例如 User、Student 等。
- 业务处理 Bean:指 Service 对象,专门用于处理业务逻辑。
- 数据处理 Bean:指 Dao 或 Mapper 对象,专门用于处理数据库访问逻辑。
View(视图):
指在应用程序中专门用来与浏览器进行交互,展示数据的资源。在 Web 应用中,View 就是我们常说的前台页面,通常由 HTML、JSP、CSS、JavaScript 等组成。
Controller(控制器):
通常指的是,应用程序的 Servlet。它负责将用户的请求交给模型(Model)层进行处理,并将 Model 层处理完成的数据,返回给视图(View)渲染并展示给用户。
3. MVC 工作流程
- 用户发送请求到服务器。
- 在服务器中,请求被控制层(Controller)接收。
- Controller 调用相应的 Model 层处理请求。
- Model 层处理完毕将结果返回到 Controller。
- Controller 再根据 Model 返回的请求处理结果,找到相应的 View 视图。
- 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
2. DispatcherServlet
=== DispatcherServlet 是 SpringMVC 抽象的 Servlet 实现
- DispatcherServlet 通常 URL 配置为 /* 用于拦截整个项目中的请求
- DispatcherServlet 拦截 URL 后、会将 URL 匹配 Controller 类中的 URL 注解方法
- 匹配 URL 方法完成后、SpringMVC 会提取 URL 中的参数、再对方法进行反射
- 并在解析 URL 参数过程中、预留了可插拔的参数校验手段
- 反射 URL 方法执行的过程中、预留了可插拔的拦截器手段、用于控制该执行过程
- URL 方法执行拿到结果后、还会检查 URL 方法配置、再决定以什么方式返回给客户端
- 返回方式有 视图页面跳转、请求转发、重定向、返回 JSON 数据 等
- 在整个拦截 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
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); } }
==测试==
=== 异常应当交由 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…
- 验证码
- 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 执行过程过于复杂
不要让拦截器有交叉逻辑、若是有交叉逻辑、请合并拦截器设计
- 官网
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
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…
==测试==
- 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/
- 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 方式
- 第三方
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); } }