Spring AOP with RequestDispatcher calls recursive calls

Spring -servlet.xml

<aop:config> <aop:advisor advice-ref="interceptor" pointcut="@annotation(Validator)"/> </aop:config> <bean id="interceptor" class="org.aopalliance.intercept.MethodInterceptor" /> 

Method Interceptor invoke () :

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

Workflow Management :

My interceptor is called before any Spring controller method that is annotated with a Validator annotation. The goal is to validate the query, if the validation fails, redirect the query to another view. This usually works. If there is an error (!valid) , RequestDispatcher.forward is RequestDispatcher.forward . This calls a different Spring controller method call, which ultimately displays an error view. This usually works.

Question:

For some Spring controllers, my RequestDispatcher errorView causes the request to be redirected to the same method, causing an infinite loop ( invoke() is called again and again). I think this is due to how the Spring controller request mappings are configured (see below).

Browsing Errors: @RequestMapping(value = URL, params="error")

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

So, when the first request is routed, it gets β€œcontinue” in the request parameters. Then, when the error and RequestDispatcher go to the view with the "error" parameter in the query line, it should go to the "View errors" method above, but this is not the case. It always goes to the continue method, calling an infinite loop on MethodInterceptor invoke() . This seems to be due to the fact that the continue option is still in the HttpServletRequest. However, this is not easy to fix, because the whole point of the interceptor is that it does not know the Spring controller itself - it only knows if an error has occurred, and that it should forward the presentation of the error if an error occurs.

Workaround:

Using the query mappings below, it fixes the problem. This is probably due to the fact that the HttpServletRequest parameter is overwritten when using the key = value notation.

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

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

Question

How can I "correct" fix a problem without accessing the workaround shown above? Is there a more standard way to navigate to the correct Spring controller?

+5
source share
1 answer

Solution # 1:

It is configured as follows:

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

You can try redirecting like this:

Method Interceptor invoke ():

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

Disadvantage: the browser will execute the second request, so the old method parameters are no longer in httpservletrequest.

WorkArround: To avoid flaws, you can use Spring MVC Flash Attribute. You can follow this guide to learn how Flash Attribute works.

Refs: FlashAttributesExample

Solution # 2:

How can I "correct" fix the problem without resorting to the workaround shown above? Is there a more standard way to redirect the correct spring controller?

You can enable by implementing your own RequestMappingHandlerAdapter .

Solution # 3:

Here is the code for the aspect:

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

One of the limitations of this work is that it can only use one validator for all controllers. You can also avoid this.

 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 configuration file using validator and meta validator together:

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

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

Solution # 4:

Another solution for validating a form with aop, you can check out the blog: form-validation-using-aspect-oriented-programming-aop-in-spring-framework

+1
source

Source: https://habr.com/ru/post/1247994/


All Articles