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?
1 Answer
Reset to default 0A 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
@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 theConfigurationPropertiesScanRegistrar
and its helpers on how Spring Boot does it. One thing is that they use anImportBeanDefinitionRegistrar
which is executed earlier then what you have now. – M. Deinum Commented Mar 12 at 7:23