建议控制器方法 *before* @Valid annotation 被处理
advise controller method *before* @Valid annotation is handled
我正在使用 Spring MVC 4.1 向 restful 网络服务添加速率限制。
我创建了一个 @RateLimited
注释,我可以将其应用于控制器方法。 Spring AOP 方面拦截对这些方法的调用,并在请求过多时抛出异常:
@Aspect
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class RateLimitingAspect {
@Autowired
private RateLimitService rateLimitService;
@Before("execution(* com.example..*.*(.., javax.servlet.ServletRequest+, ..)) " +
"&& @annotation(com.example.RateLimited)")
public void wait(JoinPoint jp) throws Throwable {
ServletRequest request =
Arrays
.stream(jp.getArgs())
.filter(Objects::nonNull)
.filter(arg -> ServletRequest.class.isAssignableFrom(arg.getClass()))
.map(ServletRequest.class::cast)
.findFirst()
.get();
String ip = request.getRemoteAddr();
int secondsToWait = rateLimitService.secondsUntilNextAllowedAttempt(ip);
if (secondsToWait > 0) {
throw new TooManyRequestsException(secondsToWait);
}
}
这一切都完美无缺,除非 @RateLimited
控制器方法的参数标记为 @Valid
,例如:
@RateLimited
@RequestMapping(method = RequestMethod.POST)
public HttpEntity<?> createAccount(
HttpServletRequest request,
@Valid @RequestBody CreateAccountRequestDto dto) {
...
}
问题:如果验证失败,验证器抛出 MethodArgumentNotValidException
,由 @ExceptionHandler
处理,returns 对客户端的错误响应,永远不会触发我的 @Before
,因此绕过了速率限制。
如何以优先于参数验证的方式拦截这样的 Web 请求?
我想过使用 Spring 拦截器或普通 servlet 过滤器,但它们是由简单的 url 模式映射的,我需要通过 GET/POST/PUT/etc.[=18 来区分=]
看看你是否可以为@@AfterThrowing advice 实现类似的逻辑,这将有类似的切入点。
我最终放弃了寻找 AOP 解决方案的尝试,转而创建了一个 Spring 拦截器。拦截器 preHandle
处理所有请求并监视处理程序为 @RateLimited
.
的请求
@Component
public class RateLimitingInterceptor extends HandlerInterceptorAdapter {
@Autowired
private final RateLimitService rateLimitService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (HandlerMethod.class.isAssignableFrom(handler.getClass())) {
rateLimit(request, (HandlerMethod)handler);
}
return super.preHandle(request, response, handler);
}
private void rateLimit(HttpServletRequest request, HandlerMethod handlerMethod) throws TooManyRequestsException {
if (handlerMethod.getMethodAnnotation(RateLimited.class) != null) {
String ip = request.getRemoteAddr();
int secondsToWait = rateLimitService.secondsUntilNextAllowedInvocation(ip);
if (secondsToWait > 0) {
throw new TooManyRequestsException(secondsToWait);
} else {
rateLimitService.recordInvocation(ip);
}
}
}
}
在您的应用程序中添加以下控制器建议。
@ControllerAdvice
public class ApplicationControllerAdvice {
@InitBinder
@RateLimited
protected void activateBeanPropertyAccess(DataBinder dataBinder) {
dataBinder.initBeanPropertyAccess();
}
}
@RateLimited 应该调用 class RateLimitingAspect。因此,在此之后将调用所有约束验证器。
我正在使用 Spring MVC 4.1 向 restful 网络服务添加速率限制。
我创建了一个 @RateLimited
注释,我可以将其应用于控制器方法。 Spring AOP 方面拦截对这些方法的调用,并在请求过多时抛出异常:
@Aspect
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class RateLimitingAspect {
@Autowired
private RateLimitService rateLimitService;
@Before("execution(* com.example..*.*(.., javax.servlet.ServletRequest+, ..)) " +
"&& @annotation(com.example.RateLimited)")
public void wait(JoinPoint jp) throws Throwable {
ServletRequest request =
Arrays
.stream(jp.getArgs())
.filter(Objects::nonNull)
.filter(arg -> ServletRequest.class.isAssignableFrom(arg.getClass()))
.map(ServletRequest.class::cast)
.findFirst()
.get();
String ip = request.getRemoteAddr();
int secondsToWait = rateLimitService.secondsUntilNextAllowedAttempt(ip);
if (secondsToWait > 0) {
throw new TooManyRequestsException(secondsToWait);
}
}
这一切都完美无缺,除非 @RateLimited
控制器方法的参数标记为 @Valid
,例如:
@RateLimited
@RequestMapping(method = RequestMethod.POST)
public HttpEntity<?> createAccount(
HttpServletRequest request,
@Valid @RequestBody CreateAccountRequestDto dto) {
...
}
问题:如果验证失败,验证器抛出 MethodArgumentNotValidException
,由 @ExceptionHandler
处理,returns 对客户端的错误响应,永远不会触发我的 @Before
,因此绕过了速率限制。
如何以优先于参数验证的方式拦截这样的 Web 请求?
我想过使用 Spring 拦截器或普通 servlet 过滤器,但它们是由简单的 url 模式映射的,我需要通过 GET/POST/PUT/etc.[=18 来区分=]
看看你是否可以为@@AfterThrowing advice 实现类似的逻辑,这将有类似的切入点。
我最终放弃了寻找 AOP 解决方案的尝试,转而创建了一个 Spring 拦截器。拦截器 preHandle
处理所有请求并监视处理程序为 @RateLimited
.
@Component
public class RateLimitingInterceptor extends HandlerInterceptorAdapter {
@Autowired
private final RateLimitService rateLimitService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (HandlerMethod.class.isAssignableFrom(handler.getClass())) {
rateLimit(request, (HandlerMethod)handler);
}
return super.preHandle(request, response, handler);
}
private void rateLimit(HttpServletRequest request, HandlerMethod handlerMethod) throws TooManyRequestsException {
if (handlerMethod.getMethodAnnotation(RateLimited.class) != null) {
String ip = request.getRemoteAddr();
int secondsToWait = rateLimitService.secondsUntilNextAllowedInvocation(ip);
if (secondsToWait > 0) {
throw new TooManyRequestsException(secondsToWait);
} else {
rateLimitService.recordInvocation(ip);
}
}
}
}
在您的应用程序中添加以下控制器建议。
@ControllerAdvice
public class ApplicationControllerAdvice {
@InitBinder
@RateLimited
protected void activateBeanPropertyAccess(DataBinder dataBinder) {
dataBinder.initBeanPropertyAccess();
}
}
@RateLimited 应该调用 class RateLimitingAspect。因此,在此之后将调用所有约束验证器。