
اعمال AOP
برنامه نویسی جنبه گرا برای انجام وظایف متقاطع طراحی شده است، که می تواند هر کدی باشد که ممکن است بارها با روش های مختلف تکرار شود، که نمی توان به طور کامل در یک ماژول جداگانه ساختار داد. بر این اساس، AOP به ما اجازه می دهد این را خارج از کد اصلی نگه داریم و آن را به صورت عمودی اعلام کنیم. به عنوان مثال استفاده از یک سیاست امنیتی در یک برنامه کاربردی است. به طور معمول، امنیت از طریق بسیاری از عناصر یک برنامه اجرا می شود. علاوه بر این، خط مشی امنیتی برنامه باید به طور یکسان برای تمام بخش های موجود و جدید برنامه اعمال شود. در عین حال، یک سیاست امنیتی در حال استفاده خود می تواند تکامل یابد. این مکان عالی برای استفاده از AOP است . همچنین، مثال دیگر ورود به سیستم است . چندین مزیت برای استفاده از رویکرد AOP برای ورود به سیستم به جای افزودن دستی عملکرد گزارش وجود دارد:-
افزودن و حذف کد ورود به سیستم آسان است: تنها کاری که باید انجام دهید این است که چند پیکربندی از جنبه های مختلف را اضافه یا حذف کنید.
-
تمام کد منبع برای ورود به سیستم در یک مکان نگهداری می شود، بنابراین نیازی نیست به صورت دستی همه مکان هایی که از آن استفاده می شود را جستجو کنید.
-
کد ثبتنام را میتوان در هر جایی اضافه کرد، چه در متدها و کلاسهایی که قبلاً نوشته شدهاند یا در عملکردهای جدید. این باعث کاهش تعداد خطاهای کدنویسی می شود.
همچنین، هنگام حذف یک جنبه از پیکربندی طراحی، می توانید مطمئن باشید که تمام کدهای ردیابی از بین رفته اند و چیزی از قلم نیفتاده است.
- جنبهها کد جداگانهای هستند که میتوان آنها را بهبود بخشید و بارها و بارها استفاده کرد.

اصول اولیه AOP
برای حرکت بیشتر در این مبحث، اجازه دهید ابتدا با مفاهیم اصلی AOP آشنا شویم. مشاوره - منطق یا کد اضافی که از نقطه اتصال فراخوانی می شود. مشاوره را می توان قبل، بعد یا به جای نقطه اتصال انجام داد (در مورد آنها بیشتر در زیر). انواع مشاوره ممکن :-
قبل - این نوع مشاوره قبل از اجرای روش های هدف، یعنی نقاط اتصال، راه اندازی می شود. هنگام استفاده از جنبهها بهعنوان کلاس، از حاشیهنویسی @Before استفاده میکنیم تا توصیههای قبلی را علامتگذاری کنیم. هنگامی که از جنبه ها به عنوان فایل های aj استفاده می کنید، این روش ()پیش خواهد بود .
- پس از — توصیه ای که پس از اجرای متدها (نقاط پیوستن) کامل می شود، چه در اجرای عادی و چه در هنگام پرتاب یک استثنا، اجرا می شود.
هنگام استفاده از جنبه ها به عنوان کلاس، می توانیم از حاشیه نویسی @After استفاده کنیم تا نشان دهیم که این توصیه ای است که بعد از آن ارائه می شود.
هنگام استفاده از جنبه ها به عنوان فایل aj ، این روش after() است .
-
پس از بازگشت - این توصیه فقط زمانی انجام می شود که روش هدف به طور معمول و بدون خطا تمام شود.
وقتی جنبهها بهعنوان کلاسها نشان داده میشوند، میتوانیم از حاشیهنویسی @AfterReturning برای علامتگذاری توصیهها بهعنوان در حال اجرا پس از تکمیل موفقیتآمیز استفاده کنیم.
هنگامی که از جنبه ها به عنوان فایل های aj استفاده می کنید، این روش پس از () بازگشت (Object obj) خواهد بود .
-
بعد از پرتاب - این توصیه برای مواردی در نظر گرفته شده است که یک روش، یعنی نقطه اتصال، یک استثنا ایجاد می کند. ما میتوانیم از این توصیه برای رسیدگی به انواع خاصی از اجرای ناموفق استفاده کنیم (به عنوان مثال، بازگرداندن کل تراکنش یا ورود به سیستم با سطح ردیابی مورد نیاز).
برای جنبه های کلاس، حاشیه نویسی @AfterThrowing استفاده می شود تا نشان دهد که این توصیه پس از پرتاب یک استثنا استفاده می شود.
هنگام استفاده از جنبه ها به عنوان فایل های aj ، این روش پرتاب after() (Exception e) خواهد بود .
-
در اطراف - شاید یکی از مهم ترین انواع مشاوره. این یک متد را احاطه می کند، یعنی یک نقطه اتصال که می توانیم از آن استفاده کنیم، برای مثال، انتخاب کنیم که آیا یک روش نقطه اتصال داده شده را انجام دهیم یا نه.
می توانید کد مشاوره ای بنویسید که قبل و بعد از اجرای متد join point اجرا می شود.
مشاوره اطراف مسئول فراخوانی متد نقطه اتصال و مقادیر بازگشتی است اگر متد چیزی را برگرداند. به عبارت دیگر، در این توصیه می توانید به سادگی عملیات یک متد هدف را بدون فراخوانی شبیه سازی کنید و هر آنچه را که می خواهید به عنوان نتیجه بازگشتی برگردانید.
با توجه به جنبههایی به عنوان کلاس، از حاشیهنویسی @Around برای ایجاد توصیههایی استفاده میکنیم که یک نقطه اتصال را میپیچد. هنگام استفاده از جنبه ها در قالب فایل های aj ، این متد متد () around خواهد بود .
-
بافندگی در زمان کامپایل - اگر کد منبع جنبه و کدی را که در آن از جنبه استفاده می کنید دارید، می توانید کد منبع و جنبه را مستقیماً با استفاده از کامپایلر AspectJ کامپایل کنید.
-
بافت پس از کامپایل (بافندگی باینری) - اگر نمیتوانید یا نمیخواهید از تبدیل کد منبع برای بافتن جنبهها در کد استفاده کنید، میتوانید کلاسهای کامپایل شده قبلی یا فایلهای jar را انتخاب کنید و جنبههایی را به آنها تزریق کنید.
-
بافندگی در زمان بارگذاری - این فقط بافندگی باینری است که تا زمانی که کلاسلودر فایل کلاس را بارگیری کند و کلاس را برای 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, ");
}
}
این فایل به نوعی شبیه یک کلاس است. بیایید ببینیم اینجا چه اتفاقی میافتد: نقطهکات مجموعهای از نقاط اتصال است. 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 نشان می دهد که این کلاس یک جنبه است.
- @Pointcut("execution(* Main.printName(String))") نقطه برشی است که برای همه فراخوانی های Main.printName با آرگومان ورودی که نوع آن String است راه اندازی می شود .
- @Before("greeting()") توصیه ای است که قبل از فراخوانی کد مشخص شده در برش () 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...");
}
}
}
با متد progress شی ProceedingJoinPoint ، متد wrapping را فراخوانی می کنیم تا محل آن را در مشاوره مشخص کنیم. بنابراین، کد موجود در متد بالا joinPoint.proceed(); قبل از است ، در حالی که کد زیر آن After است . اگر 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()، returning = "returningValue") توصیه ای است که پس از اجرای موفقیت آمیز متد هدف اجرا می شود. در اینجا دو مورد داریم:
- وقتی متد دارای مقدار بازگشتی باشد — if (returningValue! = Null) {
- وقتی مقدار برگشتی وجود ندارد — else {

GO TO FULL VERSION