简体   繁体   中英

Spring Validation not working on Service Layer level

I have a Spring Boot project ( 2.3.3 ) where I want to validate the service layer methods input parameters. So in my pom.xml I added

        <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>

as it is no more part of the parent. Next I have my service method interface and the implementing service method. My implemening service is annotated with @Validated and my method looks like

public void deleteGreetingById(@NotNull(message = "greetingId must not be null.")Integer greetingId) {

I've also read that the validation is bound per default only to the controller layer. So to enable it also for the servie layer I added a PostValidationProcesser.

@Configuration
public class MethodValidationConfig {

    @Bean
    public MethodValidationPostProcessor methodValidationPostProcessor() {
        return new MethodValidationPostProcessor();
    }
}

When I now execute my test with null as input param, nothing happens and no exception is thrown. When I do

 Assert.notNull(greetingId,"greetingId must not be null"); 

inside the method, an InvalidParameterException is thrown like expected. But I would prefere the annotation based validation because of the @Valid validation of whole class Objects as input parameter.

Can one explain why the validation is not triggered?

EDIT:

@RestController
public class GreetingsConsumerController {

    private final GreetingsService greetingsService;

    public GreetingsConsumerController(GreetingsService greetingsService) {
        this.greetingsService = greetingsService;
    }

    @PostMapping(value = "/greetings", consumes = MediaType.APPLICATION_JSON_VALUE)
    public Greeting createGreeting( @RequestBody @Valid GreetingDto greetingDto){
        return greetingsService.addGreeting(greetingDto);
    }

    @GetMapping(value = "/greetings/{id}")
    public Greeting getGreetingById(@PathVariable Integer id){
        return greetingsService.findGreetingById(id);
    }

    @GetMapping(value = "/greetings")
    public List<Greeting> getAllGreetings(){
        return greetingsService.findAllGreetings();
    }

    @DeleteMapping(value = "/greetings/{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void deleteGreetingById(@PathVariable Integer id){

        greetingsService.deleteGreetingById(id);
    }
}

Interface:

public interface GreetingsService {
    
        Greeting findGreetingById(Integer greetingId);
        List<Greeting> findAllGreetings();
        Greeting addGreeting( GreetingDto greetingDto);
        void deleteGreetingById( Integer greetingId);
    
    }

IterfaceImpl:

@Service
@Validated
public class GreetingsServiceImpl implements GreetingsService {

.
.
.
       @Override
        public void deleteGreetingById(@NotNull(message = "greetingId must not be null. ") Integer greetingId) {
            ...
        }

}

I also added the Bean to my SpringBootApplication but still no exception is thrown.

@SpringBootApplication
public class GreetingsConsumerApplication {

    public static void main(String[] args) {

        SpringApplication.run(GreetingsConsumerApplication.class, args
        );
    }
    @Bean
    public MethodValidationPostProcessor methodValidationPostProcessor() {
        return new MethodValidationPostProcessor();
    }

}

Below is the sample example to validate a model at service layer.

class   TestModel{
    @NotNull
    private String name;
    }

TestModel model= new TestModel();
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        Validator validator = factory.getValidator();
        Set<ConstraintViolation<TestModel>> violations = validator.validate(model);
        
        

I "solved" the problem. My error was that I configured my Tests wrong. I configured the test with

@Extendwith(SpringExtension.class)

as I've only written unit tests without using the context in this class before. Obviously using the parameter validation this way you have to use the Context which makes the whole scenario an integration test. I'm glad it works now and I'm sorry for the needless discussions. I should have posted my test also in the code.

Although I am glad it works now I'm also a bit confused. In genereal I don't want to start the Spring context just for constraint validation. But this is another question.

When you have services implementing interfaces and you reference the interface you need the validation annotations on the interface, not the implementing class. Add the validation annotations to the GreetingsService interface.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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