HandlerMethodArgumentResolve 优化 Controller调用参数注入
最近手搓出来一个电商系统 springMall ,基本完成了CRUD的接口编写。为了让 bite 更加的 “优雅” ,初步诊断了下“史山成分” ,发现 service层中 getCurrentUserId()这个获取用户ID的方法 重复次数较多,与 "克大哥" 深入沟通了下, 通过重构 HandlerMethodArgumentResolver 方法参数解析器 实现ID参数自动注入。
“史山”概述
后端接口调用时,部分需要获取用户的id值来做对应的处理。之前我们在 各个需要id查询的 Service 层写出 getCurrentUserId()方法,然后在Service层内具体需要id值的 **Controller接口服务 ** 中调用getCurrentUserId()方法。
1 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
| package site.geekie.shop.shoppingmall.service.impl;
import lombok.RequiredArgsConstructor; ...
@Service @RequiredArgsConstructor public class OrderServiceImpl implements OrderService {
private final OrderMapper orderMapper; ...
private Long getCurrentUserId() { SecurityUser securityUser = (SecurityUser) SecurityContextHolder.getContext() .getAuthentication().getPrincipal(); return securityUser.getUser().getId(); <-- 这里 }
@Override @Transactional(rollbackFor = Exception.class) public OrderVO createOrder(OrderDTO request) { Long userId = getCurrentUserId(); <-- 这里 return null; }
@Override public List<OrderVO> getMyOrders() { Long userId = getCurrentUserId(); <-- 这里 return null; }
...省略7处对 getCurrentUserId() 的调用
@Override @Transactional(rollbackFor = Exception.class) public void confirmReceipt(String orderNo) { OrderDO order = orderMapper.findByOrderNo(orderNo); Long userId = getCurrentUserId(); <-- 这里 } }
|
一个小小的 OrderServicellmpl.java文件就有10多出对 getCurrentUserId()方法的调用。然后在整个Service层 中,一共有4处需要 重新声明 并 调用 getCurrentUserId()方法 。It‘s horible!
image
HandlerMethodArgumentResolver重构 方案设计
在方案实施之前,补齐所需要的 Spring架构认知,先简要介绍一下 Spring 请求处理流程图:
image
HTTP 请求进入 DispatcherServlet后,通过 HandlerMapping 定位到对应的 Controller 方法,由 HandlerAdapter 调用HandlerMethodArgumentResolver 解析方法参数,完成参数绑定后执行 Controller,最终返回响应。
而我们需要扩展HandlerAdapter 层的 HandlerMethodArgumentResolver 处理器功能,通过自定义解析器,实现 @CurrentUserId 参数自动注入。
Let's Restructure
Step 1:@CurrentUserId 注解定义 (CurrenUserld.java)
先写出一个 注解定义的接口。
1 2 3 4
| @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface CurrentUserId { }
|
关键元注解说明:
| 元注解 |
作用 |
@Target(PARAMETER) |
限制注解只能标注在方法参数上 |
@Retention(RUNTIME) |
保留到运行时,否则反射读不到 |
Step 2:实现 @CurrentUserId参数解析器 (CurrenUserldResolver.java)
然后实现解析器HandlerMethodArgumentResolver的 重写,拦截 @CurrentUserId 注解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @Component public class CurrentUserIdResolver implements HandlerMethodArgumentResolver {
@Override public boolean supportsParameter(MethodParameter parameter) { return parameter.hasParameterAnnotation(CurrentUserId.class) && Long.class.isAssignableFrom(parameter.getParameterType()); }
@Override public Object resolveArgument(...) { var auth = SecurityContextHolder.getContext().getAuthentication(); SecurityUser securityUser = (SecurityUser) auth.getPrincipal(); return securityUser.getUser().getId(); } }
|
Step 3:注册解析器 CurrentUserIdResolver 到 MVC (WebMvcConfig)
把新建的解析器注册到 MVC Framework当中。
1 2 3 4 5 6 7 8 9 10 11
| @Configuration @RequiredArgsConstructor public class WebMvcConfig implements WebMvcConfigurer {
private final CurrentUserIdResolver currentUserIdResolver;
@Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) { resolvers.add(currentUserIdResolver); } }
|
执行流程图:
1 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
| 用户请求 GET /api/v1/addresses ↓ 携带 JWT Token ↓ ┌─────────────────────────────────────────────────┐ │ JwtAuthenticationFilter │ │ 解析 Token → 设置 SecurityContext │ │ SecurityContextHolder.setContext(authentication)│ └─────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────┐ │ DispatcherServlet │ │ 找到 AddressController.getAddressList() │ └─────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────┐ │ CurrentUserIdResolver │ │ │ │ 1. supportsParameter() → true │ │ (参数有 @CurrentUserId 且类型是 Long) │ │ │ │ 2. resolveArgument() │ │ SecurityContext → Authentication → Principal │ │ → SecurityUser → User → getId() → 返回id │ └─────────────────────────────────────────────────┘ ↓ Controller 方法执行 getAddressList(userId = 查询值) ↓ 返回地址列表
|
涉及的文件结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| site.geekie.shop.shoppingmall/ ├── annotation/ │ └── CurrentUserId.java # 注解定义 │ ├── config/ │ ├── CurrentUserIdResolver.java # 参数解析器 │ ├── WebMvcConfig.java # 注册解析器 │ └── SecurityConfig.java # 安全配置 │ ├── security/ │ ├── SecurityUser.java # 用户信息封装 │ └── JwtAuthenticationFilter.java # JWT 过滤器 │ └── controller/ └── AddressController.java # 使用示例
|