มาลองใช้ Spring Lazy Initialization กันเถอะ
Spring Boot 2.2 เพิ่มการรองรับ feature อย่าง Lazy Initialization ที่จะทำให้เริ่มต้น application ได้เร็วขึ้น ผมก็เลยสงสัยว่ามันจะจริงหรอและอยากจะเข้าใจว่ามันทำได้อย่างไร ในบทความนี้เลยจะมาลองใช้งาน Lazy Initialization ของ Spring Boot กันดู
Lazy Initialization
เป็นเทคนิคสำหรับเลื่อนการสร้าง object ไปจนกว่าจะมีการใช้งานมันจริงๆ ซึ่งใน Spring ก็เป็นการเลื่อนการสร้าง bean จากที่เดิมทีต้องสร้างตอนที่ application เริ่มต้นทำงาน ไปจนกว่า bean นั้นๆ จะมีการเรียกใช้ ก็จะสร้างขึ้น ทำให้มีประโยชน์ตรงที่ตอนเริ่มต้น application จะรวดเร็วกว่าปรกตินิดหน่อย เพราะไม่ต้องสร้าง bean ที่เป็น lazy initialization
ซึ่ง Spring Boot รองรับ Lazy initialization ตั้งแต่ version 2.2.0 เป็นต้นมา ดังนั้นถ้าเราต้องการใช้งาน Lazy initialization ก็ต้องเลือกสร้าง project ที่ใช้ Spring Boot ตั้งแต่ version 2.2.0 เป็นต้นไป
ใน Spring framework ใช้ @Lazy annotation มาระบุว่าจะให้ bean ระดับไหนทำงานแบบ Lazy initialization ซึ่งจะลองใช้งานในตัวอย่าง
Create Project
ใช้ start.spring.io สร้าง Spring Boot project โดยเลือก Spring Boot version ตั้งแต่ 2.2.0 เป็นต้นไป (ซึ่งในตอนเขียนบทความนี้ version 2.2.5 เป็น version ล่าสุด) และเลือก dependency เป็น Spring Web
และ Lombok
เมื่อ Generate project เรียบร้อยแล้ง ก็ใช้ IntelliJ เปิด project
Try Lazy Initialization
ลองสร้าง Rest Controller class อย่างง่ายๆ โดยใช้ endpoint เป็น /lazy
@Slf4j
@RestController
@RequestMapping("/lazy")
public class LazyController {
public LazyController() {
log.info("LazyController Initialized");
}
@GetMapping
public String hello() {
return "Hello";
}
}
ใช้คำสั่ง mvn spring-boot:run
หรือ run Application จาก IntelliJ ก็ได้ ซึ่งดูที่ console log จะเห็นได้ว่า constructor ของ LazyController
ถูกเรียกใช้งาน นั้นหมายความว่า RestController
bean นี้ถูกสร้างขึ้นในระหว่างเริ่มต้น application
และสร้าง LazyService
class เพื่อ inject เข้าไปใน LazyController
@Slf4j
@Service
public class LazyService {
public LazyService() {
log.info("LazyService initialized");
}
public String hello() {
return "Hello";
}
}
และ refactor LazyController
ให้ใช้งาน LazyService
@Slf4j
@RestController
@RequestMapping("/lazy")
public class LazyController {
private LazyService lazyService;
public LazyController(LazyService lazyService) {
log.info("LazyController Initialized");
this.lazyService = lazyService;
}
@GetMapping
public String hello() {
return lazyService.hello();
}
}
และเมื่อเริ่มต้น Application จะเห็นว่าทั้ง LazyController
และ LazyService
bean ได้สร้างขึ้นตอนเริ่มต้น Application นั้นเลย
ถ้าเราต้องการใช้งาน Lazy initialization ใน Spring Boot จะต้องตั้งค่าใช้ใช้ lazy-initialization ใน application properties โดยกำหนดให้เป็น true
spring.main.lazy-initialization=true
ซึ่งนั้นหมายความว่า bean ทั้งหมดใน application context จะถูกทำให้เป็น lazy initialization และเมื่อเราลองเริ่มต้น appliction จะเห็นได้ว่า ไม่มีการสร้าง bean ของ LazyController
และ LazyService
จนกระทั่งเราใช้ CURL เรียกไปที่ endpoint นั้น ก็จะเห็นได้ว่า LazyService
และ LazyContoller
จะถูกสร้างตามลำดับ
แต่เราก็สามารถที่จะไม่ให้ bean ทั้งหมด ใช้ Lazy Initialization ได้ด้วยการกำหนดค่า lazy-initialization
ให้เป็น false
spring.main.lazy-initialization=false
โดยที่เราจะให้ LazyController
สร้าง bean ปรกติ แต่ จะให้ LazyService
เป็น Lazy initialization ด้วยการใช้ @Lazy
annotation กับ LazyService
และ constructor ของ LazyController
เนื่องจากในตัวอย่างของบทความนี้ใช้ constructor injection แต่ถ้าใช้ field injection ก็ใช้ @Lazy
annotation กับ field ที่ใช้ @Autowired
ดังนี้
@Lazy
@Slf4j
@Service
public class LazyService {
public LazyService() {
log.info("LazyService initialized");
}
public String hello() {
return "Hello";
}
}@Slf4j
@RestController
@RequestMapping("/lazy")
public class LazyController {
private LazyService lazyService;
@Lazy
public LazyController(LazyService lazyService) {
log.info("LazyController Initialized");
this.lazyService = lazyService;
}
@GetMapping
public String hello() {
return lazyService.hello();
}
}
และเมื่อ run application จะเห็นได้ว่า LazyController
bean ถูกสร้างขึ้น แต่ LazyService ยังไม่สร้าง
เมื่อ CURL ไปที่ endpoint /lazy
ก็จะเห็นว่า LazyService
สร้างขึ้นเมื่อต้องการใช้งาน method hello()
ของ LazyService
Downside
การใช้งาน Lazy Initialization นั้นมีประโยชน์มากในการลดเวลาตอน start up ของ Application แต่ก็ใช่ว่าจะมีแต่ข้อดี ก็มีข้อเสียอยู่เหมือนกัน และเป็นเหตุผลว่าทำไม Spring Boot ไม่ได้ใช้ Lazy Initialization เป็นค่าตั้งต้น
เนื่องจาก class ไม่ได้โหลดและ bean ก็ไม่ได้สร้างขึ้นตอนเริ่มต้น จนกระทั้งต้องการใช้ bean นั้นจริงๆ ซึ่งจะทำให้เกิดปัญหาที่อาจจะพบได้ตอนเริ่มต้นได้ อย่างเช่น หา class ไม่เจอ (no class def found error), หน่วยความจำไม่เพียงพอ (out of memory error) และความผิดพลาดจากการตั้งค่า configuration ผิด (misconfiguration error)
ใน Web application ทำให้เพิ่ม latency ของ HTTP request ได้ ตอนที่ trigger ให้สร้าง bean แม้ว่าจะมีผลกระทบเพียงแค่ request แรกเท่านั้น แต่อาจจะมีผลกระทบกับ load-balancing หรือ auto-scale
สรุป
จากการลองใช้งาน Lazy Initialization ของ Spring Boot นี้จะเห็นว่า feature นี้มีประโยชน์มากกับการลดระยะเวลาในการเริ่มต้น application แต่ก็มีข้อเสียอยู่เหมือนกัน ทำให้ Developer ต้องเทียบข้อดีข้อเสียว่าคุ้มไหมที่จะเอา Lazy initialization มาใช้งานกับ project ของเรา