Using @Autowired component in custom JSR-303 valid

2020-07-31 04:49发布

问题:

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> {
}

回答1:

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...



回答2:

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.