Singleton và prototype là hai mẫu thiết kế thuộc nhóm khởi tạo, đây cũng chính là những khái niệm cốt lõi nhất trong lập trình nói chung và spring nói riêng. Tuy nhiên trong bài viết này mình sẽ chỉ để cập trong phạm vi của Spring, nếu bạn muốn tìm hiểu về Singleton và Prototype nói chung, bạn có thể tìm đọc cuốn làm chủ các mẫu thiết kế kinh điểm trong lập trình nhé.

Tạo module

Chúng ta sẽ cần tạo một module mới có tên singleton-prototype. Nếu bạn chưa biết tạo module thế nào, bạn có thể tham khảo bài viết trước nhé: https://techmaster.vn/posts/37951/tim-hieu-ve-spring-core-bai-1-hello-world.

Singleton

Mặc định thì Spring sẽ khởi tạo đối tượng ở dạng singleton. Ví dụ khi chúng ta khi chúng ta khai báo một lớp SingletonService thế này:

package vn.techmaster.singleton_prototype.service;

import org.springframework.stereotype.Component;

@Component
public class SingletonService {
}

Thì Spring sẽ chỉ tạo ra một đối tượng duy nhất cho lớp SingletonService, bạn có thể kiểm chứng thông qua việc in thông tin của đối tương SingletonService ra màn hình:

package vn.techmaster.singleton_prototype;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import vn.techmaster.singleton_prototype.service.PrototypeService;
import vn.techmaster.singleton_prototype.service.SingletonService;

public class SingletonPrototypeStartup {

    public static void main(String[] args) {
        ApplicationContext applicationContext =
            new AnnotationConfigApplicationContext(
                "vn.techmaster.singleton_prototype"
            );
        System.out.println(
            "SingletonService 1: " +
            applicationContext.getBean(SingletonService.class)
        );
        System.out.println(
            "SingletonService 2: " +
            applicationContext.getBean(SingletonService.class)
        );
    }
}

Kết quả chúng ta nhận được sẽ là:
SingletonService 1: vn.techmaster.singleton_prototype.service.SingletonService@4034c28c
SingletonService 2: vn.techmaster.singleton_prototype.service.SingletonService@4034c28c
Như bạn có thể thấy thông tin của hai lần in ra của SingletonService là giống hệt nhau.

Prototype

Nếu chúng ta muốn tạo một lớp prototype, nghĩa là cứ mỗi lần chúng ta sử dụng thì spring sẽ khởi tạo một đối tượng mới cho lớp này chúng ta có thể sử dụng @Scope(“prototype”) annotation, ví dụ chúng ta có thể tạo ra lớp PrototypeService như sau:

package vn.techmaster.singleton_prototype.service;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope("prototype")
public class PrototypeService {
}

Thì bây giờ mỗi lần chúng ta lấy ra một đối tượng từ PrototypeService nó sẽ khai báo mới, chúng ta có thể in ra thông tin của 2 đối tượng như sau:

package vn.techmaster.singleton_prototype;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import vn.techmaster.singleton_prototype.service.PrototypeService;
import vn.techmaster.singleton_prototype.service.SingletonService;

public class SingletonPrototypeStartup {

    public static void main(String[] args) {
        ApplicationContext applicationContext =
            new AnnotationConfigApplicationContext(
                "vn.techmaster.singleton_prototype"
            );
        System.out.println(
            "PrototypeService 1: " +
            applicationContext.getBean(PrototypeService.class)
        );
        System.out.println(
            "PrototypeService 2: " +
            applicationContext.getBean(PrototypeService.class)
        );
    }
}

Và kết quả chúng ta nhận được sẽ là.
PrototypeService 1: vn.techmaster.singleton_prototype.service.PrototypeService@4034c28c
PrototypeService 2: vn.techmaster.singleton_prototype.service.PrototypeService@e50a6f6
Là 2 đối tượng có hai thông tin khác nhau.

Prototype được spring tạo ra thế nào?

Để trở thành chuyên gia về Spring chúng ta sẽ cần biết sâu hơn một chút về cách mà Spring hoạt đông, và cụ thể ở đây là việc nó tạo ra Prototype thế nào. Chúng ta sẽ thay đổi lớp PrototypeService thành như sau:

package vn.techmaster.singleton_prototype.service;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope("prototype")
public class PrototypeService {

    public PrototypeService() {
        System.out.println("init");
    }
}

Chúng ta đã bổ sung hàm tạo, bây giờ hãy đặt breakpoint vào dòng thứ 11 và chạy SingletonPrototypeStartup ở chế độ debug.

Chúng ta sẽ thấy ở màn hình debug như sau:

Chúng ta có thể thấy rằng bên trong spring sử dụng:

  1. Factory design pattern: Để đóng gói việc khởi tạo đối tượng.
  2. Strategy design pattern: Để lựa chọn đối tượng khởi tạo ra prototype.
  3. Reflection để khởi tạo prototype.

Khi nào nên dùng Singleton, khi nào nên dùng Prototype?

Để biết khi nào nên sử dụng singleton, khi nào nên sử dụng prototype với spring, chúng ta sẽ thực hiện việc đo hiệu suất thì sẽ rõ nhất. Đầu tiên chúng ta sẽ tạo ra một lớp Data như sau:

package vn.techmaster.singleton_prototype.data;

public class Data {
}

Chúng ta sẽ bổ sung thêm hàm execute cho hai lớp SingletonService và PrototypeService như sau:

package vn.techmaster.singleton_prototype.service;

import org.springframework.stereotype.Component;
import vn.techmaster.singleton_prototype.data.Data;

@Component
public class SingletonService {

    public void execute(Data data) {}
}
package vn.techmaster.singleton_prototype.service;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import vn.techmaster.singleton_prototype.data.Data;

@Component
@Scope("prototype")
public class PrototypeService {

    public void execute(Data data) {}
}

Nếu như bình thường chúng ta không dùng spring, mà mỗi lần chúng ta khởi tạo đều dùng toán tử new thì sự khác biệt không quá đáng kể:

package vn.techmaster.singleton_prototype;

import vn.techmaster.singleton_prototype.data.Data;
import vn.techmaster.singleton_prototype.service.PrototypeService;
import vn.techmaster.singleton_prototype.service.SingletonService;

public class NoSpringSingletonPrototypeStartup {

    public static void main(String[] args) {
        SingletonService singletonService = new SingletonService();
        long start = System.currentTimeMillis();
        for (int i = 0; i < 1_000_000; ++i) {
            singletonService.execute(new Data());
        }
        long end = System.currentTimeMillis();
        System.out.println("singleton elapsed time: " + (end - start));

        start = System.currentTimeMillis();
        for (int i = 0; i < 1_000_000; ++i) {
            PrototypeService prototypeService = new PrototypeService();
            prototypeService.execute(new Data());
        }
        end = System.currentTimeMillis();
        System.out.println("prototype elapsed time: " + (end - start));
    }
}

Kết quả chúng nhận được sẽ là:
singleton elapsed time: 9
prototype elapsed time: 6
Tuy nhiên khi chúng ta sử dụng spring, sự khác biệt trở nên khá rõ ràng:

package vn.techmaster.singleton_prototype;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import vn.techmaster.singleton_prototype.data.Data;
import vn.techmaster.singleton_prototype.service.PrototypeService;
import vn.techmaster.singleton_prototype.service.SingletonService;

public class SingletonPrototypeStartup {

    public static void main(String[] args) {
        ApplicationContext applicationContext =
            new AnnotationConfigApplicationContext(
                "vn.techmaster.singleton_prototype"
            );
        long start = System.currentTimeMillis();
        for (int i = 0; i < 1_000_000; ++i) {
            SingletonService singletonService = applicationContext
                .getBean(SingletonService.class);
            singletonService.execute(new Data());
        }
        long end = System.currentTimeMillis();
        System.out.println("singleton elapsed time: " + (end - start));

        start = System.currentTimeMillis();
        for (int i = 0; i < 1_000_000; ++i) {
            PrototypeService prototypeService = applicationContext
                .getBean(PrototypeService.class);
            prototypeService.execute(new Data());
        }
        end = System.currentTimeMillis();
        System.out.println("prototype elapsed time: " + (end - start));
    }
}

Kết quả chúng ta nhận được sẽ là:
singleton elapsed time: 121
prototype elapsed time: 1575
Nguyên nhân đến từ việc sử dụng java reflection cộng thêm các hành động quản lý các thuộc tính bên trong của spring dẫn đến việc sử dụng Prototype tốn kém tài nguyên và chậm hơn, chúng ta có thể quan sát thông qua visual vm cho trực quan:

Sách tham khảo

Làm chủ các mẫu thiết kế kinh điển trong lập trình Nhập mã Tech10 để giảm giá 10% các bạn nhé.

Tổng kết

Như vậy là chúng ta đã cùng nhau:

  1. Tìm hiểu về Singleton.
  2. Tìm hiểu về prototype.
  3. Tìm hiểu về cách mà spring tạo ra prototype.
  4. Thấy rằng nên sử dụng singleton hơn là prototype đối với spring.

Cám ơn bạn đã quan tâm đến bài viết singleton prototype. Để nhận được thêm các kiến thức bổ ích bạn có thể:

  1. Đọc các bài viết của TechMaster trên facebook: https://www.facebook.com/techmastervn
  2. Xem các video của TechMaster qua Youtube: https://www.youtube.com/@TechMasterVietnam nếu bạn thấy video/bài viết hay bạn có thể theo dõi kênh của TechMaster để nhận được thông báo về các video mới nhất nhé.
  3. Chat với techmaster qua Discord: https://discord.gg/yQjRTFXb7a