SpringMVC快速入门
01. 概述
1. 简介
=== SpringMVC 是 Spring 框架针对 Web 提供的技术支持模块、并不是独立的框架
==官方文档==:docs.spring.io/spring-fram…
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. 机制说明
1. 环境
Spring 对 web 技术提供了两个模块:
- spring-web -- 完成对 web 技术基础抽象和基础实现 (spring-context、spring-aop、spring-aspectj...)
- spring-webmvc -- 完成对 servlet-api 规范的封装支持
- pom.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
<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
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 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
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
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
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
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
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
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
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
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(标准通用标记语言下的一个应用)这些现有的广泛流行的协议和标准。
==示例==
//查询所有人员(传统)
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
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
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
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
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
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) 用于处理通用异常
==示例==
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. 环境
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>8.0.1.Final</version>
</dependency>
- dependency
2. 配置
=== LocalValidatorFactoryBean
- LocalValidatorFactoryBean.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
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
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
@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
@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
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
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
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
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 })
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
<dependency>
<groupId>com.github.whvcse</groupId>
<artifactId>easy-captcha</artifactId>
<version>1.6.2</version>
</dependency>
- http response header
response.setContentType("image/gif");
response.setHeader("Pragma", "No-cache");
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 0);
- CaptchaController.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
<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
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
ServletContext servletContext = request.getServletContext();
FilterRegistration.Dynamic registration =
servletContext.addFilter("filterName", "x.y.z.FilterName");
registration.addMappingForUrlPatterns(拦截请求类型, true, "url");
- api [拦截指定名称的某个或多个 servlet]
ServletContext servletContext = request.getServletContext();
FilterRegistration.Dynamic registration =
servletContext.addFilter("filterName", "x.y.z.FilterName");
registration.addMappingForServletNames(拦截请求类型, true, "指定拦截的servlet名称");
2. 自定义注册
=== 业务指定 Filter 自行注册即可 | SpringMVC 提供的 Filter [编码|跨域]
- CustomFilter.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 由以下方式注册]
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 容器
@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
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
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
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
<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
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:方式
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 文件上传方式:
==配置==
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
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
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 [推荐]
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
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
<!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
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]
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 强制开启会话]
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
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
<!-- 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
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
<!-- 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
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
<!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
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>6.1.3</version>
</dependency>
=== 运用
- TextWebSocketHandlerServer.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
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
<!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
<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
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
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
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
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 客户端项目拉取依赖
<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
@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
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
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 完成接口绑定
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);
}
}
转载自:https://juejin.cn/post/7388057629774807066