最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

一次踩坑記錄 @valid注解不生效 排查過程

 更新時間:2021年06月09日 09:57:55   作者:wt_better  
這篇文章主要介紹了一次踩坑記錄 @valid注解不生效 排查過程,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教

一、背景

在進行一次Controller層單測時,方法參數(shù)違反Validation約束,發(fā)現(xiàn)卻沒有拋出預(yù)期的【違反約束】異常。

方法參數(shù)上的@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對象并沒有設(shè)置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在進行測試的時候并未拋出參數(shù)校驗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.測試過程

在執(zhí)行單元測試的時候首先暴露出的問題是缺少EL的jar包,因為Hibernate validater執(zhí)行會依賴EL的jar包。引入對應(yīng)的jar即可,@see EL依賴

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

web容器默認(rèn)會引這個jar,所以不需要添加。

2.原因探究

眾所周知,Spring Validation只是一個抽象,真正執(zhí)行參數(shù)校驗的是hibernate validator,既然以Tomcat的方式能夠生效。那么我們的辦法:以debug的方式啟動Tomcat,在org.hibernate.validator.internal.engine.ValidatorFactoryImpl#getValidator打上斷點,執(zhí)行Controller層API調(diào)用,看是誰調(diào)用的該方法,進而執(zhí)行參數(shù)校驗的。

結(jié)果發(fā)現(xiàn)是由HandlerMethodArgumentResolver(該接口的作用是對HandlerMethod的方法參數(shù)進行校驗、解析、轉(zhuǎn)換等工作)的實現(xiàn)類RequestResponseBodyMethodProcessor調(diào)用的。

RequestResponseBodyMethodProcessor類會轉(zhuǎn)發(fā)給WebDataBinder類,由WebDataBinder最終委托給真正的Validator執(zhí)行參數(shù)校驗。

如下所示:

下面是整體的調(diào)用鏈路:

繼而使用之前的UserControllerTest類進行測試,發(fā)現(xiàn)執(zhí)行路徑并不是如此,沒有進DispatcherServlet類。

問題到此明了了,是因為測試的姿勢不太對,我們應(yīng)該使用Mock mvc的方式去進行測試,這樣的話就會mock出一個mvc環(huán)境,路由到RequestResponseBodyMethodProcessor(標(biāo)記@RequestBody或者@ResponseBody注解的參數(shù)解析器)進行處理,最終執(zhí)行到方法參數(shù)校驗的邏輯。

3.解決方案

修改后的測試代碼如下所示,這樣測試返回的結(jié)果是符合預(yù)期的,【違反約束】的異常信息被封裝在了MvcResult的response字段中了。

@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>標(biāo)簽時,annotation-driven標(biāo)簽將會使用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,分別是RequestMappingHandlerMapping,RequestMappingHandlerAdapter,ExceptionHandlerExceptionResolver,代碼如下所示:

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>標(biāo)簽里有配置validator屬性,將會使用該屬性引用的validator bean作為檢驗器執(zhí)行參數(shù)校驗,否則會判斷classpath下是否存在JSR validator類,如果存在,將會使用FactoryBean的方式創(chuàng)建默認(rèn)的OptionalValidatorFactoryBean。

這個validator最終會在RequestResponseBodyMethodProcessor執(zhí)行參數(shù)解析,創(chuàng)建WebDataBinder類時被賦值給WebDataBinder的validators屬性(準(zhǔn)確來說,應(yīng)該是作為validators的一項)。

在RequestResponseBodyMethodProcessor#validateIfApplicable方法中執(zhí)行校驗邏輯。binder.validate其實會路由給binder的validators執(zhí)行校驗。

這里的validators是spring的一個抽象,最終會轉(zhuǎn)發(fā)給真實的validator(也就是配置的providerClass 類)執(zhí)行參數(shù)校驗。

至此完成了標(biāo)注@RequestBody注解的方法參數(shù)的校驗。

@Valid注解是什么

@Valid

用于驗證注解是否符合要求,直接加在變量user之前,在變量中添加驗證信息的要求,當(dāng)不符合要求時就會在方法中返回message 的錯誤提示信息。

@RestController
@RequestMapping("/user")
public class UserController {
    @PostMapping
    public User create (@Valid @RequestBody User user) {
        System.out.println(user.getId());
        System.out.println(user.getUsername());
        System.out.println(user.getPassword());
        user.setId("1");
        return user;
    }
} 

然后在 User 類中添加驗證信息的要求:

public class User {
    private String id;  
 
    @NotBlank(message = "密碼不能為空")
    private String password;
}

@NotBlank 注解所指的 password 字段,表示驗證密碼不能為空,如果為空的話,上面 Controller 中的 create 方法會將message 中的"密碼不能為空"返回。

當(dāng)然也可以添加其他驗證信息的要求:

限制 說明
@Null 限制只能為null
@NotNull 限制必須不為null
@AssertFalse 限制必須為false
@AssertTrue 限制必須為true
@DecimalMax(value) 限制必須為一個不大于指定值的數(shù)字
@DecimalMin(value) 限制必須為一個不小于指定值的數(shù)字
@Digits(integer,fraction) 限制必須為一個小數(shù),且整數(shù)部分的位數(shù)不能超過integer,小數(shù)部分的位數(shù)不能超過fraction
@Future 限制必須是一個將來的日期
@Max(value) 限制必須為一個不大于指定值的數(shù)字
@Min(value) 限制必須為一個不小于指定值的數(shù)字
@Past 限制必須是一個過去的日期
@Pattern(value) 限制必須符合指定的正則表達式
@Size(max,min) 限制字符長度必須在min到max之間
@Past 驗證注解的元素值(日期類型)比當(dāng)前時間早
@NotEmpty 驗證注解的元素值不為null且不為空(字符串長度不為0、集合大小不為0)
@NotBlank 驗證注解的元素值不為空(不為null、去除首位空格后長度為0),不同于@NotEmpty,@NotBlank只應(yīng)用于字符串且在比較時會去除字符串的空格
@Email 驗證注解的元素值是Email,也可以通過正則表達式和flag指定自定義的email格式

除此之外還可以自定義驗證信息的要求,例如下面的 @MyConstraint:

public class User {
    private String id;
    @MyConstraint(message = "這是一個測試")
    private String username;
}

注解的具體內(nèi)容:

@Constraint(validatedBy = {MyConstraintValidator.class})
@Target({ELementtype.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyConstraint {
    String message();
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {}; 
}

下面是校驗器:

public class MyConstraintValidator implements ConstraintValidator<MyConstraint, Object> {
    @Autowired
    private UserService userService;
    
    @Override
    public void initialie(@MyConstraint constarintAnnotation) {
        System.out.println("my validator init");
    }
    
    @Override
    public boolean isValid(Object value, ConstraintValidatorContext context) {
        userService.getUserByUsername("seina");
        System.out.println("valid");
        return false;
    }
}

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • Java實現(xiàn)的JSONUtil工具類與用法示例

    Java實現(xiàn)的JSONUtil工具類與用法示例

    這篇文章主要介紹了Java實現(xiàn)的JSONUtil工具類與用法,結(jié)合實例形式分析了Java操作json格式數(shù)據(jù)工具類JSONUtil的定義與簡單使用技巧,需要的朋友可以參考下
    2018-07-07
  • 關(guān)于springboot中的自定義配置項

    關(guān)于springboot中的自定義配置項

    這篇文章主要介紹了關(guān)于springboot中的自定義配置項,在項目開發(fā)的過程中,經(jīng)常需要自定義系統(tǒng)業(yè)務(wù)方面的配置文件及配置項,Spring Boot提供了@value注解、@ConfigurationProperties注解和Environment接口等3種方式自定義配置項,需要的朋友可以參考下
    2023-07-07
  • Java 集合系列(二)ArrayList詳解

    Java 集合系列(二)ArrayList詳解

    這篇文章主要介紹了Java集合系列ArrayList,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-03-03
  • 解析如何用兩個棧來實現(xiàn)隊列的方法

    解析如何用兩個棧來實現(xiàn)隊列的方法

    本篇文章是對如何用兩個棧實現(xiàn)隊列的方法進行了詳細(xì)的分析介紹,需要的朋友參考下
    2013-06-06
  • java微信公眾號企業(yè)付款開發(fā)

    java微信公眾號企業(yè)付款開發(fā)

    這篇文章主要為大家詳細(xì)介紹了java微信公眾號企業(yè)付款開發(fā),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-09-09
  • 初識MyBatis及基本配置和執(zhí)行

    初識MyBatis及基本配置和執(zhí)行

    這篇文章主要介紹了初識MyBatis的基本知識,文中給大家提到了mybatis基本配置和執(zhí)行過程,需要的朋友可以參考下
    2017-11-11
  • Java實現(xiàn)按行讀取大文件

    Java實現(xiàn)按行讀取大文件

    這篇文章主要介紹了Java實現(xiàn)按行讀取大文件的方法的小結(jié),非常的簡單實用,有需要的小伙伴尅參考下。
    2015-05-05
  • Spring中@Configuration注解的使用場景

    Spring中@Configuration注解的使用場景

    這篇文章主要介紹了Spring中@Configuration注解的使用場景,@Configuration注解是從Spring?3.0版本開始加入的一個使Spring能夠支持注解驅(qū)動開發(fā)的標(biāo)注型注解,主要用于標(biāo)注在類上,需要的朋友可以參考下
    2023-11-11
  • Java 重載、重寫、構(gòu)造函數(shù)的實例詳解

    Java 重載、重寫、構(gòu)造函數(shù)的實例詳解

    這篇文章主要介紹了Java 重載、重寫、構(gòu)造函數(shù)的實例詳解的相關(guān)資料,希望通過本文大家能理解掌握java 面向?qū)ο蟮姆椒?,需要的朋友可以參考?/div> 2017-09-09
  • hdfs集成springboot使用方法

    hdfs集成springboot使用方法

    這篇文章主要介紹了hdfs集成springboot使用,配置Configuration信息分為兩種方式,每種方式給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2024-03-03

最新評論

威远县| 华容县| 栾川县| 镇江市| 营口市| 林口县| 康定县| 太白县| 疏附县| 平凉市| 内丘县| 彰化县| 响水县| 恩施市| 淅川县| 治县。| 青神县| 诸暨市| 涟水县| 昌图县| 吴旗县| 潍坊市| 孙吴县| 乐至县| 东山县| 江城| 裕民县| 桐乡市| 固安县| 金沙县| 修武县| 灵武市| 郸城县| 伊宁市| 宜昌市| 泸西县| 逊克县| 乌拉特中旗| 高雄市| 沂源县| 洪雅县|