Giới thiệu Aspect Oriented Programming (AOP)


AOP là gì?

Khi xây dựng các chương trình ứng dụng, ta thường phải đối mặt với nhiều vấn đề liên quan đến phần mềm. Ví dụ, khi ta xây dựng một hệ thống đăng ký tài khoản cho ngân hàng, ngoài công việc chính là cho phép người dùng tạo tài khoản, ta còn phải quan tâm đến các vấn đề khác như chứng thực người dùng, kiểm tra ràng buộc, quản lý giao dịch, xử lý ngoại lệ, ghi log, debug, đo hiệu suất của ứng dụng,…

Thông thường, logic của chương trình phải thực hiện rất nhiều công việc như ghi log, quản lý giao dịch, xử lý ngoại lệ,… Khi có nhiều phương thức tương tự nhau trong ứng dụng, mã code sẽ bị liên kết chặt chẽ, sao chép mã code, phân tán khắp nơi và khó khăn trong việc thay đổi và bổ sung logic mới. Để giải quyết vấn đề này, chúng ta có thể sử dụng kỹ thuật AOP.

AOP (Aspect Oriented Programming) là một kỹ thuật lập trình cho phép phân tách chương trình thành các module riêng biệt không phụ thuộc lẫn nhau. Khi chương trình hoạt động, các module sẽ kết hợp lại để thực hiện các chức năng, nhưng chỉ cần thay đổi trên một module cụ thể khi muốn sửa đổi chức năng.

AOP còn được gọi là Aspect Oriented Software Development (AOSD), là một nguyên tắc thiết kế giúp tách rời các yêu cầu hoặc các vấn đề quan tâm (separation of concerns) trong chương trình thành các thành phần độc lập, từ đó tăng tính linh hoạt cho chương trình. Trong việc tách rời các quan tâm, nguyên tắc cho rằng các vấn đề tương tự nên được giải quyết trong một đơn vị mã. Trong lập trình hàm (functional programming), đơn vị mã này là một hàm/phương thức, trong khi đối với lập trình hướng đối tượng (OOP), đơn vị mã này là một lớp.

Trong AOP, chương trình được chia thành hai loại quan tâm:

  • Quan tâm chính/Core concern: là yêu cầu, logic xử lý chính của chương trình.
  • Quan tâm bắt ngang/Cross-cutting concern: là logic xử lý phụ cần thực hiện trong chương trình khi gọi core concern như bảo mật, ghi log, theo dõi, theo dõi,…

Một số thuật ngữ khác trong AOP:

  • Joinpoint: là một điểm trong chương trình, nơi mà cross-cutting concern có thể được chèn vào. Ví dụ, nếu ta cần ghi log sau khi thực hiện một phương thức, thì điểm ngay sau phương thức đó được gọi là joinpoint. Một joinpoint có thể là một phương thức được gọi, một ngoại lệ được ném ra hoặc một trường bị thay đổi.
  • Pointcut: là cách để xác định Joinpoint. Pointcut là một biểu thức được sử dụng để kiểm tra xem một joinpoint có khớp với nó hay không, để xác định xem Advice có cần được thực hiện hay không.
  • Advice: là xử lý phụ (cross-cutting concern) được thêm vào core concern (logic chính) của chương trình, mã để thực hiện các xử lý đó được gọi là Advice. Advice được chia thành các loại sau:
    • Before: thực hiện trước joinpoint.
    • After: thực hiện sau joinpoint.
    • Around: thực hiện trước và sau joinpoint.
    • After returning: thực hiện sau khi joinpoint hoàn thành một cách bình thường.
    • After throwing: thực hiện sau khi joinpoint kết thúc bằng một ngoại lệ.
  • Aspect: tương tự như một lớp Java. Một Aspect bao gồm toàn bộ cross-cutting concern và có thể chứa các joinpoint, pointcut, advice.
  • Target Object: là các đối tượng mà advice được áp dụng.
Có Thể Bạn Quan Tâm :   StormX( STMX) Là Gì? Hướng dẫn mua bán StormX( STMX) | Thông tin chi tiết về StormX( STMX)

Một số cross-cutting concern thường gặp trong ứng dụng:

  • Ghi log
  • Theo dõi
  • Khống chế truy cập
  • Xử lý lỗi
  • Quản lý giao dịch
  • Quản lý phiên
  • Xác thực dữ liệu vào/ra

Weaving là gì?

Weaving (đan/ dệt) là quá trình kết hợp các thành phần aspect và non-aspect của một chương trình để tạo ra đầu ra mong muốn.

Có một vài cách khác nhau giữa các hệ thống AOP về cách thực hiện weaving. Chúng ta có thể chia thành các loại weaving: Compile-time weaving (static weaving), Load-Time Weaving và Run-time weaving (dynamic weaving).

  • Compile-time weaving:
    • Pre-Compile Weaving: sử dụng bộ tiền xử lý (pre-processor) để kết hợp code của aspect và code non-aspect trước khi code được biên dịch thành byte code Java (.class).
    • Post-Compile Weaving / Binary weaving: cách này dùng để chèn mã code của aspect vào các file .class của Java sau khi biên dịch.
  • Load-Time Weaving: cách này dùng để thêm mã code của aspect khi lớp cần sử dụng aspect được nạp vào JVM trong quá trình chạy.
  • Run-time weaving: thực hiện weaving và unweaving mã code của aspect và non-aspect trong quá trình chạy.

Static weaving

Static weaving là quá trình kết hợp code của aspect và code non-aspect trước khi code được biên dịch thành Java byte code (.class) bằng cách sử dụng bộ tiền xử lý (pre-processor). Vì vậy, mã code gốc chỉ được thay đổi một lần trong quá trình biên dịch (compile). Hiệu suất của mã code sau khi được kết hợp này được đảm bảo tương đương với mã code viết theo phương thức truyền thống.

Có Thể Bạn Quan Tâm :   Kem liền sẹo làm mờ vết thâm Avene Cicalfate Creme Reparatrice

Hạn chế của phương pháp Static weaving là khó khăn trong việc tìm hiểu mã code của aspect sau này hoặc thực hiện thay đổi đối với mã code của aspect. Mỗi khi mã code của aspect thay đổi, ta phải biên dịch lại toàn bộ mã code sử dụng aspect.

Dynamic weaving

Dynamic weaving khắc phục một số hạn chế gặp phải khi weaving được thực hiện trong quá trình biên dịch (Compile-time weaving/ Static weaving). Nó cho phép tránh việc biên dịch lại (recompilation), triển khai lại (redeployment) và khởi động lại (restart) bằng cách thực hiện quá trình weaving trong quá trình chạy (run-time weaving).

Có một số khác biệt giữa load-time weaving và run-time weaving.

  • Load-time weaving chỉ đơn giản là trì hoãn quá trình weaving cho đến khi các lớp được nạp bởi class loader. Ta cần sử dụng một weaving class loader hoặc thay thế class loader bằng một class loader khác để thực hiện cách tiếp cận này. Hạn chế của load-time weaving là tăng thời gian load và thiếu quyền truy cập vào các aspect trong quá trình chạy.
  • Run-time weaving là quá trình weaving và unweaving mã code của aspect và non-aspect trong quá trình chạy. Cách tiếp cận này yêu cầu các cơ chế mới để can thiệp vào việc tính toán.

Các AOP Framework khác nhau có cách thực hiện dynamic weaving khác nhau. Ví dụ, AspectWerkz sử dụng việc chỉnh sửa mã byte thông qua tính năng cung cấp JVM và kiến trúc “hotswap” để thực hiện weaving các lớp tại run-time, trong khi Spring AOP Framework dựa trên các proxy thay vì các class loader hoặc đối số JVM.

Dynamic weaving cho phép tăng tốc các giai đoạn thiết kế và kiểm thử trong quá trình phát triển phần mềm, bởi vì ta có thể thêm mới các aspect hoặc thay đổi các aspect hiện có mà không cần biên dịch lại và triển khai lại ứng dụng. Tuy nhiên, một nhược điểm lớn là hiệu suất giảm do weaving xảy ra trong quá trình chạy.

Cài đặt AOP trong Java như thế nào?

Dưới đây là một số ý tưởng để triển khai AOP trong chương trình của chúng ta:

  • Class-weaving: như đã được đề cập ở trên.
  • Proxy-based: ta có thể sử dụng các thư viện mã hóa mã byte code như JDK proxy, CGLib proxy để chặn lệnh gọi phương thức và thêm mã code riêng để thực thi trước.
    • JDK proxy: đây là cách đơn giản nhất, nhưng chỉ có thể xử lý các phương thức public được gọi, không thể xử lý các cuộc gọi từ bên trong lớp đó.
    • CGLib proxy: yêu cầu sửa đổi mã byte code của Java giới hạn và có thể xử lý các cuộc gọi phương thức private, nhưng vẫn không thể xử lý truy cập trực tiếp vào thuộc tính.
  • Sử dụng các thư viện có sẵn như Google Guice, AspectJ, Spring AOP.
Có Thể Bạn Quan Tâm :   Tiền pháp định (Fiat) là gì?

Ví dụ tự xây dựng một AOP framework

Trong ví dụ dưới đây, chúng ta sẽ tự viết mã code để tạo một AOP Framework sử dụng JDK Proxy mà không sử dụng bất kỳ thư viện bên thứ ba nào.

Để sử dụng JDK Proxy, ta thực hiện các bước sau:

  • Tạo Invocation Handler: lớp này phải implement interface java.lang.reflect.InvocationHandler. InvocationHandler là interface được implement bởi trình xử lý lời gọi (invocation handler) của một proxy instance. Khi gọi phương thức trên một proxy instance, lời gọi phương thức được mã hoá và gửi đến phương thức gọi của invocation handler.
  • Tạo Proxy Instance: sử dụng phương thức Proxy.newProxyInstance() được cung cấp bởi factory method java.lang.reflect.Proxy.

Ví dụ: ta cần thêm một số xử lý trước và sau khi các phương thức trong lớp AccountService được gọi.

Output của chương trình:

Handling before actual method execution
addAccount: Account(owner=gpcoder, currency=USD, balance=100)
Handling after actual method execution
-
Handling before actual method execution
getSize: 1
Handling after actual method execution
-
Handling before actual method execution
removeAccount: Account(owner=gpcoder, currency=USD, balance=100)
Handling after actual method execution
-
Handling before actual method execution
getSize: 0
Handling after actual method execution
-

Lợi ích của AOP là gì?

  • Tăng hiệu suất của Object-oriented programming (OOP).
  • AOP không phải là thay thế cho OOP mà là bổ sung cho OOP, để giải quyết các ứng dụng phức tạp.
  • Tăng khả năng tái sử dụng của mã nguồn.
  • Đảm bảo nguyên tắc Single Responsibility: mỗi module chỉ làm điều mà nó cần phải làm.
  • Tuân thủ nguyên tắc “You aren’t gonna need it – YAGNI” – chỉ triển khai những thứ thực sự cần thiết, không làm trước.
  • Module hóa ở mức tiến trình/chức năng.
  • Mã code gọn gàng hơn do tách biệt logic chính và logic liên quan.
  • Chức năng chính của chương trình không cần biết đến các chức năng phụ khác.
  • Các chức năng phụ có thể được thêm vào, tắt bật tại thời điểm chạy tùy theo yêu cầu.
  • Các thay đổi về chức năng phụ không ảnh hưởng đến chương trình chính.
  • Hệ thống trở nên linh hoạt và giảm sự phụ thuộc lẫn nhau của các module.

Tài liệu tham khảo:

  • http://best-practice-software-engineering.ifs.tuwien.ac.at/technology/aop.html
  • https://en.wikipedia.org/wiki/Aspect-oriented_programming
  • https://en.wikipedia.org/wiki/Separation_of_concerns
  • https://www.eclipse.org/aspectj/doc/next/devguide/ltw.html
  • https://docs.oracle.com/javase/8/docs/technotes/guides/reflection/proxy.html
  • https://www.baeldung.com/java-dynamic-proxies
  • https://www.baeldung.com/cglib

Bình luận

bình luận

Back to top button