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:
parent
4bcb36ff41
commit
bb7d2a5d55
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue