Aspectj la gi

Nguồn: sentayho.com.vn/faq/aop-la-gi-aspect-oriented-programming-trong-java.html

1. AOP là gì?

Quảng Cáo

Aspect Oriented Programming (AOP) – lập trình hướng khía cạnh: là một kỹ thuật lập trình (kiểu như lập trình hướng đối tượng) nhằm phân tách chương trình thành cách moudule riêng rẽ, phân biệt, không phụ thuộc nhau.

Khi hoạt động, chương trình sẽ kết hợp các module lại để thực hiện các chức năng nhưng khi sửa đổi 1 chức năng thì chỉ cần sửa 1 module.

Quảng Cáo

AOP không phải dùng để thay thế OOP mà để bổ sung cho OOP.

Aspectj la gi

Ví du 1:

1 công ty được chia thành nhiều phòng ban, phòng kỹ thuật, phòng kế toán, phòng kinh doanh (mỗi phòng giống như một module trong chương trình)…

Quảng Cáo

Mỗi phòng thực hiện một nhiệm vụ riêng, nhưng cần kết hợp lại để vận hành một công ty.

– Trong AOP có khái niệm “lát cắt”, “điểm cắt”, tạm hiểu là module A sẽ xen vào module B để thực hiện 1 chức năng nào đó mà không làm ảnh hưởng module B, “điểm cắt” là vị trí mà module A xen vào module B

Ví dụ, trong công ty, tất cả các nhân viên đều cần phải quyết toán thuế, nếu từng người 1 thực hiện sẽ tốn rất nhiều thời gian và ảnh hưởng tới công ty. Phòng kế toán sẽ gộp lại và thực hiện quyết toán cho tất cả nhân viên. Bây giờ luật thuế thay đổi, thì cũng chỉ cần mỗi phòng kế toán chỉnh sửa lại cho phù hợp, không ảnh hưởng gì tới các phòng ban khác. (Module phòng kế toán thực hiện lát cắt với các phòng còn lại ở nhiệm vụ quyết toán thuế)

Điểm cắt ở đây tùy vào xếp muốn, luật yêu cầu, ví dụ luật yêu cầu là phải quyết toán thuế cuối năm thì phòng kế toán sẽ thực hiện lát cắt quyết toán thuế cho các phòng ban còn lại vào cuối năm.

Ví dụ 2:

Trong code bạn thường thực hiện viết log cho các method (method nào chạy, lỗi gì xảy ra…). Rõ ràng việc viết log chẳng liên quan gì đến chức năng của method.

Bây giờ yêu cầu chuyển sang chỉ viết log khi bắt đầu các method chẳng hạn, ta lại phải sửa tất cả các method đó.

Bây giờ áp dụng AOP, ta sẽ phân tách chức năng log ra một module riêng và dùng nó thực hiện các lát cắt với các method cần thực hiện log.

Muốn log ở đầu method, điểm cắt sẽ là ở đầu các method, muốn cắt ở cuối method thì điểm cắt sẽ là cuối method.

Ví dụ 3:

Trong lập trình web, có 1 khái nhiệm filter, tức là tất cả các request được chọn sẽ đều chạy qua filter đó.

filter ở đây cũng được coi như 1 module độc lập riêng rẽ. Chẳng hạn mỗi request gửi đến cần xác thực (kiểm tra login) ta chỉ cần cho nó đi qua 1 filter thực hiện xác thực là được, muốn sửa đổi cách xác thực thì ta chỉ cần sửa filter là xong.

2. Ưu nhược điểm của AOP

  • Thiết kế đơn giản: “You aren’t gonna need it (YAGNI)” – chúng ta chỉ cài đặt những thứ chúng ta thực sự cần mà không bao giờ cài đặt trước.
  • Cài đặt chương trình một cách trong sáng: mỗi một module chỉ làm cái mà nó cần phải làm, giải quyết được hai vấn đề code tangling và code scattering.
  • Tái sử dụng dễ dàng.

Nhược điểm:

  • Khái nhiệm khá trừu tượng, độ trừu tượng của chương trình cao
  • Luồng chương trình phức tạp.

3. Các thuật ngữ trong AOP

Mình sẽ lấy ví dụ cho việc tách chức năng log để minh họa.

  • Core concerns: hàm chính của chương trình (các method cần thực hiện log)
  • Crosscutting concerns: những chức năng khác của chương trình: (chức năng log)
  • Join points: một điểm của chương trình, là nơi có thể chèn những “custom action” của bạn
  • Pointcut: có nhiều cách để xác định joinpoint, những cách như thế được gọi là pointcut.
  • Advice: là những xử lý phụ được thêm vào xử lý chính, code để thực hiện các xử lý đó được gọi Adivce.

4. Code ví dụ bằng Java.

Để thực hiện AOP trong Java ta có thể sử dụng các cài đặt cung cấp bởi

  • AspectJ
  • Spring AOP
  • JBoss AOP Ví dụ AOP với Spring AOP: sentayho.com.vn/spring/sentayho.com.vn Ví dụ AOP với Spring + AspectJ: sentayho.com.vn/spring/spring-core-aop-aspectj.html

Nguồn: sentayho.com.vn/faq/aop-la-gi-aspect-oriented-programming-trong-java.html

Bạn thấy bài viết thế nào?

Xét ví dụ đơn giản sau, về logging

Ta có 1 class DataAccess có các function tạo, cập nhật và xóa dữ liệu database như sau:

public class DataAccess {   
    public void createData(){
        System.out.println("Created data record.");
    }
    
    public void updateData(){
        System.out.println("Updated data record");
    }
    
    public void deleteData(){
        System.out.println("Deleted data record");
    }
}

Trong chương trình chính ta thực hiện gọi các function của DataAccess:

public class Main {

    public static void main(String[] args) {
        DataAccess dao=new DataAccess();
        dao.createData();
        dao.updateData();
        dao.deleteData();
    }

}

Tất nhiên khi chạy thì chương trình sẽ in ra các message như ta mong đợi. Nhưng 1 nhiệm vụ được đặt ra là ghi lại log khi thực thi các hàm liên quan đến dữ liệu, ở đây là các function của DataAccess.

Cách chúng ta thường làm là định nghĩa 1 class Log và trong DataAccess gọi phương thức lả Log để ghi ra log

public class Log {
    public void logMessage(String message){
        System.out.println("["+ new SimpleDateFormat("yyyy-MM-dd kk:mm:ss").format(new Date()) +"] Meesage from logger: "+message);
    }
}

và sửa lại class DataAccess như sau:

public class DataAccess {
    private Log log=new Log();
    
    public void createData(){
        log.logMessage("call create data");
        System.out.println("Created data record.");
    }
    
    public void updateData(){
        log.logMessage("call update data");
        System.out.println("Updated data record");
    }
    
    public void deleteData(){
        log.logMessage("call delete data");
        System.out.println("Deleted data record");
    }
}

Mỗi function của DataAccess ta đều phải gọi hàm logMessage.

và kết quả như mong đợi

Nhưng rõ ràng với cách này ta có thể thấy rằng:

+Log và quản lý dữ liệu không liên quan gì với nhau, nhưng ở đây trong quản lý dữ liệu ta lại phải gọi log

+Tưởng tượng có 1 số lượng rất lớn các đối tượng, phương thức cần ghi log, vậy ở mỗi phương thức ta đều phải gọi hàm logMessage

+Nếu việc ghi log không có ngay trong thiết kế ban đầu, vậy lúc này ta muốn thêm vào thì phải thay đổi source rất nhiều

Vậy nếu có 1 phương pháp để định nghĩa rằng các phương thức create, update hay delete của DataAccess cần phải gọi hàm logMessage mà không phải thay đổi source của DataAccess thì các vấn đề trên sẽ được giải quyết.Với phương pháp lập trình AOP,chúng ta có thể làm được điều này m à c ụ th ể ở đ ây ta d ùng ApectJ- l à 1 implement hoàn chỉnh AOP cho ngôn ngữ JAVA.

DataAccess như ban đầu:

public class DataAccess {   
    public void createData(){
        System.out.println("Created data record.");
    }
    
    public void updateData(){
        System.out.println("Updated data record");
    }
    
    public void deleteData(){
        System.out.println("Deleted data record");
    }
}

Ta định nghĩa 1 aspect như sau:

public aspect LogDataAccessAspect {
    private Log log=new Log();
    
    pointcut dataAccessExecution() : (execution(public * DataAccess.*(..)));
    
    before() : dataAccessExecution() {
        log.logMessage("call "+thisJoinPointStaticPart.getSignature());
    }
}

Ý nghĩa của LogDataAccessAspect như sau:

+Chọn các phương thức public của class DataAccess, đưa vào 1 tập hợp tên là dataAccessExecution() 

pointcut dataAccessExecution() : (execution(public * DataAccess.*(..)));

+Với mỗi phương thức trong tập hợp trên, trước khi thực thi phương thức đó thì thực hiện câu lệnh log.logMessage

before() : dataAccessExecution() {
        log.logMessage("call "+thisJoinPointStaticPart.getSignature());
}

Ta chạy lại chương trình, kết quả:

Aspectj la gi

Message hơi khác lúc trước, nhưng rõ rang trước khi chạy mỗi phương thức ở DataAccess thì hệ thống đều log ra tên phương thức đang thực thi, điều này hoàn toàn giống với việc ta them vào mỗi phương thức ở DataAccess 1 lệnh gọi tới log.logMessage, nhưng rất khác ở chỗ ta không cần thay đổi source của DataAccess, và ta cũng phải thấy thêm rằng:

+class DataAccess và Log bây giờ đã hoàn toàn tách biệt, DataAccess không cần phải ý thức được sự tồn tại của Log, điều này rất có ý nghĩa, khi mà DataAccess chỉ cần biết đến thực hiện nhiệm vụ của nó là quản lý các tác vụ lien quan đến dữ liệu, không phải nhập nhằng với việc phải thực hiện log, và khi ta nghĩ rộng đến 2 module có thể tồn tại hoàn toàn tách biệt thì việc thiết kế hệ thống hay quản lý source code sẽ trở nên tốt hơn rất nhiều.

+Việc không cần phải thay đổi source code hay thay đổi rất ít khi thêm chức năng mới mà có tính ảnh hưởng khắp cả hệ thống sẽ giúp giảm công sức viết mã, kiểm tra, và từ đó giảm thiểu khả năng lỗi.

Thông qua ví dụ trên dù đơn giản nhưng chúng ta cũng phần nào thấy được lợi ích của phương pháp AOP

Vậy Aspect Oriented Programming(AOP) là gì?

Ta có thể tự định nghĩa rằng AOP là 1 phương pháp lập trình, giống như OOP(phương pháp lập trình hướng đối tượng) vậy, nó cũng có ràng buộc, các tính chất để tạo nên 1 phương pháp lập trình mới, và cũng như các phương pháp lập trình khác, nó cũng có các ngôn ngữ hay công cụ implement các phương pháp của nó, mà cụ thể trong bài viêt này ta dùng AspectJ, là 1 tool cho ngôn ngữ Java, implement phương pháp AOP.

AOP có phải là đối thủ của OOP không? Qua ví dụ trên thì ta có thể thấy rằng AOP bổ trợ cho OOP chứ không loại trừ OOP, và ta cũng thấy được tại sao lại sinh ra AOP.

Có những khía cạnh hay chức năng của chương trình mà nó ảnh hưởng lên toàn bộ hệ thống, ta có thể tưởng tượng là nó cắt ngang các dòng chảy của hệ thống, khiến việc implement, quản lý các khía cạnh này có thể trở nên khó khăn và tốn nhiều công sức, giống như ví dụ với Log ở trên. AOP sinh ra để giải quyết các vấn đề này, AOP gọi các khía canh này là crosscutting concern, từ việc giải quyết các khía cạnh này, AOP sẽ tăng tính module của hệ thống, như ta đã thấy ở ví dụ với Log

Một ví dụ nữa về crosscutting concern  mà ta có thể thấy là transaction, ở các phương thức liên quan đến thay đổi dữ liệu database, ta đều phải dùng transaction, như đoạn mã dưới đây:

transactionManagement.beginTransaction();
             
//thuc hien thay doi du lieu
             
transactionManagement.commit();

Trong 1 hệ thống thường thì có nhiều crosscutting concern, ví dụ 1 phương thức có thể cùng lúc phải ghi log, phải kiểm tra permission, phải thưc hiện transaction, và thực thi nhiệm vụ chính của nó, làm như vậy thì ngoài các vấn đề về chất lượng, công sức, tính hiệu quả, thì có thể giảm đi tính sử dụng lại của các module, khi mà module này phải nhận biết dự tồn tại và phục thuộc vào các module khác

Chúng ta cần nắm các khái niệm sau khi sử dụng AOP:
+join point: là các điểm thực thi của hệ thống, có thể là các lệnh gọi hàm, lệnh bắt exception, các lệnh điều kiện, các định nghĩa phương thức, các constructor.... Ở ví dụ Log ở trên, dao.createData(); hay body của phương thức public void createData() đều là các join point

+point cut: Tập hợp các join point theo 1 điều kiên nào đó. Ở ví dụ Log ta có point cut la tập các join point là các định nghĩa phương thức của class DataAccess

+Các chỉ dẫn để thực hiện thay đổi cách thực thi của hệ thống

Trong AspectJ thì dùng advice, với ví dụ Log ở trên thì advice là:

before() : dataAccessExecution() {
        log.logMessage("call "+thisJoinPointStaticPart.getSignature());
}

Advice ở đây thay đổi cách hoạt động của các hàm DataAccess(được chon ở point cut dataAccessExecution) theo cách:
Trước mỗi thực thi của các join point ở dataAccessExecution, thực thi câu lệnh logMessage

+1 module để gom các thành phần ở trên lại: ở AspectJ thì dùng aspect, như cách ta định nghĩa aspect ở ví dụ trên.

Bài viêt dừng lại ở mức giới thiệu cho người đọc hình dung về AOP cũng như cách implement cụ thể với AspectJ.

Rõ ràng là AOP mang lại rất nhiều điều mới mẻ và rất đáng để ta áp dụng. Thực tế là AOP đã được ứng dụng rất thành công trong môi trường enterprise, Spring Framework là 1 ví dụ, khi mà Spring sử dụng rất nhiều phương pháp AOP này trong cách implement của mình, tuy có hơi khác và hạn chế hơn cách AspectJ đã implement, nhưng chúng ta hoàn toàn có thể sử dụng AspectJ kết hợp với Spring 1 cách rất dễ dàng.

Tôi nghĩ không có trở ngại lớn nào khiến chúng ta không ứng dụng AOP vào hệ thống của mình, thực tế AOP đã có lich sử khá dài và mature, ngoại trừ việc phải đầu tư thời gian tương đối vào tìm hiểu và nắm vững AOP.