JavaRush /בלוג Java /Random-HE /מה זה AOP? יסודות תכנות מונחה היבטים

מה זה AOP? יסודות תכנות מונחה היבטים

פורסם בקבוצה
היי ח'ברה! בלי להבין את המושגים הבסיסיים, די קשה להתעמק במסגרות ובגישות לבניית פונקציונליות. אז היום נדבר על אחד מהמושגים האלה - AOP, או תכנות מונחה היבטים . מה זה AOP?  יסודות של תכנות מונחה-היבט - 1זהו נושא לא קל ולא נעשה בו שימוש ישיר לעתים קרובות, אך מסגרות וטכנולוגיות רבות משתמשות בו מתחת למכסה המנוע. וכמובן, לפעמים במהלך ראיונות ייתכן שתתבקשו לומר לכם באופן כללי באיזה חיה מדובר ואיפה ניתן להשתמש בה. אז בואו נסתכל על המושגים הבסיסיים וכמה דוגמאות פשוטות של AOP ב-Java . מה זה AOP?  יסודות של תכנות מונחה-היבט - 2אז, AOP - תכנות מונחה היבט - היא פרדיגמה שמטרתה להגביר את המודולריות של חלקים שונים של יישום על ידי הפרדת חששות רוחביים. לשם כך, התנהגות נוספת מתווספת לקוד הקיים, מבלי לשנות את הקוד המקורי. במילים אחרות, נראה שאנו תולים פונקציונליות נוספת על גבי שיטות ומחלקות מבלי לבצע שינויים בקוד המשונה. למה זה נחוץ? במוקדם או במאוחר אנו מגיעים למסקנה שהגישה הרגילה מכוונת עצמים לא תמיד יכולה לפתור ביעילות בעיות מסוימות. ברגע כזה, AOP נחלצת לעזרה ונותנת לנו כלים נוספים לבניית האפליקציה. וכלים נוספים פירושם גמישות מוגברת בפיתוח, שבזכותה יש יותר אפשרויות לפתרון בעיה מסוימת.

יישום AOP

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

מושגי יסוד של AOP

כדי להתקדם הלאה בניתוח הנושא, בואו נכיר תחילה את המושגים העיקריים של AOP. העצה היא היגיון נוסף, קוד, שנקרא מנקודת החיבור. את העצה ניתן לבצע לפני, אחרי או במקום נקודת החיבור (עוד עליהם בהמשך). סוגי עצות אפשריות :
  1. לפני (לפני) - ייעוץ מסוג זה יוצא לדרך לפני ביצוע שיטות מטרה - נקודות חיבור. כאשר משתמשים בהיבטים כשיעורים, אנו לוקחים את ההערה @Before כדי לסמן את סוג העצה כמגיע קודם. בעת שימוש באספקטים כקבצי .aj , זו תהיה שיטת before() .
  2. לאחר (אחרי) - ייעוץ שמתבצע לאחר סיום ביצוע שיטות - נקודות חיבור, הן במקרים רגילים והן בעת ​​זריקת חריגה.
    כשמשתמשים באספקטים כשיעורים, נוכל להשתמש בביאור @After כדי לציין שזהו טיפ שבא אחריו.
    בעת שימוש באספקטים כקבצי .aj , זו תהיה שיטת after() .
  3. לאחר החזרה - טיפים אלו מבוצעים רק אם שיטת המטרה פועלת כרגיל, ללא שגיאות.
    כאשר היבטים מיוצגים כשיעורים, אנו יכולים להשתמש בביאור @AfterReturning כדי לסמן את העצה כמבוצעת עם סיום מוצלח.
    בעת שימוש באספקטים כקבצי .aj, זו תהיה שיטת after() המחזירה (Object obj) .
  4. After Throwing - עצה מסוג זה מיועדת לאותם מקרים שבהם שיטה, כלומר נקודת חיבור, זורקת חריגה. אנו יכולים להשתמש בעצה זו לטיפול מסוים בביצוע הכושל (לדוגמה, החזרה לאחור של כל העסקה או רישום ברמת המעקב הנדרשת).
    עבור מחלקות היבט, ההערה @AfterThrowing משמשת כדי לציין כי נעשה שימוש בעצה זו לאחר זריקת חריגה.
    בעת שימוש באספקטים בצורה של קבצי .aj , זו תהיה השיטה - after() throwing (Exception e) .
  5. Around הוא אולי אחד מסוגי העצות החשובים ביותר המקיפים שיטה, כלומר נקודת חיבור, איתה אנו יכולים למשל לבחור אם לבצע שיטת נקודת חיבור נתונה או לא.
    אתה יכול לכתוב קוד עצה שרץ לפני ואחרי הפעלת שיטת ה-join point.
    תחומי האחריות של הייעוץ כוללים קריאה לשיטת ה-join point והחזרת ערכים אם השיטה מחזירה משהו. כלומר, בטיפ הזה תוכלו פשוט לחקות את פעולת שיטת המטרה מבלי לקרוא לה, ולהחזיר משהו משלכם כתוצאה מכך.
    להיבטים בצורת שיעורים, אנו משתמשים בביאור @Around כדי ליצור טיפים שעוטפים את נקודת החיבור. בעת שימוש באספקטים כקבצי .aj , זו תהיה שיטת around() .
נקודת צירוף - נקודה בתוכנית מבצעת (קריאה למתודה, יצירת אובייקט, גישה למשתנה) שבה יש ליישם עצות. במילים אחרות, מדובר בביטוי רגולרי כלשהו, ​​שבעזרתו מוצאים מקומות להכנסת קוד (מקומות ליישום טיפים). Pointcut הוא קבוצה של נקודות חיבור . החיתוך קובע אם נקודת חיבור נתונה מתאימה לקצה נתון. Aspect הוא מודול או מחלקה המיישמת פונקציונליות מקצה לקצה. היבט משנה את ההתנהגות של שאר הקוד על ידי יישום עצות בנקודות הצטרפות המוגדרות על ידי חלק כלשהו . במילים אחרות, מדובר בשילוב של טיפים ונקודות חיבור. מבוא - שינוי מבנה מחלקה ו/או שינוי היררכיית הירושה כדי להוסיף פונקציונליות היבט לקוד זר. היעד הוא האובייקט שעליו יוחל העצה. אריגה היא תהליך של קישור היבטים עם אובייקטים אחרים כדי ליצור את אובייקטי ה-proxy המומלצים. זה יכול להיעשות בזמן קומפילציה, זמן טעינה או זמן ריצה. ישנם שלושה סוגי אריגה:
  • אריגה בזמן קומפילציה - אם יש לך את קוד המקור של אספקט ואת הקוד שבו אתה משתמש באספקטים, אתה יכול להרכיב את קוד המקור והאספקט ישירות באמצעות מהדר AspectJ;
  • אריגה שלאחר קומפילציה (אריגה בינארית) - אם אינך יכול או לא רוצה להשתמש בטרנספורמציות של קוד מקור כדי לשזור היבטים לתוך הקוד שלך, אתה יכול לקחת מחלקות או צנצנות שהורכבו כבר ולהזריק היבטים;
  • אריגה בזמן טעינה היא פשוט אריגה בינארית שנדחית עד שמטען המחלקה יטען את קובץ המחלקה ויגדיר את המחלקה עבור ה-JVM.
    כדי לתמוך בכך, נדרשים "מטעני מחלקת אריגה" אחד או יותר. הם מסופקים במפורש על ידי זמן הריצה או מופעלים על ידי "סוכן האריגה".
AspectJ הוא יישום ספציפי של פרדיגמות AOP המיישמת את היכולת לפתור בעיות צולבות. תיעוד ניתן למצוא כאן .

דוגמאות בג'אווה

לאחר מכן, להבנה טובה יותר של AOP, נסתכל על דוגמאות קטנות של רמת Hello World. מה זה AOP?  יסודות תכנות מונחה-היבט - 4הרשו לי מיד לציין שבדוגמאות שלנו נשתמש באריגה בזמן הידור . ראשית עלינו להוסיף את התלות הבאה ל- pom.xml שלנו :
<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjrt</artifactId>
  <version>1.9.5</version>
</dependency>
ככלל, מהדר Ajs מיוחד משמש לשימוש באספקטים . ל- IntelliJ IDEA אין את זה כברירת מחדל, אז כשאתה בוחר בו כקומפיילר יישומים עליך לציין את הנתיב להפצת AspectJ . תוכל לקרוא עוד על שיטת הבחירה ב- Ajs כקומפיילר בעמוד זה. זו הייתה השיטה הראשונה, והשנייה (בה השתמשתי) הייתה להוסיף את התוסף הבא ל- 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("Толя");
  printName("Вова");
  printName("Sasha");
  }

  public static void printName(String name) {
     System.out.println(name);
  }
}
שום דבר מסובך: הם העבירו את השם והציגו אותו בקונסולה. אם נריץ אותו כעת, המסוף יציג:
טוליה וובה סשה
ובכן, הגיע הזמן לנצל את הכוח של AOP. כעת עלינו ליצור היבט של קובץ . הם מגיעים בשני סוגים: הראשון הוא קובץ עם סיומת .aj , השני הוא מחלקה רגילה המיישמת יכולות AOP באמצעות הערות. בואו נסתכל תחילה על קובץ עם סיומת .aj :
public aspect GreetingAspect {

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

  before() : greeting() {
     System.out.print("Привет ");
  }
}
קובץ זה דומה במקצת למחלקה. בואו להבין מה קורה כאן: pointcut - חתך או קבוצה של נקודות חיבור; greeting() - השם של פרוסה זו; : ביצוע - בעת ביצוע * - הכל, קרא - Main.printName(..) - שיטה זו. לאחר מכן מגיעה העצה הספציפית - before() - שמתבצעת לפני שנקראת שיטת המטרה, : greeting() - הפרוסה אליה מגיבה העצה הזו, ולמטה נראה את גוף השיטה עצמה, שנכתב ב-Java שפה שאנו מבינים. כאשר נפעיל את ה-main עם היבט זה נוכח, נקבל את הפלט הבא לקונסולה:
שלום טוליה שלום וובה שלום סשה
אנו יכולים לראות שכל קריאה לשיטת printName שונתה על ידי אספקט. עכשיו בואו נסתכל על איך ייראה ההיבט, אבל כמחלקת Java עם הערות:
@Aspect
public class GreetingAspect{

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

  @Before("greeting()")
  public void beforeAdvice() {
     System.out.print("Привет ");
  }
}
אחרי קובץ ה-.aj aspect , הכל ברור יותר:
  • @Aspect מציין שהמחלקה הנתונה היא היבט;
  • @Pointcut("execution(* Main.printName(String))") היא נקודת חיתוך המופעלת בכל הקריאות ל-Main.printName עם ארגומנט נכנס מסוג String ;
  • @Before("greeting()") - עצה המיושמת לפני קריאה לקוד המתואר בנקודת החיתוך של greeting() .
הפעלת ראשי עם היבט זה לא תשנה את פלט המסוף:
שלום טוליה שלום וובה שלום סשה

דוגמה מס' 2

נניח שיש לנו שיטה שמבצעת כמה פעולות עבור לקוחות ונקרא לשיטה הזו מהראשי :
public class Main {

  public static void main(String[] args) {
  makeSomeOperation("Толя");
  }

  public static void makeSomeOperation(String clientName) {
     System.out.println("Выполнение некоторых операций для клиента - " + clientName);
  }
}
באמצעות ההערה @Around , בואו נעשה משהו כמו "פסאודו-עסקה":
@Aspect
public class TransactionAspect{

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

  @Around(value = "executeOperation()")
  public void beforeAdvice(ProceedingJoinPoint joinPoint) {
     System.out.println("Открытие транзакции...");
     try {
        joinPoint.proceed();
        System.out.println("Закрытие транзакции....");
     }
     catch (Throwable throwable) {
        System.out.println("Операция не удалась, откат транзакции...");
     }
  }
  }
באמצעות שיטת המשך של האובייקט ProceedingJoinPoint , אנו קוראים למתודה של ה-wrapper כדי לקבוע את מקומו בלוח ובהתאם, הקוד בשיטה שלמעלה joinPoint.proceed(); - זה לפני , שהוא למטה - אחרי . אם נפעיל את ה-main נקבל בקונסולה:
פתיחת עסקה... ביצוע מספר פעולות עבור הלקוח - טוליה סגירת עסקה....
אם נוסיף זריקת חריגה לשיטה שלנו (פתאום הפעולה נכשלת):
public static void makeSomeOperation(String clientName)throws Exception {
  System.out.println("Выполнение некоторых операций для клиента - " + 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("<некоторое meaning>");
     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, נגדיר את הערך של המשתנה הפנימי - value , לאחר מכן באמצעות 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("Успешно выполнен метод - %s, класса- %s, с результатом выполнения - %s\n",
              joinPoint.getSignature().getName(),
              joinPoint.getSourceLocation().getWithinType().getName(),
              returningValue);
     }
     else {
        System.out.printf("Успешно выполнен метод - %s, класса- %s\n",
              joinPoint.getSignature().getName(),
              joinPoint.getSourceLocation().getWithinType().getName());
     }
  }

  @AfterThrowing(value = "methodExecuting()", throwing = "exception")
  public void recordFailedExecution(JoinPoint joinPoint, Exception exception) {
     System.out.printf("Метод - %s, класса- %s, был аварийно завершен с исключением - %s\n",
           joinPoint.getSignature().getName(),
           joinPoint.getSourceLocation().getWithinType().getName(),
           exception);
  }
}
מה קורה פה? @Pointcut("execution(* *(..))") - יתחבר לכל הקריאות לכל השיטות; @AfterReturning(value = "methodExecuting()", returning = "returningValue") - עצה שתבוצע לאחר השלמת שיטת היעד בהצלחה. יש לנו כאן שני מקרים:
  1. כאשר למתודה יש ​​ערך החזרה if (returningValue != null) {
  2. כשאין ערך החזרה אחר {
@AfterThrowing(value = "methodExecuting()", throwing = "exception") - עצה שתופעל במקרה של שגיאה, כלומר כאשר נזרק חריג מהשיטה. ובהתאם לכך, על ידי הפעלת main , נקבל סוג של התחברות בקונסולה:
השיטה - setValue, של המחלקה - Main בוצעה בהצלחה השיטה - getValue, של המחלקה - Main, בוצעה בהצלחה, עם התוצאה של ביצוע - <some value> השיטה - checkValue, של המחלקה - Main, הופסק באופן חריג עם חריג - java.lang.Exception Method - main, class-Main, קרס עם חריג - java.lang.Exception
ובכן, מכיוון שלא טיפלנו בחריג, נקבל גם את ה-stacktrace שלו: מה זה AOP?  יסודות תכנות מונחה היבטים - 5על חריגים והטיפול בהם תוכלו לקרוא במאמרים הבאים: חריגים ב-Java ו- Exceptions and their treatment . זה הכל בשבילי היום. היום הכרנו עם AOP , ואפשר היה לראות שהחיה הזו לא מפחידה כמו שהיא מצוירת. שלום לכולם!מה זה AOP?  יסודות תכנות מונחה היבטים - 6
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION