تطبيق AOP
تم تصميم البرمجة الموجهة نحو الجوانب لأداء المهام الشاملة، والتي يمكن أن تكون أي تعليمات برمجية يمكن تكرارها عدة مرات بطرق مختلفة، والتي لا يمكن تنظيمها بالكامل في وحدة منفصلة. وفقًا لذلك، يتيح لنا AOP الاحتفاظ بهذا خارج الكود الرئيسي والإعلان عنه عموديًا. مثال على ذلك هو استخدام سياسة الأمان في أحد التطبيقات. عادةً، يتم تشغيل الأمان عبر العديد من عناصر التطبيق. علاوة على ذلك، يجب تطبيق سياسة أمان التطبيق بالتساوي على جميع الأجزاء الحالية والجديدة من التطبيق. وفي الوقت نفسه، يمكن للسياسة الأمنية المستخدمة أن تتطور بحد ذاتها. هذا هو المكان المثالي لاستخدام AOP . أيضا، مثال آخر هو تسجيل . هناك العديد من المزايا لاستخدام أسلوب AOP للتسجيل بدلاً من إضافة وظيفة التسجيل يدويًا:-
من السهل إضافة وإزالة رمز التسجيل: كل ما عليك فعله هو إضافة أو إزالة بعض التكوينات لبعض الجوانب.
-
يتم الاحتفاظ بجميع التعليمات البرمجية المصدر للتسجيل في مكان واحد، لذلك لا تحتاج إلى البحث يدويًا عن جميع الأماكن التي يتم استخدامها فيها.
-
يمكن إضافة رمز التسجيل في أي مكان، سواء في الأساليب والفئات التي تمت كتابتها بالفعل أو في وظائف جديدة. وهذا يقلل من عدد أخطاء الترميز.
وأيضًا، عند إزالة جانب من تكوين التصميم، يمكنك التأكد من اختفاء كافة تعليمات التتبع البرمجية وعدم فقدان أي شيء.
- الجوانب عبارة عن تعليمات برمجية منفصلة يمكن تحسينها واستخدامها مرارًا وتكرارًا.
المبادئ الأساسية لـ AOP
للمضي قدمًا في هذا الموضوع، دعونا نتعرف أولاً على المفاهيم الأساسية لـ AOP. نصيحة - يتم استدعاء المنطق أو التعليمات البرمجية الإضافية من نقطة الانضمام. يمكن تقديم النصائح قبل نقطة الانضمام أو بعدها أو بدلاً منها (المزيد عنها أدناه). أنواع النصائح الممكنة :-
قبل — يتم إطلاق هذا النوع من النصائح قبل تنفيذ الطرق المستهدفة، أي نقاط الانضمام. عند استخدام الجوانب كفئات، نستخدم التعليق التوضيحي @Before لوضع علامة على النصيحة على أنها جاءت من قبل. عند استخدام الجوانب كملفات .aj ، ستكون هذه هي الطريقة before() .
- بعد - النصيحة التي يتم تنفيذها بعد اكتمال تنفيذ الأساليب (نقاط الانضمام)، سواء في التنفيذ العادي أو عند طرح استثناء.
عند استخدام الجوانب كفئات، يمكننا استخدام التعليق التوضيحي @After للإشارة إلى أن هذه نصيحة تأتي بعد ذلك.
عند استخدام الجوانب كملفات .aj ، فهذه هي الطريقة after() .
-
بعد العودة — يتم تنفيذ هذه النصيحة فقط عندما تنتهي الطريقة المستهدفة بشكل طبيعي، دون أخطاء.
عندما يتم تمثيل الجوانب كفئات، يمكننا استخدام التعليق التوضيحيAfterReturning لوضع علامة على النصيحة على أنها تنفذ بعد إكمالها بنجاح.
عند استخدام الجوانب كملفات .aj ، ستكون هذه هي طريقة إرجاع (Object obj) after() .
-
بعد الرمي — هذه النصيحة مخصصة للحالات التي تقوم فيها إحدى الطرق، أي نقطة الانضمام، بطرح استثناء. يمكننا استخدام هذه النصيحة للتعامل مع أنواع معينة من عمليات التنفيذ الفاشلة (على سبيل المثال، التراجع عن معاملة بأكملها أو تسجيل الدخول بمستوى التتبع المطلوب).
بالنسبة لجوانب الفصل الدراسي، يتم استخدام التعليق التوضيحيAfterThrowing للإشارة إلى استخدام هذه النصيحة بعد طرح استثناء.
عند استخدام الجوانب كملفات .aj ، ستكون هذه هي طريقة الرمي after() (الاستثناء e) .
-
حول — ربما يكون أحد أهم أنواع النصائح. إنه يحيط بطريقة ما، أي نقطة ربط يمكننا استخدامها، على سبيل المثال، لاختيار ما إذا كنت تريد تنفيذ طريقة نقطة ربط معينة أم لا.
يمكنك كتابة رمز النصيحة الذي يتم تشغيله قبل وبعد تنفيذ طريقة نقطة الانضمام.
تعتبر النصيحة المحيطة مسؤولة عن استدعاء أسلوب نقطة الانضمام وقيم الإرجاع إذا قامت الطريقة بإرجاع شيء ما. بمعنى آخر، في هذه النصيحة، يمكنك ببساطة محاكاة تشغيل الطريقة المستهدفة دون استدعائها، وإرجاع ما تريد كنتيجة إرجاع.
بالنظر إلى الجوانب كفئات، نستخدم التعليق التوضيحي @Around لإنشاء نصيحة تلتف حول نقطة الانضمام. عند استخدام الجوانب في شكل ملفات .aj ، ستكون هذه الطريقة هي الطريقة about() .
-
نسج وقت الترجمة - إذا كان لديك كود مصدر الجانب والكود الذي تستخدم فيه الجانب، فيمكنك تجميع كود المصدر والجانب مباشرة باستخدام برنامج التحويل البرمجي AspectJ؛
-
نسج ما بعد الترجمة (النسيج الثنائي) - إذا كنت لا تستطيع أو لا ترغب في استخدام تحويلات التعليمات البرمجية المصدر لنسج جوانب في التعليمات البرمجية، فيمكنك أخذ فئات مجمعة مسبقًا أو ملفات جرة وإدخال الجوانب فيها؛
-
نسج وقت التحميل - هذا مجرد نسج ثنائي يتم تأجيله حتى يقوم أداة تحميل الفصل بتحميل ملف الفئة وتحديد فئة 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 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);
}
}
لا يوجد شيء معقد هنا. لقد مررنا اسمًا وعرضناه على وحدة التحكم. إذا قمنا بتشغيل البرنامج الآن، فسنرى ما يلي على وحدة التحكم:
public aspect GreetingAspect {
pointcut greeting() : execution(* Main.printName(..));
before() : greeting() {
System.out.print("Hi, ");
}
}
هذا الملف يشبه إلى حد ما فئة. دعونا نرى ما يحدث هنا: pointcut عبارة عن مجموعة من نقاط الربط؛ تحية () هو اسم هذه النقطة؛ : يشير التنفيذ إلى تطبيقه أثناء تنفيذ جميع استدعاءات ( * ) للأسلوب 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 يشير إلى أن هذه الفئة هي أحد الجوانب؛
- @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(); هو قبل ، في حين أن الكود أدناه هو بعد . إذا قمنا بتشغيل 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();
}
}
}
بشكل رئيسي ، نستخدم 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) {
- عندما لا تكون هناك قيمة إرجاع — else {
GO TO FULL VERSION