Chaosfire
Chaosfire

Reputation: 6985

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?

Upvotes: 2

Views: 732

Answers (2)

Chaosfire
Chaosfire

Reputation: 6985

Using Simon's answer as inspiration I ended up extending ApplicationContextHolder a bit to autowire manually created beans.

@Component
public class ApplicationContextHolder implements ApplicationContextAware {

    private static ApplicationContext applicationContext;
    private static AutowireCapableBeanFactory factory;

    public static <T> T getBean(Class<T> beanClass) {
        return applicationContext.getBean(beanClass);
    }

    public static <T> void autowireBean(T bean) {
        factory.autowireBean(bean);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        ApplicationContextHolder.applicationContext = applicationContext;
        factory = applicationContext.getAutowireCapableBeanFactory();
    }
}

This can be handy when the validator depends on more than one bean.

public class EmailExistsValidator implements ConstraintValidator<EmailExists, CharSequence> {

    @Autowired
    private FirstBean firstBean;
    @Autowired
    private SecondBean secondBean;
    @Autowired
    private ThirdBean thirdBean;

    public EmailExistsValidator() {
        ApplicationContextHolder.autowireBean(this);
    }

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

Upvotes: 1

Simon Martinelli
Simon Martinelli

Reputation: 36103

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);

Upvotes: 2

Related Questions