سلام بچه ها! بدون درک مفاهیم اساسی، بررسی چارچوب ها و رویکردهای عملکرد ساخت بسیار دشوار است. بنابراین امروز در مورد یکی از این مفاهیم - AOP یا برنامه نویسی جنبه محور صحبت خواهیم کرد . این موضوع ساده ای نیست و اغلب به طور مستقیم استفاده نمی شود، اما بسیاری از فریمورک ها و فناوری ها از آن زیر هود استفاده می کنند. و البته، گاهی اوقات ممکن است در طول مصاحبه از شما خواسته شود که به طور کلی به شما بگویید که این چه نوع حیوانی است و کجا می توان از آن استفاده کرد. بنابراین بیایید به مفاهیم اساسی و چند مثال ساده از AOP در جاوا نگاه کنیم . بنابراین، AOP - برنامه نویسی جنبه گرا - یک پارادایم با هدف افزایش مدولار بودن بخش های مختلف یک برنامه کاربردی با جداسازی نگرانی های مقطعی است. برای انجام این کار، بدون تغییر کد اصلی، رفتار اضافی به کد موجود اضافه می شود. به عبارت دیگر، به نظر میرسد که بدون ایجاد اصلاحات در کد اصلاحشده، عملکردهای اضافی را در بالای متدها و کلاسها قرار میدهیم. چرا این لازم است؟ دیر یا زود به این نتیجه می رسیم که رویکرد شی گرا معمولی همیشه نمی تواند به طور موثر مشکلات خاصی را حل کند. در چنین لحظه ای، AOP به کمک می آید و ابزارهای اضافی را برای ساخت اپلیکیشن به ما می دهد. و ابزارهای اضافی به معنای افزایش انعطاف پذیری در توسعه است که به لطف آن گزینه های بیشتری برای حل یک مشکل خاص وجود دارد.
کاربرد AOP
برنامه نویسی جنبه گرا برای حل مشکلات مقطعی طراحی شده است، که می تواند هر کدی باشد که بارها به روش های مختلف تکرار می شود، که نمی تواند به طور کامل در یک ماژول جداگانه ساختار داده شود. بر این اساس، با AOP می توانیم این را خارج از کد اصلی بگذاریم و آن را به صورت عمودی تعریف کنیم. به عنوان مثال استفاده از یک سیاست امنیتی در یک برنامه کاربردی است. به طور معمول، امنیت بسیاری از عناصر یک برنامه را کاهش می دهد. علاوه بر این، سیاست امنیتی برنامه باید به طور یکسان برای تمام بخش های موجود و جدید برنامه اعمال شود. در عین حال، سیاست امنیتی مورد استفاده خود می تواند تکامل یابد. اینجاست که استفاده از AOP می تواند مفید واقع شود . همچنین مثال دیگر ورود به سیستم است . چندین مزیت برای استفاده از رویکرد AOP برای ورود به سیستم در مقایسه با درج دستی ورود به سیستم وجود دارد:- پیادهسازی و حذف کد ثبتنام آسان است: فقط باید چند پیکربندی از جنبههای مختلف را اضافه یا حذف کنید.
- تمام کد منبع برای ورود به سیستم در یک مکان ذخیره می شود و نیازی به یافتن دستی همه مکان های استفاده نیست.
- کد در نظر گرفته شده برای ورود به سیستم را می توان در هر جایی اضافه کرد، خواه متدها و کلاس های قبلاً نوشته شده باشد یا عملکردهای جدید. این باعث کاهش تعداد خطاهای توسعه دهنده می شود.
همچنین، هنگامی که یک جنبه را از پیکربندی طراحی حذف میکنید، میتوانید کاملاً مطمئن باشید که همه کدهای ردیابی حذف شدهاند و چیزی از دست نمیرود. - جنبه ها کد مستقلی هستند که می توانند بارها و بارها مورد استفاده مجدد و بهبود قرار گیرند.
مفاهیم اساسی AOP
برای حرکت بیشتر در تحلیل موضوع، ابتدا با مفاهیم اصلی AOP آشنا می شویم. مشاوره یک منطق اضافی است، کد، که از نقطه اتصال فراخوانی می شود. توصیه ها را می توان قبل، بعد یا به جای نقطه اتصال انجام داد (در ادامه در مورد آنها بیشتر توضیح می دهیم). انواع مشاوره ممکن :- قبل از (قبل) - مشاوره از این نوع قبل از اجرای روش های هدف - نقاط اتصال راه اندازی می شود. هنگام استفاده از جنبهها بهعنوان کلاس، از حاشیهنویسی @Before استفاده میکنیم تا نوع مشاوره را بهعنوان قبلی علامتگذاری کنیم. هنگامی که از جنبه ها به عنوان فایل aj استفاده می کنید، این روش قبل () خواهد بود .
- After (After) - مشاوره ای که پس از اتمام اجرای متدها اجرا می شود - نقاط اتصال، هم در موارد عادی و هم زمانی که استثنا پرتاب می شود.
هنگام استفاده از جنبهها بهعنوان کلاس، میتوانیم از حاشیهنویسی @After استفاده کنیم تا نشان دهیم که این نکتهای است که بعد از آن آمده است.
هنگام استفاده از جنبه ها به عنوان فایل aj ، این متد after() خواهد بود . - پس از بازگشت - این نکات فقط در صورتی اجرا می شوند که روش هدف به طور معمول و بدون خطا کار کند.
وقتی جنبهها بهعنوان کلاسها نشان داده میشوند، میتوانیم از حاشیهنویسی @AfterReturning برای علامتگذاری توصیهها به عنوان اجرا شده پس از تکمیل موفقیتآمیز استفاده کنیم.
هنگام استفاده از جنبه ها به عنوان فایل های aj، این متد after() خواهد بود که (Object obj) را برمی گرداند . - پس از پرتاب - این نوع توصیه برای مواردی در نظر گرفته شده است که یک روش، یعنی یک نقطه اتصال، استثنایی ایجاد می کند. ما میتوانیم از این توصیه برای برخی از مدیریت اجرای ناموفق استفاده کنیم (به عنوان مثال، بازگرداندن کل تراکنش یا ورود به سیستم با سطح ردیابی مورد نیاز).
برای کلاسهای جنبه، از حاشیهنویسی @AfterThrowing استفاده میشود تا نشان دهد که این توصیه پس از پرتاب یک استثنا استفاده میشود.
هنگام استفاده از جنبه ها در قالب فایل های aj ، این روش پرتاب ()after() (Exception e) خواهد بود . - Around شاید یکی از مهمترین انواع توصیههایی باشد که یک روش را احاطه میکند، یعنی یک نقطه اتصال، که با آن میتوانیم برای مثال انتخاب کنیم که آیا یک روش نقطه اتصال معین را اجرا کنیم یا نه.
می توانید کد مشاوره ای بنویسید که قبل و بعد از اجرای متد join point اجرا می شود.
در اطراف مسئولیت های مشاوره شامل فراخوانی متد نقطه اتصال و برگرداندن مقادیر در صورتی که متد چیزی را برمی گرداند است. یعنی در این نکته می توانید به سادگی از عملیات متد هدف بدون فراخوانی تقلید کنید و در نتیجه چیزی از خودتان برگردانید.
برای جنبههایی در قالب کلاسها، از حاشیهنویسی @Around برای ایجاد نکاتی استفاده میکنیم که نقطه اتصال را میپیچد. هنگامی که از جنبه ها به عنوان فایل های aj استفاده می کنید، این روش () اطراف خواهد بود .
- بافندگی در زمان کامپایل - اگر کد منبع یک جنبه و کدی که در آن از جنبهها استفاده میکنید دارید، میتوانید کد منبع و جنبه را مستقیماً با استفاده از کامپایلر AspectJ کامپایل کنید.
- بافندگی پس از کامپایل (بافندگی باینری) - اگر نمیتوانید یا نمیخواهید از تبدیل کد منبع برای بافتن جنبهها در کد خود استفاده کنید، میتوانید کلاسها یا شیشههای قبلاً کامپایل شده را انتخاب کنید و جنبههایی را تزریق کنید.
- load-time weaving به سادگی بافندگی باینری است تا زمانی که بارگذار کلاس فایل کلاس را بارگیری کند و کلاس را برای JVM تعریف کند به تعویق افتاده است.
برای پشتیبانی از این، یک یا چند "لودر کلاس بافت" مورد نیاز است. آنها یا به صراحت توسط زمان اجرا ارائه می شوند یا توسط "عامل بافندگی" فعال می شوند.
نمونه هایی در جاوا
در ادامه، برای درک بهتر AOP، نمونههای کوچکی از سطح Hello World را بررسی میکنیم. اجازه دهید فوراً متذکر شوم که در مثالهایمان از بافندگی زمان کامپایل استفاده خواهیم کرد . ابتدا باید وابستگی زیر را به 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 استفاده کنید. اکنون باید یک فایل - aspect ایجاد کنیم . آنها در دو نوع هستند: اولی یک فایل با پسوند aj. ، دومی یک کلاس معمولی است که قابلیت های AOP را با استفاده از حاشیه نویسی پیاده سازی می کند. بیایید ابتدا به فایلی با پسوند aj. نگاه کنیم :
public aspect GreetingAspect {
pointcut greeting() : execution(* Main.printName(..));
before() : greeting() {
System.out.print("Привет ");
}
}
این فایل تا حدودی شبیه یک کلاس است. بیایید بفهمیم که در اینجا چه خبر است: نقطه برش - یک برش یا مجموعه ای از نقاط اتصال. greeting() - نام این برش. : execution - هنگام اجرای * - all، فراخوانی - Main.printName(..) - این متد. بعد توصیه خاص - Before() - می آید که قبل از فراخوانی متد target اجرا می شود، : greeting() - برشی که این توصیه به آن واکنش نشان می دهد و در زیر بدنه خود متد را می بینیم که در جاوا نوشته شده است. زبانی که می فهمیم وقتی main را با این جنبه اجرا می کنیم، خروجی زیر را به کنسول دریافت می کنیم:
سلام تولیا سلام ووا سلام ساشا
می بینیم که هر فراخوانی به متد printName توسط یک جنبه اصلاح شده است. حالا بیایید نگاهی بیندازیم که این جنبه چگونه خواهد بود، اما به عنوان یک کلاس جاوا با حاشیه نویسی:
@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() اعمال می شود .
سلام تولیا سلام ووا سلام ساشا
مثال شماره 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("Операция не удалась, откат транзакции...");
}
}
}
با استفاده از متد progress شیء 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();
}
}
}
در اصل ، با استفاده از 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") - توصیه ای که پس از تکمیل موفقیت آمیز متد هدف اجرا می شود. در اینجا دو مورد داریم:
- وقتی یک متد مقدار بازگشتی دارد if (returningValue != null) {
- وقتی مقدار برگشتی وجود ندارد else {
متد - setValue، از کلاس - Main با موفقیت اجرا شد، متد - getValue، از کلاس - Main، با موفقیت اجرا شد، با نتیجه اجرا - <some value> متد - checkValue، از کلاس - Main، به طور غیرعادی با یک استثنا خاتمه یافت - java.lang.Exception Method - main، class-Main، با یک استثنا خراب شد - java.lang.Exception
خوب، از آنجایی که ما استثنا را مدیریت نکردیم، stacktrace آن را نیز دریافت خواهیم کرد: می توانید در مورد استثناها و مدیریت آنها در این مقالات بخوانید: استثناها در جاوا و استثناها و مدیریت آنها . امروز برای من همین است. امروز با AOP آشنا شدیم و می توانید ببینید که این جانور به اندازه نقاشی ترسناک نیست. خداحافظ همه!
GO TO FULL VERSION