2016-04-26 63 views
5

为spring-servlet.xmlSpring AOP的用的RequestDispatcher导致递归调用

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

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

的MethodInterceptor的invoke():控制的

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

工作流程:

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

问题:

对于一些春季控制器,我的RequestDispatcher的errorView导致请求被转发回相同方法造成一个无限循环(invoke()被反复调用)。我想这是因为Spring控制器的请求映射(见下文)的设置。

错误观点:@RequestMapping(value = URL, params="error")

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

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

解决方法:

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

错误观点:@RequestMapping(value = URL, params="view=error")

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

问题

我如何 “正确” 解决这个问题,而不诉诸上面显示的解决方法吗?有没有更准确的方法转发到正确的弹簧控制器?

+0

你能分享所有的源代码github或** MethodInterceptor invoke()**更多详细信息? – CrawlingKid

回答

1

解决方案#1:

具有配置如下:

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

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

你可以尝试重定向如下:

的MethodInterceptor的invoke():

if (!valid){ 

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

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

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

参考文献: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

+0

从技术上讲,这将解决问题,因为浏览器会发出第二个请求,因此旧的方法参数不再位于httpservletrequest中。然而,它基本上避免了如何挂钩到Spring生命周期以及将请求正确地转发给Spring控制器的问题。 – KyleM