CodeGym/Blog Java/Ngẫu nhiên/AOP là gì? Nguyên tắc lập trình hướng khía cạnh
John Squirrels
Mức độ
San Francisco

AOP là gì? Nguyên tắc lập trình hướng khía cạnh

Xuất bản trong nhóm
Xin chào, các bạn và các cô gái! Nếu không hiểu các khái niệm cơ bản, sẽ rất khó để đi sâu vào các khuôn khổ và cách tiếp cận để xây dựng chức năng. Vì vậy, hôm nay chúng ta sẽ nói về một khái niệm như vậy - AOP, hay còn gọi là lập trình hướng khía cạnh . AOP là gì?  Nguyên lý lập trình hướng khía cạnh - 1Chủ đề này không dễ dàng và hiếm khi được sử dụng trực tiếp, nhưng nhiều khuôn khổ và công nghệ sử dụng nó một cách bí mật. Và tất nhiên, đôi khi trong các cuộc phỏng vấn, bạn có thể được yêu cầu mô tả một cách chung chung đây là loại quái vật gì và nó có thể được áp dụng ở đâu. Vì vậy, chúng ta hãy xem các khái niệm cơ bản và một số ví dụ đơn giản về AOP trong Java . Bây giờ, AOP là viết tắt của lập trình hướng theo khía cạnh, là một mô hình nhằm tăng tính mô đun của các phần khác nhau của ứng dụng bằng cách tách biệt các mối quan tâm xuyên suốt. Để thực hiện điều này, hành vi bổ sung được thêm vào mã hiện có mà không thực hiện thay đổi đối với mã gốc. Nói cách khác, chúng ta có thể coi nó như là treo chức năng bổ sung lên trên các phương thức và lớp mà không thay đổi mã đã sửa đổi. Tại sao điều này là cần thiết? Sớm muộn gì chúng ta cũng kết luận rằng cách tiếp cận hướng đối tượng điển hình không phải lúc nào cũng có thể giải quyết hiệu quả một số vấn đề nhất định. Và khi thời điểm đó đến, AOP sẽ giải cứu và cung cấp cho chúng tôi các công cụ bổ sung để xây dựng ứng dụng. Và các công cụ bổ sung có nghĩa là tăng tính linh hoạt trong phát triển phần mềm, có nghĩa là có nhiều tùy chọn hơn để giải quyết một vấn đề cụ thể.

Áp dụng AOP

Lập trình hướng khía cạnh được thiết kế để thực hiện các nhiệm vụ xuyên suốt, có thể là bất kỳ mã nào có thể được lặp lại nhiều lần bằng các phương pháp khác nhau, không thể cấu trúc hoàn chỉnh thành một mô-đun riêng biệt. Theo đó, AOP cho phép chúng tôi giữ phần này bên ngoài mã chính và khai báo theo chiều dọc. Một ví dụ là sử dụng chính sách bảo mật trong một ứng dụng. Thông thường, bảo mật chạy qua nhiều yếu tố của một ứng dụng. Hơn nữa, chính sách bảo mật của ứng dụng nên được áp dụng như nhau cho tất cả các phần hiện có và mới của ứng dụng. Đồng thời, chính sách bảo mật đang sử dụng có thể tự phát triển. Đây là nơi hoàn hảo để sử dụng AOP . Ngoài ra, một ví dụ khác là đăng nhập. Có một số lợi thế khi sử dụng phương pháp AOP để ghi nhật ký thay vì thêm chức năng ghi nhật ký theo cách thủ công:
  1. Mã để ghi nhật ký dễ dàng thêm và xóa: tất cả những gì bạn cần làm là thêm hoặc xóa một vài cấu hình của một số khía cạnh.

  2. Tất cả mã nguồn để ghi nhật ký được giữ ở một nơi, vì vậy bạn không cần phải tìm kiếm thủ công tất cả những nơi mã nguồn được sử dụng.

  3. Mã ghi nhật ký có thể được thêm vào bất cứ đâu, cho dù trong các phương thức và lớp đã được viết hay trong chức năng mới. Điều này làm giảm số lỗi mã hóa.

    Ngoài ra, khi xóa một khía cạnh khỏi cấu hình thiết kế, bạn có thể chắc chắn rằng tất cả mã theo dõi đã biến mất và không có gì bị bỏ sót.

  4. Các khía cạnh là mã riêng biệt có thể được cải thiện và sử dụng lại nhiều lần.
AOP là gì?  Nguyên lý lập trình hướng khía cạnh - 2AOP cũng được sử dụng để xử lý ngoại lệ, lưu vào bộ nhớ đệm và trích xuất chức năng nhất định để có thể sử dụng lại.

Nguyên tắc cơ bản của AOP

Để tiến xa hơn trong chủ đề này, trước tiên chúng ta hãy tìm hiểu các khái niệm chính của AOP. Lời khuyên — Logic hoặc mã bổ sung được gọi từ một điểm nối. Lời khuyên có thể được thực hiện trước, sau hoặc thay vì điểm tham gia (thêm về chúng bên dưới). Các loại lời khuyên có thể :
  1. Before — loại lời khuyên này được khởi chạy trước khi các phương thức đích, tức là các điểm tham gia, được thực thi. Khi sử dụng các khía cạnh làm lớp, chúng tôi sử dụng chú thích @B Before để đánh dấu lời khuyên là đến trước. Khi sử dụng các khía cạnh dưới dạng tệp .aj , đây sẽ là phương thức before() .

  2. Sau — lời khuyên được thực thi sau khi hoàn tất việc thực hiện các phương thức (điểm nối), cả trong quá trình thực thi bình thường cũng như khi đưa ra một ngoại lệ.

    Khi sử dụng các khía cạnh làm lớp, chúng ta có thể sử dụng chú thích @after để chỉ ra rằng đây là lời khuyên xuất hiện sau đó.

    Khi sử dụng các khía cạnh dưới dạng tệp .aj , đây là phương thức after() .

  3. Sau khi quay lại - lời khuyên này chỉ được thực hiện khi phương thức đích kết thúc bình thường, không có lỗi.

    Khi các khía cạnh được biểu diễn dưới dạng các lớp, chúng ta có thể sử dụng chú thích @AfterReturning để đánh dấu lời khuyên là đang thực thi sau khi hoàn thành thành công.

    Khi sử dụng các khía cạnh dưới dạng tệp .aj , đây sẽ là phương thức after() trả về (Object obj) .

  4. Sau khi Ném — lời khuyên này dành cho các trường hợp khi một phương thức, nghĩa là, điểm nối, đưa ra một ngoại lệ. Chúng tôi có thể sử dụng lời khuyên này để xử lý một số loại thực thi không thành công (ví dụ: khôi phục toàn bộ giao dịch hoặc ghi nhật ký với mức theo dõi được yêu cầu).

    Đối với các khía cạnh của lớp, chú thích @afterThrowing được sử dụng để chỉ ra rằng lời khuyên này được sử dụng sau khi đưa ra một ngoại lệ.

    Khi sử dụng các khía cạnh dưới dạng tệp .aj , đây sẽ là phương thức ném after() (Ngoại lệ e) .

  5. Xung quanh — có lẽ là một trong những loại lời khuyên quan trọng nhất. Nó bao quanh một phương thức, nghĩa là, một điểm nối mà chúng ta có thể sử dụng, ví dụ, chọn có thực hiện hay không thực hiện một phương thức điểm nối đã cho.

    Bạn có thể viết mã tư vấn chạy trước và sau khi phương pháp điểm nối được thực thi.

    Lời khuyên xung quanh chịu trách nhiệm gọi phương thức điểm nối và các giá trị trả về nếu phương thức trả về một cái gì đó. Nói cách khác, trong lời khuyên này, bạn có thể chỉ cần mô phỏng hoạt động của một phương thức đích mà không cần gọi nó và trả về bất kỳ thứ gì bạn muốn làm kết quả trả về.

    Các khía cạnh được cung cấp dưới dạng các lớp, chúng tôi sử dụng chú thích @Around để tạo lời khuyên bao bọc một điểm nối. Khi sử dụng các khía cạnh ở dạng tệp .aj , phương thức này sẽ là phương thức around() .

Điểm tham gia — điểm trong một chương trình đang chạy (nghĩa là gọi phương thức, tạo đối tượng, truy cập biến) nơi nên áp dụng lời khuyên. Nói cách khác, đây là một loại biểu thức chính quy được sử dụng để tìm các vị trí chèn mã (những vị trí nên áp dụng lời khuyên). Pointcut — một tập hợp các điểm nối . Một điểm cắt xác định xem lời khuyên đã cho có thể áp dụng cho một điểm nối nhất định hay không. Khía cạnh - một mô-đun hoặc lớp thực hiện chức năng xuyên suốt. Aspect thay đổi hành vi của mã còn lại bằng cách áp dụng lời khuyên tại các điểm nối được xác định bởi một số pointcut . Nói cách khác, nó là sự kết hợp của lời khuyên và tham gia điểm. Giới thiệu— thay đổi cấu trúc của một lớp và/hoặc thay đổi hệ thống phân cấp thừa kế để thêm chức năng của khía cạnh vào mã nước ngoài. Mục tiêu — đối tượng mà lời khuyên sẽ được áp dụng. Dệt — quá trình liên kết các khía cạnh với các đối tượng khác để tạo các đối tượng proxy được khuyến nghị. Điều này có thể được thực hiện tại thời gian biên dịch, thời gian tải hoặc thời gian chạy. Có ba kiểu dệt:
  • Kết hợp thời gian biên dịch — nếu bạn có mã nguồn của khía cạnh và mã mà bạn sử dụng khía cạnh đó, thì bạn có thể biên dịch trực tiếp mã nguồn và khía cạnh đó bằng cách sử dụng trình biên dịch AspectJ;

  • Dệt sau biên dịch (dệt nhị phân) — nếu bạn không thể hoặc không muốn sử dụng các biến đổi mã nguồn để dệt các khía cạnh vào mã, bạn có thể lấy các lớp hoặc tệp jar đã biên dịch trước đó và đưa các khía cạnh vào chúng;

  • Dệt thời gian tải - đây chỉ là dệt nhị phân bị trì hoãn cho đến khi trình nạp lớp tải tệp lớp và xác định lớp cho JVM.

    Cần có một hoặc nhiều bộ tải lớp dệt để hỗ trợ điều này. Chúng hoặc được cung cấp rõ ràng bởi thời gian chạy hoặc được kích hoạt bởi một "tác nhân dệt".

AspectJ — Một triển khai cụ thể của mô hình AOP triển khai khả năng thực hiện các nhiệm vụ xuyên suốt. Các tài liệu có thể được tìm thấy ở đây .

Ví dụ trong Java

Tiếp theo, để hiểu rõ hơn về AOP , chúng ta sẽ xem xét các ví dụ nhỏ kiểu "Xin chào thế giới". Ngay lập tức, tôi sẽ lưu ý rằng các ví dụ của chúng tôi sẽ sử dụng dệt thời gian biên dịch . Trước tiên, chúng ta cần thêm phần phụ thuộc sau vào tệp pom.xml của mình :
<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjrt</artifactId>
  <version>1.9.5</version>
</dependency>
Theo quy định, trình biên dịch ajc đặc biệt là cách chúng tôi sử dụng các khía cạnh. IntelliJ IDEA không bao gồm nó theo mặc định, vì vậy khi chọn nó làm trình biên dịch ứng dụng, bạn phải chỉ định đường dẫn đến bản phân phối 5168 75 AspectJ . Đây là cách đầu tiên. Cái thứ hai, cái tôi đã sử dụng, là đăng ký plugin sau trong tệp pom.xml :
<build>
  <plugins>
     <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>aspectj-maven-plugin</artifactId>
        <version>1.7</version>
        <configuration>
           <complianceLevel>1.8</complianceLevel>
           <source>1.8</source>
           <target>1.8</target>
           <showWeaveInfo>true</showWeaveInfo>
           <<verbose>true<verbose>
           <Xlint>ignore</Xlint>
           <encoding>UTF-8</encoding>
        </configuration>
        <executions>
           <execution>
              <goals>
                 <goal>compile</goal>
                 <goal>test-compile</goal>
              </goals>
           </execution>
        </executions>
     </plugin>
  </plugins>
</build>
Sau này, bạn nên nhập lại từ Maven và chạy mvn clean compile . Bây giờ hãy tiến hành trực tiếp đến các ví dụ.

Ví dụ số 1

Hãy tạo một lớp Main . Trong đó, chúng ta sẽ có một điểm vào và một phương thức in tên đã truyền trên bàn điều khiển:
public class Main {

  public static void main(String[] args) {
  printName("Tanner");
  printName("Victor");
  printName("Sasha");
  }

  public static void printName(String name) {
     System.out.println(name);
  }
}
Không có gì phức tạp ở đây. Chúng tôi đã chuyển một tên và hiển thị nó trên bảng điều khiển. Nếu chúng ta chạy chương trình ngay bây giờ, chúng ta sẽ thấy như sau trên bảng điều khiển:
Thợ thuộc da Victor Sasha
Bây giờ, đã đến lúc tận dụng sức mạnh của AOP. Bây giờ chúng ta cần tạo một tệp khía cạnh . Chúng có hai loại: loại thứ nhất có phần mở rộng tệp .aj . Thứ hai là một lớp thông thường sử dụng các chú thích để triển khai các khả năng AOP . Trước tiên hãy xem tệp có phần mở rộng .aj :
public aspect GreetingAspect {

  pointcut greeting() : execution(* Main.printName(..));

  before() : greeting() {
     System.out.print("Hi, ");
  }
}
Tệp này giống như một lớp. Hãy xem điều gì đang xảy ra ở đây: pointcut là một tập hợp các điểm nối; lời chào () là tên của điểm cắt này; : thực thi cho biết áp dụng nó trong quá trình thực hiện tất cả các lệnh gọi ( * ) của phương thức Main.printName(...) . Tiếp theo là một lời khuyên cụ thể — before() — được thực thi trước khi phương thức đích được gọi. : lời chào () là điểm cắt mà lời khuyên này phản hồi. Chà, và bên dưới, chúng ta thấy phần thân của phương thức, được viết bằng ngôn ngữ Java mà chúng ta hiểu. Khi chúng tôi chạy chính với khía cạnh này, chúng tôi sẽ nhận được đầu ra bảng điều khiển này:
Xin chào, Thợ thuộc da Xin chào, Victor Chào, Sasha
Chúng ta có thể thấy rằng mọi cuộc gọi đến phương thức printName đã được sửa đổi nhờ một khía cạnh. Bây giờ chúng ta hãy xem khía cạnh trông như thế nào với tư cách là một lớp Java với các chú thích:
@Aspect
public class GreetingAspect{

  @Pointcut("execution(* Main.printName(String))")
  public void greeting() {
  }

  @Before("greeting()")
  public void beforeAdvice() {
     System.out.print("Hi, ");
  }
}
Sau tệp khía cạnh .aj , mọi thứ trở nên rõ ràng hơn ở đây:
  • @Aspect chỉ ra rằng lớp này là một khía cạnh;
  • @Pointcut("execution(* Main.printName(String))") là điểm cắt được kích hoạt cho tất cả các lệnh gọi tới Main.printName với đối số đầu vào có loại là String ;
  • @B Before("greeting()") là lời khuyên được áp dụng trước khi gọi mã được chỉ định trong điểm dừng hello() .
Chạy chính với khía cạnh này không thay đổi đầu ra của bàn điều khiển:
Xin chào, Thợ thuộc da Xin chào, Victor Chào, Sasha

Ví dụ số 2

Giả sử chúng ta có một số phương thức thực hiện một số thao tác cho khách hàng và chúng ta gọi phương thức này từ main :
public class Main {

  public static void main(String[] args) {
  performSomeOperation("Tanner");
  }

  public static void performSomeOperation(String clientName) {
     System.out.println("Performing some operations for Client " + clientName);
  }
}
Hãy sử dụng chú thích @Around để tạo một "giao dịch giả":
@Aspect
public class TransactionAspect{

  @Pointcut("execution(* Main.performSomeOperation(String))")
  public void executeOperation() {
  }

  @Around(value = "executeOperation()")
  public void beforeAdvice(ProceedingJoinPoint joinPoint) {
     System.out.println("Opening a transaction...");
     try {
        joinPoint.proceed();
        System.out.println("Closing a transaction...");
     }
     catch (Throwable throwable) {
        System.out.println("The operation failed. Rolling back the transaction...");
     }
  }
  }
Với phương thức tiến hành của đối tượng ProceedingJoinPoint , chúng tôi gọi phương thức gói để xác định vị trí của nó trong lời khuyên. Do đó, mã trong phương thức trên joinPoint.proceed(); Before , trong khi mã bên dưới nó là After . Nếu chúng tôi chạy main , chúng tôi sẽ nhận được điều này trong bảng điều khiển:
Mở giao dịch... Thực hiện một số thao tác cho Client Tanner Đóng giao dịch...
Nhưng nếu chúng ta ném và ngoại lệ trong phương thức của mình (để mô phỏng một thao tác không thành công):
public static void performSomeOperation(String clientName) throws Exception {
  System.out.println("Performing some operations for Client " + clientName);
  throw new Exception();
}
Sau đó, chúng tôi nhận được đầu ra bảng điều khiển này:
Đang mở giao dịch... Thực hiện một số thao tác cho Client Tanner Thao tác không thành công. Khôi phục giao dịch...
Vì vậy, những gì chúng tôi đã đạt được ở đây là một loại khả năng xử lý lỗi.

Ví dụ số 3

Trong ví dụ tiếp theo của chúng tôi, hãy làm điều gì đó như đăng nhập vào bảng điều khiển. Trước tiên, hãy xem Main , nơi chúng tôi đã thêm một số logic nghiệp vụ giả:
public class Main {
  private String value;

  public static void main(String[] args) throws Exception {
     Main main = new Main();
     main.setValue("<some value>");
     String valueForCheck = main.getValue();
     main.checkValue(valueForCheck);
  }

  public void setValue(String value) {
     this.value = value;
  }

  public String getValue() {
     return this.value;
  }

  public void checkValue(String value) throws Exception {
     if (value.length() > 10) {
        throw new Exception();
     }
  }
}
Trong main , chúng tôi sử dụng setValue để gán giá trị cho biến thể hiện giá trị . Sau đó, chúng tôi sử dụng getValue để lấy giá trị và sau đó chúng tôi gọi checkValue để xem giá trị đó có dài hơn 10 ký tự hay không. Nếu vậy, thì một ngoại lệ sẽ được ném ra. Bây giờ hãy xem khía cạnh mà chúng ta sẽ sử dụng để ghi lại công việc của các phương thức:
@Aspect
public class LogAspect {

  @Pointcut("execution(* *(..))")
  public void methodExecuting() {
  }

  @AfterReturning(value = "methodExecuting()", returning = "returningValue")
  public void recordSuccessfulExecution(JoinPoint joinPoint, Object returningValue) {
     if (returningValue != null) {
        System.out.printf("Successful execution: method — %s method, class — %s class, return value — %s\n",
              joinPoint.getSignature().getName(),
              joinPoint.getSourceLocation().getWithinType().getName(),
              returningValue);
     }
     else {
        System.out.printf("Successful execution: method — %s, class — %s\n",
              joinPoint.getSignature().getName(),
              joinPoint.getSourceLocation().getWithinType().getName());
     }
  }

  @AfterThrowing(value = "methodExecuting()", throwing = "exception")
  public void recordFailedExecution(JoinPoint joinPoint, Exception exception) {
     System.out.printf("Exception thrown: method — %s, class — %s, exception — %s\n",
           joinPoint.getSignature().getName(),
           joinPoint.getSourceLocation().getWithinType().getName(),
           exception);
  }
}
Những gì đang xảy ra ở đây? @Pointcut("execution(* *(..))") sẽ tham gia tất cả các lệnh gọi của tất cả các phương thức. @afterReturning(value = "methodExecuting()", return = "returningValue") là lời khuyên sẽ được thực hiện sau khi thực hiện thành công phương thức đích. Chúng tôi có hai trường hợp ở đây:
  1. Khi phương thức có giá trị trả về — if (returningValue! = Null) {
  2. Khi không có giá trị trả về — other {
@afterThrowing(value = "methodExecuting()", throw = "Exception") là lời khuyên sẽ được kích hoạt trong trường hợp có lỗi, tức là khi phương thức ném ngoại lệ. Và theo đó, bằng cách chạy main , chúng ta sẽ nhận được một loại ghi nhật ký dựa trên bảng điều khiển:
Thực thi thành công: phương thức — setValue, lớp — Chính Thực thi thành công: phương thức — getValue, lớp — Chính, giá trị trả về — <một số giá trị> Đã ném ngoại lệ: phương thức — checkValue, lớp — Ngoại lệ chính — java.lang.Exception Đã ném ngoại lệ: phương thức — chính, lớp — Chính, ngoại lệ — java.lang.Exception
Và vì chúng tôi không xử lý các ngoại lệ, nên chúng tôi vẫn sẽ nhận được dấu vết ngăn xếp: AOP là gì?  Nguyên lý lập trình hướng khía cạnh - 3Bạn có thể đọc về các ngoại lệ và xử lý ngoại lệ trong các bài viết này: Ngoại lệ trong JavaNgoại lệ: nắm bắt và xử lý . Đó là tất cả cho tôi ngày hôm nay. Hôm nay chúng ta đã làm quen với AOP , và bạn có thể thấy rằng con quái vật này không đáng sợ như một số người nghĩ. Tạm biệt tất cả mọi người!
Bình luận
  • Phổ biến
  • Mới
Bạn phải đăng nhập để đăng nhận xet
Trang này chưa có bất kỳ bình luận nào