Á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:-
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.
-
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.
-
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.
- 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.
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ể :-
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() .
- 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() .
-
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) .
-
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) .
-
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() .
-
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".
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:
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:
@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() .
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(); là 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:
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:
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:
- Khi phương thức có giá trị trả về — if (returningValue! = Null) {
- Khi không có giá trị trả về — other {
GO TO FULL VERSION