問題
我有一個帶有基本 RestController 的簡單 Spring Boot 應用程式(此處提供完整代碼)。它使用 JSON 并使用 Jackson 將請求從 JSON 和回應轉換為 JSON。
@RestController("/")
@RequestMapping(consumes = MediaType.APPLICATION_JSON_VALUE)
public class SomeController {
@Autowired
private SomeService someService;
@PostMapping
public ResponseEntity<SomeResponseDto> post(@RequestBody @Valid SomeRequestDto someRequestDto) {
final SomeResponseDto responseDto = new SomeResponseDto();
responseDto.setMessage(someRequestDto.getInputMessage());
responseDto.setUuid(someService.getUuid());
return ResponseEntity.ok(responseDto);
}
啟動后,第一個請求比任何后續請求慢約 10 倍。我除錯并分析了該應用程式,似乎在第一次請求時,Jackson JSON 決議器正在AbstractMessageConverterMethodArgumentResolver.readWithMessageConverters和AbstractJackson2HttpMessageConverter 中的某處初始化。
在后續請求中,它似乎被重新使用。
題
如何在啟動期間初始化 Jackson JSON 決議,以便第一個請求也很快?
我知道如何在 Spring 啟動后觸發一個方法。在PreloadComponent 中,我添加了如何針對控制器執行 REST 請求的示例。
@Component
public class PreloadComponent implements ApplicationListener<ApplicationReadyEvent> {
private final Logger logger = LoggerFactory.getLogger(PreloadComponent.class);
@Autowired
private Environment environment;
@Autowired
private WebClient.Builder webClientBuilder;
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
// uncomment following line to directly send a REST request on app start-up
// sendRestRequest();
}
private void sendRestRequest() {
final String serverPort = environment.getProperty("local.server.port");
final String baseUrl = "http://localhost:" serverPort;
final String warmUpEndpoint = baseUrl "/warmup";
logger.info("Sending REST request to force initialization of Jackson...");
final SomeResponseDto response = webClientBuilder.build().post()
.uri(warmUpEndpoint)
.header(CONTENT_TYPE, APPLICATION_JSON_VALUE)
.body(Mono.just(createSampleMessage()), SomeRequestDto.class)
.retrieve()
.bodyToMono(SomeResponseDto.class)
.timeout(Duration.ofSeconds(5))
.block();
logger.info("...done, response received: " response.toString());
}
private SomeRequestDto createSampleMessage() {
final SomeRequestDto someRequestDto = new SomeRequestDto();
someRequestDto.setInputMessage("our input message");
return someRequestDto;
}
}
這僅適用于這個玩具示例。實際上,我有許多帶有復雜 DTO 的 REST 端點,我需要在每個“真實”端點旁邊添加一個“預熱”端點,因為我無法呼叫我的真實端點。
我已經嘗試過什么?
我添加了具有不同 DTO 的第二個端點,并在我的PreloadComponent. 這并不能解決問題。我假設為每種型別創建了一個 Jackson / 任何實體。
我自動連接ObjectMapper到我的PreloadComponent并將 JSON 決議為我的 DTO。同樣,這并不能解決問題。
完整源代碼位于:https : //github.com/steinsag/warm-me-up
uj5u.com熱心網友回復:
我相信,很多類都會被延遲加載。如果首次呼叫性能很重要,那么我認為通過呼叫每個端點來熱身是可行的方法。
你為什么說你不能呼叫端點?如果您有一個資料庫并且您不想更改資料,請將所有內容包裝在一個事務中并在預熱呼叫后將其回滾。
我還沒有看到任何其他方法來解決這個問題,這并不一定意味著它不存在;)
uj5u.com熱心網友回復:
事實證明,杰克遜驗證是問題所在。我添加了 JVM 選項
-verbose:class
查看類何時加載。我注意到在第一個請求中,加載了許多 Jackson 驗證類。
為了證實我的假設,我重新撰寫了我的示例并添加了另一個具有獨特 DTO 的獨立預熱控制器。
這DTO使用所有的Java驗證注解也存在像真正的DTO,如@NotNull,@Min等。此外,它也有一個自定義列舉也有子型別的驗證。
在啟動期間,我現在向這個預熱端點發出 REST 請求,它不需要包含任何業務邏輯。
啟動后,我的第一個請求現在只比任何后續請求慢 2-3 倍。這是可以接受的。之前,第一個請求慢了 20-40 倍。
我還評估了是否真的需要 REST 請求,或者僅對 DTO 進行 JSON 決議或驗證就足夠了(請參閱PreloadComponent)。這會稍微減少第一個請求的運行時間,但它仍然比適當的預熱慢 5-15 倍。所以我想還需要一個 REST 請求來加載 Spring Dispatcher 等中的其他類。
我更新了我的例子:https : //github.com/steinsag/warm-me-up
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/342766.html
