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