Spring Framework, một nền tảng hàng đầu trong phát triển ứng dụng Java, là ví dụ điển hình về việc áp dụng hiệu quả các mẫu thiết kế (design patterns). Việc hiểu cách Spring sử dụng các mẫu này không chỉ giúp chúng ta làm việc hiệu quả hơn với framework mà còn cung cấp những kiến thức quý giá về thiết kế phần mềm.
1. Nhà Máy Đơn Giản (Simple Factory)
Mẫu Nhà Máy Đơn Giản, còn được gọi là phương thức nhà máy tĩnh (Static Factory Method), không nằm trong 23 mẫu của Gang of Four (GoF). Bản chất của nó là một lớp nhà máy sẽ tạo ra các đối tượng sản phẩm cụ thể dựa trên một tham số đầu vào.
Trong Spring, BeanFactory hoạt động như một nhà máy đơn giản. Nó chịu trách nhiệm khởi tạo và cung cấp các bean (đối tượng) dựa trên định danh (id) được yêu cầu. Quá trình tạo có thể diễn ra ngay khi truy cập hoặc được tạo sẵn tùy thuộc vào cấu hình.
Ví dụ cấu hình XML cho các bean mà BeanFactory sẽ quản lý:
<beans>
<bean id="greetingService" class="dev.example.GreetingService">
<constructor-arg value="Chào mừng bạn đến với ứng dụng!" />
</bean>
<bean id="logService" class="dev.example.LoggingService" scope="prototype">
<constructor-arg value="Ghi nhật ký hoạt động..." />
</bean>
</beans>
2. Phương Pháp Nhà Máy (Factory Method)
Thay vì ứng dụng trực tiếp tạo đối tượng bằng new, mẫu Phương Pháp Nhà Máy giao trách nhiệm tạo và khởi tạo đối tượng cho một đối tượng nhà máy. Trong Spring, chúng ta có thể cấu hình các "factory bean" hoặc chỉ định một phương thức tĩnh trong một lớp để tạo bean.
Ví dụ về cách sử dụng một phương thức tĩnh để tạo bean trong Spring:
Đầu tiên, một lớp chứa phương thức tĩnh để tạo đối tượng:
package dev.example;
import java.util.UUID;
public class DataGeneratorFactory {
public static String generateUniqueId() {
return UUID.randomUUID().toString();
}
}
Sau đó, cấu hình bean trong Spring bằng cách sử dụng thuộc tính factory-method:
<beans>
<bean id="uniqueIdBean" class="dev.example.DataGeneratorFactory" factory-method="generateUniqueId" scope="prototype"/>
</beans>
Khi ứng dụng yêu cầu bean có id="uniqueIdBean", Spring sẽ gọi phương thức tĩnh generateUniqueId() của lớp DataGeneratorFactory để tạo và trả về một chuỗi ID duy nhất.
Một ứng dụng khách sử dụng bean này:
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
public class App {
public static void main(String[] args) {
XmlBeanFactory factory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
System.out.println("ID duy nhất từ factory: " + factory.getBean("uniqueIdBean"));
System.out.println("ID duy nhất khác từ factory: " + factory.getBean("uniqueIdBean"));
}
}
3. Đơn Thể (Singleton)
Mẫu Đơn Thể đảm bảo rằng một lớp chỉ có duy nhất một thể hiện và cung cấp một điểm truy cập toàn cục cho thể hiện đó. Spring áp dụng mẫu này bằng cách mặc định tạo các bean dưới dạng đơn thể. Khi bạn định nghĩa một bean trong cấu hình Spring, container sẽ chỉ tạo một thể hiện duy nhất của bean đó và tái sử dụng nó cho mọi yêu cầu.
Trong Spring, mặc dù bạn có thể cấu hình để tạo nhiều thể hiện (scope="prototype"), nhưng mặc định, tất cả các bean đều là singleton (scope="singleton" hoặc không định nghĩa). Spring kiểm soát vòng đời của thể hiện này, cung cấp một điểm truy cập toàn cục thông qua ApplicationContext hoặc BeanFactory.
4. Bộ Điều Hợp (Adapter)
Mẫu Bộ Điều Hợp cho phép các giao diện không tương thích làm việc cùng nhau. Trong Spring AOP (Lập Trình Hướng Khía Cạnh), mẫu Adapter được sử dụng để chuyển đổi các loại Advice (lời khuyên) khác nhau thành một định dạng chung mà hệ thống AOP có thể xử lý, thường là MethodInterceptor.
Ví dụ, Spring cung cấp giao diện AdviceAdapter:
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.springframework.aop.Advisor;
public interface AdviceAdapter {
boolean supportsAdvice(Advice advice);
MethodInterceptor getInterceptor(Advisor advisor);
}
Và một triển khai cụ thể như MethodBeforeAdviceAdapter chuyển đổi MethodBeforeAdvice thành MethodInterceptor:
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.springframework.aop.Advisor;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor; // Assuming this class exists
class BeforeAdviceAdapter implements AdviceAdapter {
@Override
public boolean supportsAdvice(Advice advice) {
return (advice instanceof MethodBeforeAdvice);
}
@Override
public MethodInterceptor getInterceptor(Advisor advisor) {
MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice();
return new MethodBeforeAdviceInterceptor(advice); // Chuyển đổi Advice thành Interceptor
}
}
Cách tiếp cận này cho phép Spring xử lý nhiều loại lời khuyên khác nhau một cách thống nhất.
5. Trình Trang Trí (Decorator) / Bao Bọc (Wrapper)
Mẫu Decorator cho phép thêm chức năng mới vào một đối tượng hiện có mà không làm thay đổi cấu trúc của nó. Trong Spring, chúng ta thường thấy các lớp có tên chứa "Wrapper" hoặc "Decorator" được sử dụng để mở rộng hoặc thay đổi hành vi của một đối tượng một cách linh hoạt.
Một trường hợp điển hình là khi một ứng dụng cần kết nối với nhiều cơ sở dữ liệu khác nhau. Thay vì thay đổi SessionFactory hoặc DAO mỗi khi chuyển đổi nguồn dữ liệu, một DataSourceWrapper hoặc DataSourceDecorator có thể được sử dụng để động chuyển đổi giữa các DataSource cấu hình trong Spring ApplicationContext, tùy thuộc vào yêu cầu của người dùng hoặc ngữ cảnh hiện tại. Điều này cho phép mở rộng chức năng (thay đổi nguồn dữ liệu) mà không sửa đổi trực tiếp các lớp cốt lõi.
6. Ủy Quyền (Proxy)
Mẫu Proxy cung cấp một đối tượng đại diện (proxy) để kiểm soát quyền truy cập vào đối tượng thực. Trong Spring, mẫu Proxy là nền tảng của AOP. Khi bạn định nghĩa một khía cạnh (aspect), Spring sẽ tạo một đối tượng proxy bao bọc đối tượng mục tiêu của bạn. Mọi lời gọi phương thức đến đối tượng mục tiêu đều thông qua đối tượng proxy, cho phép proxy chèn thêm logic (advice) trước, sau hoặc xung quanh phương thức gốc.
Spring sử dụng hai loại proxy chính:
- JDK Dynamic Proxy: Sử dụng cho các đối tượng triển khai ít nhất một giao diện.
- CGLib Proxy: Sử dụng cho các lớp không triển khai giao diện, hoặc khi cấu hình rõ ràng yêu cầu. Các lớp như
JdkDynamicAopProxyvàCglib2AopProxylà các triển khai cụ thể trong Spring AOP.
7. Quan Sát Viên (Observer)
Mẫu Observer định nghĩa một mối quan hệ phụ thuộc một-nhiều giữa các đối tượng: khi một đối tượng (chủ thể) thay đổi trạng thái, tất cả các đối tượng phụ thuộc (quan sát viên) sẽ được thông báo và tự động cập nhật. Trong Spring, mẫu này được triển khai thông qua cơ chế sự kiện.
ApplicationEvent và ApplicationListener là các thành phần chính:
ApplicationEvent: Là sự kiện được phát ra khi có thay đổi trạng thái.ApplicationListener: Là giao diện mà các quan sát viên triển khai để lắng nghe và phản ứng với cácApplicationEventcụ thể.
Spring ApplicationContext đóng vai trò là chủ thể, phát ra các sự kiện như ContextRefreshedEvent, ContextStartedEvent, v.v., và các ApplicationListener đã đăng ký sẽ nhận được thông báo về các sự kiện này.
8. Chiến Lược (Strategy)
Mẫu Strategy định nghĩa một họ các thuật toán, đóng gói chúng thành các lớp riêng biệt và làm cho chúng có thể hoán đổi cho nhau. Mẫu này cho phép thuật toán thay đổi độc lập với các đối tượng sử dụng nó. Spring tận dụng mẫu Strategy để cung cấp sự linh hoạt trong nhiều khía cạnh của framework.
Ví dụ, xem xét một hệ thống xử lý thanh toán:
Giao diện chiến lược:
public interface PaymentStrategy {
void processPayment(double amount);
}
Các triển khai chiến lược cụ thể:
public class CreditCardPayment implements PaymentStrategy {
@Override
public void processPayment(double amount) {
System.out.println("Xử lý thanh toán thẻ tín dụng cho số tiền: " + amount);
}
}
public class PayPalPayment implements PaymentStrategy {
@Override
public void processPayment(double amount) {
System.out.println("Xử lý thanh toán PayPal cho số tiền: " + amount);
}
}
Lớp ngữ cảnh sử dụng chiến lược (có thể được Spring quản lý):
public class PaymentService {
private PaymentStrategy paymentStrategy;
// Spring có thể inject PaymentStrategy thông qua constructor hoặc setter
public PaymentService(PaymentStrategy paymentStrategy) {
this.paymentStrategy = paymentStrategy;
}
public void setPaymentStrategy(PaymentStrategy paymentStrategy) {
this.paymentStrategy = paymentStrategy;
}
public void executePayment(double amount) {
if (paymentStrategy == null) {
throw new IllegalStateException("Chưa thiết lập chiến lược thanh toán.");
}
paymentStrategy.processPayment(amount);
}
}
Spring cho phép dễ dàng cấu hình và inject các triển khai PaymentStrategy khác nhau vào PaymentService, thay đổi hành vi thanh toán mà không cần sửa đổi lớp PaymentService.
9. Phương Pháp Mẫu (Template Method)
Mẫu Template Method định nghĩa cấu trúc xương sống của một thuật toán trong một phương thức, nhưng giao lại một số bước cụ thể cho các lớp con hoặc đối tượng callback. Điều này cho phép các lớp con tùy chỉnh các bước của thuật toán mà không làm thay đổi cấu trúc tổng thể.
Spring sử dụng mẫu này rộng rãi, đặc biệt là trong các lớp như JdbcTemplate, RestTemplate, JmsTemplate. Thay vì yêu cầu người dùng kế thừa lớp Template, Spring sử dụng các đối tượng callback để tùy chỉnh các bước biến đổi của thuật toán.
Ví dụ với JdbcTemplate:
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
public class ProductDao {
private JdbcTemplate jdbcTemplate;
public ProductDao(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public long addProduct(String name, double price) {
final String SQL = "INSERT INTO products (name, price) VALUES (?, ?)";
KeyHolder keyHolder = new GeneratedKeyHolder();
// JdbcTemplate.update() là phương thức mẫu, PreparedStatementCreator là callback
jdbcTemplate.update(new PreparedStatementCreator() {
@Override
public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
PreparedStatement ps = con.prepareStatement(SQL, Statement.RETURN_GENERATED_KEYS);
ps.setString(1, name);
ps.setDouble(2, price);
return ps;
}
}, keyHolder);
return keyHolder.getKey().longValue();
}
}
Trong ví dụ trên, phương thức jdbcTemplate.update() là "phương thức mẫu" cung cấp luồng xử lý cơ bản (quản lý kết nối, giao dịch, xử lý ngoại lệ). Tuy nhiên, bước cụ thể tạo PreparedStatement được ủy quyền cho đối tượng PreparedStatementCreator, cho phép người dùng định nghĩa câu truy vấn và các tham số riêng mà không cần kế thừa JdbcTemplate.