You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将多Java控制器中的提前返回校验逻辑提取为共享方法?

如何将多Java控制器中的提前返回校验逻辑提取为共享方法?

这个问题我太有共鸣了——重复的校验代码散在几十个控制器里,不仅维护麻烦,还容易写错!给你几个从简单到优雅的解决方案,按需选择:

1. 工具类快速落地(最直接)

先搞一个静态工具类把校验逻辑封装起来,虽然不是最优雅,但改动最小,能快速解决重复代码问题。

工具类实现

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

public class UserValidationUtils {
    private static final String SUCCESS = "success";
    private static final String ERROR = "error";

    // 封装校验逻辑,返回Optional:校验不通过时返回封装好的响应,否则返回空
    public static Optional<ResponseEntity<?>> validateUserMatch(Authentication authentication, Long requestUserId) {
        User user = (User) authentication.getPrincipal();
        if (Objects.isNull(user) || !user.getId().equals(requestUserId)) {
            return Optional.of(ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of(
                SUCCESS, false,
                ERROR, ErrorCode.RETRIEVING_ANOTHER_USER_DATA_PROHIBITED.getMessage()
            )));
        }
        return Optional.empty();
    }
}

控制器中使用

原来的校验代码可以替换成这两行:

@PostMapping("results")
@ResponseBody
public ResponseEntity<?> results(
    Authentication authentication,
    @RequestBody ResultsRequest request
) {
    try {
        // 调用工具类做校验,不通过直接返回
        Optional<ResponseEntity<?>> validationResult = UserValidationUtils.validateUserMatch(authentication, request.getUserId());
        if (validationResult.isPresent()) {
            return validationResult.get();
        }

        // 校验通过,继续业务逻辑
        return resultsService.getResults(request.getUserId());
    } catch (Exception e) {
        LOGGER.warn(ErrorCode.ERROR_GETTING_RESULTS.getMessage(), e);
    }
    return ResponseEntity.ok(List.of());
}

2. 自定义注解+AOP(最优雅,无侵入)

如果想彻底把校验逻辑从控制器里剥离,用AOP是最优解——只需要给目标方法加个注解,就能自动触发校验,完全消除重复代码。

步骤1:定义自定义注解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckUserIdMatch {
}

步骤2:统一请求DTO的接口

让所有包含userId的请求DTO实现一个通用接口,方便切面统一获取userId:

public interface HasUserId {
    Long getUserId();
}

比如你的ResultsRequest就改成:

public class ResultsRequest implements HasUserId {
    // 原有的字段和方法
    @Override
    public Long getUserId() {
        return this.userId;
    }
}

步骤3:编写切面类

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.Objects;

@Aspect
@Component
public class UserValidationAspect {
    private static final String SUCCESS = "success";
    private static final String ERROR = "error";

    // 拦截所有加了@CheckUserIdMatch的方法
    @Around("@annotation(com.yourpackage.CheckUserIdMatch)")
    public Object validateUserMatch(ProceedingJoinPoint joinPoint) throws Throwable {
        Authentication authentication = null;
        HasUserId request = null;

        // 从方法参数中找到Authentication和请求DTO
        for (Object arg : joinPoint.getArgs()) {
            if (arg instanceof Authentication) {
                authentication = (Authentication) arg;
            } else if (arg instanceof HasUserId) {
                request = (HasUserId) arg;
            }
        }

        // 参数合法性校验(避免方法参数不符合要求的情况)
        if (authentication == null || request == null) {
            throw new IllegalArgumentException("目标方法必须包含Authentication参数和实现HasUserId的请求DTO");
        }

        // 核心校验逻辑
        User user = (User) authentication.getPrincipal();
        if (Objects.isNull(user) || !user.getId().equals(request.getUserId())) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of(
                SUCCESS, false,
                ERROR, ErrorCode.RETRIEVING_ANOTHER_USER_DATA_PROHIBITED.getMessage()
            ));
        }

        // 校验通过,继续执行原方法
        return joinPoint.proceed();
    }
}

步骤4:控制器中使用

只需要给方法加个注解,原来的校验代码可以全部删掉:

@PostMapping("results")
@ResponseBody
@CheckUserIdMatch // 加这个注解就自动校验了
public ResponseEntity<?> results(
    Authentication authentication,
    @RequestBody ResultsRequest request
) {
    try {
        // 直接走业务逻辑,不用再写校验
        return resultsService.getResults(request.getUserId());
    } catch (Exception e) {
        LOGGER.warn(ErrorCode.ERROR_GETTING_RESULTS.getMessage(), e);
    }
    return ResponseEntity.ok(List.of());
}

这种方式彻底解耦了校验逻辑和业务代码,以后新增控制器方法只要加个注解就行,维护成本极低。

3. Spring Security表达式(适合权限体系结合)

如果你的项目已经在用Spring Security,也可以用@PreAuthorize结合自定义SpEL方法来实现,能和现有权限控制体系无缝融合。

步骤1:编写权限校验服务

import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;
import java.util.Objects;

@Service("userSecurityService")
public class UserSecurityService {
    public boolean matchesUserId(Authentication authentication, Long requestUserId) {
        User user = (User) authentication.getPrincipal();
        return user != null && user.getId().equals(requestUserId);
    }
}

步骤2:控制器中使用@PreAuthorize

import org.springframework.security.access.prepost.PreAuthorize;

@PostMapping("results")
@ResponseBody
// 用SpEL调用自定义的校验方法
@PreAuthorize("@userSecurityService.matchesUserId(authentication, #request.userId)")
public ResponseEntity<?> results(
    Authentication authentication,
    @RequestBody ResultsRequest request
) {
    try {
        return resultsService.getResults(request.getUserId());
    } catch (Exception e) {
        LOGGER.warn(ErrorCode.ERROR_GETTING_RESULTS.getMessage(), e);
    }
    return ResponseEntity.ok(List.of());
}

步骤3:全局异常处理

默认情况下,校验不通过会抛出AccessDeniedException,需要写一个全局异常处理器把它转换成你要的响应格式:

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import java.util.Map;

@ControllerAdvice
public class GlobalExceptionHandler {
    private static final String SUCCESS = "success";
    private static final String ERROR = "error";

    @ExceptionHandler(AccessDeniedException.class)
    public ResponseEntity<?> handleAccessDenied(AccessDeniedException e) {
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of(
            SUCCESS, false,
            ERROR, ErrorCode.RETRIEVING_ANOTHER_USER_DATA_PROHIBITED.getMessage()
        ));
    }
}

总结

  • 如果想快速改造,选工具类方案,改动最小;
  • 如果想彻底解耦、代码更干净,选AOP+自定义注解方案;
  • 如果项目已经用了Spring Security,选SpEL表达式方案,和权限体系结合更紧密。

备注:内容来源于stack exchange,提问作者lenny

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.22 07:14:31