CodeGym /مدونة جافا /Random-AR /ما هو AOP؟ مبادئ البرمجة الموجهة نحو الجانب
John Squirrels
مستوى
San Francisco

ما هو AOP؟ مبادئ البرمجة الموجهة نحو الجانب

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

تطبيق AOP

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

  2. يتم الاحتفاظ بجميع التعليمات البرمجية المصدر للتسجيل في مكان واحد، لذلك لا تحتاج إلى البحث يدويًا عن جميع الأماكن التي يتم استخدامها فيها.

  3. يمكن إضافة رمز التسجيل في أي مكان، سواء في الأساليب والفئات التي تمت كتابتها بالفعل أو في وظائف جديدة. وهذا يقلل من عدد أخطاء الترميز.

    وأيضًا، عند إزالة جانب من تكوين التصميم، يمكنك التأكد من اختفاء كافة تعليمات التتبع البرمجية وعدم فقدان أي شيء.

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

المبادئ الأساسية لـ AOP

للمضي قدمًا في هذا الموضوع، دعونا نتعرف أولاً على المفاهيم الأساسية لـ AOP. نصيحة - يتم استدعاء المنطق أو التعليمات البرمجية الإضافية من نقطة الانضمام. يمكن تقديم النصائح قبل نقطة الانضمام أو بعدها أو بدلاً منها (المزيد عنها أدناه). أنواع النصائح الممكنة :
  1. قبل — يتم إطلاق هذا النوع من النصائح قبل تنفيذ الطرق المستهدفة، أي نقاط الانضمام. عند استخدام الجوانب كفئات، نستخدم التعليق التوضيحي @Before لوضع علامة على النصيحة على أنها جاءت من قبل. عند استخدام الجوانب كملفات .aj ، ستكون هذه هي الطريقة before() .

  2. بعد - النصيحة التي يتم تنفيذها بعد اكتمال تنفيذ الأساليب (نقاط الانضمام)، سواء في التنفيذ العادي أو عند طرح استثناء.

    عند استخدام الجوانب كفئات، يمكننا استخدام التعليق التوضيحي @After للإشارة إلى أن هذه نصيحة تأتي بعد ذلك.

    عند استخدام الجوانب كملفات .aj ، فهذه هي الطريقة after() .

  3. بعد العودة — يتم تنفيذ هذه النصيحة فقط عندما تنتهي الطريقة المستهدفة بشكل طبيعي، دون أخطاء.

    عندما يتم تمثيل الجوانب كفئات، يمكننا استخدام التعليق التوضيحيAfterReturning لوضع علامة على النصيحة على أنها تنفذ بعد إكمالها بنجاح.

    عند استخدام الجوانب كملفات .aj ، ستكون هذه هي طريقة إرجاع (Object obj) after() .

  4. بعد الرمي — هذه النصيحة مخصصة للحالات التي تقوم فيها إحدى الطرق، أي نقطة الانضمام، بطرح استثناء. يمكننا استخدام هذه النصيحة للتعامل مع أنواع معينة من عمليات التنفيذ الفاشلة (على سبيل المثال، التراجع عن معاملة بأكملها أو تسجيل الدخول بمستوى التتبع المطلوب).

    بالنسبة لجوانب الفصل الدراسي، يتم استخدام التعليق التوضيحيAfterThrowing للإشارة إلى استخدام هذه النصيحة بعد طرح استثناء.

    عند استخدام الجوانب كملفات ‎.aj ، ستكون هذه هي طريقة الرمي after() (الاستثناء e) .

  5. حول — ربما يكون أحد أهم أنواع النصائح. إنه يحيط بطريقة ما، أي نقطة ربط يمكننا استخدامها، على سبيل المثال، لاختيار ما إذا كنت تريد تنفيذ طريقة نقطة ربط معينة أم لا.

    يمكنك كتابة رمز النصيحة الذي يتم تشغيله قبل وبعد تنفيذ طريقة نقطة الانضمام.

    تعتبر النصيحة المحيطة مسؤولة عن استدعاء أسلوب نقطة الانضمام وقيم الإرجاع إذا قامت الطريقة بإرجاع شيء ما. بمعنى آخر، في هذه النصيحة، يمكنك ببساطة محاكاة تشغيل الطريقة المستهدفة دون استدعائها، وإرجاع ما تريد كنتيجة إرجاع.

    بالنظر إلى الجوانب كفئات، نستخدم التعليق التوضيحي @Around لإنشاء نصيحة تلتف حول نقطة الانضمام. عند استخدام الجوانب في شكل ملفات .aj ، ستكون هذه الطريقة هي الطريقة about() .

نقطة الانضمام - النقطة في برنامج قيد التشغيل (أي استدعاء الأسلوب، وإنشاء الكائن، والوصول المتغير) حيث يجب تطبيق النصيحة. بمعنى آخر، هذا نوع من التعبير العادي المستخدم للعثور على أماكن لإدخال التعليمات البرمجية (الأماكن التي يجب تطبيق النصائح فيها). Pointcut - مجموعة من نقاط الانضمام . يحدد Pointcut ما إذا كانت النصيحة المقدمة قابلة للتطبيق على نقطة ربط معينة. الجانب - وحدة نمطية أو فئة تنفذ وظائف شاملة. يغير الجانب سلوك التعليمات البرمجية المتبقية من خلال تطبيق النصائح عند نقاط الربط المحددة بواسطة بعض النقاط . بمعنى آخر، هو مزيج من النصائح ونقاط الانضمام. المقدمة - تغيير بنية الفصل و/أو تغيير التسلسل الهرمي للوراثة لإضافة وظائف الجانب إلى التعليمات البرمجية الأجنبية. الهدف - الكائن الذي سيتم تطبيق النصيحة عليه. النسيج — عملية ربط الجوانب بكائنات أخرى لإنشاء كائنات بديلة موصى بها. يمكن القيام بذلك في وقت الترجمة أو وقت التحميل أو وقت التشغيل. هناك ثلاثة أنواع من النسيج:
  • نسج وقت الترجمة - إذا كان لديك كود مصدر الجانب والكود الذي تستخدم فيه الجانب، فيمكنك تجميع كود المصدر والجانب مباشرة باستخدام برنامج التحويل البرمجي AspectJ؛

  • نسج ما بعد الترجمة (النسيج الثنائي) - إذا كنت لا تستطيع أو لا ترغب في استخدام تحويلات التعليمات البرمجية المصدر لنسج جوانب في التعليمات البرمجية، فيمكنك أخذ فئات مجمعة مسبقًا أو ملفات جرة وإدخال الجوانب فيها؛

  • نسج وقت التحميل - هذا مجرد نسج ثنائي يتم تأجيله حتى يقوم أداة تحميل الفصل بتحميل ملف الفئة وتحديد فئة JVM.

    مطلوب واحد أو أكثر من محمل فئة النسيج لدعم ذلك. يتم توفيرها بشكل صريح من خلال وقت التشغيل أو يتم تنشيطها بواسطة "عامل النسيج".

AspectJ — تطبيق محدد لنموذج AOP الذي ينفذ القدرة على أداء المهام الشاملة. يمكن العثور على الوثائق هنا .

أمثلة في جافا

بعد ذلك، من أجل فهم أفضل لـ 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 cleancompile . الآن دعنا ننتقل مباشرة إلى الأمثلة.

المثال رقم 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);
  }
}
لا يوجد شيء معقد هنا. لقد مررنا اسمًا وعرضناه على وحدة التحكم. إذا قمنا بتشغيل البرنامج الآن، فسنرى ما يلي على وحدة التحكم:
تانر فيكتور ساشا
والآن، حان الوقت للاستفادة من قوة AOP. الآن نحن بحاجة إلى إنشاء ملف الجانب . وهي نوعان: الأول له امتداد الملف .aj . والثاني هو فئة عادية تستخدم التعليقات التوضيحية لتنفيذ قدرات AOP . دعونا نلقي نظرة أولاً على الملف ذو الامتداد .aj :
public aspect GreetingAspect {

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

  before() : greeting() {
     System.out.print("Hi, ");
  }
}
هذا الملف يشبه إلى حد ما فئة. دعونا نرى ما يحدث هنا: pointcut عبارة عن مجموعة من نقاط الربط؛ تحية () هو اسم هذه النقطة؛ : يشير التنفيذ إلى تطبيقه أثناء تنفيذ جميع استدعاءات ( * ) للأسلوب Main.printName(...) . بعد ذلك تأتي نصيحة محددة — before() — والتي يتم تنفيذها قبل استدعاء الطريقة الهدف. : Greeting() هي النقطة التي تستجيب لها هذه النصيحة. حسنًا، وفيما يلي نرى نص الطريقة نفسها، وهي مكتوبة بلغة جافا، والتي نفهمها. عندما نقوم بتشغيل main مع وجود هذا الجانب، سنحصل على مخرجات وحدة التحكم هذه:
مرحبًا، تانر، مرحبًا، فيكتور، مرحبًا، ساشا
يمكننا أن نرى أن كل استدعاء للتابع printName قد تم تعديله بفضل أحد الجوانب. الآن دعونا نلقي نظرة على الشكل الذي سيبدو عليه الجانب كفئة Java مع التعليقات التوضيحية:
@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()") هي نصيحة يتم تطبيقها قبل استدعاء الكود المحدد في نقطة الترحيب() .
تشغيل main مع هذا الجانب لا يغير مخرجات وحدة التحكم:
مرحبًا، تانر، مرحبًا، فيكتور، مرحبًا، ساشا

المثال رقم 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(); هو قبل ، في حين أن الكود أدناه هو بعد . إذا قمنا بتشغيل main ، فسنحصل على هذا في وحدة التحكم:
فتح صفقة... تنفيذ بعض العمليات للعميل تانر إغلاق الصفقة...
ولكن إذا قمنا بالرمي والاستثناء في طريقتنا (لمحاكاة عملية فاشلة):
public static void performSomeOperation(String clientName) throws Exception {
  System.out.println("Performing some operations for Client " + clientName);
  throw new Exception();
}
ثم نحصل على إخراج وحدة التحكم هذه:
فتح معاملة... تنفيذ بعض العمليات لـ Client Tanner فشلت العملية. التراجع عن الصفقة...
إذن ما انتهينا إليه هنا هو نوع من القدرة على معالجة الأخطاء.

المثال رقم 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();
     }
  }
}
بشكل رئيسي ، نستخدم 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") هي نصيحة سيتم تنفيذها بعد التنفيذ الناجح للطريقة المستهدفة. لدينا حالتان هنا:
  1. عندما يكون للطريقة قيمة إرجاع — if (returningValue! = Null) {
  2. عندما لا تكون هناك قيمة إرجاع — else {
@AfterThrowing(value = "methodExecuting()"، throwing = "exception") هي نصيحة سيتم تشغيلها في حالة حدوث خطأ، أي عندما تطرح الطريقة استثناءً. وبناء على ذلك، من خلال تشغيل main ، سنحصل على نوع من التسجيل المعتمد على وحدة التحكم:
التنفيذ الناجح: الطريقة - setValue، الفئة - الرئيسية التنفيذ الناجح: الطريقة - getValue، الفئة - الرئيسية، قيمة الإرجاع - <بعض القيمة> تم طرح الاستثناء: الطريقة - checkValue، الفئة - الاستثناء الرئيسي - java.lang.Exception تم طرح الاستثناء: الطريقة - رئيسي، فئة - رئيسي، استثناء - java.lang.Exception
وبما أننا لم نتعامل مع الاستثناءات، فسوف نحصل على تتبع المكدس: ما هو AOP؟  مبادئ البرمجة الموجهة نحو الجوانب - 3يمكنك القراءة عن الاستثناءات ومعالجة الاستثناءات في هذه المقالات: الاستثناءات في Java والاستثناءات : التقاط ومعالجة . هذا كل شيء بالنسبة لي اليوم. تعرفنا اليوم على AOP ، واستطعت أن ترى أن هذا الوحش ليس مخيفًا كما يتصوره البعض. الى اللقاء جميعا!
تعليقات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION