0

The following code yields null on the teamResource auto-wired object inside isValid() function:

@Component //I am not sure it is required he
public class TeamIdValidator implements ConstraintValidator<TeamIdConstraint, Integer> {
    
    @Autowired private TeamResource teamResource;
    
    public TeamIdValidator() {
    }

    @Override
    public void initialize(TeamIdConstraint teamIdConstraint) {
        // this.teamIdConstraint = teamIdConstraint;
    }

    @Override
    public boolean isValid(Integer teamId, ConstraintValidatorContext cxt) {

        int numOfAvailableTeams = teamResource.retrieveAllTeams().size(); //teamResource is null :(

        return teamId < 0 || teamId >= numOfAvailableTeams;
    }
}

TeamResource class:

@RestController
@CrossOrigin
public class TeamResource {
    
    @Autowired private TeamRepository teamRepository;
    
    public TeamResource() {
    }
    ...

   //mapping methods
}

And if it relevant...

@Documented
@Constraint(validatedBy = TeamIdValidator.class)
@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface TeamIdConstraint {
    
    String message() default "Invalid team id!";

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

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

Why on earth teamResource is null?

It is not being explicitly initialized with new anywhere else.

E_net4
  • 27,810
  • 13
  • 101
  • 139
dushkin
  • 1,939
  • 3
  • 37
  • 82
  • 2
    How about notorious "autowiring to ConstraintValidator" stuff? Maybe [this](https://stackoverflow.com/q/3587317/6413377) or [this](https://stackoverflow.com/q/37958145/6413377) helps? I remember struggling with same problem and the reason might have been that the ConstraintValidators are not "injected" like the "normal" Beans are but (can not remember anymore). No matter if you use @Component – pirho Nov 04 '20 at 21:43
  • I will check your links. Thanks :) – dushkin Nov 04 '20 at 21:52
  • 1
    I suspect instances are being created by something other than Spring - the validator framework. Why not stick a breakpoint in the TeamIdValidator empty constructor and check the stack trace - presumably it gets initialised once by Spring but then again by something else. Check the object id when isValid gets called - I'll bet it's the non-spring bean instance. – Chris Nov 04 '20 at 21:56
  • @pirho your links are totally relevant - I have struggled with this some weeks ago. The default way is the hibernate one. – ggr Nov 05 '20 at 07:58
  • @ggr Can you elaborate please about the way? Any specific link? – dushkin Nov 05 '20 at 15:03
  • @Chris Yes, it did cross the ctor twice. That is because I used (just to try) (at)Component over the class. On both trials - it didn't work... – dushkin Nov 05 '20 at 20:03
  • First, read the links with attention. you need to declare a validator, check most rated [answer](https://stackoverflow.com/questions/37958145/autowired-gives-null-value-in-custom-constraint-validator) – ggr Nov 05 '20 at 20:54
  • Yes, I wouldn't expect the annotation to work because I suspect the instance used during validation is not the one created by Spring anyway, the purpose of debugging is to check object IDs just to confirm that is the case. Assuming it is, then there is an ugly hack you can do to statically get hold of the Spring bean factory from a non Spring managed object and then grab the bean you need and set it, maybe do that in the initialize method if that is always called after spring has gone through its startup, else during isValid, do a null check and grab at that point. There might be nicer ways – Chris Nov 05 '20 at 21:10
  • The hack approach means having a BeanFactoryAware component that stores the BeanFactory as a static variable and exposes a static getBean method. Ugly and wrong in many ways but works when you have to cross the bridge between non Spring managed objects that require access to Spring managed objects. As I say there are probably far more elegant approaches – Chris Nov 05 '20 at 21:14
  • @ggr I am sorry, but looking at this question I cannot connect between the problem code and the answer. I mean, I can copy the code, but where exactly the EmployeeValidator fits in the solution? – dushkin Nov 05 '20 at 21:18
  • @dushkin whats the problem ? TeamResource is not injected in your validator ? right ? Its because validator are not managed by spring – ggr Nov 05 '20 at 21:22
  • @ggr I cannot understand where should I insert the validator() method? To which class? And should I make changes in the method. Sorry, It is my first Spring project... – dushkin Nov 05 '20 at 21:46
  • Just to be clear, when isValid is called, it is called on an object that wasn't created by Spring hence no spring annotations will help and in particular nothing will be autowired. So you are in a situation where you need a non Spring managed object to reference a spring bean. There may be other ways but statically exposing access to the spring bean factory so that you can get hold of that reference is one approach that will work – Chris Nov 06 '20 at 06:43

1 Answers1

1

Already mentioned in the comments but declare a a bean like this

@Configuration
public class ValidationConfig{

    @Bean
    public LocalValidatorFactoryBean validator() {
        return new LocalValidatorFactoryBean();
    }

    @Bean
    public MethodValidationPostProcessor methodValidationPostProcessor(Validator validator) {
        MethodValidationPostProcessor methodValidationPostProcessor = new MethodValidationPostProcessor();
        methodValidationPostProcessor.setValidator(validator);
        return methodValidationPostProcessor;
    }

}

Remove all the @autowired, prefer constructor injection. (check the internet why).

And then, it makes no sense to inject a RestContoller to validator.

ggr
  • 294
  • 1
  • 9
  • Thanks ggr, but as I asked above, where should I put this method? In the TeamIdValidator class? – dushkin Nov 05 '20 at 21:48
  • see edits. May be you should try to understand how spring works, this are juste some basics of the framework. – ggr Nov 06 '20 at 06:51
  • Well, yes... It might be that it is still not automatic for me that beans should be inside a Configuration annotated class. I'll keep it in mind :) Anyways, I added this class and flow passes through as expected. I removed the Autowired annotation from the teamResource field, and added public TeamIdValidator(TeamResource teamResource) { this.teamResource = teamResource; } The result is: "HV000064: Unable to instantiate ConstraintValidator: com.ttt.bbsim.validation.TeamIdValidator. – dushkin Nov 06 '20 at 12:47
  • maybe you should configure methodValidationPostProcessor bean too (see edits) – ggr Nov 06 '20 at 15:09