gsmet wrote:
Any chance you could provide us with a Maven project allowing to reproduce the issue? It's really hard to provide feedback if we can't have a look at the setup and run the profiling by ourselves.
Sure. Below you can find the complete code and the pom.xml:
com.example.demo.controller.Controller
Code:
package com.example.demo.controller;
import java.security.InvalidParameterException;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Valid;
import javax.validation.Validator;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.example.demo.model.Shop;
@RestController
@RequestMapping("/test")
public class Controller {
   private final Validator validator;
   public Controller() {
      validator = ValidatorFactorySingleton.getInstance().getValidator();
   }
   /**
    * No input-validation.
    * 
    * @param shop
    * @return
    */
   @RequestMapping(path = "/no_validation", method = RequestMethod.POST)
   public ResponseEntity<Object> postNoValidation(@RequestBody Shop shop) {
      return new ResponseEntity<>(HttpStatus.CREATED);
   }
   /**
    * Validation using @Valid.
    * 
    * @param shop
    * @return
    */
   @RequestMapping(path = "/spring", method = RequestMethod.POST)
   public ResponseEntity<Object> postSpringValidation(@RequestBody @Valid Shop shop) {
      return new ResponseEntity<>(HttpStatus.CREATED);
   }
   /**
    * Validation using the Validator.
    * 
    * @param shop
    * @return
    */
   @RequestMapping(path = "/validator", method = RequestMethod.POST)
   public ResponseEntity<Object> postValidator(@RequestBody Shop shop) {
      final Set<ConstraintViolation<Shop>> violations = this.validator.validate(shop);
      if (!violations.isEmpty()) {
         throw new InvalidParameterException("invalid data");
      }
      return new ResponseEntity<>(HttpStatus.CREATED);
   }
   /**
    * Validation using our own method.
    * 
    * @param shop
    * @return
    */
   @RequestMapping(path = "/own", method = RequestMethod.POST)
   public ResponseEntity<Object> postOwnValidation(@RequestBody Shop shop) {
      if (!shop.isValidData()) {
         throw new InvalidParameterException("invalid data");
      }
      return new ResponseEntity<>(HttpStatus.CREATED);
   }
}
com.example.demo.controller.ValidatorFactorySingleton
Code:
package com.example.demo.controller;
import javax.validation.Validation;
import javax.validation.ValidatorFactory;
public class ValidatorFactorySingleton {
   private static ValidatorFactory instance = null;
   private ValidatorFactorySingleton() {
   }
   public static ValidatorFactory getInstance() {
      if (instance == null) {
         instance = Validation.buildDefaultValidatorFactory();
      }
      return instance;
   }
}
com.example.demo.model.Article
Code:
package com.example.demo.model;
import javax.validation.constraints.NotNull;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
public class Article {
   @NotNull
   private String id;
   public boolean isValidData() {
      if (id == null)
         return false;
      return true;
   }
}
com.example.demo.model.Shop
Code:
package com.example.demo.model;
import java.util.List;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
public class Shop {
   @NotNull
   private String id;
   @Valid
   @NotNull
   private List<Article> articles;
   public boolean isValidData() {
      if (id == null)
         return false;
      for (Article article : articles) {
         if (!article.isValidData()) {
            return false;
         }
      }
      return true;
   }
}
com.example.demo.PerformanceValidationApplication
Code:
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class PerformanceValidationApplication {
   public static void main(String[] args) {
      SpringApplication.run(PerformanceValidationApplication.class, args);
   }
}
com.example.demo.controller.ControllerTest
Code:
package com.example.demo.controller;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.client.RequestCallback;
import org.springframework.web.client.ResponseExtractor;
import com.example.demo.model.Article;
import com.example.demo.model.Shop;
import com.fasterxml.jackson.databind.ObjectMapper;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class ControllerTest {
   private static final int NUMBER_OF_RUNS_FOR_AVERAGE = 200;
   private static final int NUMBER_OF_ARTICLES_PER_SHOP = 20000;
   @Autowired
   private ApplicationContext appContext;
   @Autowired
   private ObjectMapper objectMapper;
   @Autowired
   private TestRestTemplate testRestTemplate;
   @Test
   public void contextLoads() throws Exception {
      assertThat(this.appContext).isNotNull();
   }
   @Test
   public void noValidation() {
      testEndpoint("/test/no_validation");
   }
   @Test
   public void springValidation() {
      testEndpoint("/test/spring");
   }
   @Test
   public void validator() {
      testEndpoint("/test/validator");
   }
   @Test
   public void ownValidation() {
      testEndpoint("/test/own");
   }
   private void testEndpoint(String url) {
      double runtime = getAverageRuntimeMillis(url, NUMBER_OF_RUNS_FOR_AVERAGE);
      System.out.println(url + " -> " + runtime + " ms");
   }
   private double getAverageRuntimeMillis(String url, int runs) {
      List<Long> runtimes = new ArrayList<>(runs);
      for (int i = 0; i < runs; i++) {
         long runtime = getRuntimeMillis(url);
         runtimes.add(runtime);
      }
      return runtimes.stream().mapToDouble(x -> x).average().getAsDouble();
   }
   private long getRuntimeMillis(String url) {
      long start = System.currentTimeMillis();
      this.executePost(url, createShop());
      long end = System.currentTimeMillis();
      return end - start;
   }
   private Shop createShop() {
      List<Article> articles = new ArrayList<>(NUMBER_OF_ARTICLES_PER_SHOP);
      for (int i = 0; i < NUMBER_OF_ARTICLES_PER_SHOP; i++) {
         Article article = new Article();
         article.setId("a" + i);
         articles.add(article);
      }
      Shop shop = new Shop();
      shop.setId("shop");
      shop.setArticles(articles);
      return shop;
   }
   private void executePost(String url, Shop shop) {
      assertThat(shop).isNotNull();
      final RequestCallback requestCallback = request -> {
         request.getHeaders().setContentType(MediaType.APPLICATION_JSON);
         request.getBody().write(objectMapper.writeValueAsBytes(shop));
      };
      final ResponseExtractor<?> responseExtractor = response -> {
         assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED);
         return null;
      };
      this.testRestTemplate.execute(url, HttpMethod.POST, requestCallback, responseExtractor);
   }
}
pom.xml
Code:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>
   <groupId>com.example</groupId>
   <artifactId>performance_validation</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <packaging>jar</packaging>
   <name>performance_validation</name>
   <description>Demo project for Spring Boot</description>
   <parent>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-parent</artifactId>
      <version>1.5.3.RELEASE</version>
      <relativePath /> <!-- lookup parent from repository -->
   </parent>
   <properties>
      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
      <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
      <java.version>1.8</java.version>
   </properties>
   <dependencies>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
      <dependency>
         <groupId>org.projectlombok</groupId>
         <artifactId>lombok</artifactId>
         <optional>true</optional>
      </dependency>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-test</artifactId>
         <scope>test</scope>
      </dependency>
   </dependencies>
   <build>
      <plugins>
         <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
         </plugin>
      </plugins>
   </build>
</project>
gsmet wrote:
Could you post the profiling output with HV 6? It should be a bit different, particularly regarding the classmate calls.
I will come back to this