+3

Bí mật của "Thư ký" Proxy: Làm chủ Spring AOP và tránh bẫy gọi hàm nội bộ (Phần 2)

Chào mừng bạn trở lại với thế giới phía sau bức màn sân khấu của Spring Boot!

Phần 1, chúng ta đã bóc trần sự thật đằng sau "phép thuật" của các annotation: đó chính là cô Thư ký mẫn cán mang tên Proxy. Chúng ta cũng đã rèn luyện được nhãn quan sắc bén để né tránh cạm bẫy "Giám đốc tự đóng cửa làm việc" (Self-invocation) khét tiếng.

Nhưng hãy thử tưởng tượng một chút. Biết cách Thư ký chặn cửa là một chuyện, nhưng làm sao để giao việc cho cô ấy? Nếu mỗi lần muốn Thư ký in ra một dòng log hay bấm giờ một hàm, bạn lại phải tự tay viết một lớp kế thừa dài ngoằng, gọi Enhancer rồi thiết lập callback thủ công như cách CGLIB vận hành thô... thì mã nguồn của bạn sẽ nhanh chóng trở thành một mớ bòng bong rối rắm.

May mắn thay, các kỹ sư của Spring không bắt chúng ta phải làm việc "bằng tay" như vậy. Họ cung cấp một hệ thống quản lý cực kỳ thanh lịch để bạn dễ dàng lên danh sách công việc cho Thư ký. Hệ thống đó mang tên AOP (Aspect-Oriented Programming - Lập trình hướng khía cạnh).

Trong Phần 2 này, chúng ta sẽ không học lý thuyết suông. Chúng ta sẽ cùng nhau "phiên dịch" các thuật ngữ học thuật khô khan của AOP (như Aspect, Pointcut, Advice) sang ngôn ngữ văn phòng đời thường. Hơn thế nữa, bạn sẽ được tự tay chế tạo một "phép thuật" của riêng mình: viết một Annotation từ con số không để bấm giờ bất kỳ hàm nào trong hệ thống!

Cùng bắt đầu nhé!

VII. AOP là gì?

AOP (Aspect-Oriented Programming - Lập trình hướng khía cạnh) là một phương pháp lập trình bổ trợ, sinh ra để giải quyết "một điểm yếu chí mạng" của lập trình hướng đối tượng truyền thống (OOP).

Điểm yếu chí mạng đó là gì? OOP cực kỳ xuất sắc trong việc chia nhỏ hệ thống theo chiều dọc thành các đối tượng độc lập (như User, Order, Product). Theo nguyên tắc, mỗi đối tượng chỉ nên tự quản lý chuyên môn của riêng mình.

Tuy nhiên, trong một dự án thực tế, luôn tồn tại những đoạn logic không thuộc về riêng một đối tượng nào cả, mà nó "cắt ngang" qua toàn bộ hệ thống. Ví dụ: Ghi log lịch sử, kiểm tra quyền bảo mật, hay mở/đóng giao dịch cơ sở dữ liệu. Vì OOP không có cơ chế xử lý tốt những "logic cắt ngang" này, lập trình viên thường phải copy-paste chúng rải rác ở hàng tá class khác nhau. Điều này khiến mã nguồn trở nên rối rắm, khó bảo trì và vi phạm nghiêm trọng nguyên tắc Code Sạch (Clean Code).

Và đó là lúc AOP xuất hiện như một vị cứu tinh. Khái niệm "hướng khía cạnh" (Aspect) ở đây chính là việc bóc tách toàn bộ những logic cắt ngang trùng lặp đó ra khỏi luồng code chính, gom chúng lại vào một nơi duy nhất, và tự động "tiêm" vào bất kỳ đâu khi ứng dụng chạy.

Để hiểu rõ sự tách biệt này diễn ra kỳ diệu như thế nào trên thực tế, chúng ta hãy đặt logic code trở lại với hình tượng Giám đốc (Logic nghiệp vụ cốt lõi) và Thư ký (Logic phụ trợ) quen thuộc ở Phần 1.

Giả sử bạn viết một hàm chuyenTien(). Công việc chính của Giám đốc (logic kinh doanh cốt lõi) chỉ là: Trừ tiền người gửi và cộng tiền người nhận. Tuy nhiên, nếu không có AOP, đích thân Giám đốc sẽ phải tự tay làm toàn bộ các thủ tục bao quanh:

  1. 🔒 Bảo mật: Kiểm tra xem người dùng có đủ quyền không.

  2. 📝 Ghi log: Bấm đồng hồ, ghi chép thời gian bắt đầu giao dịch.

  3. 🗄️ Database: Mở kết nối giao dịch (Transaction) an toàn.

  4. 💰 Nghiệp vụ chính: Thực hiện trừ tiền và cộng tiền.

  5. 📝 Ghi log: Ghi chép lại kết quả giao dịch.

Hậu quả nếu Giám đốc phải ôm đồm (Không có AOP):

  • Mã nguồn rối rắm (Code Tangling): Hàm chuyenTien() dài 100 dòng thì có tới 80 dòng là code kiểm tra quyền, ghi log, kết nối database. Phần logic cốt lõi bị chìm nghỉm, khiến code cực kỳ khó đọc và dễ sinh lỗi.

  • Mã nguồn phân tán (Code Scattering): Hệ thống của bạn có 50 hàm khác như rutTien(), napTien(),... Các đoạn code "Ghi log" hay "Mở database" bị copy-paste lặp đi lặp lại ở cả 50 nơi. Nếu sau này sếp yêu cầu đổi định dạng ghi log, bạn phải mở 50 file ra để sửa thủ công.

Sự giải thoát mang tên AOP:

AOP cho phép bạn "bốc" toàn bộ các thao tác phụ trợ (trong kỹ thuật gọi là các Cross-cutting concerns) ra khỏi hàm gốc. Bạn đóng gói chúng vào những cuốn "sổ tay" riêng và giao toàn quyền cho cô Thư ký (Proxy) đứng ngoài cửa tự động thực hiện.

  • Giám đốc chỉ tập trung đúng vào 1 việc: kinh doanh sinh lời.

  • Code sạch sẽ, nguyên tắc (Chỉ làm một việc - Single Responsibility) được đảm bảo tuyệt đối.

Để cô Thư ký làm việc hoàn toàn tự động, AOP sử dụng một bộ "mật ngữ" riêng để giao việc. Dựa theo logic thông thường, nếu bạn là người viết nội quy cho cô Thư ký, bạn nghĩ mình cần cung cấp cho cô ấy những thông tin gì để cô ấy biết chính xác hành động nào cần làm, làm ở hàm nào, và làm vào thời điểm nào?

VIII. Giải mã "mật ngữ" AOP qua lăng kính văn phòng.

Để cô Thư ký (Proxy) làm việc hoàn toàn tự động, AOP sử dụng một bộ thuật ngữ riêng để giao việc. Lần đầu đọc tài liệu Spring, lập trình viên thường bị "ngợp" bởi mớ từ vựng học thuật này. Nhưng khi ghép vào câu chuyện của chúng ta, mọi thứ sẽ cực kỳ dễ hiểu:

  • 🎯 Target (Giám đốc): Đây chính là đối tượng (class) chứa logic nghiệp vụ cốt lõi của bạn (ví dụ: UserService, OrderService).

  • ⚡ Join Point (Điểm kết nối): Bất kỳ hành động (hàm) nào của Giám đốc mà Thư ký có khả năng đứng ngoài cửa chặn lại. Trong Spring AOP, Join Point luôn luôn là việc thực thi một hàm (method execution).

  • ✂️ Pointcut (Bộ lọc): Quy tắc chặn cửa của Thư ký. Khách đến đông nhưng không phải ai cũng bị chặn. Pointcut chính là danh sách ưu tiên. Ví dụ: "Chỉ chặn những hàm có dán nhãn @DoThoiGian" hoặc "Chỉ chặn những hàm có tên bắt đầu bằng find".

  • 🛠️ Advice (Hành động): Định nghĩa chính xác Thư ký phải làm gì và làm vào lúc nào. Có các thời điểm chính:

    • @Before: Làm trước khi sếp làm.

    • @After: Làm sau khi sếp làm.

    • @Around: Bao trọn cả trước và sau. Đây là Advice quyền lực nhất vì nó nắm giữ lệnh proceed() (quyết định lúc nào sếp được làm việc, hoặc có cho sếp làm hay không).

  • 📖 Aspect (Sổ tay hành động): Là một class gom chung Pointcut (chặn ai) và Advice (làm gì) lại với nhau. Cứ đưa cuốn sổ này cho Spring, framework sẽ tự động chỉ việc cho Thư ký.

Để mình xem bạn đã nắm bắt các mảnh ghép này thế nào nhé.

Giả sử bạn yêu cầu Spring AOP làm một việc: "Hãy tìm tất cả các hàm có tên bắt đầu bằng chữ find (như findUser, findOrder), và in ra màn hình chữ 'Đang tìm kiếm dữ liệu...' TRƯỚC KHI các hàm đó chạy."

Trong yêu cầu trên, phần nào tương ứng với Pointcut, và phần nào tương ứng với Advice?

Với ví dụ vừa rồi chùng ta sẽ cói sự phân vai như sau:

  • ✂️ Pointcut: Chính là bộ lọc "Tất cả các hàm có tên bắt đầu bằng chữ find". (Trong Spring, nó thường được mô tả bằng một biểu thức, ví dụ: execution(* find*(..))).

  • 🛠️ Advice: Bao gồm thời điểm là "TRƯỚC KHI" (tương ứng với annotation @Before) và hành động là lệnh "in ra màn hình chữ 'Đang tìm kiếm dữ liệu...'".

Sự kết hợp hoàn hảo này giúp Spring (hay cô Thư ký) biết chính xác cần làm gì (Advice) và làm ở đâu (Pointcut) mà không cần phải đi sửa code của từng hàm một.

Vậy là chúng ta đã chinh phục xong cả phần "cứng" (Cơ chế Proxy bên dưới) lẫn phần "mềm" (Thuật ngữ AOP bên trên), cũng không có gì khó đúng khum.

Bây giờ, để những lý thuyết này thực sự trở thành kỹ năng của bạn, bước tiếp theo tốt nhất là ráp chúng lại thành code thực tế. Bạn đã sẵn sàng để chúng ta cùng tự tay tạo ra một Annotation tùy chỉnh trong Spring Boot (ví dụ: tạo một annotation @DoThoiGian để đo xem một hàm chạy mất bao nhiêu mili-giây) chưa?

IX. Thực hành: Tự chế tạo "Phép thuật" @DoThoiGian

Chúng ta sẽ cùng nhau chế tạo một "phép thuật" riêng cho Spring Boot: tạo một annotation tên là @DoThoiGian. Mục tiêu là khi bạn gắn @DoThoiGian lên bất kỳ hàm nào, hệ thống (Proxy) sẽ tự động bấm giờ xem hàm đó chạy mất bao nhiêu mili-giây.

Để làm được điều này, chúng ta cần đi qua 2 bước:

  1. Tạo ra chiếc nhãn dán (@DoThoiGian).

  2. Viết Aspect (Sổ tay hành động) để dặn hệ thống: "Cứ thấy hàm nào dán nhãn này thì hãy chèn logic bấm giờ vào".

Trong Java, để tạo một class, chúng ta dùng từ khóa class. Để tạo một interface, chúng ta dùng interface. Theo bạn, chúng ta dùng từ khóa nào để khai báo một Annotation mới?

Đó chính là từ khóa @interface

Để tạo ra chiếc nhãn dán @DoThoiGian, chúng ta chỉ cần một đoạn code rất ngắn. Tuy nhiên, chúng ta phải nói cho Java biết hai quy tắc quan trọng về chiếc nhãn này:

  • Dán ở đâu? (@Target): Chúng ta chỉ muốn đo thời gian của các hàm, nên sẽ thiết lập để nhãn này chỉ được dán lên hàm (Method).

  • Tồn tại bao lâu? (@Retention): Chiếc nhãn này phải tồn tại trong suốt lúc chương trình đang chạy (Runtime) để cô Thư ký (Spring Proxy) còn "nhìn thấy" nó.

Đoạn code tạo Annotation sẽ trông như thế này:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD) // Chỉ được dán lên hàm
@Retention(RetentionPolicy.RUNTIME) // Tồn tại trong lúc ứng dụng chạy
public @interface DoThoiGian {
    // Chúng ta để trống vì nhãn này chỉ dùng để đánh dấu
}

Vậy là xong Bước 1! Bây giờ bạn đã có thể đem @DoThoiGian đi dán lên bất kỳ hàm nào bạn muốn.

Chúng ta chuyển sang Bước 2: Viết Aspect (Sổ tay hành động).

Trong cuốn sổ tay này, chúng ta cần bấm đồng hồ (lấy thời gian hiện tại) ⏱️, cho phép hàm gốc chạy, sau đó bấm đồng hồ lần nữa để tính thời gian trôi qua, và cuối cùng in kết quả ra màn hình.

Dựa vào quy trình bấm giờ bao gồm cả hành động xảy ra trước và sau khi hàm gốc chạy như vậy, theo bạn, chúng ta nên sử dụng loại Advice nào trong Spring AOP?

Chúng ta sẽ sử dụng bảo bối mạnh mẽ nhất trong các loại Advice: @Around

Khác với @Before chỉ chạy trước hoặc @After chỉ chạy sau, @Around cho phép chúng ta "ôm trọn" cả hai thời điểm đó. Nó giống như việc cô Thư ký cầm sẵn đồng hồ, tự tay mở cửa cho Sếp vào làm việc, kiên nhẫn chờ Sếp làm xong rồi bấm ngưng đồng hồ vậy.

Dưới đây là mã nguồn cho cuốn "Sổ tay hành động" (Aspect) của chúng ta:

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect // Khai báo đây là một Aspect
@Component // Giao cho Spring quản lý class này
public class DoThoiGianAspect {

    // Pointcut: Chặn bất kỳ hàm nào có gắn nhãn @DoThoiGian
    @Around("@annotation(DoThoiGian)")
    public Object doThoiGianChay(ProceedingJoinPoint joinPoint) throws Throwable {
        // 1. TRƯỚC KHI CHẠY: Lấy thời gian hiện tại
        long thoiGianBatDau = System.currentTimeMillis();

        // 2. CHO PHÉP HÀM GỐC CHẠY (Sếp bắt đầu làm việc)
        Object ketQua = joinPoint.proceed();

        // 3. SAU KHI CHẠY: Lấy thời gian kết thúc và tính toán
        long thoiGianKetThuc = System.currentTimeMillis();
        long thoiGianDaTroiQua = thoiGianKetThuc - thoiGianBatDau;

        // Lấy tên hàm để in ra cho rõ ràng
        String tenHam = joinPoint.getSignature().getName();
        System.out.println("⏱️ Hàm [" + tenHam + "] chạy mất: " + thoiGianDaTroiQua + " ms");

        // 4. Trả lại kết quả của hàm gốc
        return ketQua;
    }
}

Bây giờ, bạn chỉ cần dán @DoThoiGian lên bất kỳ hàm nào trong hệ thống, mỗi khi hàm đó chạy, console sẽ tự động in ra số mili-giây nó tiêu tốn!

Hãy nhìn kỹ vào dòng số 2: Object ketQua = joinPoint.proceed();. Lệnh proceed() này mang quyền lực tối cao của @Around.

Theo bạn, nếu chúng ta viết hàm này mà lại quên (hoặc cố ý xóa) dòng joinPoint.proceed() đi, thì điều gì sẽ xảy ra với logic chính của hàm gốc (ví dụ: hàm gốc là một hàm lưu dữ liệu vào database) khi có người gọi nó?

Nếu câu trả lời của bạn là "Logic hàm gốc sẽ không được chạy qua" thì bạn đã nhìn ra được sức mạnh (và cả sự nguy hiểm) của @Around rồi đấy. 🎯

Khi bạn sử dụng @Around, cô Thư ký (Proxy) đã chặn đứng hoàn toàn luồng giao tiếp giữa Khách hàng và Giám đốc (hàm gốc). Lệnh joinPoint.proceed() chính là hành động Thư ký mở cửa phòng và nói: "Mời sếp làm việc".

Nếu bạn xóa dòng đó đi, chuyện gì sẽ xảy ra?

  • Cô Thư ký ở ngoài cửa vẫn bấm đồng hồ... rồi tự ý trả khách về.
  • Ông Giám đốc ngồi trong phòng không hề hay biết có khách đến!

Hậu quả thực tế:

Nếu hàm gốc của bạn có nhiệm vụ lưu đơn hàng vào Database hoặc trừ tiền trong tài khoản, thì khi thiếu proceed(), các thao tác đó sẽ bị bỏ qua hoàn toàn. Khách hàng gọi hàm không thấy báo lỗi gì (vì Proxy vẫn chạy bình thường), nhưng thực chất dữ liệu không hề được lưu.

Tuy nhiên, đôi khi người ta lại cố tình không gọi proceed() Ví dụ: Khi bạn viết một Aspect để kiểm tra quyền truy cập (Security). Nếu phát hiện người dùng không có quyền, cô Thư ký sẽ ném ra lỗi hoặc báo "Từ chối truy cập" và tuyệt đối không gọi proceed() để bảo vệ hàm gốc.

X. Lời nhắc nhở: Khi AOP cũng dính bẫy "Bóng ma"

Một điểm mà bạn cần lưu ý khi làm việc với AOP, đó chính là "Self-invocation" mà chúng ta đã nhắc tới trong phần 1.

Với Spring AOP, chúng ta hoàn toàn có thể rơi vào cạm bẫy này

Lý do là vì Spring AOP thực chất được xây dựng hoàn toàn dựa trên cơ chế Proxy (JDK Dynamic Proxy hoặc CGLIB) mà chúng ta đã mổ xẻ từ đầu.

  • AOP cung cấp các khái niệm (Aspect, Pointcut, Advice) để chúng ta khai báo logic phụ trợ một cách gọn gàng.

  • Nhưng bên dưới lớp vỏ đó, công cụ thực thi những khai báo đó vẫn là một cô Thư ký (Proxy) đứng chặn ngoài cửa.

XI🧠 Bài tập tình huống thực tế:

Thử sức với một số kịch bản lỗi thực tế (code review) kết hợp tất cả những gì đã học trong cả hai phần để xem bạn có tìm ra "thủ phạm" giấu mặt không nhé.

Tình huống 1

Hãy thử áp dụng cạm bẫy Self-invocation vào annotation @DoThoiGian mà chúng ta vừa tự tay viết nhé. Giả sử trong class DichVu của bạn có hai hàm:

  1. Hàm baoCaoNhanh(): Không gắn @DoThoiGian. Nhưng bên trong hàm này lại có một dòng lệnh tự gọi hàm this.tinhToanPhucTap().

  2. Hàm tinhToanPhucTap(): Có gắn nhãn @DoThoiGian.

Khi một request từ bên ngoài gọi vào hàm baoCaoNhanh(), Proxy kiểm tra và thấy không có nhãn nên cho qua mà không bấm giờ. Sau đó, vào bên trong class, hàm baoCaoNhanh() tự gọi nội bộ đến tinhToanPhucTap(). Lúc này, luồng thực thi đi hoàn toàn bên trong lớp gốc, qua mặt Proxy. Hậu quả là khối lệnh tính toán thời gian trong Aspect của chúng ta sẽ không bao giờ được chạy, dù hàm đó có gắn annotation.

Nhớ lại cách chúng ta đã thảo luận ở phần @Transactional, dựa vào cấu trúc của hai hàm trên, bạn sẽ dời vị trí chiếc nhãn @DoThoiGian đi đâu, hoặc sắp xếp lại các hàm như thế nào để Proxy có thể kích hoạt được đồng hồ bấm giờ?

Hoàn toàn chính xác! 🎯 Cả hai cách bạn đưa ra đều là giải pháp chuẩn mực để xử lý vấn đề Self-invocation trong thực tế.

  • Cách 1: Dời @DoThoiGian lên hàm baoCaoNhanh(). Khi đó, Proxy sẽ bắt được yêu cầu ngay từ cửa trước và đồng hồ sẽ được bấm bao trọn toàn bộ thời gian chạy của cả hai hàm.

  • Cách 2: Tách tinhToanPhucTap() sang một class (hoặc Service) khác. Lúc này, hàm baoCaoNhanh() muốn gọi tinhToanPhucTap() thì bắt buộc phải "đi vòng" qua lớp vỏ Proxy của Service mới, và chiếc nhãn @DoThoiGian lại phát huy tác dụng.

Như vậy, nạn đã xâu chuỗi rất tốt cách Proxy hoạt động bên dưới để áp dụng trực tiếp vào việc gỡ lỗi logic ở tầng trên. Vậy là chúng ta đã giải quyết trọn vẹn từ bản chất Proxy cho đến các ứng dụng thực tế nhất của AOP!

Tình huống 2

Dưới đây là đoạn code bạn Dev nào đó vừa viết. Nhiệm vụ của class này là xử lý quá trình đăng ký người dùng mới. Bạn ấy đã cẩn thận gắn @Transactional để bảo vệ quá trình lưu vào cơ sở dữ liệu.

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class UserService {

    // Hàm này được gọi trực tiếp từ UserController
    public void registerUser(String username) {
        System.out.println("Bắt đầu xử lý đăng ký cho: " + username);
        
        // Gọi hàm phụ trợ bên dưới để lưu dữ liệu
        luuVaoDatabase(username); 
        
        System.out.println("Đăng ký hoàn tất!");
    }

    // Bạn Junior muốn đảm bảo hàm này an toàn nên gắn @Transactional
    @Transactional
    private void luuVaoDatabase(String username) {
        System.out.println("Đang lưu [" + username + "] vào Database...");
        // Bỏ qua các dòng code lưu DB thực tế ở đây
    }
}

Tình huống: Khi code chạy thực tế, nếu quá trình lưu DB xảy ra lỗi, dữ liệu hoàn toàn không được rollback (khôi phục lại). Nghĩa là @Transactional đã hoàn toàn "tàng hình" và bị vô hiệu hóa.

Dựa vào những gì chúng ta đã mổ xẻ về Proxy, Self-invocation, và CGLIB, đoạn code ngắn này thực chất đang mắc phải tận hai lỗi chí mạng khiến Proxy không thể hoạt động.

Bạn hãy thử vận dụng "nhãn quan" của mình để chỉ ra ít nhất một (hoặc cả hai) lỗi đó nhé! 5️⃣4️⃣3️⃣2️⃣1️⃣ Time!!!

Hai lỗi trong đoạn code trên đó chính là:

  1. 🔄 Lỗi Self-invocation: Hàm registerUser() được gọi từ bên ngoài không hề có nhãn @Transactional. Khi vào đến bên trong, nó tự gọi this.luuVaoDatabase(). Luồng đi này diễn ra hoàn toàn trong phòng kín của "Giám đốc", bỏ qua lớp vỏ Proxy bên ngoài nên transaction không được mở.

  2. 🔒 Lỗi đặt trên hàm private: Ngay cả khi không mắc lỗi số 1, việc đặt @Transactional lên một hàm private cũng vô tác dụng. Như chúng ta đã biết, CGLIB (dùng kế thừa) không thể ghi đè hàm private, và JDK Proxy (dùng Interface) cũng không thể chứa hàm private. Do đó, Spring quy định sẽ mặc định bỏ qua mọi annotation AOP dán trên hàm private.

Bây giờ, bước tiếp theo của việc review code là đưa ra giải pháp khắc phục.

Nếu bạn Dev đó hỏi bạn: "Vậy em phải sửa lại class này như thế nào để vừa giữ nguyên logic, vừa làm cho @Transactional hoạt động?", bạn sẽ hướng dẫn bạn ấy thay đổi đoạn code trên ra sao?

  1. Gắn @Transactional lên registerUser: Đây là cách dọn dẹp gọn gàng nhất. Proxy sẽ chặn yêu cầu ngay từ ngoài cửa (hàm public registerUser) và mở sẵn một transaction. Sau đó, việc bên trong hàm này tự gọi các hàm private khác sẽ vẫn được bao bọc an toàn trong transaction đó. Chúng ta không cần thay đổi cấu trúc class hay từ khóa private.

  2. Tách ra Service khác và đổi thành public: Cách này rất phù hợp nếu đoạn code luuVaoDatabase của bạn phức tạp và có thể được tái sử dụng bởi nhiều tính năng khác. Bằng cách tách ra một class riêng (ví dụ: DatabaseService) và để hàm ở mức public, chúng ta buộc Spring phải tạo ra một lớp Proxy mới để quản lý riêng hàm đó.

Tổng kết: Bước ra khỏi "Hộp đen" của Framework

Hành trình của chúng ta đi từ việc sử dụng annotation như những "câu thần chú" học vẹt, đến việc tận mắt bóc tách cô Thư ký Proxy, và cuối cùng là làm chủ bộ mật ngữ AOP đã khép lại.

Giờ đây, bạn không chỉ biết gõ @Transactional hay @Async, mà còn hiểu tường tận tại sao hệ thống lại từ chối khôi phục dữ liệu khi bạn lỡ tay gọi hàm nội bộ (Self-invocation). Thông qua việc tự chế tạo chiếc nhãn @DoThoiGian, bạn đã chính thức nắm trong tay quyền năng bóc tách toàn bộ các logic râu ria ra khỏi nghiệp vụ chính, giữ cho "Giám đốc" luôn được làm việc trong một môi trường code sạch sẽ và chuẩn mực nhất.

Spring Boot không hề có phép thuật, nó chỉ ẩn chứa những thiết kế kiến trúc xuất sắc. Khi bạn mạnh dạn bước ra phía sau bức màn sân khấu để tìm hiểu cơ chế bên dưới, bạn đã chính thức chuyển mình từ một người "tiêu dùng" framework thành một kỹ sư thực thụ làm chủ hệ thống.

Thử thách nhỏ trước khi chia tay:

Hãy thử áp dụng ngay lăng kính AOP bạn vừa trang bị vào một bài toán thực tế này nhé:

Giả sử dự án yêu cầu một tính năng: "Tự động gửi email cảnh báo cho đội bảo mật chỉ khi các hàm bắt đầu bằng chữ delete trong hệ thống ném ra lỗi (Exception)."

Dựa vào bộ từ vựng AOP chúng ta đã học, bạn sẽ thiết lập Pointcut (bộ lọc chặn cửa) như thế nào, và bạn nghĩ hệ thống Spring AOP có cung cấp sẵn loại Advice nào chuyên biệt để cô Thư ký xử lý tình huống "sếp làm việc thất bại" này không?


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.