2016-04-26 10 views
5

Primavera-servlet.xml:Spring AOP con RequestDispatcher provoca chiamate ricorsive

<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); 
} 

Lavorare flusso di controllo:

Il mio intercettore è chiamato prima di ogni primavera metodo del controllore annotato con l'annotazione Validator. L'intenzione è di convalidare la richiesta, se la convalida fallisce, inoltrare la richiesta a un'altra vista. Questo di solito funziona. Se si verifica un errore (!valid), viene chiamato lo RequestDispatcher.forward. Ciò causa la chiamata di un altro metodo di controllo Spring che alla fine mostra la visualizzazione dell'errore. Questo normalmente funziona.

Problema:

Per alcuni controller di primavera, di mio RequestDispatcher errorView fa sì che la richiesta da inoltrare di nuovo al metodo stessa causando un ciclo infinito (invoke() viene chiamato più e più volte). Penso che questo sia dovuto al modo in cui sono configurati i mapping delle richieste del controller Spring (vedi sotto).

vista Errore: @RequestMapping(value = URL, params="error")

vista normale: @RequestMapping(value = URL, params="proceed")

Così, quando la prima richiesta viene indirizzata è ottenuto 'procedere' nei params richiesta. Quindi, quando c'è un errore e il RequestDispatcher inoltra alla vista con il parametro 'error' nella stringa di query, dovrebbe inoltrare al metodo "Error view" sopra, ma non lo fa. Si inoltra sempre al metodo 'proceed' causando un loop infinito su MethodInterceptor invoke(). Questo sembra essere dovuto al fatto che il parametro 'proceed' è ancora in HttpServletRequest. Tuttavia questo non è facile da risolvere perché l'intero punto dell'intercettatore è che non ha alcuna conoscenza del controller Spring stesso: sa solo se si è verificato un errore e che dovrebbe inoltrare alla vista errori se si è verificato un errore.

Soluzione:

Utilizzando le mappature di richiesta di seguito, si risolve il problema. Ciò è probabilmente dovuto al fatto che il parametro HttpServletRequest viene sovrascritto quando si utilizza la notazione key = value.

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

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

Domanda

Come posso "correttamente" risolvere il problema senza ricorrere alla soluzione mostrata in precedenza? Esiste un modo più standard per inoltrare il controller a molla corretto?

+0

Puoi condividere tutti i codici sorgente con github o ** MethodInterceptor invoke() ** maggiori dettagli? – CrawlingKid

risposta

1

Soluzione # 1:

Dopo aver configurato come segue:

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

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

si potrebbe provare per il rinvio come segue:

MethodInterceptor invoke():

if (!valid){ 

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

Svantaggio: il browser farebbe una seconda richiesta, quindi i vecchi parametri del metodo non sono più nella richiesta httpservlet.

WorkArround: Per evitare lo svantaggio, è possibile utilizzare Spring MVC Flash Attribute. Puoi seguire questo tutorial per sapere come funziona l'Attributo Flash.

Refs: FlashAttributesExample

Soluzione # 2:

Come posso "correttamente" risolvere il problema senza ricorrere alla soluzione mostrato sopra? Esiste un modo più standard per inoltrare al controller di primavera corretto?

Si potrebbe incorporare da voi attuazione possiedono RequestMappingHandlerAdapter.

Soluzione # 3:

Ecco il codice per l'aspetto:

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; 
    } 
} 

Una limitazione di questo work-around è che può applicarsi solo un singolo validatore per tutti i controllori. Puoi anche evitarlo.

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; 
    } 
} 

file di configurazione XML Primavera utilizzando l'aspetto validatore e il meta-validatore insieme:

<!-- 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"/> 

Risorse Refs: scottfrederick:pring-3-Validation-Aspect

Soluzione # 4:

un'altra soluzione per la convalida del modulo utilizzando aop, è possibile controllare il blog: form-validation-using-aspect-oriented-programming-aop-in-spring-framework

+0

Tecnicamente questo risolverebbe il problema perché il browser farebbe una seconda richiesta, quindi i vecchi parametri del metodo non sono più nella richiesta httpservlet. Tuttavia, fondamentalmente, evita le domande su come collegarsi al ciclo di vita di Spring e "correttamente" inoltrare la richiesta a un controller Spring. – KyleM