JavaRush /مدونة جافا /Random-AR /ما هو AOP؟ أساسيات البرمجة الموجهة نحو الجانب

ما هو AOP؟ أساسيات البرمجة الموجهة نحو الجانب

نشرت في المجموعة
مرحبا يا شباب! بدون فهم المفاهيم الأساسية، من الصعب جدًا الخوض في الأطر والأساليب لبناء الوظائف. لذلك سنتحدث اليوم عن أحد هذه المفاهيم - AOP، أو البرمجة الموجهة نحو الجوانب . ما هو AOP؟  أساسيات البرمجة الموجهة نحو الجوانب - 1هذا ليس موضوعًا سهلاً ولا يتم استخدامه غالبًا بشكل مباشر، لكن العديد من الأطر والتقنيات تستخدمه تحت الغطاء. وبالطبع، في بعض الأحيان قد يُطلب منك خلال المقابلات أن تخبرك بشكل عام عن نوع هذا الحيوان وأين يمكن استخدامه. لذلك دعونا نلقي نظرة على المفاهيم الأساسية وبعض الأمثلة البسيطة لـ AOP في Java . ما هو AOP؟  أساسيات البرمجة الموجهة نحو الجوانب - 2لذلك، AOP - البرمجة الموجهة نحو الجانب - هي نموذج يهدف إلى زيادة نمطية الأجزاء المختلفة من التطبيق عن طريق فصل الاهتمامات الشاملة. للقيام بذلك، تتم إضافة سلوك إضافي إلى التعليمات البرمجية الموجودة، دون تغيير التعليمات البرمجية الأصلية. بمعنى آخر، يبدو أننا نعلق وظائف إضافية فوق الأساليب والفئات دون إجراء تعديلات على الكود المعدل. لماذا هذا ضروري؟ عاجلاً أم آجلاً نتوصل إلى استنتاج مفاده أن النهج المعتاد الموجه للكائنات لا يمكنه دائمًا حل مشكلات معينة بشكل فعال. في مثل هذه اللحظة، يأتي AOP للإنقاذ ويمنحنا أدوات إضافية لبناء التطبيق. والأدوات الإضافية تعني زيادة المرونة في التطوير، بفضل وجود المزيد من الخيارات لحل مشكلة معينة.

تطبيق AOP

تم تصميم البرمجة الموجهة نحو الجوانب لحل المشكلات الشاملة، والتي يمكن أن تكون أي تعليمات برمجية يتم تكرارها عدة مرات بطرق مختلفة، والتي لا يمكن تنظيمها بالكامل في وحدة منفصلة. وفقا لذلك، مع AOP يمكننا ترك هذا خارج الكود الرئيسي وتعريفه عموديا. ومن الأمثلة على ذلك تطبيق سياسة الأمان في أحد التطبيقات. عادةً ما يتقاطع الأمان مع العديد من عناصر التطبيق. علاوة على ذلك، يجب تطبيق سياسة أمان التطبيق بالتساوي على جميع الأجزاء الحالية والجديدة من التطبيق. وفي الوقت نفسه، يمكن للسياسة الأمنية المستخدمة أن تتطور بحد ذاتها. هذا هو المكان الذي يمكن أن يكون فيه استخدام AOP مفيدًا . مثال آخر أيضا هو تسجيل . هناك العديد من المزايا لاستخدام أسلوب AOP للتسجيل مقارنةً بإدخال التسجيل يدويًا:
  1. من السهل تنفيذ وإزالة رمز التسجيل: ما عليك سوى إضافة أو إزالة بعض التكوينات لبعض الجوانب.
  2. يتم تخزين جميع التعليمات البرمجية المصدر للتسجيل في مكان واحد وليست هناك حاجة للعثور على جميع أماكن الاستخدام يدويًا.
  3. يمكن إضافة التعليمات البرمجية المخصصة للتسجيل في أي مكان، سواء كانت أساليب وفئات مكتوبة بالفعل أو وظائف جديدة. وهذا يقلل من عدد أخطاء المطورين.
    وأيضًا، عندما تقوم بإزالة جانب من تكوين التصميم، يمكنك التأكد تمامًا من إزالة كافة تعليمات التتبع البرمجية وعدم فقدان أي شيء.
  4. الجوانب عبارة عن تعليمات برمجية مستقلة يمكن إعادة استخدامها وتحسينها مرارًا وتكرارًا.
ما هو AOP؟  أساسيات البرمجة الموجهة نحو الجوانب - 3يتم استخدام AOP أيضًا لمعالجة الاستثناءات والتخزين المؤقت وإزالة بعض الوظائف لجعلها قابلة لإعادة الاستخدام.

المفاهيم الأساسية لـ AOP

للمضي قدمًا في تحليل الموضوع، دعونا نتعرف أولاً على المفاهيم الرئيسية لـ AOP. النصيحة هي منطق إضافي، رمز، يتم استدعاؤه من نقطة الاتصال. يمكن تنفيذ النصيحة قبل نقطة الاتصال أو بعدها أو بدلاً منها (المزيد عنها أدناه). أنواع النصائح الممكنة :
  1. قبل (قبل) - يتم إطلاق نصيحة من هذا النوع قبل تنفيذ الأساليب المستهدفة - نقاط الاتصال. عند استخدام الجوانب كفئات، نأخذ التعليق التوضيحي @Before لوضع علامة على نوع النصيحة على أنه يأتي من قبل. عند استخدام الجوانب كملفات .aj ، ستكون هذه هي الطريقة before() .
  2. بعد (بعد) - النصيحة التي يتم تنفيذها بعد الانتهاء من تنفيذ الأساليب - نقاط الاتصال، سواء في الحالات العادية أو عند طرح استثناء.
    عند استخدام الجوانب كفئات، يمكننا استخدام التعليق التوضيحي @After للإشارة إلى أن هذه نصيحة تأتي بعد ذلك.
    عند استخدام الجوانب كملفات .aj ، ستكون هذه هي الطريقة after() .
  3. بعد العودة - يتم تنفيذ هذه النصائح فقط إذا كانت الطريقة المستهدفة تعمل بشكل طبيعي، دون أخطاء.
    عندما يتم تمثيل الجوانب كفئات، يمكننا استخدام التعليق التوضيحيAfterReturning لوضع علامة على أن النصيحة يتم تنفيذها عند إكمالها بنجاح.
    عند استخدام الجوانب كملفات ‎.aj، ستكون هذه هي الطريقة after() ‎ التي تعيد (Object obj) .
  4. بعد الرمي - هذا النوع من النصائح مخصص لتلك الحالات التي تقوم فيها إحدى الطرق، أي نقطة الاتصال، بطرح استثناء. يمكننا استخدام هذه النصيحة لمعالجة بعض عمليات التنفيذ الفاشلة (على سبيل المثال، التراجع عن المعاملة بأكملها أو التسجيل بمستوى التتبع المطلوب).
    بالنسبة لفئات العرض، يتم استخدام التعليق التوضيحي @AfterThrowing للإشارة إلى أنه سيتم استخدام هذه النصيحة بعد طرح استثناء.
    عند استخدام الجوانب في شكل ملفات .aj ، ستكون هذه هي الطريقة - after() throw (Exception e) .
  5. ربما يكون " حول " أحد أهم أنواع النصائح التي تحيط بالطريقة، أي نقطة الاتصال، والتي يمكننا من خلالها، على سبيل المثال، اختيار ما إذا كنا نريد تنفيذ طريقة نقطة اتصال معينة أم لا.
    يمكنك كتابة رمز النصيحة الذي يتم تشغيله قبل وبعد تنفيذ طريقة نقطة الانضمام.
    تتضمن مسؤوليات النصيحة استدعاء طريقة نقطة الانضمام وإرجاع القيم إذا قامت الطريقة بإرجاع شيء ما. أي أنه في هذه النصيحة يمكنك ببساطة تقليد تشغيل الطريقة المستهدفة دون استدعائها، وإرجاع شيء خاص بك نتيجة لذلك.
    بالنسبة للجوانب في شكل فئات، نستخدم التعليق التوضيحي Around لإنشاء نصائح تلتف حول نقطة الاتصال. عند استخدام الجوانب كملفات .aj ، سيكون هذا هو الأسلوب around() .
نقطة الانضمام - نقطة في برنامج تنفيذي (استدعاء أسلوب، إنشاء كائن، الوصول إلى متغير) حيث يجب تطبيق النصيحة. بمعنى آخر، هذا هو نوع من التعبير العادي، حيث يتم العثور على أماكن إدخال التعليمات البرمجية (أماكن تطبيق النصائح). Pointcut عبارة عن مجموعة من نقاط الاتصال . يحدد القطع ما إذا كانت نقطة اتصال معينة تناسب طرفًا معينًا. Aspect عبارة عن وحدة نمطية أو فئة تنفذ وظائف شاملة. يقوم أحد الجوانب بتعديل سلوك بقية التعليمات البرمجية من خلال تطبيق النصائح عند نقاط الربط المحددة بواسطة بعض الشرائح . وبعبارة أخرى، فهو عبارة عن مزيج من النصائح ونقاط الاتصال. مقدمة - تغيير بنية الفصل و/أو تغيير التسلسل الهرمي للوراثة لإضافة وظيفة الجانب إلى التعليمات البرمجية الأجنبية. الهدف هو الكائن الذي سيتم تطبيق النصيحة عليه. النسيج هو عملية ربط الجوانب بكائنات أخرى لإنشاء كائنات الوكيل الموصى بها. يمكن القيام بذلك في وقت الترجمة أو وقت التحميل أو وقت التشغيل. هناك ثلاثة أنواع من النسيج:
  • نسج وقت الترجمة - إذا كان لديك الكود المصدري لجانب ما والكود الذي تستخدم فيه الجوانب، فيمكنك تجميع كود المصدر والجانب مباشرة باستخدام برنامج التحويل البرمجي 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 - قطع أو مجموعة من نقاط الاتصال؛ تحية () - اسم هذه الشريحة؛ : التنفيذ - عند التنفيذ * - الكل، اتصل - 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 إلى أن الفئة المحددة هي أحد الجوانب؛
  • @Pointcut("execution(* Main.printName(String))") هي نقطة قطع يتم تشغيلها على كافة الاستدعاءات إلى Main.printName مع وسيطة واردة من النوع String ؛
  • @Before("greeting()") - نصيحة يتم تطبيقها قبل استدعاء الكود الموضح عند نقطة القطع Greeting() .
لن يؤدي تشغيل main مع هذا الجانب إلى تغيير مخرجات وحدة التحكم:
مرحبًا توليا مرحبًا فوفا مرحبًا ساشا

المثال رقم 2

لنفترض أن لدينا طريقة ما تنفذ بعض العمليات للعملاء ونستدعي هذه الطريقة من main :
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 ، نستدعي طريقة الغلاف لتحديد مكانها في اللوحة، وبالتالي، الكود الموجود في الطريقة أعلاه 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();
     }
  }
}
بشكل رئيسي ، باستخدام 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()"، return = "returningValue") - نصيحة سيتم تنفيذها بعد اكتمال الطريقة المستهدفة بنجاح. لدينا حالتان هنا:
  1. عندما يكون للأسلوب قيمة إرجاع إذا (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
حسنًا، بما أننا لم نتعامل مع الاستثناء، فسنحصل أيضًا على تتبع المكدس الخاص به: ما هو AOP؟  أساسيات البرمجة الموجهة نحو الجوانب - 5يمكنك القراءة عن الاستثناءات ومعالجتها في هذه المقالات: الاستثناءات في Java والاستثناءات ومعالجتها . هذا كل شيء بالنسبة لي اليوم. اليوم تعرفنا على AOP ، ويمكنك أن ترى أن هذا الوحش ليس مخيفًا كما هو مرسوم. الى اللقاء جميعا!ما هو AOP؟  أساسيات البرمجة الموجهة نحو الجوانب - 6
تعليقات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION