自定义spring invalidator注解校验数据合法性

在项目中经常会对用户输入的数据,或者外部导入到系统的数据做合法性检查。在spring boot框架的微服务中可以使用invalidator注解对数据做合法性,安全性校验。

下面给一个样例说明如何自定义注解实现校验逻辑。

1、定义校验属性字符串长度的注解

package com.elon.springbootdemo.manager.invalidator;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
/**
 * 属性字段长度校验注解定义。
 * 
 * @author elon
 * @version 2018年9月19日
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = FieldLengthInvalidatorImpl.class)
@Documented
public @interface FieldLengthInvalidator {
    // 字段支持的最大长度(字符数)
    int maxLength() default 50;
    // 校验失败后返回的错误信息
    String message() default "";
    // 分组
    Class<?>[] groups() default {};
    // 负载
    Class<? extends Payload>[] payload() default {};
}

在定义注解时可声明变量用于辅助校验。上面的注解中定义了maxLength变量用于指定最大长度限制。变量可以设置默认值,使用注解时不传参数,变量就使用默认值。

2、实现校验逻辑,校验失败后返回错误提示

package com.elon.springbootdemo.manager.invalidator;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
/**
 * 字段长度校验实现类。
 * 
 * @author elon
 * @version 2018年9月19日
 */
public class FieldLengthInvalidatorImpl implements ConstraintValidator<FieldLengthInvalidator, String> {
    private int maxLength = 0;
    @Override
    public void initialize(FieldLengthInvalidator invalidator) {
        maxLength = invalidator.maxLength();
    }
    @Override
    public boolean isValid(String fieldValue, ConstraintValidatorContext context) {
        if (fieldValue.length() > maxLength) {
            context.disableDefaultConstraintViolation();
            context.buildConstraintViolationWithTemplate("对象属性长度超过限制。").addConstraintViolation();
            // 校验失败返回false。返回true上游收集不到错误信息。
            return false;
        }
        return true;
    }
}

3、在模型字段属性上增加校验的注解

public class User
{
    private int userId = -1;
    @FieldLengthInvalidator(maxLength=10)
    private String name = "";
}

4、提供统一的校验方法

package com.elon.springbootdemo.manager;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
/**
 * 有效性校验管理类。对外提供统一的校验调用接口。
 * @author elon
 * @version 2018年9月19日
 */
public class InvalidatorMgr {
    private InvalidatorMgr() {
        
    }
    
    /**
     * 获取单例对象。
     * 
     * @return 单例对象
     */
    public static InvalidatorMgr instance() {
        return InvalidatorMgrBuilder.instance;
    }
    
    /**
     * 校验模型所有属性的有效性。
     * 
     * @param model 待校验模型
     * @return 错误信息列表
     */
    public <T> List<String> validate(T model) {
        
        ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
        Validator validator = validatorFactory.getValidator();
        Set<ConstraintViolation<T>> resultSet = validator.validate(model);
        
        List<String> messageList = new ArrayList<>();
        resultSet.forEach((r)->messageList.add(r.getMessage()));        
        return messageList;
    }
    
    /**
     * 单例构建器。
     * @author elon
     * @version 2018年9月19日
     */
    private static class InvalidatorMgrBuilder{
        private static InvalidatorMgr instance = new InvalidatorMgr();
    }
}

5、业务层调用校验方法

        User user = new User();
        user.setName("ahskahskhqlwjqlwqlwhqlhwlqjwlqhwlhqwhqlwjjqlwl");
        List<String> messageList = InvalidatorMgr.instance().validate(user);
        System.out.println(messageList);

invalidator注解主要用于实现长度,范围,非法字符等通用的规则校验。不适合用于做业务逻辑的校验,特定的业务校验写在业务层。 

springboot 参数验证 validation

1、综述

springboot提供了强大的基于注解的、开箱即用的验证功能,这种基于bean validation的实现和 hibernate validator类似

2、依赖

创建springboot项目,包含以下依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency> 
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency> 
<dependency> 
    <groupId>com.h2database</groupId> 
    <artifactId>h2</artifactId>
    <version>1.4.197</version> 
    <scope>runtime</scope>
</dependency>

3、定义实体类

测试项目为了方便,直接用JPA,使用@NotBlank指定非空字段,message是验证触发后返回的信息,还有@Null、@NotNull、@NotBlank、@Email、@Max、@Min、@Size、@Negative、@DecimalMax、@DecimalMin、@Positive、@PositiveOrZero、@NegativeOrZero、@AssertTrue、@AssertFalse、@Future、@FutureOrPresent、@Past、@PastOrPresent、@Pattern

@Entity
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;
     
    @NotBlank(message = "Name is mandatory")
    private String name;
     
    @NotBlank(message = "Email is mandatory")
    private String email;
    
    // standard constructors / setters / getters / toString    
    
}

创建JPA的repository定义增删改查接口

@Repository
public interface UserRepository extends CrudRepository<User, Long> {}

4、创建rest controller

@RestController
public class UserController {
 
    @PostMapping("/users")
    ResponseEntity<String> addUser(@Valid @RequestBody User user) {
        // persisting the user
        return ResponseEntity.ok("User is valid");
    }
     
    // standard constructors / other methods
     
}

接收到的user对象添加了@Valid,当Spring Boot发现带有@Valid注解的参数时,会自动引导默认的JSR 380验证器验证参数。当目标参数未能通过验证时,Spring Boot将抛出一个MethodArgumentNotValidException

5、实现ExceptionHandler

直接抛出异常显然是不合理的,大部分情况需要经过处理返回给前端更友好的提示信息,通过@ExceptionHandler来处理抛出的异常实现该功能

@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException.class)
public Map<String, String> handleValidationExceptions(
  MethodArgumentNotValidException ex) {
    Map<String, String> errors = new HashMap<>();
    ex.getBindingResult().getAllErrors().forEach((error) -> {
        String fieldName = ((FieldError) error).getField();
        String errorMessage = error.getDefaultMessage();
        errors.put(fieldName, errorMessage);
    });
    return errors;
}

MethodArgumentNotValidException作为上一步抛出的异常,当springboot执行validition触发时会调用此实现,该方法将每个无效字段的名称和验证后错误消息存储在映射中,然后它将映射作为JSON表示形式发送回客户端进行进一步处理。

6、写测试代码

使用springboot自带的插件进行测试rest controller,

@RunWith(SpringRunner.class) 
@WebMvcTest
@AutoConfigureMockMvc
public class UserControllerIntegrationTest {
 
    @MockBean
    private UserRepository userRepository;
     
    @Autowired
    UserController userController;
 
    @Autowired
    private MockMvc mockMvc;
 
    //...     
}

@WebMvcTest允许我们使用MockMvcRequestBuilders和MockMvcResultMatchers实现的一组静态方法测试请求和响应。测试addUser()方法,在请求体中传递一个有效的User对象和一个无效的User对象。

@Test
public void whenPostRequestToUsersAndValidUser_thenCorrectResponse() throws Exception {
    MediaType textPlainUtf8 = new MediaType(MediaType.TEXT_PLAIN, Charset.forName("UTF-8"));
    String user = "{\"name\": \"bob\", \"email\" : \"bob@domain.com\"}";
    mockMvc.perform(MockMvcRequestBuilders.post("/users")
      .content(user)
      .contentType(MediaType.APPLICATION_JSON_UTF8))
      .andExpect(MockMvcResultMatchers.status().isOk())
      .andExpect(MockMvcResultMatchers.content()
        .contentType(textPlainUtf8));
}
 
@Test
public void whenPostRequestToUsersAndInValidUser_thenCorrectResponse() throws Exception {
    String user = "{\"name\": \"\", \"email\" : \"bob@domain.com\"}";
    mockMvc.perform(MockMvcRequestBuilders.post("/users")
      .content(user)
      .contentType(MediaType.APPLICATION_JSON_UTF8))
      .andExpect(MockMvcResultMatchers.status().isBadRequest())
      .andExpect(MockMvcResultMatchers.jsonPath("$.name", Is.is("Name is mandatory")))
      .andExpect(MockMvcResultMatchers.content()
        .contentType(MediaType.APPLICATION_JSON_UTF8));
    }
}

也可以使用postman或fiddler来测试REST controller API。

7、跑测试

@SpringBootApplication
public class Application {
     
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
     
    @Bean
    public CommandLineRunner run(UserRepository userRepository) throws Exception {
        return (String[] args) -> {
            User user1 = new User("Bob", "bob@domain.com");
            User user2 = new User("Jenny", "jenny@domain.com");
            userRepository.save(user1);
            userRepository.save(user2);
            userRepository.findAll().forEach(System.out::println);
        };
    }
}

如果用没有用户名或邮箱的数据发送请求会收到返回的提示信息

{
  "name":"Name is mandatory",
  "email":"Email is mandatory"
}

8、自定义注解

在进行参数验证的时候,往往存在现有的约束注解不能满足的情况,此时就需要我们自己定义validation注解了,下次介绍如何自己定义一个验证注解。 

以上为个人经验,希望能给大家一个参考,也希望大家多多支持Devmax。

如何通过自定义spring invalidator注解校验数据合法性的更多相关文章

  1. Spring JdbcTemplate执行数据库操作详解

    JdbcTemplate是Spring框架自带的对JDBC操作的封装,目的是提供统一的模板方法使对数据库的操作更加方便、友好,效率也不错,这篇文章主要介绍了Spring JdbcTemplate执行数据库操作,需要的朋友可以参考下

  2. Spring Batch批处理框架操作指南

    Spring Batch 是 Spring 提供的一个数据处理框架。企业域中的许多应用程序需要批量处理才能在关键任务环境中执行业务操作,这篇文章主要介绍了Spring Batch批处理框架操作指南,需要的朋友可以参考下

  3. Spring详细讲解@Autowired注解

    @Autowired注解可以用在类属性,构造函数,setter方法和函数参数上,该注解可以准确地控制bean在何处如何自动装配的过程。在默认情况下,该注解是类型驱动的注入

  4. 使用Spring AOP实现用户操作日志功能

    这篇文章主要介绍了使用Spring AOP实现了用户操作日志功能,功能实现需要一张记录日志的log表,结合示例代码给大家讲解的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  5. Spring Security认证器实现过程详解

    一些权限框架一般都包含认证器和决策器,前者处理登陆验证,后者处理访问资源的控制,这篇文章主要介绍了Spring Security认证器实现过程,需要的朋友可以参考下

  6. spring学习JdbcTemplate数据库事务管理

    这篇文章主要为大家介绍了spring学习JdbcTemplate数据库事务管理,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  7. Spring Boot 集成Redisson实现分布式锁详细案例

    这篇文章主要介绍了Spring Boot 集成Redisson实现分布式锁详细案例,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的朋友可以参考一下

  8. Spring Security实现接口放通的方法详解

    在用Spring Security项目开发中,有时候需要放通某一个接口时,我们需要在配置中把接口地址配置上,这样做有时候显得麻烦。本文将通过一个注解的方式快速实现接口放通,感兴趣的可以了解一下

  9. 如何利用Spring把元素解析成BeanDefinition对象

    这篇文章主要介绍了如何利用Spring把元素解析成BeanDefinition对象,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的小伙伴可以参考一下

  10. Spring解决循环依赖问题及三级缓存的作用

    这篇文章主要介绍了Spring解决循环依赖问题及三级缓存的作用,所谓的三级缓存只是三个可以当作是全局变量的Map,Spring的源码中大量使用了这种先将数据放入容器中等使用结束再销毁的代码风格

随机推荐

  1. 基于EJB技术的商务预订系统的开发

    用EJB结构开发的应用程序是可伸缩的、事务型的、多用户安全的。总的来说,EJB是一个组件事务监控的标准服务器端的组件模型。基于EJB技术的系统结构模型EJB结构是一个服务端组件结构,是一个层次性结构,其结构模型如图1所示。图2:商务预订系统的构架EntityBean是为了现实世界的对象建造的模型,这些对象通常是数据库的一些持久记录。

  2. Java利用POI实现导入导出Excel表格

    这篇文章主要为大家详细介绍了Java利用POI实现导入导出Excel表格,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  3. Mybatis分页插件PageHelper手写实现示例

    这篇文章主要为大家介绍了Mybatis分页插件PageHelper手写实现示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  4. (jsp/html)网页上嵌入播放器(常用播放器代码整理)

    网页上嵌入播放器,只要在HTML上添加以上代码就OK了,下面整理了一些常用的播放器代码,总有一款适合你,感兴趣的朋友可以参考下哈,希望对你有所帮助

  5. Java 阻塞队列BlockingQueue详解

    本文详细介绍了BlockingQueue家庭中的所有成员,包括他们各自的功能以及常见使用场景,通过实例代码介绍了Java 阻塞队列BlockingQueue的相关知识,需要的朋友可以参考下

  6. Java异常Exception详细讲解

    异常就是不正常,比如当我们身体出现了异常我们会根据身体情况选择喝开水、吃药、看病、等 异常处理方法。 java异常处理机制是我们java语言使用异常处理机制为程序提供了错误处理的能力,程序出现的错误,程序可以安全的退出,以保证程序正常的运行等

  7. Java Bean 作用域及它的几种类型介绍

    这篇文章主要介绍了Java Bean作用域及它的几种类型介绍,Spring框架作为一个管理Bean的IoC容器,那么Bean自然是Spring中的重要资源了,那Bean的作用域又是什么,接下来我们一起进入文章详细学习吧

  8. 面试突击之跨域问题的解决方案详解

    跨域问题本质是浏览器的一种保护机制,它的初衷是为了保证用户的安全,防止恶意网站窃取数据。那怎么解决这个问题呢?接下来我们一起来看

  9. Mybatis-Plus接口BaseMapper与Services使用详解

    这篇文章主要为大家介绍了Mybatis-Plus接口BaseMapper与Services使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  10. mybatis-plus雪花算法增强idworker的实现

    今天聊聊在mybatis-plus中引入分布式ID生成框架idworker,进一步增强实现生成分布式唯一ID,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

返回
顶部