2016-04-26 60 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