Spring MVC 数据返回
# Spring MVC 数据返回
Spring MVC提供了多种方式返回数据给客户端,可以满足不同场景下的需求,包括传统Web应用中的视图渲染、前后端分离架构中的JSON数据交互、文件下载等功能。主要的返回方式包括:
- 返回视图页面(如JSP、Thymeleaf模板)
- 返回JSON/XML等格式的数据
- 返回纯文本数据
- 使用重定向和转发控制请求流程
- 返回二进制文件(图片、PDF、Excel等)
# 1. 返回视图页面
功能说明:在传统Web应用中,控制器处理完请求后通常返回一个视图名称,Spring MVC会使用视图解析器查找对应的视图模板,并渲染为HTML页面返回给客户端。
常用注解与组件:
@Controller
:标识类为Spring MVC控制器,返回值默认解析为视图名Model
/ModelMap
/ModelAndView
:向视图传递数据的容器- 视图解析器:根据返回的视图名查找实际的视图模板文件
示例代码:
@Controller // 标识这是一个控制器类,返回视图名将被视图解析器处理
@RequestMapping("/view") // 设置控制器的基本URL路径前缀
public class ViewController {
// 处理GET请求,返回视图
@GetMapping("/hello")
public String helloView(Model model) {
// 添加数据到Model中,供视图模板使用
model.addAttribute("message", "欢迎来到Spring MVC世界!");
model.addAttribute("currentTime", new Date());
// 返回视图名称,视图解析器会查找对应的模板文件
// 例如:如果视图解析器配置的前缀是/WEB-INF/views/,后缀是.jsp
// 则实际查找的视图文件路径为 /WEB-INF/views/hello.jsp
return "hello";
}
// 使用ModelAndView返回视图和数据
@GetMapping("/welcome")
public ModelAndView welcomeView() {
// 创建ModelAndView对象,指定视图名称
ModelAndView mav = new ModelAndView("welcome");
// 添加数据到ModelAndView
mav.addObject("username", "张三");
mav.addObject("loginTime", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
// 返回ModelAndView对象
return mav;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
JSP视图模板示例:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hello Spring MVC</title>
</head>
<body>
<h1>${message}</h1>
<p>当前时间: ${currentTime}</p>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
Thymeleaf视图模板示例:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Hello Spring MVC</title>
</head>
<body>
<h1 th:text="${message}">默认消息</h1>
<p>当前时间: <span th:text="${currentTime}">2023-01-01</span></p>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
关键点说明:
@Controller
注解的类返回的String值默认被解释为视图名- 可以通过
Model
、ModelMap
或ModelAndView
向视图传递数据 - 实际视图文件的位置取决于视图解析器的配置
- 适用于服务端渲染的传统Web应用
# 2. 返回JSON数据
功能说明:在现代Web应用,特别是前后端分离架构中,后端通常需要返回JSON格式的数据而非HTML页面。Spring MVC可以自动将Java对象转换为JSON格式的响应。
常用注解与组件:
@RestController
:组合注解,等同于@Controller
+@ResponseBody
@ResponseBody
:标识方法返回值应该直接作为响应体,而不是视图名HttpEntity
/ResponseEntity
:封装响应数据并控制HTTP响应状态码、头信息等
示例代码:
@RestController // 标识这是一个REST控制器,所有方法返回值都会作为响应体而非视图名
@RequestMapping("/api") // 设置API的基本URL路径前缀
public class UserApiController {
// 返回单个用户对象作为JSON
@GetMapping("/user/{id}")
public User getUser(@PathVariable("id") long id) {
// 在实际应用中通常从数据库查询用户信息
// 这里简化为直接创建一个用户对象
return new User(id, "用户" + id, 20 + (int)(id % 20));
}
// 返回用户列表作为JSON数组
@GetMapping("/users")
public List<User> getUsers() {
// 创建一个用户列表
List<User> users = new ArrayList<>();
for (long i = 1; i <= 5; i++) {
users.add(new User(i, "用户" + i, 20 + (int)(i % 20)));
}
return users;
}
// 使用ResponseEntity返回JSON数据,可以控制状态码和响应头
@GetMapping("/user/detail/{id}")
public ResponseEntity<UserDetail> getUserDetail(@PathVariable("id") long id) {
// 模拟查询用户详情
if (id <= 0) {
// 如果ID无效,返回404 Not Found状态码
return ResponseEntity.notFound().build();
}
// 创建用户详情对象
UserDetail detail = new UserDetail(id, "用户" + id, "user" + id + "@example.com",
"北京市朝阳区", new Date());
// 添加自定义响应头并返回200 OK状态码
return ResponseEntity.ok()
.header("X-Custom-Header", "Custom Value")
.body(detail);
}
}
// 用户实体类
class User {
private long id; // 用户ID
private String name; // 用户姓名
private int age; // 用户年龄
// 构造函数
public User(long id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
// getter和setter方法(必要的,用于JSON序列化)
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
// 用户详情类
class UserDetail extends User {
private String email; // 电子邮件
private String address; // 地址
private Date createTime; // 创建时间
// 构造函数
public UserDetail(long id, String name, String email, String address, Date createTime) {
super(id, name, 0); // 调用父类构造函数
this.email = email;
this.address = address;
this.createTime = createTime;
}
// getter和setter方法
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
使用@ResponseBody
的替代方式:
@Controller // 使用Controller而非RestController
@RequestMapping("/data")
public class DataController {
@GetMapping("/stats")
@ResponseBody // 使用@ResponseBody注解表示返回的是数据而非视图名
public Map<String, Object> getStats() {
Map<String, Object> stats = new HashMap<>();
stats.put("userCount", 1024);
stats.put("activeUsers", 128);
stats.put("newRegistrations", 56);
return stats;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
关键点说明:
@RestController
或@ResponseBody
注解告诉Spring将返回值直接序列化为响应体- Spring Boot默认使用Jackson库将对象转换为JSON
- 可以返回各种Java对象,包括POJO、Map、List等,都会被自动转换为JSON
ResponseEntity
允许更精细地控制HTTP响应的状态码、头信息等- 确保实体类有getter方法,否则属性可能无法正确序列化为JSON
- 适用于前后端分离架构、RESTful API开发
# 3. 返回纯文本数据
功能说明:有时需要返回简单的文本而非复杂的JSON或HTML,例如返回操作状态信息、调试信息等。
示例代码:
@RestController // RestController默认将返回值作为响应体
@RequestMapping("/text")
public class TextController {
// 返回简单文本
@GetMapping("/hello")
public String getHello() {
// 直接返回字符串作为响应内容
return "Hello, Spring MVC!";
}
// 返回带格式的文本
@GetMapping("/status")
public String getStatus() {
// 构建多行文本
StringBuilder sb = new StringBuilder();
sb.append("系统状态信息:\n");
sb.append("- 运行时长: 3天12小时\n");
sb.append("- 内存使用: 512MB/1024MB\n");
sb.append("- CPU负载: 32%\n");
return sb.toString();
}
// 使用ResponseEntity返回纯文本,并设置自定义Content-Type
@GetMapping("/message")
public ResponseEntity<String> getMessage() {
String message = "这是一条重要消息!";
// 返回文本并设置响应头
return ResponseEntity.ok()
.contentType(MediaType.TEXT_PLAIN)
.body(message);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
使用@Controller
和@ResponseBody
的替代方式:
@Controller
@RequestMapping("/info")
public class InfoController {
@GetMapping("/version")
@ResponseBody
public String getVersion() {
// 返回版本信息
return "Application Version: 1.2.3";
}
// 设置produces属性指定Content-Type
@GetMapping(value = "/readme", produces = "text/plain;charset=UTF-8")
@ResponseBody
public String getReadme() {
return "这是应用程序的说明文档内容...";
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
关键点说明:
@RestController
中返回的String默认会作为纯文本返回- 可以使用
@Controller
+@ResponseBody
注解组合实现相同效果 - 使用
ResponseEntity
可以更精确地控制响应的状态码和头信息 - 默认的Content-Type是
text/plain
,可以通过produces
属性或ResponseEntity
修改 - 适用于返回简单状态信息、调试信息等场景
# 4. 重定向和转发
功能说明:在某些场景下,需要将请求从一个URL路径转到另一个URL路径,Spring MVC支持两种主要的跳转方式:重定向(redirect)和转发(forward)。
重定向与转发的区别:
- 重定向(redirect):返回302状态码和新的URL给浏览器,浏览器会发起新的请求;URL会改变,request对象不会共享
- 转发(forward):在服务器内部将请求转发给另一个处理器;URL不会改变,request对象会共享
示例代码:
@Controller
@RequestMapping("/navigate")
public class NavigationController {
// 重定向到外部URL
@GetMapping("/to-external")
public String redirectToExternal() {
// 重定向到外部绝对URL
return "redirect:https://www.example.com";
}
// 重定向到应用内的另一个路径
@GetMapping("/to-home")
public String redirectToHome() {
// 重定向到应用内的相对路径,会自动补全应用上下文路径
// 浏览器URL会变成 /app/home (假设应用上下文是/app)
return "redirect:/home";
}
// 带参数的重定向
@GetMapping("/to-dashboard")
public String redirectToDashboard(RedirectAttributes redirectAttributes) {
// 添加重定向Flash属性(会被临时保存并在重定向后的请求中可用)
redirectAttributes.addFlashAttribute("message", "重定向成功!");
// 添加URL参数
redirectAttributes.addAttribute("section", "summary");
redirectAttributes.addAttribute("period", "monthly");
// 重定向到仪表盘页面(带参数)
// 结果URL类似: /app/dashboard?section=summary&period=monthly
return "redirect:/dashboard";
}
// 服务器内部转发
@GetMapping("/process-and-show")
public String forwardToView() {
// 服务器内部转发,浏览器URL不会改变
// 浏览器看到的还是 /navigate/process-and-show
// 但实际处理请求的是 /internal/result 对应的控制器方法
return "forward:/internal/result";
}
// 接收来自重定向的Flash属性
@GetMapping("/dashboard")
public String dashboard(@ModelAttribute("message") String message,
@RequestParam String section,
@RequestParam String period,
Model model) {
// Flash属性已自动添加到Model中,但我们也可以添加其他数据
model.addAttribute("section", section);
model.addAttribute("period", period);
// 返回仪表盘视图
return "dashboard";
}
}
// 处理被转发的请求的控制器
@Controller
@RequestMapping("/internal")
public class InternalController {
@GetMapping("/result")
public String showResult(Model model) {
// 添加数据到模型
model.addAttribute("processingTime", new Date());
model.addAttribute("result", "处理完成");
// 返回结果视图
return "result";
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
关键点说明:
- 重定向通过返回以
redirect:
开头的视图名实现 - 转发通过返回以
forward:
开头的视图名实现 - 重定向会导致浏览器发起新请求并改变URL,适合操作完成后的页面跳转
- 重定向可以使用
RedirectAttributes
传递临时数据(Flash属性)和URL参数 - 转发在服务器内部进行,浏览器URL不变,请求作用域内的属性会被保留
- 重定向更适合POST请求后的页面跳转,符合POST-Redirect-GET模式,避免表单重复提交
- 在前后端分离架构中,重定向和转发的使用较少,主要用于传统Web应用
# 5. 返回文件数据
功能说明:Spring MVC支持返回各种类型的文件数据,如图片、PDF、Excel等,常用于文件下载、图片显示等场景。
实现方式:
- 使用
HttpServletResponse
直接写入响应流 - 使用
ResponseEntity<byte[]>
或ResponseEntity<Resource>
返回文件数据 - 使用Spring提供的
ResourceHttpMessageConverter
实现文件资源转换
示例代码:
@RestController
@RequestMapping("/files")
public class FileController {
// 使用HttpServletResponse返回文件
@GetMapping("/download1")
public void downloadFile1(HttpServletResponse response) throws IOException {
// 获取文件路径
File file = new File("D:/temp/example.pdf");
// 设置响应头信息
response.setContentType("application/pdf");
response.setHeader("Content-Disposition", "attachment; filename=\"example.pdf\"");
response.setContentLength((int) file.length());
// 将文件内容写入响应输出流
try (FileInputStream fis = new FileInputStream(file);
OutputStream os = response.getOutputStream()) {
// 使用缓冲区复制文件内容到响应流
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
os.write(buffer, 0, bytesRead);
}
// 刷新输出流
os.flush();
} catch (IOException e) {
throw new RuntimeException("文件下载失败", e);
}
}
// 使用ResponseEntity<byte[]>返回文件
@GetMapping("/download2")
public ResponseEntity<byte[]> downloadFile2() throws IOException {
// 获取文件路径
Path filePath = Paths.get("D:/temp/example.pdf");
byte[] fileContent = Files.readAllBytes(filePath);
// 构建HTTP响应
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_PDF)
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"example.pdf\"")
.contentLength(fileContent.length)
.body(fileContent);
}
// 使用ResponseEntity<Resource>返回文件
@GetMapping("/download3")
public ResponseEntity<Resource> downloadFile3() throws IOException {
// 创建文件资源
File file = new File("D:/temp/example.pdf");
Resource resource = new FileSystemResource(file);
// 构建HTTP响应
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_PDF)
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + file.getName() + "\"")
.contentLength(file.length())
.body(resource);
}
// 返回图片
@GetMapping("/image")
public ResponseEntity<byte[]> getImage() throws IOException {
// 获取图片文件
Path imagePath = Paths.get("D:/temp/logo.png");
byte[] imageData = Files.readAllBytes(imagePath);
// 返回图片数据
return ResponseEntity.ok()
.contentType(MediaType.IMAGE_PNG)
.body(imageData);
}
// 从类路径获取资源文件
@GetMapping("/template")
public ResponseEntity<Resource> getTemplate() {
// 从类路径获取资源
Resource resource = new ClassPathResource("templates/sample.docx");
try {
// 返回Word文档
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.wordprocessingml.document"))
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"template.docx\"")
.contentLength(resource.contentLength())
.body(resource);
} catch (IOException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
// 动态生成Excel文件
@GetMapping("/excel")
public ResponseEntity<byte[]> generateExcel() throws IOException {
// 实际应用中通常使用Apache POI等库生成Excel文件
// 这里简化为返回预先准备的Excel文件
Path excelPath = Paths.get("D:/temp/report.xlsx");
byte[] excelData = Files.readAllBytes(excelPath);
// 返回Excel文件
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"))
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"report.xlsx\"")
.body(excelData);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
关键点说明:
- 文件下载需要设置正确的
Content-Type
和Content-Disposition
头 Content-Disposition: attachment
表示文件应作为附件下载,而非在浏览器中直接打开- 可以使用
ResponseEntity
更灵活地控制响应的各个方面 - Spring的
Resource
接口提供了对各种资源的统一访问方式 - 对于大文件,应考虑使用流式处理而非一次性加载到内存
- 响应二进制数据时需要注意设置正确的MIME类型
- 在处理文件操作时应妥善处理异常,并提供用户友好的错误信息
# 6. 自定义响应格式
功能说明:在实际应用中,特别是RESTful API开发中,往往需要对响应数据进行统一封装,包括状态码、消息、数据等,以提供一致的接口规范。
示例代码:
@RestController
@RequestMapping("/api/v1")
public class ApiController {
// 定义统一的响应结果包装类
public static class ApiResult<T> {
private int code; // 业务状态码
private String message; // 状态消息
private T data; // 数据负载
private long timestamp; // 时间戳
// 构造方法
public ApiResult(int code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
this.timestamp = System.currentTimeMillis();
}
// 成功结果静态工厂方法
public static <T> ApiResult<T> success(T data) {
return new ApiResult<>(200, "操作成功", data);
}
// 失败结果静态工厂方法
public static <T> ApiResult<T> fail(int code, String message) {
return new ApiResult<>(code, message, null);
}
// getter和setter方法
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public long getTimestamp() {
return timestamp;
}
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
}
// 返回成功结果
@GetMapping("/products")
public ApiResult<List<Product>> getProducts() {
// 创建产品列表
List<Product> products = new ArrayList<>();
products.add(new Product(1L, "iPhone 13", 6999.00));
products.add(new Product(2L, "MacBook Pro", 12999.00));
products.add(new Product(3L, "iPad Air", 4999.00));
// 使用统一的响应格式返回数据
return ApiResult.success(products);
}
// 返回失败结果
@GetMapping("/product/{id}")
public ApiResult<Product> getProduct(@PathVariable("id") Long id) {
// 模拟查询产品
if (id <= 0) {
// 返回参数错误响应
return ApiResult.fail(400, "产品ID无效");
}
// 模拟查询不存在的产品
if (id > 100) {
// 返回资源不存在响应
return ApiResult.fail(404, "产品不存在");
}
// 返回查询结果
Product product = new Product(id, "产品" + id, 99.00 * id);
return ApiResult.success(product);
}
// 结合ResponseEntity使用自定义响应格式
@PostMapping("/products")
public ResponseEntity<ApiResult<Product>> createProduct(@RequestBody Product product) {
// 模拟产品创建,假设ID是自动生成的
product.setId(System.currentTimeMillis() % 1000);
// 返回201 Created状态码和Location头
return ResponseEntity.created(URI.create("/api/v1/product/" + product.getId()))
.body(ApiResult.success(product));
}
}
// 产品实体类
class Product {
private Long id; // 产品ID
private String name; // 产品名称
private double price; // 产品价格
// 构造方法
public Product(Long id, String name, double price) {
this.id = id;
this.name = name;
this.price = price;
}
// getter和setter方法
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
统一的响应格式JSON示例:
{
"code": 200,
"message": "操作成功",
"data": [
{
"id": 1,
"name": "iPhone 13",
"price": 6999.0
},
{
"id": 2,
"name": "MacBook Pro",
"price": 12999.0
}
],
"timestamp": 1629884718291
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
关键点说明:
- 使用统一的响应格式可以提高API的一致性和可维护性
- 自定义响应类通常包含状态码、消息、数据和时间戳等字段
- 可以结合
ResponseEntity
控制HTTP状态码和响应头 - 在RESTful API设计中,应区分HTTP状态码和业务状态码的不同职责
- 统一响应格式有利于前端处理响应和异常
- 在微服务架构中,统一的响应格式能够提高各服务之间的互操作性
# 7. 异步处理与响应
功能说明:Spring MVC支持异步处理请求,适用于处理长时间运行的任务、提高服务器吞吐量。
异步响应方式:
- 返回
Callable<?>
- 返回
DeferredResult<?>
- 返回
CompletableFuture<?>
或ListenableFuture<?>
- 使用
WebAsyncTask<?>
示例代码:
@RestController
@RequestMapping("/async")
public class AsyncController {
// 使用线程池执行异步任务
private final ExecutorService executorService = Executors.newFixedThreadPool(10);
// 使用Callable实现异步处理
@GetMapping("/callable")
public Callable<String> asyncCallable() {
System.out.println("主线程开始: " + Thread.currentThread().getName());
return () -> {
System.out.println("工作线程开始: " + Thread.currentThread().getName());
// 模拟耗时操作
Thread.sleep(2000);
System.out.println("工作线程结束: " + Thread.currentThread().getName());
return "Callable异步处理完成";
};
}
// 使用DeferredResult实现异步处理
@GetMapping("/deferred")
public DeferredResult<String> asyncDeferred() {
System.out.println("主线程开始: " + Thread.currentThread().getName());
// 创建DeferredResult对象,超时时间5秒
DeferredResult<String> deferredResult = new DeferredResult<>(5000L, "处理超时");
// 在另一个线程中处理耗时任务
executorService.submit(() -> {
System.out.println("工作线程开始: " + Thread.currentThread().getName());
try {
// 模拟耗时操作
Thread.sleep(2000);
// 设置结果,此时响应才会返回给客户端
deferredResult.setResult("DeferredResult异步处理完成");
} catch (InterruptedException e) {
// 设置错误结果
deferredResult.setErrorResult("处理出错: " + e.getMessage());
}
System.out.println("工作线程结束: " + Thread.currentThread().getName());
});
System.out.println("主线程结束: " + Thread.currentThread().getName());
// 立即返回DeferredResult,但响应会在setResult后才发送
return deferredResult;
}
// 使用CompletableFuture实现异步处理
@GetMapping("/completable")
public CompletableFuture<String> asyncCompletable() {
System.out.println("主线程开始: " + Thread.currentThread().getName());
// 创建并返回CompletableFuture
return CompletableFuture.supplyAsync(() -> {
System.out.println("工作线程开始: " + Thread.currentThread().getName());
try {
// 模拟耗时操作
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("工作线程结束: " + Thread.currentThread().getName());
return "CompletableFuture异步处理完成";
}, executorService);
}
// 使用WebAsyncTask实现异步处理
@GetMapping("/task")
public WebAsyncTask<String> asyncTask() {
System.out.println("主线程开始: " + Thread.currentThread().getName());
// 创建WebAsyncTask,指定超时时间和回调函数
WebAsyncTask<String> asyncTask = new WebAsyncTask<>(3000, () -> {
System.out.println("工作线程开始: " + Thread.currentThread().getName());
// 模拟耗时操作
Thread.sleep(2000);
System.out.println("工作线程结束: " + Thread.currentThread().getName());
return "WebAsyncTask异步处理完成";
});
// 设置超时和错误回调
asyncTask.onTimeout(() -> "处理超时");
asyncTask.onError(() -> "处理出错");
System.out.println("主线程结束: " + Thread.currentThread().getName());
return asyncTask;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
关键点说明:
- 异步处理可以释放Servlet容器线程,提高系统吞吐量
Callable
:由Spring管理的线程池执行任务DeferredResult
:完全自主控制何时完成处理,适合事件驱动场景CompletableFuture
:Java 8引入的异步编程工具,支持链式调用和组合操作WebAsyncTask
:扩展了Callable,支持超时处理和错误回调- 异步处理适合长时间运行的任务,如第三方API调用、大量计算等
- 在实际应用中,要注意线程池的合理配置和资源管理