javaspring-bootvaadinvaadin23

Vaadin, how to autowire spring component in custom constraint validator


I am working on an app using spring boot for the backend and vaadin for the frontend. I need to add validation, which needs to do a database check - is email registered in this particular example.

Example of what I want to achieve:

@Component
public class EmailExistsValidator implements ConstraintValidator<EmailExists, CharSequence> {

    private final UserRepo userRepo;

    @Autowired
    public EmailExistsValidator(UserRepo userRepo) {
        this.userRepo = userRepo;
    }

    @Override
    public boolean isValid(CharSequence value, ConstraintValidatorContext context) {
        //check email does not exist logic here
    }
}

I have successfuly used this setup in spring mvc and spring rest applications, with no additional configurations. Unfortunately, the above is not working with vaadin. After some debugging I found out that spring indeed creates and manages those components, but they are not the ones being used. Instead vaadin creates and manages other instances of ConstraintValidator, when the actual validation is happening. The validation is done with Binder.writeBeanIfValid(), if that matters.

I went through:

  1. Autowired Repository is Null in Custom Constraint Validator
  2. Spring Boot: repository does not autowire in the custom validator
  3. All questions linked in the above as possible solutions
  4. Few more questions, which I can no longer find unfortunately
  5. I tried getting WebApplicationContext in order to use AutowireCapableBeanFactory.autowireBean() to autowire the annotated fields. Unsurprisingly, the context was null when vaadin creates/manages the instance, so it did not work.

What I am currently using.

@Component
public class EmailExistsValidator implements ConstraintValidator<EmailExists, CharSequence> {

    private static UserRepo repo;

    private final UserRepo userRepo;

    public EmailExistsValidator() {
        this.userRepo = repo;
    }

    @Bean
    public static UserRepo setRepo(UserRepo userRepo) {
        repo = userRepo;
        return repo;
    }

    @Override
    public boolean isValid(CharSequence value, ConstraintValidatorContext context) {
        //validation logic
    }
}

This approach is based on this answer (from the second question I linked). It does the job (only this worked for me), but it's way too hacky for my tastes.

How can I configure vaadin to use spring managed ConstraintValidators, instead of vaadin managed ones? Or how can I autowire spring components in ConstraintValidators created and managed by vaadin?


Solution

  • There is often a situation where auto wiring is not working because Spring does not manage the component.

    For that purpose, you can create a class that will hold the ApplicationContext like this:

    @Component
    public class ApplicationContextHolder implements ApplicationContextAware {
    
        private static ApplicationContext applicationContext;
    
        public static <T> T getBean(Class<T> type) {
            return applicationContext.getBean(type);
        }
    
        @Override
        public void setApplicationContext(@SuppressWarnings("NullableProblems") ApplicationContext applicationContext) throws BeansException {
            ApplicationContextHolder.applicationContext = applicationContext;
        }
    }
    

    Now you can use this class to get a reference to a bean using the static method:

    UserRepo userRepo = ApplicationContextHolder.getBean(UserRepo.class);