How does spring singleton beans serve concurrent requests (#11987)

* add service base structure
- controllers & services & sanity check tests

* finalize code

* reformat continues lines with 2 spaces

* reformat continues lines with 2 spaces
This commit is contained in:
Vlad Fernoaga 2022-04-09 06:29:33 +03:00 committed by GitHub
parent 4bcb36ff41
commit bb7d2a5d55
6 changed files with 165 additions and 0 deletions

View File

@ -0,0 +1,11 @@
package com.baeldung.concurrentrequest;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ConcurrentRequestApplication {
public static void main(String[] args) {
SpringApplication.run(ConcurrentRequestApplication.class, args);
}
}

View File

@ -0,0 +1,26 @@
package com.baeldung.concurrentrequest;
public class Product {
private final int id;
private final String name;
private final Stock stock;
public Product(int id, String name, Stock stock) {
this.id = id;
this.name = name;
this.stock = stock;
}
public Stock getStock() {
return stock;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
}

View File

@ -0,0 +1,30 @@
package com.baeldung.concurrentrequest;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController()
@RequestMapping("product")
public class ProductController {
private final ProductService productService;
public ProductController(ProductService productService) {
this.productService = productService;
}
@GetMapping("/{id}")
public Product getProductDetails(@PathVariable("id") int productId) {
return productService.getProductById(productId)
.orElse(null);
}
@GetMapping("{id}/stock")
public Stock getProductStock(@PathVariable("id") int productId) {
return productService.getProductById(productId)
.map(Product::getStock)
.orElse(null);
}
}

View File

@ -0,0 +1,32 @@
package com.baeldung.concurrentrequest;
import static java.lang.Thread.currentThread;
import static java.util.Arrays.asList;
import java.util.List;
import java.util.Optional;
import org.springframework.stereotype.Service;
@Service
public class ProductService {
// @formatter:off
private final static List<Product> productRepository = asList(
new Product(1, "Product 1", new Stock(100)),
new Product(2, "Product 2", new Stock(50))
);
// @formatter:on
public Optional<Product> getProductById(int id) {
Optional<Product> product = productRepository.stream()
.filter(p -> p.getId() == id)
.findFirst();
String productName = product.map(Product::getName)
.orElse(null);
System.out.printf("Thread: %s; bean instance: %s; product id: %s has the name: %s%n", currentThread().getName(), this, id, productName);
return product;
}
}

View File

@ -0,0 +1,13 @@
package com.baeldung.concurrentrequest;
public class Stock {
private final int inStockItems;
public Stock(int inStockItems) {
this.inStockItems = inStockItems;
}
public int getInStockItems() {
return inStockItems;
}
}

View File

@ -0,0 +1,53 @@
package com.baeldung.concurrentrequest;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultMatcher;
@SpringBootTest
@AutoConfigureMockMvc
public class ConcurrentRequestUnitTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ProductController controller;
@Test
public void givenContextLoads_thenProductControllerIsAvailable() {
assertThat(controller).isNotNull();
}
@Test
public void givenMultipleCallsRunInParallel_thenAllCallsReturn200() throws Exception {
ExecutorService executor = Executors.newFixedThreadPool(2);
executor.submit(() -> performCall("/product/1", status().isOk()));
executor.submit(() -> performCall("/product/2/stock", status().isOk()));
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
}
private void performCall(String url, ResultMatcher expect) {
try {
this.mockMvc.perform(get(url))
.andExpect(expect);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}