以下是前后端分离架构下,Spring Boot 请求从发起到响应的完整执行流程,结合你提出的所有问题,按真实执行顺序和职责链条重新整理所有核心概念、结构、关键类、数据转换点和典型代码示例:
一、前端发起请求(步骤1-2)
关键组件:React/Vue + Axios + JSON
axios.get('/api/users', { headers: { Authorization: 'Bearer xxx' } });
-
不会包含 JSON body(因 GET 请求规范所限)
-
请求参数以 query param 附带,如
/api/users?active=true
二、后端接收请求与过滤(步骤3)
关键组件:Spring Security Filter Chain
public class JwtAuthenticationFilter extends OncePerRequestFilter {
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) {
String header = request.getHeader("Authorization");
String token = header != null && header.startsWith("Bearer ") ? header.substring(7) : null;
if (token != null && jwtUtil.validate(token)) {
Authentication auth = jwtUtil.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(auth);
}
chain.doFilter(request, response);
}
}
-
JWT 验证只发生在 Filter 中,不会进入 Controller 就已判定是否通过
-
验证通过后构造
Authentication
对象放入SecurityContext
三、DispatcherServlet 分发请求(步骤4)
核心作用:统一接收请求,协调执行链
-
DispatcherServlet 并不执行具体处理,只负责流程编排
四、HandlerMapping 确定 Handler(步骤5)
RequestMappingHandlerMapping:匹配@Controller + @RequestMapping 控制器方法
-
负责查找哪个 Controller 的哪个方法应该处理这个请求
-
构建好 HandlerExecutionChain(包含拦截器)
五、HandlerAdapter 调用处理器(步骤6)
RequestMappingHandlerAdapter:负责调用注解控制器方法
-
supports(Object handler)
方法确认适配器是否支持此处理器 -
参数解析、类型转换、方法调用都由它完成
六、Controller 执行业务分发(步骤7)
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
@GetMapping
public ResponseEntity<List<UserDto>> getUsers() {
List<UserDto> list = userService.getAllUsers();
return ResponseEntity.ok(list);
}
}
-
Controller 不应处理认证逻辑,而是专注于业务与 DTO 构造
-
使用 ResponseEntity 是为了更灵活控制 HTTP 状态码与头部
七、Service 层执行业务逻辑(步骤8)
@Service
public class UserServiceImpl implements UserService {
public List<UserDto> getAllUsers() {
List<User> entities = userRepository.findAll();
return entities.stream().map(this::toDto).toList();
}
private UserDto toDto(User user) {
return new UserDto(user.getId(), user.getName(), user.getEmail());
}
}
-
接收 Entity 对象 → 转换为 DTO(数据传输对象)
-
不应返回 ResponseEntity,这保持了关注点分离
八、Repository 访问数据库(步骤9-11)
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
List<User> findByActiveTrue();
}
@Entity
@Table(name = "users")
public class User {
@Id
private Long id;
private String name;
private String email;
}
-
DAO 实质上是 Repository 接口本身(即 Data Access Object)
-
Entity 是 ORM 框架(JPA/Hibernate)使用的 Java → 表 的映射结构
-
从数据库返回的是 Entity 实例,非 DTO,非 DAO
九、响应流程(步骤12-17)
数据返回结构:
步骤 | 返回对象 | 类型 |
---|---|---|
12 | Repository → Service | Entity 实体类 |
13 | Service → Controller | DTO(如 UserDto) |
14 | Controller → HandlerAdapter | ResponseEntity 或直接 DTO |
15 | HandlerAdapter → DispatcherServlet | 包装为 ModelAndView(内部结构) |
17 | DispatcherServlet → 前端 | JSON 数据(通过 HttpMessageConverter 序列化) |
十、常见问题澄清汇总
❓JWT是谁生成的?前端能编辑它吗?
-
❌ 前端不能生成 JWT,必须由后端签名生成
-
✅ JWT 中的 claim 字段(sub/iss/exp)由后端控制
❓为什么需要 Authentication?不能直接验证放行?
-
✅ 因为权限注解(如 @PreAuthorize)等依赖 SecurityContextHolder 中的 Authentication 对象
❓HandlerMapping vs HandlerAdapter 为什么都要有?
组件 | 功能 | 是否必须 |
---|---|---|
HandlerMapping | 确定哪个 Controller 处理请求 | ✅ |
HandlerAdapter | 执行该 Controller 的方法 | ✅ |
-
二者实现了解耦:DispatcherServlet 不直接依赖 Controller 类型
-
实际开发可以注册自定义 Mapping/Adapter
❓ResponseEntity vs DTO vs ResponseObject 区别?
类型 | 定义 | 用途 |
---|---|---|
DTO | 纯Java对象,字段只为数据交换 | Service 返回给 Controller |
ResponseEntity | Spring类,用于封装响应状态、头、体 | Controller 返回给框架 |
自定义 ResponseObject | 如 ApiResponse,封装统一格式 | 可选嵌入于 ResponseEntity 中 |
✅ 不是必须使用 ResponseEntity,直接返回 DTO,Spring 也会自动序列化
建议实践结构总结
前端请求 → Filter链认证(JWT) → DispatcherServlet协调 → HandlerMapping确定Controller
→ HandlerAdapter调用Controller方法 → Controller调用Service → Service调用Repository
→ Repository返回Entity → Service转换为DTO → Controller包装为ResponseEntity
→ 返回给前端(JSON)
✅ 先明确三个概念:
注解 | 用于 | 描述 |
---|---|---|
| 控制器或异常处理类中的方法 | 声明某个方法用来处理特定类型异常 |
| 类级别 | 用于集中定义所有控制器的全局异常处理 |
|
| 自动将返回值作为JSON响应 |
✅ 情况分类:异常处理的三个层级
异常发生层 | 谁处理 | 示例 |
---|---|---|
Controller层内部异常 | 当前 Controller 类中的 | 请求参数缺失、非法访问 |
Service层抛出业务异常 | 交由 Controller 接收并传播给 | 用户名重复、余额不足 |
Repository层抛出数据访问异常 | Service 可以选择 catch/转义,也可以上抛 | JPA抛出 |
❓1. “用了@ExceptionHandler,为什么还需要@ControllerAdvice?”
-
@ExceptionHandler
是方法级,@ControllerAdvice
是全局级的,集中管理多个Controller的异常处理 。所以两者不是“互相替代”,而是范围不同:ControllerAdvice 是容器,ExceptionHandler 是容器里的处理器方法
❓2. 那为什么还说“Controller处理Service抛出的异常”?“Controller处理Service异常”指的是:“异常发生于Service层,但由Controller层抛出并触发了统一异常处理机制”。
✅ 代码级举例(完整调用链)
➤ 1. 自定义异常类:
➤ 2. Service层抛出异常:
➤ 3. Controller调用Service:
➤ 4. 全局异常处理器:
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(UserAlreadyExistsException.class)
public ResponseEntity<ApiError> handleUserExists(UserAlreadyExistsException ex) {
ApiError error = new ApiError(409, ex.getMessage());
return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ApiError> handleOther(Exception ex) {
ApiError error = new ApiError(500, \"Internal server error\");
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
}
}
✅ 小结(极简结论):
问题 | 结论 |
---|---|
@ExceptionHandler 和 @ControllerAdvice 冲突吗? | ❌ 不冲突,前者方法级,后者类级,全局异常需要配合使用 |
Controller是否“处理”Service抛出的异常? | ✅ 表面上Controller收到异常,实则由 @ControllerAdvice 捕获 |
实际开发该怎么用? | ✅ 建议所有 Controller 异常集中交由 @RestControllerAdvice 来统一处理 |
✅ 一、前后端通信中的数据结构与序列化处理
❓1. 前端发送 GET 请求 /api/users
,不做 JSON 清洗吗?后端收到的是什么?直接处理 JSON 吗?
-
✅ GET 请求不会带有 JSON 请求体(规范禁止),数据清洗体现在构造 Query 参数(如
/api/users?active=true
)。 -
✅ 后端不会处理 JSON,而是通过
@RequestParam
解析 URL 查询参数。 -
✅ JSON 序列化只发生在 POST/PUT 请求体 或 响应数据 中。
❓2. JSON 序列化 / 反序列化发生在哪一步?DispatcherServlet 负责吗?
-
✅ 反序列化(JSON → Java)发生在 HandlerAdapter 调用 Controller 方法前。
-
✅ 序列化(Java → JSON)发生在 Controller 方法返回后。
-
❌
DispatcherServlet
不负责 JSON 处理,它只负责请求转发。 -
✅ JSON 的处理由
HttpMessageConverter
(默认使用 Jackson)完成。
✅ 二、DAO / DTO 的职责与流程中的定位
❓3. 什么是 DAO 和 DTO?图中的第 12~14 步分别返回的是什么?
-
✅ DAO(Data Access Object)是用于封装数据库访问逻辑的接口,如
UserRepository extends JpaRepository
。 -
✅ DTO(Data Transfer Object)是前后端或多层之间传递数据的载体,如
UserDto
。 -
图中:
-
第12步:DAO 返回的是实体类(Entity);
-
第13步:Service 转换为 DTO,返回给 Controller;
-
第14步:Controller 使用
ResponseEntity<DTO>
作为响应,框架底层封装为ModelAndView
。
-
❓4. ResponseEntity 和 Model 有什么区别?第 15 步为什么是 ModelAndView?
-
✅
ResponseEntity
用于前后端分离,返回完整 JSON 响应; -
✅
Model
是传统 MVC 模式中用于传数据给视图模板的结构; -
✅
ModelAndView
是 Spring MVC 底层用于封装所有结果的统一结构(即使你返回的是ResponseEntity
,框架也封装为ModelAndView(null, body)
)。 -
✅ 前后端分离时,开发者只用
ResponseEntity
,无需直接接触ModelAndView
。
✅ 三、Spring Security 中的 JWT 验证机制
❓5. JWT 验证流程在 Spring Boot 中是怎样的?在哪一层?
-
✅ JWT 验证发生在
FilterChain
阶段,通常在OncePerRequestFilter
中实现; -
✅ 验证步骤:
-
从请求 Header 获取
Authorization: Bearer <token>
; -
验证签名、结构、过期时间;
-
解析生成
Authentication
; -
设置到
SecurityContextHolder
; -
放行到 DispatcherServlet;
-
-
❌ DispatcherServlet 和 Handler 不参与认证,只接收已验证请求。
❓6. JWT 是由前端编辑生成的吗?字段如 iss、sub、exp、roles 是前端决定的吗?
-
❌ JWT 不能由前端生成;
-
✅ JWT 是后端使用密钥生成的,包含的字段完全由后端控制;
-
✅ 前端只是接收、存储并在请求中携带;
-
JWT 示例字段如
sub: userId, exp: timestamp, roles: ["ADMIN"]
都由后端代码设置。
❓7. 为什么 JWT 验证完还要构建 Authentication
对象?不能直接转发请求吗?
-
✅ 构建
Authentication
的目的是让 Spring Security 安全机制生效:-
权限判断(如
@PreAuthorize
)依赖SecurityContext
; -
日志、审计、日志追踪、用户上下文等都依赖它;
-
-
❌ 如果跳过该步骤,系统将视为匿名用户,请求将不具备认证身份。
✅ 四、Spring Security 的方法级授权机制
❓8. @PreAuthorize 注解用在哪些类?在哪一步执行?为什么叫 Pre?看起来像是在认证前
-
✅
@PreAuthorize
用于 Controller 或 Service 方法上,限制某角色、用户是否能执行该方法; -
✅ 执行时机是 在方法体执行之前,不是在登录认证之前;
-
✅ 所谓“Pre”指的是在方法调用前进行权限判断;
-
✅ 注解本质通过 AOP 拦截方法,依赖于
Authentication
已存在(由 Filter 中设置); -
❌ 如果没有
Authentication
,@PreAuthorize
判断失效,默认拒绝访问。
用法:
@PreAuthorize("hasRole('ADMIN')")
public void deleteUser(Long id) { ... }
@PreAuthorize("#userId == authentication.name")
public void updateUser(Long userId, UserDto dto) { ... }
✅ 总结关键词表
关键词 | 解释 |
---|---|
DTO | 传输数据结构体,跨层/跨系统使用 |
DAO | 数据访问接口,用于封装数据库操作 |
ResponseEntity | 用于返回 JSON 响应体的 Spring 结构 |
ModelAndView | Spring MVC 内部统一封装结构 |
JWT | JSON Web Token,认证令牌,由后端生成 |
Authentication | Spring Security 中用于表示认证用户的对象 |
SecurityContextHolder | 保存当前请求上下文的认证信息 |
@PreAuthorize | 方法级授权注解,执行方法前做权限判断 |