Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document autowiring of javax.validation.Validator with use of @EnableWebMvc [SPR-12194] #16808

Closed
spring-issuemaster opened this issue Sep 15, 2014 · 8 comments
Assignees
Milestone

Comments

@spring-issuemaster
Copy link
Collaborator

@spring-issuemaster spring-issuemaster commented Sep 15, 2014

Andy Wilkinson opened SPR-12194 and commented

I'm seeing some strange behaviour with @EnableWebMvc and autowiring of javax.validation.Validator.

When there's no explicitly-configured LocalValidatorFactoryBean, auto-wiring of javax.validation.Validator fails as no matching bean is found. When there's a single explicitly-configured LocalValidatorFactoryBean, auto-wiring fails as there are now two matching beans: the explicitly configured one and the one that's provided by @EnableWebMvc.

I'll open a Spring Framework Issues PR with some code to reproduce both behaviours.


Affects: 4.0.7, 4.1 GA

Reference URL: spring-projects/spring-boot#1539

3 votes, 9 watchers

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Sep 15, 2014

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Sep 15, 2014

Andy Wilkinson commented

Some further information that I shared with Rossen on HipChat:

With noExplicitValidator the context.refresh() call fails because there's no Validator bean at all in in the context. With explicitValidator() the context.refresh() call suceeds (implying there's one and only one Validator bean) but the getBean(Validator.class) call then fails because there are two Validator beans

I think the bug is that explicitly configuring a LocalValidatorFactoryBean makes the number of Validators jump from 0 to 2

I did spend a bit of time in the debugger (sorry, should have shared this in the issue) and noticed that the validator from @EnableWebMvc hasn't been instantiated when it's looking for a match during auto-wiring processing. When it's analysing the type it thinks its a Spring Validator (and only a Spring validator) so the match fails.

In the getBean() case in explicitValidator(), the @EnableWebMvc validator's been instantiated so it's able to use Class.isAssignableTo and figures out that it's also a javax.validation.Validator

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Oct 16, 2014

Gunnar Hillert commented

Not sure how related this issue is. I noticed an @Autowired issue in my case though with org.springframework.validation.Validator

I have a custom Validator in PersistenceConfig.class

@Bean
LocalValidatorFactoryBean validator() {
     LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean();
     localValidatorFactoryBean.setValidationMessageSource(messageSource());
     return localValidatorFactoryBean;
}

In WebConfig.class which extends WebMvcConfigurerAdapter and uses @EnableWebMvc, I try to reuse the the existing Validator.
(Autowired the Validator)

@Override
public Validator getValidator() {
     return this.validator;
}

However, getValidator() never gets executed. Code fails with:

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'mvcValidator': Requested bean is currently in creation: Is there an unresolvable circular reference?
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:346)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:298)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:193)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:1081)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1006)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:904)
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:527)
	... 60 more

If I declare:

@Bean
LocalValidatorFactoryBean validator() {
     LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean();
     localValidatorFactoryBean.setValidationMessageSource(messageSource());
     return localValidatorFactoryBean;
}

Inside WebConfig.class, everything works.

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Feb 13, 2015

Chris Beams commented

Hey guys, I'm getting bit by this bug as well in the following environment:

  • spring-boot:1.2.1.RELEASE
  • spring-framework:4.1.4.RELEASE
  • javax.validation:1.1.0
  • hibenate-validator:5.1.3

Any attempt to directly @Autowire the Validator fails as Andy has already described. I am, however, able to #getBean(Validator.class) after context refresh, so this seems to be a timing issue in which the Validator bean (OptionalValidatoryFactoryBean as of 4.0.1) doesn't isn't getting registered with the container until after normal initialization/autowiring is complete.

I do have a workaround, though it is unpleasant: @Autowire the enclosing BeanFactory into the target component (a @Controller in my case), then look up the Validator bean within the controller's @InitBinder method. Doing so accesses the Validator bean sufficiently late so as to give it time to get fully registered and available.

@Controller
class MyController {

    private final BeanFactory beanFactory;
    private Validator validator;

    @Autowired
    public MyController(BeanFactory beanFactory) {
        this.beanFactory = beanFactory;
    }

    @InitBinder
    protected void initBinder() {
        // work around #16808
        this.validator = beanFactory.getBean(Validator.class);
    }

    // ...
}
@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented May 10, 2015

Matt Byrne commented

Just to give another scenario of where this fails ... I've encountered the same problem using spring boot 1.2.3.RELEASE and hibernate validator 5.2.0.Beta1, however I'm also using a Collections validator library to validate collections of Strings, Integers https://github.com/jirutka/validator-collection which autowires in a Validator instance. Workaround by injecting BeanFactory or using @Qualifier does not work for these kind of scenarios, unfortunately.

In some of my own classes I need to autowire the Validator to trigger validation, however the validator-collection library I use has a bunch of ConstraintValidators that have @Inject for Validator and get instantiated by spring at Runtime.

To get around the problem I had to avoid configuring my own Validator altogether and trust that Spring would create one later in the lifecycle. I had to write my own ValidatorWrapper that exposes the methods from Validator without implementing the same interface so that I can create it as a bean without conflicts. This class uses BeanFactory to lazily load the Validator from spring the first time it is used. In my own classes where I need to call validation I had to autowire in ValidatorWrapper instead.

Not nice, but the only way I could get around this before it's fixed.

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented May 11, 2015

Rossen Stoyanchev commented

The @EnableWebMvc setup exposes a Spring Validator (i.e. org.springframework.validation.Validator) bean for use wherever Spring MVC applies validation through that contract. If JSR-303 happens to be on the classpath, that bean will be also be an implementation of JSR-303 Validator with LocalValidatorFactoryBean adapting it to Spring's Validator contract. In other words we are not exposing a JSR-303 Validator generally speaking but it will adapt to JSR-303 at runtime.

An application can make things more explicit either by "overriding" the mvcValidator bean (and declaring it as LocalValidatorFactoryBean) or by declaring a separate LocalValidatorFactoryBean and marking it @Primary.

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented May 11, 2015

Matt Byrne commented

Thanks Rossen, @Primary did the trick!

I had already tried overriding the mvcValidator definition in my Config class and tried declaring it as LocalValidatorFactoryBean and even OptionalValidatorFactoryBean which seems to be the one MVC instantiates, however it seems that my bean was the one being overridden by MVC's version and autowiring failed.

If this is the final solution it would be worth adding some instructions to the doco since it seemed to get us by surprise.

@spring-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Jul 7, 2015

Rossen Stoyanchev commented

See 6890e6.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
2 participants
You can’t perform that action at this time.