Lời nói đầu
Trước khi đến với Dynamic Proxy, ta sẽ tìm hiểu Proxy Pattern là gì ?
I. Java Proxy Pattern
Để nói về proxy pattern, trước hết ta cần hiểu khái niệm proxy (đối tượng đại diện/trung gian). Trong hình minh họa bên dưới, đối tượng trung gian (hoặc người đại diện) chính là thứ mà chúng ta gọi là proxy.
(Lưu ý: Hình ảnh có thể minh họa kịch bản người thuê nhà → người môi giới/đại diện → chủ nhà.)
Trong Java, Proxy Pattern là một mẫu thiết kế cấu trúc (structural design pattern), nó cung cấp một đối tượng thay thế hoặc đối tượng giữ chỗ (surrogate/placeholder) để kiểm soát việc truy cập tới một đối tượng khác.
!image.png
Giải thích:
- Người thuê nhà (tenant) chính là bạn – người sử dụng.
- Người môi giới (agent) đóng vai trò là proxy (đối tượng trung gian).
- Người môi giới thay mặt bạn làm việc với chủ nhà (landlord – đối tượng thật) và giúp bạn tiếp cận căn hộ cho thuê.
- Mô hình này phản ánh Proxy Design Pattern trong Java, trong đó đối tượng proxy kiểm soát quyền truy cập tới đối tượng thật.
II. Static Proxy (Proxy tĩnh)
2.1. Hiểu Static Proxy một cách đơn giản
Hãy dùng ví dụ:
Người thuê nhà muốn thuê một căn hộ thông qua người môi giới, và người môi giới sẽ làm việc trực tiếp với chủ nhà.
Để hiện thực ví dụ này trong Java, chúng ta cần 4 file:
- Rental interface – đại diện cho khái niệm trừu tượng của việc thuê nhà
- Landlord class – chủ nhà, đối tượng thật
- Agent class (Proxy) – người môi giới, đối tượng proxy
- Tenant class (Client) – người thuê nhà, phía client
Một số người có thể thắc mắc vì sao rental property lại được định nghĩa dưới dạng interface.
Điều này phù hợp với các nguyên lý thiết kế của Java – có thể hiểu tương tự như pure virtual function trong C++.
Bạn cũng có thể xem video của Kuang Shen để có giải thích sâu hơn về static proxy.
Rent.java – Interface Rent
1 | package src.JdkProxy.StaticProxy; |
- Interface này đại diện cho khái niệm thuê nhà (rental property).
- Nó định nghĩa method
rent(), mô tả hành vi thuê mà các class triển khai interface này phải thực hiện.
Host.java - The Landlord Class
1 | package src.JdkProxy.StaticProxy; |
- Class này đại diện cho landlord (chủ nhà).
- Landlord implements interface
Rentvà cung cấp implementation thực tế cho methodrent(). - Nói cách khác, đây là real object trong Proxy Pattern.
Proxy.java – The Agent (Proxy) Class
1 | package src.JdkProxy.StaticProxy; |
- Class này đại diện cho agent (proxy object).
- Proxy không extends
Host. - Thay vào đó, nó giữ một reference private tới object
Host - Khi client gọi
proxy.rent(), proxy sẽ delegate lời gọi này sanghost.rent()
- Proxy không extends
- Đây chính là bản chất của Static Proxy:
👉 logic được chuyển tiếp (delegation) sang real object.
Client.java – The Tenant (Client)
1 | package src.JdkProxy.StaticProxy; |
Đây là startup class, đóng vai trò là client (tenant).
- Client không làm việc trực tiếp với
Host - Thay vào đó, client thông qua Proxy
- Proxy chịu trách nhiệm gọi method của real object
Kết luận
Với setup này, logic “tenant xem nhà thông qua agent” đã được hoàn thiện:
- Client (tenant) → gọi Proxy (agent)
- Proxy → kiểm soát và chuyển tiếp request
- Host (landlord) → thực hiện logic thực tế
Đây chính là Static Proxy Pattern trong Java, nơi:
- Proxy và Real Object được xác định tại compile time
- Proxy có thể dễ dàng mở rộng logic (logging, security, validation, transaction, …) mà không sửa code của Host
!image.png
Code Comment:
1 | // 启动器 |
👉 Comment này có nghĩa là class khởi động chương trình, nơi chương trình bắt đầu chạy (entry point).
Console Output:
1 | 房东要出租房子! |
👉 Đây là output được in ra trên console khi method rent() của Host được gọi thông qua Proxy.
Nhưng quy trình thuê nhà chỉ kết thúc như vậy thôi sao?
👉 Tất nhiên là chưa.
Bởi vì agent (proxy) vẫn cần phải thu phí môi giới (agency fee).
Ngoài ra, có một số hành động mà:
- agent có thể làm
- nhưng landlord không làm trực tiếp
Ví dụ:
- dẫn khách đi xem nhà (showing the house)
- thu phí môi giới (collecting agency fees)
- xử lý giấy tờ, hợp đồng, v.v.
Những hành vi này không thuộc trách nhiệm của landlord, nên không nên đặt trong class Host.
Giải pháp
👉 Chúng ta sẽ implement các hành vi bổ sung này trong Proxy.java.
Điều này thể hiện đúng tinh thần của Proxy Pattern:
- Proxy mở rộng hành vi trước / sau khi gọi method của real object
- Không cần sửa code của
Host - Giữ cho trách nhiệm của mỗi class rõ ràng (Single Responsibility Principle)
Improved Proxy.java
1 | package src.JdkProxy.StaticProxy; |
!image.png
Console Output (Translated):
1 | The landlord wants to rent out the house! |
👉 Output này cho thấy proxy không chỉ gọi method của real object (Host), mà còn thực hiện thêm business logic như:
- ký hợp đồng thuê nhà (signing the rental contract)
- thu phí môi giới (collecting agency fee)
Advantages (Ưu điểm):
- Proxy giúp real role (real class) trở nên tập trung và “pure” hơn, không còn phải xử lý các common concerns.
- Common business logic được đặt trong proxy, từ đó đạt được separation of concerns.
- Khi cần mở rộng common business logic, việc chỉnh sửa sẽ tập trung, dễ quản lý và dễ bảo trì hơn.
Disadvantages (Nhược điểm):
- Một real class sẽ tương ứng với một proxy class, dẫn đến:
- số lượng class tăng gấp đôi
- code nhiều hơn
- giảm development efficiency
👉 Chính vì chúng ta muốn giữ ưu điểm của static proxy nhưng tránh nhược điểm của nó, nên Dynamic Proxy ra đời.
2.2. Hiểu sâu hơn về Static Proxy
Hãy xét một ví dụ rất phổ biến trong thực tế:
👉 CRUD operations (Create, Read, Update, Delete).
UserService.java- Interface
1 | package src.JdkProxy.MoreStaticProxy; |
Interface này định nghĩa 4 abstract methods, đại diện cho các basic user operations trong hệ thống.
add()→ Createdelete()→ Deleteupdate()→ Updatequery()→ Read
Đây là một ví dụ rất điển hình để thấy rõ:
- Khi số lượng method tăng
- Static proxy sẽ phình to rất nhanh
- Và việc maintain proxy trở nên kém hiệu quả
UserServiceImpl.java- The Real Implementation
1 | package src.JdkProxy.MoreStaticProxy; |
- Đây là real object, class trực tiếp thực hiện các CRUD actions.
New Requirement
Bây giờ, bạn muốn thêm logging functionality.
Vậy nên implement như thế nào?
Approach 1
👉 Thêm code logging trực tiếp vào implementation class
- Cách này rườm rà
- Làm bẩn business logic
- Vi phạm separation of concerns
Approach 2
👉 Sử dụng proxy
- Đây là cách tốt nhất để thêm functionality
- Không cần sửa code gốc
- Giữ cho original business logic được clean
UserServiceProxy.java: The Static Proxy with Logging
1 | package src.JdkProxy.MoreStaticProxy; |
Class này là static proxy, implement interface UserService.
- Proxy giữ reference tới
UserServiceImpl(real object) - Trước khi gọi mỗi business method, proxy sẽ gọi method
log() - Sau đó mới delegate lời gọi sang real object
Client.java: The Launcher
1 | package src.JdkProxy.MoreStaticProxy; |
Đây là launcher (entry point) của chương trình.
- Client tạo real object
UserServiceImpl - Client tạo proxy object
- Client inject real object vào proxy
- Client chỉ làm việc với proxy, không gọi trực tiếp real object
Result
👉 Không cần chỉnh sửa UserServiceImpl, bạn vẫn:
- Thêm được logging functionality
- Áp dụng logging cho các business methods
Cách làm này:
- Giữ cho original code clean
- Tuân thủ Open-Closed Principle
(open for extension, closed for modification) - Centralize các cross-cutting concerns như logging
Nhưng vấn đề vẫn còn…
- Mỗi interface mới → cần một proxy mới
- Mỗi method mới → phải viết lại logic proxy
- Code lặp lại nhiều, khó scale
👉 Đây chính là lý do Dynamic Proxy ra đời.
!image.png
Console Output (Translated):
1 | [Debug] Used method: add |
👉 Output này cho thấy:
- proxy đã log method
add - Sau đó real object thực hiện business logic tương ứng