I have tried to create a custom annotation with a constraint validator in the spring boot framework and I'm getting an error of injected services being null when executing the custom validator.
And, I have checked with the previous answers related to this. But, any answer didn't work for me now.
Some of them are as follows,
javax.validation.ValidationException: HV000064: Unable to instantiate ConstraintValidator
Autowired gives Null value in Custom Constraint validator
http://dolszewski.com/spring/custom-validation-annotation-in-spring/
My custom annotation and constraint validator as follows,
Custom Annotation
#Documented
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.FIELD, ElementType.PARAMETER})
#Constraint(validatedBy = UniqueFoodNameValidator.class)
public #interface UniqueFoodName {
String message() default "{UniqueFoodName}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
Custom Constraint Validator
public class UniqueFoodNameValidator implements ConstraintValidator<UniqueFoodName, String> {
private FoodService foodService;
#Autowired
public UniqueFoodNameValidator(FoodService foodService) {
this.foodService = foodService;
}
#Override
public void initialize(UniqueFoodName constraintAnnotation) {
}
#Override
public boolean isValid(String foodName, ConstraintValidatorContext context) {
return Objects.isNull(foodName) || foodService.get(foodName).isEmpty();
}
}
So, I would really appreciate If someone cloud help me to figure out the issue and to resolve this.
Note that: FoodService is an interface and it's methods implemented in the FoodServiceImpl class.
A bit late answer, but hope it will be useful for someone.
I suppose the problem is in your Hibernate configuration. The thing is that you validate the field twice. It happens because both Spring and Hibernate run validation automatically.
If you debug isValid method, you will see the flow runs into the method twice first time your FoodService will be injected, but not the second time.
To fix it you can disable Hibernate auto validation. For example:
private Properties hibernateSettings() {
Properties settings = new Properties();
// some settings
settings.put("javax.persistence.validation.mode", "none");
return settings;
}
#Bean
public LocalSessionFactoryBean getSessionFactory() {
LocalSessionFactoryBean sessionFactoryBean = new LocalSessionFactoryBean();
sessionFactoryBean.setDataSource(dataSource());
sessionFactoryBean.setHibernateProperties(hibernateSettings());
sessionFactoryBean.setPackagesToScan("com.springmvc.models");
return sessionFactoryBean;
}
Related
I'm using Spring Boot 2.2.4 and Hibernate 5.4.10. I cannot understand why the validator objects are created twice: the first time with Spring Boot context and the second time without it. I tried javax.persistance.validation.mode=none - nothing changed. Main problem is that objects created with Spring context are ignored.
Annotation:
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = {CustomForBaseEntity.class, CustomForDto.class})
public #interface Custom {
String message() default "{Your license plate has the wrong format. "
+ "Please don't use special symbols.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Base class:
abstract class CustomValidator<T> implements ConstraintValidator<LicensePlateValidation, T> {
private Pattern dependency;
#Autowired
public void setSpringDependency(#Value("${path}") final String dependency) {
this.dependency = Pattern.compile(dependency);
}
}
Both derived classes looks same, so there is only one:
#Component
public class CustomForBaseEntity extends LicensePlateValidator<Vehicle> {
#Override
public boolean isValid(Vehicle vehicle, ConstraintValidatorContext constraintValidatorContext) {
String licensePlate = vehicle.getLicensePlate();
return checkLicensePlate(licensePlate, constraintValidatorContext);
}
}
As mentioned in Spring Boot documentation, I'm created bean of LocalValidatorFactoryBean:
#Bean
public Validator validator() {
return new LocalValidatorFactoryBean();
}
config class
#ComponentScan(basePackages = {"validator"})
class AppConfiguration { ... }
annotation class
#Target({ElementType.METHOD, ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = UniqueLoginValidator.class)
public #interface UniqueLogin {
String message() default "{com.dolszewski.blog.UniqueLogin.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
validator class
#Component
class UniqueLoginValidator implements ConstraintValidator<UniqueLogin, String> {
private UserRepository userRepository;
public UniqueLoginValidator(UserRepository userRepository) {
this.userRepository = userRepository;
}
public void initialize(UniqueLogin constraint) {
}
public boolean isValid(String login, ConstraintValidatorContext context) {
return login != null && !userRepository.findByLogin(login).isPresent();
}
}
I have a class with property #UniqueLogin String login, I also use other annotations like #Size and #Max, the last 2 works, but my custom annotation does not work.
Can you please help to understand why spring do not call custom validator?
It worked for me to create inside src/main/resources/META-INF/services a file named javax.validation.ConstraintValidator with a list new line separated of all qualified name of custom constraint validators you created.
This way, Spring will automatically register the custom validator.
This file will be automatically checked from Spring and included into built artifact.
Be careful of annotation configuration after applying this solution. You should annotate with #Constraint(validatedBy = { }) to prevent double validator initialization.
I'm trying to make a custom validation (checking if an email is already present in the database). For single class my annotation is working fine but I need to make this validation work for two objects implementing common interface. I have User interface and Visitor and Exhibitor classes which are implementing it.
Here is my annotation:
#Documented
#Constraint(validatedBy = UniqueEmailValidator.class)
#Target({ElementType.METHOD, ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
public #interface UniqueEmail {
String message() default "Email is already existing!";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Here is Validator class
public class UniqueEmailValidator implements ConstraintValidator<UniqueEmail, String> {
#Autowired
private ApplicationContext applicationContext;
private UserService userService;
#Override
public void initialize(UniqueEmail uniqueEmail) {
}
#Override
public boolean isValid(String email, ConstraintValidatorContext constraintValidatorContext) {
return !userService.isEmailPresent(email);
}
}
UserService is a common interface of VisitorService and ExhibitorService
public interface UserService {
boolean isEmailPresent(String email);
}
And it's implementation...
#Service
public class VisitorService implements UserService {
#Autowired
VisitorDao visitorDao;
#Override
public boolean isEmailPresent(String email) {
try {
return !visitorDao.findAllByEmail(email).isEmpty();
} catch (NullPointerException e) {
return false;
}
}
}
Currently I'm getting NullPointerException
java.lang.NullPointerException: null
at pl.com.sremski.testapp.validators.UniqueEmailValidator.isValid(UniqueEmailValidator.java:24) ~[classes/:na]
at pl.com.sremski.testapp.validators.UniqueEmailValidator.isValid(UniqueEmailValidator.java:10)
Any ideas what's the reason? I was trying to debug and UserService is null... but I'm trying to add a new visitor so it should use VisitorService. Please help.
I managed to solve my problem by specifing exact implementation in annotation's parameter
#UniqueEmail(service = VisitorService.class)
and then creating it's instance in
#Override
public void initialize(UniqueEmail uniqueEmail) {
}
However I had to separate all Spring-based validation from Hibernate's entity to make it work.
If you don't want to have two different annotations with different #Qualifier() marked beans, you can consider to choose that bean in runtime using application context. But injecting whole Spring context to your business logic is considered as a very bad practice:
1) Mixing infrastructure (your WHOLE infrastructure) with business processes makes your code hard to understand and decouple.
2) It is hard to unit test this, need to mock the context object instead of your services.
But after using Google Guice DI I found myself in using Provider<Service> pattern, because injecting spring into spring is OK. So you can create a class like:
#Service
class UserServiceProvider<T extends UserService> implements Provider<T> {
#Autowired private ApplicationContext context;
public UserService get(Class<T> exactServiceType) {
return (UserService) context.getBean(exactServiceType);
}
}
Maybe there is a much better "Spring way" to do this, but this code is easy to understand and maintain. Works lile a Scope.Prototype bean, but a bit more flexible.
Google Guice Provider tutorial
EDIT:
Spring has a similar interface FactoryBean<T> and it has an enhanced handling, because DI will inject not the factory, but what factory provides.
But the one problem is you can't do it with some condition.
Simple example
I am trying to perform validation in spring and for that I need some data to be available before I execute the validation. That data is in my sql inside the table . I am looking for solution which will load my spring bean from the mysql table and that bean I can use to get the data for the validation .
Check out this example.. you just need declare your validator as bean.
#Target({ ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = MyValidatorImpl.class)
#Documented
public #interface MyValidator {
String message() default "invalid";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
#Component // <---- this will allow you to access spring component
public class MyValidatorImpl implements ConstraintValidator<MyValidator, String> {
#Autowired MyDAO myDAO;
public void initialize(MyValidator constraint) {
}
public boolean isValid(String s, ConstraintValidatorContext context) {
return false;
}
}
I'm trying to implement a custom validator for my model classes that autowires a custom bean of mine (declared via #Component).
In this, I followed the Spring documentation on that topic. My AuthenticationFacade object is implemented according to this tutorial.
When running my tests, however, the autowired attribute in the Validator object is always null. Why is that?
Here are the relevant parts of my code:
My custom bean, AuthenticationFacadeImpl.java
#Component
public class AuthenticationFacadeImpl implements AuthenticationFacade {
boolean hasAnyRole(Collection<String> roles) {
// checks currently logged in user roles
}
}
My custom constraint, HasAnyRoleConstraint.java
#Constraint(validatedBy = HasAnyRoleConstraintValidator.class)
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.METHOD, ElementType.FIELD})
public #interface HasAnyRole {
String[] value();
String message() default "{HasAnyRole}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
My custom validator, HasAnyRoleConstraintValidator.java
#Component
public class HasAnyRoleConstraintValidator implements ConstraintValidator<HasAnyRole, Object> {
#Autowired
AuthenticationFacade authenticationFacade;
private String[] roles;
#Override
public void initialize(HasAnyRole hasAnyRole) {
this.roles = hasAnyRole.value();
}
#Override
public boolean isValid(Object target, ConstraintValidatorContext constraintValidatorContext) {
return target == null || authenticationFacade.hasAnyRole(Arrays.asList(this.roles));
}
}
The model class, Article.java
#Entity
public class Article {
// ...
#HasAnyRole({"EDITOR", "ADMIN"})
private String title;
// ...
}
The service object, ArticleServiceImpl.java
#Service
public class ArticleServiceImpl implements ArticleService {
#Autowired
private ArticleRepository articleRepository;
#Autowired
private AuthenticationFacade authenticationFacade;
#Autowired
private Validator validator;
#Override
#PreAuthorize("hasAnyRole('ADMIN', 'EDITOR')")
public boolean createArticle(Article article, Errors errors) {
articleRepository.save(article);
return true;
}
The Errors object that gets fed into the createArticle method is intended to come from the Spring controller, which gets fed a model object with the #Valid annotation.
The repository, ArticleRepository.java, uses Spring Data JPA's JpaRepository
public interface ArticleRepository extends JpaRepository<Article, Long> {
}
I solved this for now by ditching Dependency Injection for the Validator class, instead instantiating an instance of AuthenticationFacadeImpl in the constructor.
Would still be interesting, though how to combine the use of #Valid in the Controllers with custom validators + #Autowired attributes in the Model without explicitely calling the Validator in the code...
If your validator is instantiated outside the Spring context, then you can use Spring’s AOP #Configurable magic to register it in context and get autowiring work. All what you need is to annotate HasAnyRoleConstraintValidator with #Configurable and enable compile time, or load time aspects weaving.