【问题标题】:Spring AOP with RequestDispatcher causes recursive calls带有 RequestDispatcher 的 Spring AOP 导致递归调用
【发布时间】:2016-08-20 23:23:24
【问题描述】:

Spring-servlet.xml

<aop:config>
    <aop:advisor advice-ref="interceptor" pointcut="@annotation(Validator)"/>
</aop:config>

<bean id="interceptor" class="org.aopalliance.intercept.MethodInterceptor" />

MethodInterceptor 调用()

if (!valid){
   RequestDispatcher rd = request.getRequestDispatcher(errorView);
   rd.forward(request, response);
}

控制工作流程

我的拦截器在任何带有 Validator 注释的 Spring 控制器方法之前被调用。目的是验证请求,如果验证失败,则将请求转发到不同的视图。这通常是有效的。如果有错误(!valid),则调用RequestDispatcher.forward。这会导致调用另一个 Spring 控制器方法,最终显示错误视图。这通常有效。

问题:

对于某些 Spring 控制器,我的 RequestDispatcher 的 errorView 导致请求被转发回 same 方法,从而导致无限循环(invoke()gets 被反复调用)。我认为这是因为 Spring 控制器的请求映射(见下文)是如何设置的。

错误视图:@RequestMapping(value = URL, params="error")

普通视图:@RequestMapping(value = URL, params="proceed")

因此,当第一个请求被路由时,它会在请求参数中“继续”。然后,当出现错误并且 RequestDispatcher 转发到查询字符串中带有“错误”参数的视图时,它应该转发到上面的“错误视图”方法,但它没有。它总是转发到“继续”方法,导致MethodInterceptor invoke() 上的无限循环。这似乎是因为 'proceed' 参数仍在 HttpServletRequest 中。然而,这并不容易解决,因为拦截器的全部意义在于它不了解 Spring 控制器本身——它只知道是否发生了错误,并且如果发生错误,它应该转发到错误视图。

解决方法:

使用下面的请求映射,它解决了这个问题。这可能是因为 HttpServletRequest 参数在使用 key=value 表示法时被覆盖。

错误视图:@RequestMapping(value = URL, params="view=error")

普通视图:@RequestMapping(value = URL, params="view=proceed")

问题

如何在不采用上述解决方法的情况下“正确”解决问题?有没有更标准的方式转发到正确的弹簧控制器?

【问题讨论】:

  • 能不能把所有源码分享到github或者MethodInterceptor invoke()详细点?

标签: spring spring-mvc


【解决方案1】:

解决方案#1:

配置如下:

Error view: @RequestMapping(value = URL, params="error")

Normal view: @RequestMapping(value = URL, params="proceed")

您可以尝试如下重定向:

MethodInterceptor 调用():

if (!valid){

 //  RequestDispatcher rd = request.getRequestDispatcher(errorView);
 //  rd.forward(request, response);
     response.sendRedirect(errorView);
}

缺点:浏览器会发出第二次请求,因此旧的方法参数不再在 httpservletrequest 中。

WorkArround:为了避免缺点,您可以使用 Spring MVC Flash 属性。您可以按照本教程了解 Flash 属性的工作原理。

参考:FlashAttributesExample

解决方案#2:

如何在不采取变通办法的情况下“正确”解决问题 如上所示?有没有更标准的方式转发到正确的 弹簧控制器?

您可以通过实现自己的 RequestMappingHandlerAdapter 来合并。

解决方案#3:

这是方面的代码:

public class RequestBodyValidatorAspect {
  private Validator validator;

  @Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
  private void controllerInvocation() {
  }

  @Around("controllerInvocation()")
  public Object aroundController(ProceedingJoinPoint joinPoint) throws Throwable {

    MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
    Method method = methodSignature.getMethod();
    Annotation[][] argAnnotations = method.getParameterAnnotations();
    String[] argNames = methodSignature.getParameterNames();
    Object[] args = joinPoint.getArgs();

    for (int i = 0; i < args.length; i++) {
      if (hasRequestBodyAndValidAnnotations(argAnnotations[i])) {
        validateArg(args[i], argNames[i]);
      }
    }

    return joinPoint.proceed(args);
  }

  private boolean hasRequestBodyAndValidAnnotations(Annotation[] annotations) {
    if (annotations.length < 2)
      return false;

    boolean hasValid = false;
    boolean hasRequestBody = false;

    for (Annotation annotation : annotations) {
      if (Valid.class.isInstance(annotation))
        hasValid = true;
      else if (RequestBody.class.isInstance(annotation))
        hasRequestBody = true;

      if (hasValid &amp;&amp; hasRequestBody)
        return true;
    }
    return false;
  }

  @SuppressWarnings({"ThrowableInstanceNeverThrown"})
  private void validateArg(Object arg, String argName) {
    BindingResult result = getBindingResult(arg, argName);
    validator.validate(arg, result);
    if (result.hasErrors()) {
      throw new HttpMessageConversionException("Validation of controller input parameter failed",
              new BindException(result));
    }
  }

  private BindingResult getBindingResult(Object target, String targetName) {
    return new BeanPropertyBindingResult(target, targetName);
  }

  @Required
  public void setValidator(Validator validator) {
    this.validator = validator;
  }
}

此解决方法的一个限制是它只能将单个验证器应用于所有控制器。你也可以避免它。

public class TypeMatchingValidator implements Validator, InitializingBean, ApplicationContextAware {
  private ApplicationContext context;
  private Collection<Validator> validators;

  public void afterPropertiesSet() throws Exception {
    findAllValidatorBeans();
  }

  public boolean supports(Class clazz) {
    for (Validator validator : validators) {
      if (validator.supports(clazz)) {
        return true;
      }
    }

    return false;
  }

  public void validate(Object target, Errors errors) {
    for (Validator validator : validators) {
      if (validator.supports(target.getClass())) {
        validator.validate(target, errors);
      }
    }
  }

  private void findAllValidatorBeans() {
    Map<String, Validator> validatorBeans =
            BeanFactoryUtils.beansOfTypeIncludingAncestors(context, Validator.class, true, false);
    validators = validatorBeans.values();
    validators.remove(this);
  }

  public void setApplicationContext(ApplicationContext context) throws BeansException {
    this.context = context;
  }
}

同时使用验证器方面和元验证器的 Spring XML 配置文件:

 <!-- enable Spring AOP support -->
  <aop:aspectj-autoproxy proxy-target-class="true"/>

  <!-- declare the validator aspect and inject the validator into it -->
  <bean id="validatorAspect" class="com.something.RequestBodyValidatorAspect">
    <property name="validator" ref="validator"/>
  </bean>

  <!-- inject the validator into the DataBinder framework -->
  <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
    <property name="webBindingInitializer">
      <bean class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer" p:validator-ref="validator"/>
    </property>
  </bean>

  <!-- declare the meta-validator bean -->
  <bean id="validator" class="com.something.TypeMatchingValidator"/>

  <!-- declare all Validator beans, these will be discovered by TypeMatchingValidator -->
  <bean class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
  <bean class="com.something.PersonValidator"/>
  <bean class="com.something.AccountValidator"/>

资源参考:scottfrederick:pring-3-Validation-Aspect

解决方案#4:

使用 aop 进行表单验证的另一种解决方案,您可以查看博客:form-validation-using-aspect-oriented-programming-aop-in-spring-framework

【讨论】:

  • 从技术上讲,这将解决问题,因为浏览器会发出第二个请求,因此旧的方法参数不再在 httpservletrequest 中。然而,它基本上避免了如何连接到 Spring 生命周期并将请求“正确”转发到 Spring 控制器的问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-02-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多