มาลองใช้ Spring Lazy Initialization กันเถอะ

Phayao Boonon
4 min readFeb 29, 2020

--

Photo by Colin Watts on Unsplash

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 ของเรา

--

--

Phayao Boonon
Phayao Boonon

Written by Phayao Boonon

Software Engineer 👨🏻‍💻 Stay Hungry Stay Foolish

No responses yet