java - Validating dynamic Spring beans generated by BeanDefinitionRegistryPostProcessor - Stack Overflow

I have a class that implements the Spring BeanDefinitionRegistryPostProcessor:package com.example.demo

I have a class that implements the Spring BeanDefinitionRegistryPostProcessor:

package com.example.demo;

import .springframework.beans.BeansException;
import .springframework.beans.factory.support.BeanDefinitionBuilder;
import .springframework.beans.factory.support.BeanDefinitionRegistry;
import .springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import .springframework.context.annotation.Configuration;

@Configuration
class ConfigLoader implements BeanDefinitionRegistryPostProcessor {

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
        throws BeansException {
        registry.registerBeanDefinition("fooBarProperties",
            BeanDefinitionBuilder
                .genericBeanDefinition(FooBarProperties.class)
                .addPropertyValue("foo", "bar")
                .addPropertyValue("bar", "")
                .getBeanDefinition());
    }
}

This (dynamically) registers a bean of class FooBarProperties, which is defined as:

package com.example.demo;

import jakarta.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.Setter;
import .springframework.validation.annotation.Validated;

@Getter
@Setter
@Validated
public class FooBarProperties {
    @NotBlank
    private String foo;

    @NotBlank
    private String bar;
}

In the actual application there may be multiple FooBarProperties, and the actual values are populated from an external configuration. I would like to make sure that these properties are valid. Given the definition below, I would expect Spring to throw an error, because the value of bar is empty while the attribute has a @NotBlank annotation. However, this does not happen:

@Component
public class HelloWorldComponent {

    public HelloWorldComponent(FooBarProperties fooBarProperties) {
        System.out.printf("foo is '%s', bar is '%s'%n", fooBarProperties.getFoo(),
            fooBarProperties.getBar());
        // foo is 'bar', bar is ''
    }
}

I would also be okay with manually validating the properties. However, if I try to do that, the validator thinks all fields are null, instead of only the bar field being blank:

@Component
public class HelloWorldComponent {

    public HelloWorldComponent(SmartValidator validator, FooBarProperties fooBarProperties) {
        System.out.printf("foo is '%s', bar is '%s'%n", fooBarProperties.getFoo(),
            fooBarProperties.getBar());
        // foo is 'bar', bar is ''

        var errors = new BeanPropertyBindingResult(fooBarProperties, "FooBarProperties");
        validator.validate(fooBarProperties, errors);
        for (var error : errors.getFieldErrors()) {
            System.out.println(error.getField() + ": " + error.getDefaultMessage());
        }
        // foo: must not be blank
        // bar: must not be blank
    }
}

Upon inspection the bean seems to be of class FooBarProperties$$SpringCGLIB$$0 instead of FooBarProperties.

Given all this, is there a way to validate FooBarProperties with their jakarta validation annotations?

I have a class that implements the Spring BeanDefinitionRegistryPostProcessor:

package com.example.demo;

import .springframework.beans.BeansException;
import .springframework.beans.factory.support.BeanDefinitionBuilder;
import .springframework.beans.factory.support.BeanDefinitionRegistry;
import .springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import .springframework.context.annotation.Configuration;

@Configuration
class ConfigLoader implements BeanDefinitionRegistryPostProcessor {

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
        throws BeansException {
        registry.registerBeanDefinition("fooBarProperties",
            BeanDefinitionBuilder
                .genericBeanDefinition(FooBarProperties.class)
                .addPropertyValue("foo", "bar")
                .addPropertyValue("bar", "")
                .getBeanDefinition());
    }
}

This (dynamically) registers a bean of class FooBarProperties, which is defined as:

package com.example.demo;

import jakarta.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.Setter;
import .springframework.validation.annotation.Validated;

@Getter
@Setter
@Validated
public class FooBarProperties {
    @NotBlank
    private String foo;

    @NotBlank
    private String bar;
}

In the actual application there may be multiple FooBarProperties, and the actual values are populated from an external configuration. I would like to make sure that these properties are valid. Given the definition below, I would expect Spring to throw an error, because the value of bar is empty while the attribute has a @NotBlank annotation. However, this does not happen:

@Component
public class HelloWorldComponent {

    public HelloWorldComponent(FooBarProperties fooBarProperties) {
        System.out.printf("foo is '%s', bar is '%s'%n", fooBarProperties.getFoo(),
            fooBarProperties.getBar());
        // foo is 'bar', bar is ''
    }
}

I would also be okay with manually validating the properties. However, if I try to do that, the validator thinks all fields are null, instead of only the bar field being blank:

@Component
public class HelloWorldComponent {

    public HelloWorldComponent(SmartValidator validator, FooBarProperties fooBarProperties) {
        System.out.printf("foo is '%s', bar is '%s'%n", fooBarProperties.getFoo(),
            fooBarProperties.getBar());
        // foo is 'bar', bar is ''

        var errors = new BeanPropertyBindingResult(fooBarProperties, "FooBarProperties");
        validator.validate(fooBarProperties, errors);
        for (var error : errors.getFieldErrors()) {
            System.out.println(error.getField() + ": " + error.getDefaultMessage());
        }
        // foo: must not be blank
        // bar: must not be blank
    }
}

Upon inspection the bean seems to be of class FooBarProperties$$SpringCGLIB$$0 instead of FooBarProperties.

Given all this, is there a way to validate FooBarProperties with their jakarta validation annotations?

Share Improve this question asked Mar 10 at 13:47 RexRex 6109 silver badges19 bronze badges 6
  • Validating properties like this is a spring boot feature and is not present in Spring itself. So unless you are using Spring Boot (then you are using a very complicated way of registering a properties bean) this isn't possible without adding additional components which are available in Spring Boot. – M. Deinum Commented Mar 10 at 14:45
  • I am using Spring Boot. This is indeed a complicated way of registering a properties bean, but the properties classes have to be dynamically generated. – Rex Commented Mar 10 at 18:33
  • This isn't dynamically generating classes but dynamicaly registering them. The validation support for properties classes is build into the Spring Boot properties support it is not intended to be used on components (nor will it). So the problem of it not working is the fact that you are bypassing the properties support in Spring Boot. Why aren't you just tapping into the default support? – M. Deinum Commented Mar 11 at 6:41
  • It's a library that should generate zero, one or multiple sets of beans, dependent on properties defined by the main applications. The current setup is inspired by stackoverflow/a/75046618. – Rex Commented Mar 12 at 7:16
  • The problem is that the setup you link to is for regular components not for @ConfigurationProperties type of beans. So do you really need those properties why not just create the components/beans instead? I would suggest to take a look at the ConfigurationPropertiesScanRegistrar and its helpers on how Spring Boot does it. One thing is that they use an ImportBeanDefinitionRegistrar which is executed earlier then what you have now. – M. Deinum Commented Mar 12 at 7:23
 |  Show 1 more comment

1 Answer 1

Reset to default 0

A thread about the Hibernate Validator sent me to this note in the documentation:

When lazy loaded associations are supposed to be validated it is recommended to place the constraint on the getter of the association. Hibernate replaces lazy loaded associations with proxy instances which get initialized/loaded when requested via the getter. If, in such a case, the constraint is placed on field level the actual proxy instance is used which will lead to validation errors.

If I replace the Lombok @Getter with custom getters and move the annotations to these methods, the constraints do seem to be applied:

@Setter
@Validated
public class FooBarProperties {

    private String foo;
    private String bar; 

    @NotBlank(message="foo must not be blank")
    public String getFoo() {
        return foo;
    }

    @NotBlank(message="bar must not be blank")
    public String getBar() {
        return bar;
    }
}
2025-03-10T19:28:43.227+01:00 ERROR 341213 --- [           main] o.s.boot.SpringApplication               : Application run failed

.springframework.beans.factory.BeanCreationException: Error creating bean with name 'helloWorldComponent' defined in file [/demo/target/classes/com/example/demo/HelloWorldComponent.class]: Failed to instantiate [com.example.demo.HelloWorldComponent]: Constructor threw exception
...
Caused by: jakarta.validation.ConstraintViolationException: getBar.<return value>: bar must not be blank

While not the most beautiful solution, it does solve my use case in which I would like the properties to be validated before use.

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1744843737a4596710.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信