简体   繁体   English

使用 Spring/Spring Boot 注册 Bean 验证 ValueExtractor

[英]Register Bean Validation ValueExtractor with Spring/Spring Boot

Setup设置

I have a custom container class:我有一个自定义容器类:

public class PatchField<T> {

    private boolean isSet;
    private T value;

    public PatchField(T value) {
        this.isSet = true;
        this.value = value;
    }

    public PatchField(boolean isSet, T value) {
        this.isSet = isSet;
        this.value = value;
    }

    //Getters and Setters
}

I have a request which I would like to use as a @RequestBody in Spring REST endpoint.我有一个请求,我想在 Spring REST 端点中将其用作 @RequestBody。 Do note that I annotated String with @NotEmpty.请注意,我用@NotEmpty 注释了字符串。 That means I need a custom ValueExtractor.这意味着我需要一个自定义的 ValueExtractor。

Request:要求:

@ApiModel
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
public class Request {

    private PatchField<@NotEmpty String> name = PatchField.empty();

    //Getters and Setters

}

I have created PatchFieldValueExtractor as per documentation :我已经按照文档创建了 PatchFieldValueExtractor :

public class PatchFieldValueExtractor implements ValueExtractor<PatchField<@ExtractedValue ?>> {

    @Override
    public void extractValues(PatchField<?> originalValue, ValueReceiver receiver) {
        receiver.value(null, originalValue.getValue());
    }
}

Problem问题

However, I cannot find a way to register PatchFieldValueExtractor with Spring or customize automatically created Validator like I can, for example RestTemplate via RestTemplateBuilder .不过,我不能找到一种方法,使其与Spring注册PatchFieldValueExtractor或通过RestTemplateBuilder定制自动生成验证就像我可以,例如RestTemplate。 If I annotate PatchFieldValueExtractor with @Component in Spring Boot, it does not get picked up automatically.如果我在 Spring Boot 中使用@Component注释PatchFieldValueExtractor ,它不会被自动拾取。 I get the following error:我收到以下错误:

javax.validation.ConstraintDeclarationException: HV000197: No value extractor found for type parameter 'T' of type com.example.PatchField.
    at org.hibernate.validator.internal.metadata.core.MetaConstraints.addValueExtractorDescriptorForTypeArgumentLocation(MetaConstraints.java:145) ~[hibernate-validator-6.0.17.Final.jar:6.0.17.Final]
    at org.hibernate.validator.internal.metadata.core.MetaConstraints.create(MetaConstraints.java:61) ~[hibernate-validator-6.0.17.Final.jar:6.0.17.Final]
    at org.hibernate.validator.internal.metadata.provider.AnnotationMetaDataProvider.createTypeArgumentMetaConstraint(AnnotationMetaDataProvider.java:795) ~[hibernate-validator-6.0.17.Final.jar:6.0.17.Final]
    at org.hibernate.validator.internal.metadata.provider.AnnotationMetaDataProvider.lambda$findTypeUseConstraints$2(AnnotationMetaDataProvider.java:783) ~[hibernate-validator-6.0.17.Final.jar:6.0.17.Final]
    at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195) ~[na:na]
    at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1654) ~[na:na]
    at java.base/java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:658) ~[na:na]
    at java.base/java.util.stream.ReferencePipeline$7$1.accept(ReferencePipeline.java:274) ~[na:na]
    at java.base/java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948) ~[na:na]
    at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484) ~[na:na]
    at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474) ~[na:na]
    at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913) ~[na:na]
    at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[na:na]
    at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578) ~[na:na]
    at org.hibernate.validator.internal.metadata.provider.AnnotationMetaDataProvider.findTypeUseConstraints(AnnotationMetaDataProvider.java:784) ~[hibernate-validator-6.0.17.Final.jar:6.0.17.Final]
    at org.hibernate.validator.internal.metadata.provider.AnnotationMetaDataProvider.findTypeArgumentsConstraints(AnnotationMetaDataProvider.java:762) ~[hibernate-validator-6.0.17.Final.jar:6.0.17.Final]
    at org.hibernate.validator.internal.metadata.provider.AnnotationMetaDataProvider.findTypeAnnotationConstraints(AnnotationMetaDataProvider.java:581) ~[hibernate-validator-6.0.17.Final.jar:6.0.17.Final]
    at org.hibernate.validator.internal.metadata.provider.AnnotationMetaDataProvider.findPropertyMetaData(AnnotationMetaDataProvider.java:237) ~[hibernate-validator-6.0.17.Final.jar:6.0.17.Final]
    at org.hibernate.validator.internal.metadata.provider.AnnotationMetaDataProvider.getFieldMetaData(AnnotationMetaDataProvider.java:225) ~[hibernate-validator-6.0.17.Final.jar:6.0.17.Final]
    at org.hibernate.validator.internal.metadata.provider.AnnotationMetaDataProvider.retrieveBeanConfiguration(AnnotationMetaDataProvider.java:133) ~[hibernate-validator-6.0.17.Final.jar:6.0.17.Final]
    at org.hibernate.validator.internal.metadata.provider.AnnotationMetaDataProvider.getBeanConfiguration(AnnotationMetaDataProvider.java:124) ~[hibernate-validator-6.0.17.Final.jar:6.0.17.Final]
    at org.hibernate.validator.internal.metadata.BeanMetaDataManager.getBeanConfigurationForHierarchy(BeanMetaDataManager.java:232) ~[hibernate-validator-6.0.17.Final.jar:6.0.17.Final]
    at org.hibernate.validator.internal.metadata.BeanMetaDataManager.createBeanMetaData(BeanMetaDataManager.java:199) ~[hibernate-validator-6.0.17.Final.jar:6.0.17.Final]
    at org.hibernate.validator.internal.metadata.BeanMetaDataManager.getBeanMetaData(BeanMetaDataManager.java:166) ~[hibernate-validator-6.0.17.Final.jar:6.0.17.Final]
    at org.hibernate.validator.internal.engine.ValidatorImpl.validate(ValidatorImpl.java:157) ~[hibernate-validator-6.0.17.Final.jar:6.0.17.Final]
    at org.springframework.validation.beanvalidation.SpringValidatorAdapter.validate(SpringValidatorAdapter.java:117) ~[spring-context-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.validation.DataBinder.validate(DataBinder.java:889) ~[spring-context-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver.validateIfApplicable(AbstractMessageConverterMethodArgumentResolver.java:266) ~[spring-webmvc-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:137) ~[spring-webmvc-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121) ~[spring-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:167) ~[spring-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:134) ~[spring-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106) ~[spring-webmvc-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:888) ~[spring-webmvc-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:793) ~[spring-webmvc-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040) ~[spring-webmvc-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943) ~[spring-webmvc-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:880) ~[spring-webmvc-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:741) ~[tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) ~[tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.27.jar:9.0.27]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:108) ~[spring-boot-actuator-2.2.0.RELEASE.jar:2.2.0.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) ~[tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) ~[tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:526) ~[tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139) ~[tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) ~[tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) ~[tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408) ~[tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) ~[tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:861) ~[tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1579) ~[tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.27.jar:9.0.27]
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) ~[na:na]
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) ~[na:na]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.27.jar:9.0.27]
    at java.base/java.lang.Thread.run(Thread.java:834) ~[na:na]

Ofcourse, I could, just create my own Validator Bean .当然,我可以,只是创建我自己的Validator Bean I have tested it and it works:我已经测试过它并且它有效:


@Configuration
public class CustomValidator {

    @Bean
    public Validator validator() {
        ValidatorFactory validatorFactory = Validation.byDefaultProvider()
                .configure()
                .addValueExtractor(new PatchFieldValueExtractor())
                .buildValidatorFactory();
        return validatorFactory.getValidator();
    }
}

However, I fear that by doing so, I opt-out of current or future AutoConfiguration "goodies" that I am not aware.但是,我担心这样做会选择退出我不知道的当前或未来的 AutoConfiguration “好东西”。 For example, there are nuances with RestTemplate and RestTemplateBuilder, which can lead to unexpected runtime behaviours as outlined in this article .例如,RestTemplate 和 RestTemplateBuilder 存在细微差别,这可能导致本文中概述的意外运行时行为。 Thus I am reluctant to go this route.所以我不愿意走这条路。 Is there a better version on how to customize Validator?是否有关于如何自定义 Validator 的更好版本?

Sources I have looked我看过的来源

Spring Framework Documentation. Spring 框架文档。 Bean Validation . 豆验证 Per documentation:根据文档:

You can use the LocalValidatorFactoryBean to configure a default Validator as a Spring bean.您可以使用 LocalValidatorFactoryBean 将默认验证器配置为 Spring bean。

The basic configuration in the preceding example triggers bean validation to initialize by using its default bootstrap mechanism.前面示例中的基本配置通过使用其默认引导机制触发 bean 验证进行初始化。 A JSR-303 or JSR-349 provider, such as the Hibernate Validator, is expected to be present in the classpath and is automatically detected. JSR-303 或 JSR-349 提供程序,例如 Hibernate Validator,预计会出现在类路径中并被自动检测到。

Additional Configuration Options其他配置选项

The default LocalValidatorFactoryBean configuration suffices for most cases.对于大多数情况,默认的 LocalValidatorFactoryBean 配置就足够了。 There are a number of configuration options for various Bean Validation constructs, from message interpolation to traversal resolution.各种 Bean 验证构造有许多配置选项,从消息插值到遍历解析。 See the LocalValidatorFactoryBean javadoc for more information on these options.有关这些选项的更多信息,请参阅 LocalValidatorFactoryBean javadoc。

LocalValidatorFactoryBean 本地验证器工厂Bean

This API does not contain clear information how could I register custom ValueExtractor.此 API 不包含如何注册自定义 ValueExtractor 的明确信息。

Spring Boot Documentation . Spring Boot 文档 No mentioning of LocalValidatorFactoryBean or how to customize it.没有提到 LocalValidatorFactoryBean 或如何自定义它。

也许你可以在 Resources/META-INF/services/javax.validation.valueextraction.ValueExtractor 中注册

com.yourpackagename.PatchFieldValueExtractor

To register custom ValueExtractor , one has extend LocalValidatorFactoryBean :要注册自定义ValueExtractor ,可以扩展LocalValidatorFactoryBean

@Component
public class CustomLocalValidatorFactoryBean extends LocalValidatorFactoryBean {

    @Override
    protected void postProcessConfiguration(Configuration<?> configuration) {
        super.postProcessConfiguration(configuration);

        configuration.addValueExtractor(new PatchFieldValueExtractor());
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM