Spring-servlet.xml:
<aop:config>
<aop:advisor advice-ref="interceptor" pointcut="@annotation(Validator)"/>
</aop:config>
<bean id="interceptor" class="org.aopalliance.intercept.MethodInterceptor" />
Run Code Online (Sandbox Code Playgroud)
MethodInterceptor invoke():
if (!valid){
RequestDispatcher rd = request.getRequestDispatcher(errorView);
rd.forward(request, response);
}
Run Code Online (Sandbox Code Playgroud)
工作流程控制:
在使用注释注释的任何Spring控制器方法之前调用我的拦截器Validator.目的是验证请求,如果验证失败,则将请求转发到其他视图.这通常有效.如果有错误(!valid),RequestDispatcher.forward则调用.这会导致调用另一个Spring控制器方法,最终显示错误视图.这通常有效.
问题:
对于某些Spring控制器,我的RequestDispatcher errorView导致请求被转发回相同的方法,导致无限循环(invoke()被反复调用).我认为这是因为Spring控制器的请求映射(见下文)是如何设置的.
错误视图: @RequestMapping(value = URL, params="error")
普通视图: @RequestMapping(value = URL, params="proceed")
因此,当第一个请求被路由时,它会在请求参数中"继续".然后当出现错误并且RequestDispatcher转发到查询字符串中带有"错误"参数的视图时,它应转发到上面的"错误视图"方法,但事实并非如此.它始终转发到'proceed'方法,导致无限循环MethodInterceptor invoke().这似乎是因为'proceed'参数仍然在HttpServletRequest中.然而,这并不容易解决,因为拦截器的全部意义在于它不了解Spring控制器本身 - 它只知道是否发生了错误,并且如果发生错误它应该转发到错误视图.
解决方法:
使用下面的请求映射,它解决了问题.这可能是因为HttpServletRequest使用key = value表示法时会覆盖参数.
错误视图: @RequestMapping(value = URL, params="view=error")
普通视图: @RequestMapping(value = URL, params="view=proceed")
题
如何在不诉诸上述解决方法的情况下"正确"解决问题?是否有更标准的方法转发到正确的弹簧控制器?
解决方案#1:
配置如下:
Error view: @RequestMapping(value = URL, params="error")
Normal view: @RequestMapping(value = URL, params="proceed")
Run Code Online (Sandbox Code Playgroud)
您可以尝试如下重定向:
方法拦截器调用():
if (!valid){
// RequestDispatcher rd = request.getRequestDispatcher(errorView);
// rd.forward(request, response);
response.sendRedirect(errorView);
}
Run Code Online (Sandbox Code Playgroud)
缺点:浏览器会发出第二次请求,因此旧方法参数不再在 httpservletrequest 中。
解决方法:为了避免缺点,您可以使用 Spring MVC Flash 属性。您可以按照本教程了解 Flash 属性的工作原理。
解决方案#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 && 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;
}
}
Run Code Online (Sandbox Code Playgroud)
此解决方法的一个限制是它只能将单个验证器应用于所有控制器。您也可以避免它。
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;
}
}
Run Code Online (Sandbox Code Playgroud)
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"/>
Run Code Online (Sandbox Code Playgroud)
资源参考:scottfrederick:pring-3-Validation-Aspect
解决方案#4:
使用 aop 进行表单验证的另一种解决方案,您可以查看博客:form-validation-using-aspect-oriented-programming-aop-in-spring-framework
| 归档时间: |
|
| 查看次数: |
348 次 |
| 最近记录: |