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