
החלת AOP
תכנות מונחה היבטים נועד לבצע משימות צולבות, שיכולות להיות כל קוד שעלול לחזור על עצמו פעמים רבות בשיטות שונות, שלא ניתן לבנות לחלוטין במודול נפרד. בהתאם לכך, AOP מאפשר לנו לשמור את זה מחוץ לקוד הראשי ולהכריז על כך בצורה אנכית. דוגמה לכך היא שימוש במדיניות אבטחה באפליקציה. בדרך כלל, אבטחה עוברת דרך אלמנטים רבים של יישום. יתרה מכך, מדיניות האבטחה של האפליקציה צריכה להיות מיושמת באופן שווה על כל החלקים הקיימים והחדשים של האפליקציה. במקביל, מדיניות אבטחה בשימוש יכולה להתפתח בעצמה. זה המקום המושלם להשתמש ב-AOP . כמו כן, דוגמה נוספת היא רישום . ישנם מספר יתרונות לשימוש בגישת AOP לרישום ולא בהוספת פונקציונלי רישום ידנית:-
קל להוסיף ולהסיר את הקוד לרישום: כל מה שאתה צריך לעשות הוא להוסיף או להסיר כמה תצורות של היבט כלשהו.
-
כל קוד המקור לרישום נשמר במקום אחד, כך שלא תצטרך לחפש ידנית את כל המקומות שבהם נעשה בו שימוש.
-
ניתן להוסיף קוד רישום בכל מקום, בין אם בשיטות ומחלקות שכבר נכתבו ובין אם בפונקציונליות חדשה. זה מקטין את מספר שגיאות הקידוד.
כמו כן, בעת הסרת היבט מתצורת עיצוב, אתה יכול להיות בטוח שכל קוד המעקב נעלם וששום דבר לא הוחמצ.
- היבטים הם קוד נפרד שניתן לשפר ולהשתמש בו שוב ושוב.

עקרונות בסיסיים של AOP
כדי להתקדם בנושא זה, בואו נכיר תחילה את המושגים העיקריים של AOP. עצה - הגיון נוסף או קוד שנקרא מנקודת הצטרפות. ניתן לבצע ייעוץ לפני, אחרי או במקום נקודת הצטרפות (עוד עליהם בהמשך). סוגי עצות אפשריות :-
לפני - ייעוץ מסוג זה מופעל לפני ביצוע שיטות יעד, כלומר נקודות צירוף. בעת שימוש בהיבטים כשיעורים, אנו משתמשים בהערת @Before כדי לסמן את העצה כמגיעה לפני. בעת שימוש באספקטים כקבצי .aj , זו תהיה שיטת before() .
- לאחר - עצה שמתבצעת לאחר ביצוע שיטות (נקודות צירוף) הושלמה, הן בביצוע רגיל והן בעת זריקת חריגה.
כאשר משתמשים בהיבטים כשיעורים, אנו יכולים להשתמש בביאור @After כדי לציין שזו עצה שמגיעה לאחר מכן.
כאשר משתמשים באספקטים כקבצי .aj , זוהי שיטת after() .
-
לאחר החזרה - עצה זו מבוצעת רק כאשר שיטת היעד מסתיימת כרגיל, ללא שגיאות.
כאשר היבטים מיוצגים כשיעורים, אנו יכולים להשתמש בביאור @AfterReturning כדי לסמן את העצה כמבוצעת לאחר סיום מוצלח.
בעת שימוש באספקטים כקבצי .aj , זו תהיה שיטת after() המחזירה (Object obj) .
-
לאחר השלכה - עצה זו מיועדת למקרים שבהם שיטה, כלומר נקודת הצטרפות, זורקת חריגה. אנו יכולים להשתמש בעצה זו כדי לטפל בסוגים מסוימים של ביצוע כושל (לדוגמה, לבטל עסקה שלמה או יומן רישום עם רמת המעקב הנדרשת).
עבור היבטי הכיתה, ההערה @AfterThrowing משמשת כדי לציין שהעצה הזו משמשת לאחר זריקת חריגה.
בעת שימוש באספקטים כקבצי .aj , זו תהיה שיטת after() throwing (Exception e) .
-
מסביב - אולי אחד מסוגי העצות החשובים ביותר. הוא מקיף שיטה, כלומר נקודת צירוף שבה נוכל להשתמש כדי, למשל, לבחור אם לבצע שיטת נקודת צירוף נתונה או לא.
אתה יכול לכתוב קוד עצה שרץ לפני ואחרי ביצוע שיטת ה-join point.
העצה סביב אחראית לקריאה לשיטת ה-join point ולערכי ההחזרה אם השיטה מחזירה משהו. במילים אחרות, בעצה זו תוכלו פשוט לדמות פעולת שיטת מטרה מבלי לקרוא לה, ולהחזיר מה שתרצו כתוצאה מהחזרה.
בהתחשב בהיבטים כשיעורים, אנו משתמשים בביאור @Around כדי ליצור עצות שעוטפות נקודת הצטרפות. בעת שימוש בהיבטים בצורה של קבצי .aj , שיטה זו תהיה שיטת around() .
-
אריגה בזמן קומפילציה — אם יש לך את קוד המקור של ההיבט ואת הקוד שבו אתה משתמש בהיבט, אז אתה יכול לקמפל את קוד המקור ואת ההיבט ישירות באמצעות מהדר AspectJ;
-
אריגה לאחר הידור (אריגה בינארית) - אם אינך יכול או רוצה להשתמש בטרנספורמציות של קוד מקור כדי לארוג היבטים לתוך הקוד, אתה יכול לקחת מחלקות או קבצי jar שהידור קודם לכן ולהחדיר לתוכם היבטים;
-
אריגה בזמן טעינה - זוהי רק אריגה בינארית שמתעכבת עד שה-classloader יטען את קובץ המחלקה ויגדיר את המחלקה עבור ה-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
בואו ניצור מחלקה ראשית . בו תהיה לנו נקודת כניסה ושיטה שמדפיסה שם עבר בקונסולה:
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() הוא השם של נקודת חיתוך זו; : ביצוע מציין להחיל אותו במהלך ביצוע כל ( * ) הקריאות של השיטה Main.printName(...) . לאחר מכן מגיעה עצה ספציפית - before() - שמתבצעת לפני שנקראת שיטת המטרה. : greeting() היא נקודת החיתוך שהעצה הזו מגיבה אליה. ובכן, ולמטה אנו רואים את גוף השיטה עצמה, שנכתב בשפת ג'אווה, אותה אנו מבינים. כשנריץ את ה-main עם ההיבט הזה נוכח, נקבל את הפלט הזה של המסוף:
@Aspect
public class GreetingAspect{
@Pointcut("execution(* Main.printName(String))")
public void greeting() {
}
@Before("greeting()")
public void beforeAdvice() {
System.out.print("Hi, ");
}
}
אחרי קובץ ה- .aj aspect, הכל הופך ברור יותר כאן:
- @Aspect מציין שמחלקה זו היא היבט;
- @Pointcut("execution(* Main.printName(String))") היא נקודת החיתוך המופעלת עבור כל הקריאות ל- Main.printName עם ארגומנט קלט שהסוג שלו הוא String ;
- @Before("greeting()") היא עצה המיושמת לפני קריאה לקוד שצוין בנקודת החיתוך של greeting() .
דוגמה מס' 2
נניח שיש לנו שיטה כלשהי שמבצעת פעולות מסוימות עבור לקוחות, ואנחנו קוראים לשיטה הזו מהראשי :
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(); הוא לפני , בעוד שהקוד שמתחתיו הוא אחרי . אם נריץ את 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()", returning = "returningValue") היא עצה שתבוצע לאחר ביצוע מוצלח של שיטת המטרה. יש לנו כאן שני מקרים:
- כאשר למתודה יש ערך החזרה - if (returningValue! = Null) {
- כשאין ערך החזרה - else {

GO TO FULL VERSION