Injecting Spring Dependencies into ConstrantValidator

天涯浪子 提交于 2021-02-05 12:22:39

问题


I'm using Bean Validation. I have a custom validator @MyValidator that needs to look up a value with an injected Spring managed DAO object. How can I get access to this? Spring isn't injecting the DAO into my "MyValidator" object.

@Component
public class CodeListValidator implements ConstraintValidator<CodeList, String> {
    @Autowired
    private ICodeListCommonService codeListCommonService;

    private CodeListEnum codeListID;

    @Override
    public void initialize(CodeList constraintAnnotation) {
        this.codeListID = constraintAnnotation.value();
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        return codeListCommonService.doesCodeEntityExistForCodeList(codeListID.getDbCodeListId(), value, ProviderConstants.CODE_LIST_STATUS_ACTIVE);
    }
}

The "codeListCommonService" is null. This is because Spring isn't creating the class - but how can I get this to work with Spring AoP?

The use of this validator looks like this:


        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        Validator validator = factory.getValidator();

        MyObject validateMe = new MyObject();

        Set<ConstraintViolation<MyObject>> constraintViolations = validator.validate(validateMe);

For MyObject:

public class MyObject {
   @Size(max = 1)
   @CodeList(CodeListEnum.CARTYPE)
   public String carType;
}

So when the validator runs, it processes the annotations... I just need to get a service injected into the CodeListValidator I made to it can do a DB lookup to verify the value against the DB list of "valid car type values".

EDIT: The solution:

Played around with the idea of making a Spring aware factory- too much integration with existing code.

The solution that seems the best (and it works here) is to make a Spring service that stores the ApplicationContext in a static method so "non-Spring managed" beans can get to them.

So a new service:

@Service
public class SpringApplicationContextService implements ISpringApplicationContextService, ApplicationContextAware {

    private static ApplicationContext applicationContext;

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

    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }
}

And then any validator or non-Spring bean can get at the Spring beans via:

public class CodeListValidator implements ConstraintValidator<CodeList, String> {
    @Autowired
    private ICodeListCommonService codeListCommonService;

    private CodeListEnum codeListID;

    @Override
    public void initialize(CodeList constraintAnnotation) {
        this.codeListID = constraintAnnotation.value();
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        ICodeListCommonService codeListCommonService = SpringApplicationContextService.getApplicationContext().getBean(ICodeListCommonService.class);
        return codeListCommonService.doesCodeEntityExistForCodeList(codeListID.getDbCodeListId(), value, ProviderConstants.CODE_LIST_STATUS_ACTIVE);
    }
}

And, of course, the reason for all of this (which is used dozens of times in this app):

    @CodeList(CodeListEnum.EMAIL_OPT_OUT_FLAG)
    public String emailOptOutFlag;

    @CodeList(CodeListEnum.CLEARING_HOUSE)
    public String clearingHouse;


回答1:


The minimum setup for @Autowired to work properly in ConstraintValidator implementation is to have this bean in a Spring @Configuration:

    @Bean
    public Validator defaultValidator() {
        return new LocalValidatorFactoryBean();
    }

This allows any beans, including ApplicationContext, to be injected directly into a ConstraintValidator:

@Constraint(validatedBy = DemoValidator.class)
public @interface DemoAnnotation {
    // ...
    Class<?> beanClass();
}

public class DemoValidator implements ConstraintValidator<DemoAnnotation, String> {

    private final ApplicationContext applicationContext;

    private Object bean;

    @Autowired
    public DemoValidator(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    @Override
    public void initialize(DemoAnnotation constraint) {
        Class<?> beanClass = constraint.beanClass();
        bean = applicationContext.getBean(beanClass);
    }

    @Override
    public boolean isValid(String obj, ConstraintValidatorContext context) {
        return !obj.isEmpty();
    }
}

Demo

For a really flexible validation solution I would recommend Jakub Jirutka's Bean Validator utilizing Spring Expression Language (SpEL) which allows things like:

public class Sample {

    @SpELAssert("@myService.calculate(#this) > 42")
    private int value;
}


来源:https://stackoverflow.com/questions/58088188/injecting-spring-dependencies-into-constrantvalidator

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!