
การสมัคร AOP
การเขียนโปรแกรมเชิงมุมมองได้รับการออกแบบให้ทำงานแบบตัดขวาง ซึ่งสามารถเป็นรหัสใดๆ ที่อาจทำซ้ำได้หลายครั้งด้วยวิธีการต่างๆ ซึ่งไม่สามารถจัดโครงสร้างเป็นโมดูลแยกต่างหากได้อย่างสมบูรณ์ ดังนั้นAOP จึง ช่วยให้เราเก็บสิ่งนี้ไว้นอกโค้ดหลักและประกาศในแนวตั้ง ตัวอย่างคือการใช้นโยบายความปลอดภัยในแอปพลิเคชัน โดยปกติแล้ว การรักษาความปลอดภัยจะทำงานผ่านองค์ประกอบหลายอย่างของแอปพลิเคชัน ยิ่งไปกว่านั้น นโยบายความปลอดภัยของแอปพลิเคชันควรนำไปใช้กับส่วนที่มีอยู่และส่วนใหม่ทั้งหมดของแอปพลิเคชันอย่างเท่าเทียมกัน ในขณะเดียวกัน นโยบายความปลอดภัยที่ใช้งานอยู่สามารถพัฒนาได้เอง นี่คือสถานที่ที่สมบูรณ์แบบสำหรับการใช้AOP อีกตัวอย่างหนึ่งคือการบันทึก. มีข้อดีหลายประการในการใช้วิธี AOP ในการบันทึกแทนที่จะเพิ่มฟังก์ชันการบันทึกด้วยตนเอง:-
รหัสสำหรับการบันทึกนั้นง่ายต่อการเพิ่มและลบ: ทั้งหมดที่คุณต้องทำคือเพิ่มหรือลบการกำหนดค่าบางอย่างของบางลักษณะ
-
ซอร์สโค้ดทั้งหมดสำหรับการบันทึกจะถูกเก็บไว้ในที่เดียว ดังนั้นคุณไม่จำเป็นต้องค้นหาตำแหน่งทั้งหมดที่ถูกใช้งานด้วยตนเอง
-
สามารถเพิ่มรหัสบันทึกได้ทุกที่ ไม่ว่าจะในเมธอดและคลาสที่เขียนไปแล้วหรือในฟังก์ชันใหม่ สิ่งนี้จะลดจำนวนข้อผิดพลาดในการเข้ารหัส
นอกจากนี้ เมื่อลบด้านออกจากการกำหนดค่าการออกแบบ คุณจะมั่นใจได้ว่าโค้ดการติดตามทั้งหมดหายไปและไม่มีอะไรหายไป
- ลักษณะเป็นรหัสแยกต่างหากที่สามารถปรับปรุงและใช้ซ้ำแล้วซ้ำอีก

หลักการพื้นฐานของ AOP
หากต้องการก้าวต่อไปในหัวข้อนี้ เรามาทำความรู้จักกับแนวคิดหลักของ AOP กันก่อน คำแนะนำ — ตรรกะหรือรหัสเพิ่มเติมที่เรียกจากจุดรวม คำแนะนำสามารถดำเนินการก่อน หลัง หรือแทนการเข้าร่วม (เพิ่มเติมเกี่ยวกับสิ่งเหล่านี้ด้านล่าง) ประเภทของคำแนะนำที่เป็นไปได้:-
ก่อนหน้า — คำแนะนำประเภทนี้ถูกเรียกใช้ก่อนที่เมธอดเป้าหมาย เช่น จุดรวม จะถูกดำเนินการ เมื่อใช้คุณลักษณะเป็นคลาส เราจะใช้ คำอธิบาย ประกอบ @Beforeเพื่อทำเครื่องหมายคำแนะนำว่ามาก่อน เมื่อใช้ลักษณะเป็น ไฟล์ .ajนี่จะเป็นเมธอดbefore()
- After — คำแนะนำที่ดำเนินการหลังจากการดำเนินการของเมธอด (จุดร่วม) เสร็จสิ้น ทั้งในการดำเนินการปกติและเมื่อมีการโยนข้อยกเว้น
เมื่อใช้คุณลักษณะเป็นคลาส เราสามารถใช้ คำอธิบายประกอบ @Afterเพื่อระบุว่านี่คือคำแนะนำที่ตามมา
เมื่อใช้ลักษณะเป็น ไฟล์ .ajนี่คือเมธอดafter()
-
After Returning — คำแนะนำนี้จะดำเนินการเฉพาะเมื่อวิธีการเป้าหมายเสร็จสิ้นตามปกติ โดยไม่มีข้อผิดพลาด
เมื่อลักษณะต่างๆ แสดงเป็นคลาส เราสามารถใช้ คำอธิบาย ประกอบ @AfterReturningเพื่อทำเครื่องหมายคำแนะนำว่ากำลังดำเนินการหลังจากดำเนินการเสร็จสิ้น
เมื่อใช้ลักษณะเป็น ไฟล์ .ajนี่จะเป็นเมธอดการคืนค่าหลัง () (Object obj)
-
After Throwing — คำแนะนำนี้มีไว้สำหรับกรณีที่เมธอด เช่น join point โยนข้อยกเว้น เราสามารถใช้คำแนะนำนี้เพื่อจัดการกับการดำเนินการที่ล้มเหลวบางประเภท (เช่น เพื่อย้อนกลับธุรกรรมทั้งหมดหรือบันทึกด้วยระดับการติดตามที่จำเป็น)
สำหรับแง่มุมของคลาส คำอธิบายประกอบ @AfterThrowingใช้เพื่อระบุว่าคำแนะนำนี้ใช้หลังจากโยนข้อยกเว้น
เมื่อใช้ลักษณะเป็น ไฟล์ .ajนี่จะเป็นเมธอดafter() โยน (ยกเว้น e)
-
รอบ ๆ - อาจเป็นหนึ่งในคำแนะนำที่สำคัญที่สุด มันล้อมรอบเมธอด นั่นคือ จุดรวมที่เราสามารถใช้ ตัวอย่างเช่น เลือกว่าจะดำเนินการเมธอดจุดรวมที่กำหนดหรือไม่
คุณสามารถเขียนโค้ดคำแนะนำที่ทำงานก่อนและหลังการดำเนินการเมธอดจุดรวม
คำแนะนำรอบ ๆมีหน้าที่รับผิดชอบในการเรียกเมธอดจุดรวมและค่าที่ส่งคืนหากเมธอดส่งคืนบางสิ่ง กล่าวอีกนัยหนึ่ง ในคำแนะนำนี้ คุณสามารถจำลองการทำงานของเมธอดเป้าหมายโดยไม่ต้องเรียกใช้ และส่งคืนสิ่งที่คุณต้องการเป็นผลลัพธ์การส่งคืน
ให้ลักษณะเป็นคลาส เราใช้ คำอธิบาย ประกอบ @Aroundเพื่อสร้างคำแนะนำที่ล้อมรอบจุดรวม เมื่อใช้ลักษณะต่างๆ ในรูปแบบ ไฟล์ .ajเมธอดนี้จะเป็นเมธอดaround()
-
คอมไพล์ไทม์เวฟ — ถ้าคุณมีซอร์สโค้ดของส่วนและรหัสที่คุณใช้ส่วนนั้น คุณสามารถคอมไพล์ซอร์สโค้ดและส่วนได้โดยตรงโดยใช้คอมไพเลอร์ AspectJ
-
การทอหลังการคอมไพล์ (การทอแบบไบนารี) — หากคุณไม่สามารถหรือไม่ต้องการใช้การแปลงซอร์สโค้ดเพื่อสานลักษณะต่างๆ ลงในโค้ด คุณสามารถใช้คลาสหรือไฟล์ jar ที่คอมไพล์ไว้ก่อนหน้านี้และใส่ลักษณะต่างๆ เข้าไปได้
-
การทอเวลาโหลด — นี่เป็นเพียงการทอแบบไบนารีที่ล่าช้าจนกว่าตัวโหลดคลาสจะโหลดไฟล์คลาสและกำหนดคลาสสำหรับ JVM
จำเป็นต้องมีตัวโหลดคลาสการทอผ้าหนึ่งตัวขึ้นไปเพื่อรองรับสิ่งนี้ มีให้โดยรันไทม์หรือเปิดใช้งานโดย "ตัวแทนการทอ"
ตัวอย่างในภาษาจาวา
ต่อไป เพื่อความเข้าใจที่ดีขึ้นเกี่ยวกับAOPเราจะดูตัวอย่างรูปแบบ "Hello World" เล็กๆ ทางขวาของค้างคาว ฉันจะสังเกตว่าตัวอย่างของเราจะใช้ การคอมไพ ล์เวลาทอ ก่อนอื่น เราต้องเพิ่มการพึ่งพาต่อไปนี้ใน ไฟล์ pom.xml ของเรา :
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.5</version>
</dependency>
ตามกฎแล้ว คอมไพเลอร์ ajc พิเศษ คือวิธีที่เราใช้ลักษณะต่างๆ IntelliJ IDEAไม่รวมไว้โดยค่าเริ่มต้น ดังนั้นเมื่อเลือกเป็นคอมไพเลอร์ของแอปพลิเคชัน คุณต้องระบุพาธไปยังการแจกจ่าย 5168 75 AspectJ นี่เป็นวิธีแรก ประการที่สองซึ่งเป็นสิ่งที่ฉันใช้คือการลงทะเบียนปลั๊กอินต่อไปนี้ใน ไฟล์ 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>
หลังจากนี้ เป็นความคิดที่ดีที่จะนำเข้าจากMaven ใหม่และรันmvn clean compile ตอนนี้เรามาดำเนินการโดยตรงกับตัวอย่าง
ตัวอย่างที่ 1
มาสร้างMain class กัน เถอะ ในนั้น เราจะมีจุดเริ่มต้นและเมธอดที่พิมพ์ชื่อผ่านบนคอนโซล:
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);
}
}
ไม่มีอะไรซับซ้อนที่นี่ เราส่งชื่อและแสดงบนคอนโซล หากเราเรียกใช้โปรแกรมตอนนี้ เราจะเห็นสิ่งต่อไปนี้บนคอนโซล:
public aspect GreetingAspect {
pointcut greeting() : execution(* Main.printName(..));
before() : greeting() {
System.out.print("Hi, ");
}
}
ไฟล์นี้เป็นเหมือนคลาส มาดูกันว่าเกิดอะไรขึ้นที่นี่: pointcutคือชุดของจุดรวม greeting()คือชื่อของ pointcut นี้ : การดำเนินการบ่งชี้ว่าจะใช้ระหว่างการดำเนินการเรียก ( * ) ทั้งหมดของเมธอดMain.printName(...) ถัดมาเป็นคำแนะนำเฉพาะ — before() — ซึ่งดำเนินการก่อนที่จะเรียกเมธอดเป้าหมาย : greeting()เป็นจุดตัดที่คำแนะนำนี้ตอบสนอง และด้านล่างเราจะเห็นเนื้อหาของวิธีการเองซึ่งเขียนด้วยภาษา Java ซึ่งเราเข้าใจ เมื่อเราเรียกใช้mainด้วยลักษณะนี้ เราจะได้เอาต์พุตคอนโซลนี้:
@Aspect
public class GreetingAspect{
@Pointcut("execution(* Main.printName(String))")
public void greeting() {
}
@Before("greeting()")
public void beforeAdvice() {
System.out.print("Hi, ");
}
}
หลังจาก ไฟล์ขนาด .ajทุกอย่างจะชัดเจนมากขึ้นที่นี่:
- @Aspectระบุว่าคลาสนี้เป็นลักษณะ
- @Pointcut("execution(* Main.printName(String))")เป็นจุดตัดที่เรียกใช้สำหรับการเรียกทั้งหมดไปยังMain.printNameด้วยอาร์กิวเมนต์อินพุตที่มีประเภทคือString ;
- @Before("greeting()")เป็นคำแนะนำที่ใช้ก่อนที่จะเรียกรหัสที่ระบุในจุดตัดการทักทาย()
ตัวอย่างหมายเลข 2
สมมติว่าเรามีเมธอดที่ดำเนินการบางอย่างสำหรับลูกค้า และเราเรียกเมธอดนี้จาก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);
}
}
ลองใช้ คำอธิบายประกอบ @Aroundเพื่อสร้าง "ธุรกรรมหลอก":
@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...");
}
}
}
ด้วย วิธี การดำเนินการของ วัตถุ ProceedingJoinPointเราเรียกวิธีการห่อเพื่อระบุตำแหน่งในคำแนะนำ ดังนั้นโค้ดในเมธอดด้านบนjoinPoint.proceed(); คือBeforeในขณะที่โค้ดด้านล่างคือAfter ถ้าเราเรียกใช้mainเราจะได้สิ่งนี้ในคอนโซล:
public static void performSomeOperation(String clientName) throws Exception {
System.out.println("Performing some operations for Client " + clientName);
throw new Exception();
}
จากนั้นเราจะได้เอาต์พุตคอนโซลนี้:
ตัวอย่างหมายเลข 3
ในตัวอย่างต่อไป เราจะทำบางอย่าง เช่น การเข้าสู่คอนโซล อันดับแรก ให้ดูที่Mainซึ่งเราได้เพิ่มตรรกะทางธุรกิจหลอก:
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();
}
}
}
ในmainเราใช้setValueเพื่อกำหนดค่าให้กับตัวแปรอินสแตนซ์ของค่า จากนั้นเราใช้getValueเพื่อรับค่า แล้วเรียก checkValue เพื่อดูว่ามีความยาวเกิน 10 อักขระหรือไม่ หากเป็นเช่นนั้น ข้อยกเว้นจะถูกโยนทิ้งไป ทีนี้มาดูลักษณะที่เราจะใช้ในการบันทึกการทำงานของเมธอด:
@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);
}
}
เกิดอะไรขึ้นที่นี่? @Pointcut("execution(* *(..))")จะเข้าร่วมการโทรทุกวิธี @AfterReturning(value = "methodExecuting()", return = "returningValue")เป็นคำแนะนำที่จะดำเนินการหลังจากการดำเนินการตามวิธีการเป้าหมายสำเร็จ เรามีสองกรณีที่นี่:
- เมื่อเมธอดมีค่าตอบแทน — if (returningValue! = Null) {
- เมื่อไม่มีค่าส่งคืน — อื่น {

GO TO FULL VERSION