Spring Boot - 函数式 Web
# Spring Boot - 函数式 Web
# 1. 函数式Web编程概述
# 1.1 传统Web开发模式的局限
在传统的Spring Boot Web开发中,我们主要使用@Controller
、@RestController
和@RequestMapping
等注解来处理HTTP请求。这种基于注解的声明式编程模型直观易用,但也存在一些局限性:
- 路由与业务逻辑耦合:请求映射(路由)和业务处理逻辑紧密结合在一个控制器类中
- 代码可组合性有限:注解驱动的方式难以在运行时动态组合路由
- 测试复杂性:测试控制器通常需要加载完整的Spring上下文
- 代码冗余:处理相似路由模式时可能需要重复代码
# 1.2 函数式Web框架的优势
Spring 5引入的函数式Web框架提供了一种全新的编程范式,通过将路由定义与业务处理分离,解决了上述问题:
- 路由与业务逻辑分离:明确区分"路由定义"与"业务处理"两个关注点
- 更高的组合性:路由可以被组合、分解和重用
- 更好的测试性:处理函数可以独立测试,无需启动Spring上下文
- 显式定义:路由配置更加清晰可见,不依赖于注解的"魔法"
- 与响应式编程契合:自然支持非阻塞响应式编程模型
# 1.3 适用场景
函数式Web框架特别适合以下场景:
- 需要动态路由配置的应用
- 微服务或API网关层
- 追求高性能、低延迟的Web应用
- 需要清晰分离关注点的大型项目
- 使用响应式编程模型的应用
函数式Web并非要替代传统的注解方式,而是作为一种补充,为开发者提供更多选择。在同一个应用中,可以同时使用这两种方式。
# 2. 函数式Web的核心组件
Spring函数式Web框架的核心由四个主要组件构成,它们共同定义了请求的路由、匹配、处理和响应过程:
# 2.1 RouterFunction
RouterFunction
是函数式Web框架的核心,负责将HTTP请求路由到相应的处理函数。它替代了传统的@RequestMapping
注解,提供了一种编程方式来定义请求映射。
主要特点:
- 可组合性:多个
RouterFunction
可以通过and()
、andRoute()
等方法组合 - 类型安全:编译时就能发现路由配置错误
- 灵活性:可以根据任意条件决定路由行为
- 可读性:路由定义集中且清晰
# 2.2 RequestPredicate
RequestPredicate
用于确定一个请求是否匹配特定的路由。它判断请求的各个方面,如HTTP方法、路径、内容类型等。
常用的预定义RequestPredicates:
method(HttpMethod)
- 匹配特定HTTP方法path(String)
- 匹配特定路径模式accept(MediaType...)
- 匹配Accept头contentType(MediaType...)
- 匹配Content-Type头headers(Predicate<HttpHeaders>)
- 匹配请求头queryParam(String, String)
- 匹配查询参数
这些预定义可以通过and()
、or()
、negate()
等逻辑操作符组合使用。
# 2.3 ServerRequest
ServerRequest
是HTTP请求的不可变表示,提供对请求各个方面的访问,包括:
- 头部信息
- 查询参数
- 路径变量
- 请求体
- 表单数据
- 多部分数据
它的设计使得函数式处理程序能够以声明性方式访问请求数据,而不需要注入原始的HttpServletRequest
。
# 2.4 ServerResponse
ServerResponse
是HTTP响应的不可变表示,通过流畅的API构建HTTP响应:
- 状态码
- 头部信息
- Cookie
- 响应体
ServerResponse
提供了静态工厂方法来创建常见响应,如ok()
、created(URI)
、badRequest()
等,使响应构建更加简洁。
# 3. 函数式Web开发实战
下面我们通过一个完整的用户管理API示例,展示函数式Web框架的实际应用:
# 3.1 用户实体类
首先,创建一个简单的用户实体类:
/**
* 用户实体类
* 用于表示用户基本信息
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 用户ID,系统生成
*/
private Long id;
/**
* 创建时间
*/
private Date createTime;
/**
* 构造函数 - 仅包含用户名和密码
* @param username 用户名
* @param password 密码
*/
public User(String username, String password) {
this.username = username;
this.password = password;
this.createTime = new Date();
}
}
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
# 3.2 用户业务处理器
接下来,创建一个处理用户相关业务逻辑的处理器类。该类包含多个方法,每个方法对应一个具体的业务操作:
/**
* 用户业务处理器
* 负责处理与用户相关的所有业务逻辑
* 在函数式Web中充当"处理器"角色,替代传统控制器
*/
@Slf4j
@Service
public class UserBizHandler {
/**
* 模拟用户数据库
*/
private final Map<Long, User> userDatabase = new ConcurrentHashMap<>();
/**
* 处理获取单个用户的请求
*
* @param request 包含用户ID的服务器请求
* @return 包含用户数据的服务器响应
*/
public ServerResponse getUser(ServerRequest request) {
// 从路径变量中提取用户ID
String idStr = request.pathVariable("id");
Long userId;
try {
userId = Long.parseLong(idStr);
} catch (NumberFormatException e) {
// 如果ID不是有效数字,返回400错误
return ServerResponse.badRequest()
.contentType(MediaType.APPLICATION_JSON)
.body(Map.of("error", "Invalid user ID format"));
}
// 查找用户
User user = userDatabase.getOrDefault(userId, new User("张三", "12345678", userId, new Date()));
// 返回用户数据,状态码200
return ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(user);
}
/**
* 处理获取所有用户的请求
*
* @param request 服务器请求
* @return 包含所有用户数据的服务器响应
*/
public ServerResponse getUsers(ServerRequest request) {
// 如果数据库为空,返回模拟数据
List<User> users;
if (userDatabase.isEmpty()) {
// 模拟从数据库获取用户列表
users = List.of(
new User("张三", "1234", 1L, new Date()),
new User("李四", "abc", 2L, new Date()),
new User("王五", "hello", 3L, new Date())
);
} else {
// 返回实际数据库中的用户
users = new ArrayList<>(userDatabase.values());
}
// 返回用户列表,状态码200
return ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(users);
}
/**
* 处理创建用户的请求
*
* @param request 包含用户数据的服务器请求
* @return 创建成功的服务器响应
* @throws ServletException 如果请求体解析失败
* @throws IOException 如果读取请求体时发生IO错误
*/
public ServerResponse createUser(ServerRequest request) throws ServletException, IOException {
// 从请求体中解析出User对象
User user = request.body(User.class);
// 验证用户数据
if (user.getUsername() == null || user.getPassword() == null) {
return ServerResponse.badRequest()
.contentType(MediaType.APPLICATION_JSON)
.body(Map.of("error", "Username and password are required"));
}
// 生成用户ID并设置创建时间
Long userId = System.currentTimeMillis();
user.setId(userId);
user.setCreateTime(new Date());
// 保存用户到"数据库"
userDatabase.put(userId, user);
log.info("创建用户: {}", user);
// 返回201状态码,表示资源已创建
return ServerResponse.created(URI.create("/user/" + userId))
.contentType(MediaType.APPLICATION_JSON)
.body(user);
}
/**
* 处理更新用户的请求
*
* @param request 包含用户ID和更新数据的服务器请求
* @return 更新结果的服务器响应
* @throws ServletException 如果请求体解析失败
* @throws IOException 如果读取请求体时发生IO错误
*/
public ServerResponse updateUser(ServerRequest request) throws ServletException, IOException {
// 从路径变量中提取用户ID
String idStr = request.pathVariable("id");
Long userId;
try {
userId = Long.parseLong(idStr);
} catch (NumberFormatException e) {
return ServerResponse.badRequest()
.contentType(MediaType.APPLICATION_JSON)
.body(Map.of("error", "Invalid user ID format"));
}
// 检查用户是否存在
if (!userDatabase.containsKey(userId)) {
return ServerResponse.notFound().build();
}
// 从请求体中解析更新的用户数据
User updatedUser = request.body(User.class);
User existingUser = userDatabase.get(userId);
// 更新用户信息,保留原有ID和创建时间
if (updatedUser.getUsername() != null) {
existingUser.setUsername(updatedUser.getUsername());
}
if (updatedUser.getPassword() != null) {
existingUser.setPassword(updatedUser.getPassword());
}
// 保存更新后的用户
userDatabase.put(userId, existingUser);
log.info("更新用户: {}", existingUser);
// 返回更新后的用户数据
return ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(existingUser);
}
/**
* 处理删除用户的请求
*
* @param request 包含用户ID的服务器请求
* @return 删除结果的服务器响应
*/
public ServerResponse deleteUser(ServerRequest request) {
// 从路径变量中获取用户ID
String idStr = request.pathVariable("id");
Long userId;
try {
userId = Long.parseLong(idStr);
} catch (NumberFormatException e) {
return ServerResponse.badRequest()
.contentType(MediaType.APPLICATION_JSON)
.body(Map.of("error", "Invalid user ID format"));
}
// 检查用户是否存在
if (!userDatabase.containsKey(userId)) {
return ServerResponse.notFound().build();
}
// 从"数据库"中删除用户
User removedUser = userDatabase.remove(userId);
log.info("删除用户: {}", removedUser);
// 返回204状态码,表示无内容
return ServerResponse.noContent().build();
}
}
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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# 3.3 路由配置
现在,创建一个配置类来定义API路由,将HTTP请求映射到相应的处理方法:
/**
* 函数式Web路由配置类
* 定义所有API路由和它们对应的处理函数
*/
@Configuration
public class WebFunctionConfig {
/**
* 配置用户API的路由规则
*
* @param userBizHandler 用户业务处理器,由Spring自动注入
* @return 路由函数,定义所有API端点
*/
@Bean
public RouterFunction<ServerResponse> userRoutes(UserBizHandler userBizHandler) {
return RouterFunctions.route()
// 获取单个用户 - GET /user/{id}
.GET("/user/{id}",
// 接受任何媒体类型的请求
RequestPredicates.accept(MediaType.ALL),
// 映射到userBizHandler的getUser方法
userBizHandler::getUser)
// 获取所有用户 - GET /users
.GET("/users",
RequestPredicates.accept(MediaType.ALL),
userBizHandler::getUsers)
// 创建新用户 - POST /user
.POST("/user",
// 只接受JSON格式的请求体
RequestPredicates.contentType(MediaType.APPLICATION_JSON),
userBizHandler::createUser)
// 更新用户 - PUT /user/{id}
.PUT("/user/{id}",
RequestPredicates.contentType(MediaType.APPLICATION_JSON),
userBizHandler::updateUser)
// 删除用户 - DELETE /user/{id}
.DELETE("/user/{id}",
RequestPredicates.accept(MediaType.ALL),
userBizHandler::deleteUser)
.build();
}
/**
* 配置其他API的路由(可选)
* 展示如何组合多个路由函数
*
* @return 其他API端点的路由函数
*/
@Bean
public RouterFunction<ServerResponse> otherRoutes() {
return RouterFunctions.route()
// API信息端点 - GET /api/info
.GET("/api/info",
request -> ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(Map.of(
"name", "用户管理API",
"version", "1.0.0",
"timestamp", new Date()
)))
// 健康检查端点 - GET /api/health
.GET("/api/health",
request -> ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(Map.of("status", "UP")))
.build();
}
/**
* 组合所有路由函数(可选)
* 当有多个路由Bean时,可以将它们组合成一个总路由
*
* @param userRoutes 用户API路由
* @param otherRoutes 其他API路由
* @return 组合后的总路由函数
*/
@Bean
public RouterFunction<ServerResponse> allRoutes(
RouterFunction<ServerResponse> userRoutes,
RouterFunction<ServerResponse> otherRoutes) {
return userRoutes.and(otherRoutes);
}
}
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
# 4. 函数式Web的工作原理
# 4.1 请求处理流程
函数式Web框架处理HTTP请求的完整流程如下:
接收请求:Spring接收到HTTP请求后,将其传递给
DispatcherHandler
(WebFlux)或DispatcherServlet
(WebMVC)。创建ServerRequest:框架将原始请求(如
HttpServletRequest
)封装为ServerRequest
对象,提供更便捷的API访问请求数据。路由匹配:框架使用注册的
RouterFunction
来确定应该处理请求的函数。它按照定义顺序检查每个路由,直到找到第一个匹配的路由。预测评估:对于每个路由,框架使用其关联的
RequestPredicate
来确定请求是否匹配。例如,检查HTTP方法、请求路径、内容类型等。处理函数调用:一旦找到匹配的路由,框架调用相应的处理函数,传入
ServerRequest
对象。处理请求:处理函数执行业务逻辑,并创建一个
ServerResponse
对象。写入响应:框架使用
ServerResponse
对象中的信息,写入HTTP响应并发送给客户端。
# 4.2 RequestPredicate
RequestPredicate
是决定请求是否匹配特定路由的关键。它可以基于多种条件进行匹配:
// 基于HTTP方法的匹配
RequestPredicate getPredicate = RequestPredicates.method(HttpMethod.GET);
// 基于路径的匹配(支持变量)
RequestPredicate userPathPredicate = RequestPredicates.path("/user/{id}");
// 基于接受内容类型的匹配
RequestPredicate acceptJsonPredicate = RequestPredicates.accept(MediaType.APPLICATION_JSON);
// 基于请求内容类型的匹配
RequestPredicate jsonContentPredicate = RequestPredicates.contentType(MediaType.APPLICATION_JSON);
// 基于请求头的匹配
RequestPredicate authHeaderPredicate = RequestPredicates.headers(
headers -> headers.firstHeader("Authorization") != null
);
// 基于查询参数的匹配
RequestPredicate hasPageParam = RequestPredicates.queryParam("page", value -> true);
// 组合多个条件
RequestPredicate combinedPredicate = getPredicate.and(userPathPredicate).and(acceptJsonPredicate);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 4.3 ServerRequest
ServerRequest
提供了丰富的API来访问请求数据:
// 处理函数示例
public ServerResponse handleRequest(ServerRequest request) {
// 获取请求方法
HttpMethod method = request.method();
// 获取请求URI
URI uri = request.uri();
// 获取请求路径
String path = request.path();
// 获取所有头部信息
HttpHeaders headers = request.headers().asHttpHeaders();
// 获取特定头部信息
String userAgent = request.headers().firstHeader("User-Agent");
// 获取所有Cookie
MultiValueMap<String, HttpCookie> cookies = request.cookies();
// 获取特定Cookie
String sessionId = request.cookies().getFirst("JSESSIONID")
.map(HttpCookie::getValue)
.orElse(null);
// 获取路径变量
String id = request.pathVariable("id");
// 获取查询参数
String page = request.queryParam("page").orElse("1");
// 获取所有查询参数
MultiValueMap<String, String> params = request.params();
// 检查内容类型
boolean isJson = request.headers().contentType()
.map(mediaType -> mediaType.isCompatibleWith(MediaType.APPLICATION_JSON))
.orElse(false);
// 解析请求体为特定类型
try {
User user = request.body(User.class);
// 处理用户数据...
} catch (Exception e) {
// 处理解析错误...
}
// 返回响应...
}
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
# 4.4 ServerResponse
ServerResponse
提供了多种创建常见HTTP响应的方法:
// 创建200 OK响应
ServerResponse ok = ServerResponse.ok().build();
// 创建带JSON数据的200响应
ServerResponse okWithJson = ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(Map.of("message", "Success"));
// 创建201 Created响应(带Location头)
ServerResponse created = ServerResponse.created(URI.create("/resource/123"))
.build();
// 创建202 Accepted响应
ServerResponse accepted = ServerResponse.accepted().build();
// 创建204 No Content响应
ServerResponse noContent = ServerResponse.noContent().build();
// 创建301 Moved Permanently响应
ServerResponse movedPermanently = ServerResponse.permanentRedirect(URI.create("/new-url"))
.build();
// 创建302 Found响应
ServerResponse found = ServerResponse.temporaryRedirect(URI.create("/temp-url"))
.build();
// 创建400 Bad Request响应
ServerResponse badRequest = ServerResponse.badRequest()
.contentType(MediaType.APPLICATION_JSON)
.body(Map.of("error", "Invalid parameters"));
// 创建401 Unauthorized响应
ServerResponse unauthorized = ServerResponse.status(HttpStatus.UNAUTHORIZED)
.body(Map.of("error", "Authentication required"));
// 创建403 Forbidden响应
ServerResponse forbidden = ServerResponse.status(HttpStatus.FORBIDDEN)
.body(Map.of("error", "Access denied"));
// 创建404 Not Found响应
ServerResponse notFound = ServerResponse.notFound().build();
// 创建500 Internal Server Error响应
ServerResponse serverError = ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(Map.of("error", "Server error occurred"));
// 设置自定义响应头
ServerResponse withCustomHeader = ServerResponse.ok()
.header("X-Custom-Header", "custom-value")
.build();
// 设置Cookie
ServerResponse withCookie = ServerResponse.ok()
.cookie(ResponseCookie.from("session", "abc123")
.maxAge(Duration.ofHours(1))
.httpOnly(true)
.secure(true)
.path("/")
.build())
.build();
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
# 5. 函数式Web进阶技巧
# 5.1 路由嵌套和分组
您可以使用嵌套和分组来组织大型应用程序的路由:
/**
* 嵌套和分组路由配置示例
*/
@Configuration
public class NestedRouteConfig {
/**
* 定义API版本v1的路由
* 所有路由都以/api/v1为前缀
*/
@Bean
public RouterFunction<ServerResponse> apiV1Routes(UserBizHandler userHandler) {
// 用户相关API路由
RouterFunction<ServerResponse> userRoutes = RouterFunctions.route()
.GET("/users", userHandler::getUsers)
.GET("/user/{id}", userHandler::getUser)
.POST("/user", RequestPredicates.contentType(MediaType.APPLICATION_JSON),
userHandler::createUser)
.build();
// 将所有路由嵌套在/api/v1前缀下
return RouterFunctions.route()
.path("/api/v1", builder -> builder
.add(userRoutes)
// 可以添加其他API路由
.GET("/health", request -> ServerResponse.ok().body("OK"))
)
.build();
}
/**
* 定义API版本v2的路由
* 展示另一种分组方式
*/
@Bean
public RouterFunction<ServerResponse> apiV2Routes() {
return RouterFunctions.nest(
// 所有/api/v2开头的请求会进入这个嵌套路由
RequestPredicates.path("/api/v2"),
// 嵌套路由中的路径是相对于/api/v2的
RouterFunctions.route()
.GET("/features", request ->
ServerResponse.ok().body("V2 Features"))
.build()
);
}
}
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
# 5.2 路由过滤器
您可以使用过滤器来为一组路由添加通用功能,如日志记录、安全检查等:
/**
* 路由过滤器配置示例
*/
@Configuration
public class FilterConfig {
/**
* 定义带过滤器的API路由
*/
@Bean
public RouterFunction<ServerResponse> filteredRoutes(UserBizHandler userHandler) {
// 定义API路由
RouterFunction<ServerResponse> routes = RouterFunctions.route()
.GET("/users", userHandler::getUsers)
.GET("/user/{id}", userHandler::getUser)
.POST("/user", userHandler::createUser)
.build();
// 添加日志记录过滤器
routes = routes.filter((request, next) -> {
log.info("收到请求: {} {}", request.method(), request.path());
long startTime = System.currentTimeMillis();
// 继续处理请求
return next.handle(request)
.doOnNext(response -> {
long duration = System.currentTimeMillis() - startTime;
log.info("请求处理完成: {} {} - 状态: {} - 耗时: {}ms",
request.method(), request.path(),
response.statusCode(), duration);
});
});
// 添加安全过滤器
routes = routes.filter((request, next) -> {
// 检查Authorization头
if (request.method() == HttpMethod.POST &&
!request.headers().header("Authorization").contains("Bearer ")) {
return ServerResponse.status(HttpStatus.UNAUTHORIZED)
.contentType(MediaType.APPLICATION_JSON)
.body(Map.of("error", "缺少有效的认证令牌"));
}
// 继续处理请求
return next.handle(request);
});
return routes;
}
}
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
# 5.3 异常处理
函数式Web框架中的异常处理需要明确指定,您可以使用onError
方法来处理异常:
/**
* 异常处理配置示例
*/
@Configuration
public class ErrorHandlingConfig {
/**
* 带异常处理的路由配置
*/
@Bean
public RouterFunction<ServerResponse> routesWithErrorHandling(UserBizHandler userHandler) {
return RouterFunctions.route()
.GET("/user/{id}", userHandler::getUser)
.onError(IllegalArgumentException.class, (e, request) ->
ServerResponse.badRequest()
.contentType(MediaType.APPLICATION_JSON)
.body(Map.of("error", e.getMessage())))
.onError(Exception.class, (e, request) -> {
log.error("处理请求时发生异常", e);
return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR)
.contentType(MediaType.APPLICATION_JSON)
.body(Map.of(
"error", "服务器内部错误",
"message", e.getMessage()
));
})
.build();
}
}
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
# 5.4 内容协商
您可以根据请求的Accept头来返回不同格式的响应:
/**
* 内容协商示例处理方法
*/
public ServerResponse getUserWithContentNegotiation(ServerRequest request) {
// 获取用户数据
User user = getUserFromDatabase(request.pathVariable("id"));
// 根据Accept头选择响应格式
List<MediaType> acceptableMediaTypes = request.headers().accept();
// 默认为JSON
if (acceptableMediaTypes.isEmpty() ||
acceptableMediaTypes.contains(MediaType.APPLICATION_JSON)) {
return ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(user);
}
// XML格式
else if (acceptableMediaTypes.contains(MediaType.APPLICATION_XML)) {
// 假设有一个将用户转换为XML的方法
String userXml = convertToXml(user);
return ServerResponse.ok()
.contentType(MediaType.APPLICATION_XML)
.body(userXml);
}
// HTML格式
else if (acceptableMediaTypes.contains(MediaType.TEXT_HTML)) {
// 假设有一个将用户渲染为HTML的方法
String userHtml = renderUserHtml(user);
return ServerResponse.ok()
.contentType(MediaType.TEXT_HTML)
.body(userHtml);
}
// 不支持的格式
else {
return ServerResponse.status(HttpStatus.NOT_ACCEPTABLE)
.build();
}
}
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
# 6. 与传统注解Web开发的对比
# 6.1 代码对比
为了更直观地理解函数式Web与传统注解式Web的区别,下面展示同一个API的两种实现方式:
传统注解方式:
/**
* 传统注解驱动的REST控制器
*/
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
/**
* 获取所有用户
*/
@GetMapping
public List<User> getAllUsers() {
return userService.getAllUsers();
}
/**
* 获取单个用户
*/
@GetMapping("/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
return userService.findUserById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
/**
* 创建用户
*/
@PostMapping
public ResponseEntity<User> createUser(@RequestBody User user) {
User createdUser = userService.createUser(user);
URI location = ServletUriComponentsBuilder
.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(createdUser.getId())
.toUri();
return ResponseEntity.created(location).body(createdUser);
}
/**
* 更新用户
*/
@PutMapping("/{id}")
public ResponseEntity<User> updateUser(
@PathVariable Long id,
@RequestBody User user) {
return userService.updateUser(id, user)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
/**
* 删除用户
*/
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
if (userService.deleteUser(id)) {
return ResponseEntity.noContent().build();
}
return ResponseEntity.notFound().build();
}
}
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
函数式Web方式:
/**
* 用户服务
*/
@Service
public class UserService {
// 用户服务实现...
}
/**
* 用户处理器
*/
@Component
public class UserHandler {
private final UserService userService;
public UserHandler(UserService userService) {
this.userService = userService;
}
/**
* 获取所有用户
*/
public ServerResponse getAllUsers(ServerRequest request) {
List<User> users = userService.getAllUsers();
return ServerResponse.ok().body(users);
}
/**
* 获取单个用户
*/
public ServerResponse getUserById(ServerRequest request) {
Long id = Long.parseLong(request.pathVariable("id"));
return userService.findUserById(id)
.map(user -> ServerResponse.ok().body(user))
.orElse(ServerResponse.notFound().build());
}
/**
* 创建用户
*/
public ServerResponse createUser(ServerRequest request) throws IOException {
User user = request.body(User.class);
User createdUser = userService.createUser(user);
URI location = URI.create("/api/users/" + createdUser.getId());
return ServerResponse.created(location).body(createdUser);
}
/**
* 更新用户
*/
public ServerResponse updateUser(ServerRequest request) throws IOException {
Long id = Long.parseLong(request.pathVariable("id"));
User user = request.body(User.class);
return userService.updateUser(id, user)
.map(updatedUser -> ServerResponse.ok().body(updatedUser))
.orElse(ServerResponse.notFound().build());
}
/**
* 删除用户
*/
public ServerResponse deleteUser(ServerRequest request) {
Long id = Long.parseLong(request.pathVariable("id"));
if (userService.deleteUser(id)) {
return ServerResponse.noContent().build();
}
return ServerResponse.notFound().build();
}
}
/**
* 路由配置
*/
@Configuration
public class RouterConfig {
@Bean
public RouterFunction<ServerResponse> userRoutes(UserHandler handler) {
return RouterFunctions.route()
.path("/api/users", builder -> builder
.GET("", handler::getAllUsers)
.POST("", handler::createUser)
.GET("/{id}", handler::getUserById)
.PUT("/{id}", handler::updateUser)
.DELETE("/{id}", handler::deleteUser)
)
.build();
}
}
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
# 6.2 优缺点分析
函数式Web优势:
- 关注点分离 - 路由定义与处理逻辑清晰分开
- 显式配置 - 路由规则集中在一处,提高可读性
- 灵活组合 - 路由可以更灵活地组合和重用
- 测试简便 - 处理函数可以独立测试,无需Spring上下文
- 动态路由 - 可以根据运行时条件动态修改路由
函数式Web挑战:
- 学习曲线 - 相比注解方式,需要学习新的API和概念
- 文档较少 - 相比注解方式,学习资源和示例较少
- IDE支持 - 部分IDE可能对函数式路由的支持不如注解方式完善
- 代码量增加 - 某些简单场景下可能比注解方式代码量略多
何时选择函数式Web:
- 需要更高灵活性和组合性时
- 在微服务或API网关中
- 对路由有复杂处理逻辑时
- 追求更清晰的关注点分离时
- 与响应式编程结合时
何时选择注解式Web:
- 简单的CRUD应用程序
- 团队更熟悉传统Spring MVC开发模式
- 需要使用大量现有的Spring MVC功能和工具
- 快速原型开发
- 较少关注路由和业务逻辑的分离
# 7. 在实际项目中如何选择
# 7.1 混合使用两种方式
在实际项目中,函数式Web和注解式Web可以在同一个应用程序中共存,这给开发者提供了更大的灵活性:
/**
* 混合使用示例 - 注解控制器
*/
@RestController
@RequestMapping("/api/products")
public class ProductController {
@GetMapping
public List<Product> getAllProducts() {
// 处理逻辑...
return productList;
}
@GetMapping("/{id}")
public Product getProduct(@PathVariable String id) {
// 处理逻辑...
return product;
}
}
/**
* 混合使用示例 - 函数式路由
*/
@Configuration
public class UserRouterConfig {
@Bean
public RouterFunction<ServerResponse> userRoutes(UserHandler handler) {
return RouterFunctions.route()
.path("/api/users", builder -> builder
.GET("", handler::getAllUsers)
.GET("/{id}", handler::getUserById)
// 更多路由...
)
.build();
}
}
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
这种混合方式允许开发团队根据具体场景选择最合适的方式,并能够渐进式地从一种模式过渡到另一种模式。
# 7.2 决策指南
以下指南可帮助您为项目选择合适的Web开发模式:
选择函数式Web的场景:
- API网关或路由服务 - 当应用程序主要负责路由和转发请求时
- 高级路由需求 - 需要基于复杂条件或规则进行路由决策
- 响应式架构 - 与Spring WebFlux响应式编程模型结合使用
- 微服务架构 - 轻量级的微服务API实现
- 动态路由配置 - 需要在运行时修改路由规则
选择注解式Web的场景:
- 标准CRUD应用 - 具有典型增删改查功能的应用
- 团队熟悉度 - 团队已熟悉Spring MVC注解模式
- 丰富的视图层交互 - 需要与模板引擎集成的Web应用
- 大量使用Spring MVC功能 - 依赖拦截器、参数解析器等特性
- 快速开发原型 - 需要快速搭建应用框架
# 7.3 平滑迁移策略
如果您决定将现有应用从注解式迁移到函数式或混合模式,可以考虑以下策略:
- 增量迁移 - 新功能使用函数式Web,保留现有注解控制器
- 按模块迁移 - 选择独立性强的模块先进行迁移
- 先迁移简单API - 从简单的、独立的API端点开始
- 创建适配层 - 创建一个适配层,使两种方式共享相同的业务逻辑
- 双重实现 - 临时维护两套实现,逐步切换流量
/**
* 适配层示例 - 将处理逻辑共享给注解式和函数式实现
*/
@Service
public class UserService {
// 核心业务逻辑,被两种实现方式共享
public List<User> getAllUsers() {
// 实现逻辑...
return userList;
}
public Optional<User> getUserById(String id) {
// 实现逻辑...
return optionalUser;
}
// 更多业务方法...
}
/**
* 注解式控制器 - 使用共享服务
*/
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping
public List<User> getUsers() {
return userService.getAllUsers();
}
@GetMapping("/{id}")
public ResponseEntity<User> getUser(@PathVariable String id) {
return userService.getUserById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
}
/**
* 函数式处理器 - 使用相同的共享服务
*/
@Component
public class UserHandler {
private final UserService userService;
public UserHandler(UserService userService) {
this.userService = userService;
}
public ServerResponse getAllUsers(ServerRequest request) {
return ServerResponse.ok().body(userService.getAllUsers());
}
public ServerResponse getUserById(ServerRequest request) {
String id = request.pathVariable("id");
return userService.getUserById(id)
.map(user -> ServerResponse.ok().body(user))
.orElse(ServerResponse.notFound().build());
}
}
/**
* 函数式路由配置
*/
@Configuration
public class FunctionalRouterConfig {
@Bean
public RouterFunction<ServerResponse> functionalUserRoutes(UserHandler handler) {
return RouterFunctions.route()
.path("/functional/users", builder -> builder
.GET("", handler::getAllUsers)
.GET("/{id}", handler::getUserById)
)
.build();
}
}
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