一次 @valid注解不生效 排查过程

一、背景

在进行一次Controller层单测时,方法参数违反Validation约束,发现却没有抛出预期的【违反约束】异常。

方法参数上的@Valid注解不生效??

但是以Tomcatweb容器方式启动,请求该API,@Valid注解却生效了,甚是怪异。

代码如下:

@RestController
@RequestMapping("/api/user/")
public class UserController

    @RequestMapping(value = "")
	public Response test(@RequestBody @Valid User user) {
		...
	}
}

其中Test对象如下所示

@Data
public class User {

    @NotNull(message = "用户名称不能为空!")
    private String name;
}

单元测试代码如下,注意:这里的user对象并没有设置name属性。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
        "classpath:/config/spring/application-core.xml",
        "classpath:/config/spring/application-mvc.xml"
})
@Transactional
@Commit
public class UserControllerTest {

   @Autowired
   private UserController controller;

   @Test
   public void test(){
      controller.test(new User());
   }
}

以上UserControllerTest在进行测试的时候并未抛出参数校验ConstraintViolationException的异常。

下面是mvc配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <context:component-scan base-package="com.mtdp" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
    
    <mvc:annotation-driven validator="validator"/>

    <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
        <property name="providerClass" value="org.hibernate.validator.HibernateValidator"/>
    </bean>
  
</beans>

二、解决过程

1.测试过程

在执行单元测试的时候首先暴露出的问题是缺少EL的jar包,因为Hibernate validater执行会依赖EL的jar包。引入对应的jar即可,@see EL依赖

<dependency>
    <groupId>org.glassfish</groupId>
    <artifactId>jakarta.el</artifactId>
    <version>3.0.3</version>
</dependency>

web容器默认会引这个jar,所以不需要添加。

2.原因探究

众所周知,Spring Validation只是一个抽象,真正执行参数校验的是hibernate validator,既然以Tomcat的方式能够生效。那么我们的办法:以debug的方式启动Tomcat,在org.hibernate.validator.internal.engine.ValidatorFactoryImpl#getValidator打上断点,执行Controller层API调用,看是谁调用的该方法,进而执行参数校验的。

结果发现是由HandlerMethodArgumentResolver该接口的作用是对HandlerMethod的方法参数进行校验、解析、转换等工作)的实现类RequestResponseBodyMethodProcessor调用的。

RequestResponseBodyMethodProcessor类会转发给WebDataBinder类,由WebDataBinder最终委托给真正的Validator执行参数校验。如下所示:
在这里插入图片描述
下面是整体的调用链路:
在这里插入图片描述
继而使用之前的UserControllerTest类进行测试,发现执行路径并不是如此,没有进DispatcherServlet类。

问题到此明了了,是因为测试的姿势不太对,我们应该使用Mock mvc的方式去进行测试,这样的话就会mock出一个mvc环境,路由到RequestResponseBodyMethodProcessor(标记@RequestBody或者@ResponseBody注解的参数解析器)进行处理,最终执行到方法参数校验的逻辑。

3.解决方案

修改后的测试代码如下所示,这样测试返回的结果是符合预期的,【违反约束】的异常信息被封装在了MvcResultresponse字段中了。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
        "classpath:/config/spring/application-core.xml",
        "classpath:/config/spring/application-mvc.xml"
})
@Transactional
@Commit
@WebAppConfiguration
@EnableWebMvc
public class UserControllerTest {

    @Autowired
    private WebApplicationContext context;

    private MockMvc mockMVC;

    @Before
    public void initMockMvc() {
        mockMVC = MockMvcBuilders.webAppContextSetup(context).build();
    }

    @Test
    public void testPage() throws Exception {
        String userJson = new Gson().toJson(new User());
		MvcResult mvcResult = mockMVC.perform(MockMvcRequestBuilders.post("/api/user").contentType(MediaType.APPLICATION_JSON).content(userJson)).andReturn();
        System.out.println(mvcResult.getResponse());
    }
}

三、Controller 层@Valid注解原理探究

众所周知,spring mvc XML文件中如果配置了<mvc:annotation-driven>标签时,annotation-driven标签将会使用MvcNamespaceHandler中的org.springframework.web.servlet.config.AnnotationDrivenBeanDefinitionParser解析器进行解析。
MVC xml handler类如下:

public class MvcNamespaceHandler extends NamespaceHandlerSupport {

	@Override
	public void init() {
		registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());
		registerBeanDefinitionParser("default-servlet-handler", new DefaultServletHandlerBeanDefinitionParser());
		registerBeanDefinitionParser("interceptors", new InterceptorsBeanDefinitionParser());
		registerBeanDefinitionParser("resources", new ResourcesBeanDefinitionParser());
		registerBeanDefinitionParser("view-controller", new ViewControllerBeanDefinitionParser());
		registerBeanDefinitionParser("redirect-view-controller", new ViewControllerBeanDefinitionParser());
		registerBeanDefinitionParser("status-controller", new ViewControllerBeanDefinitionParser());
		registerBeanDefinitionParser("view-resolvers", new ViewResolversBeanDefinitionParser());
		registerBeanDefinitionParser("tiles-configurer", new TilesConfigurerBeanDefinitionParser());
		registerBeanDefinitionParser("freemarker-configurer", new FreeMarkerConfigurerBeanDefinitionParser());
		registerBeanDefinitionParser("velocity-configurer", new VelocityConfigurerBeanDefinitionParser());
		registerBeanDefinitionParser("groovy-configurer", new GroovyMarkupConfigurerBeanDefinitionParser());
		registerBeanDefinitionParser("script-template-configurer", new ScriptTemplateConfigurerBeanDefinitionParser());
		registerBeanDefinitionParser("cors", new CorsBeanDefinitionParser());
	}

}

org.springframework.web.servlet.config.AnnotationDrivenBeanDefinitionParser解析器主要是向spring容器中注册了几个mvc组件bean,分别是RequestMappingHandlerMappingRequestMappingHandlerAdapterExceptionHandlerExceptionResolver,代码如下所示:

mvc:annotation-driven will registers a RequestMappingHandlerMapping, a RequestMappingHandlerAdapter, and an ExceptionHandlerExceptionResolver (among others) in support of processing requests with annotated controller methods using annotations such as @RequestMapping, @ExceptionHandler, and others.
在这里插入图片描述

可以看到在上图(1)(2)处解析了<mvc:annotation-driven>中的validator属性,并将获取到的validator赋值给RequestMappingHandlerAdapter中的webBindingInitializer中的validator属性。

获取validator的方法如下所示
这里的逻辑是,如果<mvc:annotation-driven>标签里有配置validator属性,将会使用该属性引用的validator bean作为检验器执行参数校验,否则会判断classpath下是否存在JSR validator类,如果存在,将会使用FactoryBean的方式创建默认的OptionalValidatorFactoryBean
在这里插入图片描述
这个validator最终会在RequestResponseBodyMethodProcessor执行参数解析,创建WebDataBinder类时被赋值给WebDataBindervalidators属性(准确来说,应该是作为validators的一项)。
在这里插入图片描述
RequestResponseBodyMethodProcessor#validateIfApplicable方法中执行校验逻辑。binder.validate其实会路由给bindervalidators执行校验。
这里的validators是spring的一个抽象,最终会转发给真实的validator(也就是配置的providerClass 类)执行参数校验。
在这里插入图片描述
至此完成了标注@RequestBody注解的方法参数的校验。

  • 8
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
在Java中,@Valid和@NotEmpty是用于数据校验的注解。@Valid注解用于标记需要进行数据校验的对象,而@NotEmpty注解用于标记字段不能为空。 如果在使用@Valid和@NotEmpty注解时发现不生效,可能有以下几个原因: 1. 未引入相关依赖:在使用数据校验注解时,需要引入相关的依赖包。在Java中,常用的数据校验框架是Hibernate Validator,因此需要引入相关的依赖,例如在Maven项目中可以添加以下依赖: ```xml <dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator</artifactId> <version>6.2.0.Final</version> </dependency> ``` 2. 未配置校验器:在使用@Valid注解时,需要配置校验器。可以通过在Spring Boot项目中添加以下配置来启用校验器: ```java @Configuration public class ValidationConfig { @Bean public Validator validator() { return Validation.buildDefaultValidatorFactory().getValidator(); } } ``` 3. 未开启校验功能:在使用@Valid注解时,需要确保校验功能已经开启。可以通过在Controller类或方法上添加@Validated注解来开启校验功能: ```java @RestController @Validated public class UserController { @PostMapping("/user") public void addUser(@Valid @RequestBody User user) { // 处理用户添加逻辑 } } ``` 如果以上步骤都已正确配置,但仍然发现@Valid和@NotEmpty注解不生效,可能是因为其他原因导致的。可以提供更多的代码或错误信息,以便更好地帮助您解决问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值