简体   繁体   中英

Spring Framework's Validation is Not Working with JSR-303 Validation

My question is related to bean validation. Apparently SpringBoot comes with two different validation mechanisms, one JSR-303 compilant (in javax.validation package) and the other provided by Spring framework (in org.springframework.validation package).

While it is easy to enforce JSR-303 validation via @Validated and @Valid annotations, I couldn't find the proper way for Spring's ones.

Take the following User bean.

@Entity
public class User {
    @Column
    @Id
    private String id;

    @Column(unique = true)
    @Username
    private String username;

    // [...]
}

Where @Username constraint is defined as follows. Basically, it's just a composition of @Pattern and @Size costraints.

@Constraint(validatedBy = {})
@Documented
@Pattern(regexp = "[A-Za-z0-9_]+")
@Retention(RUNTIME)
@Size(max = 24, min = 3)
@Target(FIELD)
public @interface Username {
    String message() default "";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

User beans are stored in a repository named UserRepository , defined as follows.

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

To access the repository I wrote a service, shown below.

@Transactional
@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;

    @Validated
    public void create(@Valid User user) {
        userRepository.save(user);
    }
}

Until now, things are pretty neat and everything works. With just a couple of annotations I've achieved everything.

Now, I have this other validator (not JSR-303 ).

// [...]
import org.springframework.validation.Validator;

@Component
public class UniqueUsernameValidator implements Validator {
    @Autowired
    private UserRepository userRepository;

    @Override
    public boolean supports(Class<?> clazz) {
        return User.class.isAssignableFrom(clazz);
    }

    @Override
    public void validate(Object target, Errors errors) {
        User user = (User) target;

        if (userRepository.count(where(usernameIsEqualTo(user.getUsername()))) > 0) {
            errors.rejectValue("username", "username.exists");
        }
    }
}

Ideally, I would like to have it enforced via the same @Validated and @Valid annotations but until now I've been unlucky. I guess it is definitely possible given that the documentation of the class org.springframework.validation.Validator says

This interface is totally divorced from any infrastructure or context; that is to say it is not coupled to validating only objects in the web tier, the data-access tier, or the whatever-tier. As such it is amenable to being used in any layer of an application , and supports the encapsulation of validation logic as a first-class citizen in its own right.

@Valid is a JSR-303 bean validation annotation, and it won't call your UniqueUsernameValidator Spring Validator.

If you want to do that in your service, you need to invoke it manually:

public void create(@Valid User user) {
    Errors errors = new BeanPropertyBindingResult(user, "user");

    // Validator component has been previously injected into the service.
    uniqueUsernameValidator.validate(user, errors); 
    if (errors.hasErrors()) {
        throw new RuntimeException(errors.toString());
    }

    userRepository.save(user);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM