گاهی لازم می شه داخل پروژه بعد یا قبل از انجام یک سری متد ها، عملیات خاصی مانند Logگیری یا احراز هویت انجام شود که مفهوم (aop (aspect-oriented programming به میان می آید.
با فرض این که مفاهیم اولیه aop و cross-cutting concern را می دانیم ، می خواهیم یه annotation سفارشی برای پروژه ی خودمون بسازیم و بگیم هر متدی که بالا سر خودش این annotation را صدا کرده یکسری عملیات خاص مثل log را انجام دهد. این کار از تکرار کد های شبیه به هم در ماژول های مختلف کم می کنه هم چنین در صورت تغییر در این بخش لازم نیست کل کد ها رو تغییر دهیم.
اول از همه باید وابستگی های مورد نظر را به پروژه اضافه کنیم.
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.2.RELEASE</version> </parent>
<dependencies>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
</dependencies>
کافیه یه annotation مانند زیر بسازیم .@target: تعیین می کنه که annotation تعریف شده ما در کجا ها معتبر و مناسب است. ( در مثال زیر در صورتی که بالاسر کلاس یا متغیر ها استفاده شود، کامپایلر خطا می گیرد )@Retention هم تعیین می کنه که annotation تعریف شده از کجا برای jvm قابل دسترس باشد.
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface SystemAspect {}
حالا که annotation رو ساختیم کافیه یه کلاس aspect ایجاد کنیم که عملیاتی که قرار است هر دفعه اعمال شود را داخل آن پیاده سازی کنیم.
@Aspect @Component public class ExampleAspect { @After("@annotation(SystemAspect)") public void logExecutionTime(JoinPoint joinPoint) throws Throwable { .... } }
بر اساس پیاده سازی بالا، متد logExecutionTime، بعد از اجرای متد هایی که از@SystemAspect استفاده کرده اند فراخوانی می شود .
ما تو بدنه ی این متد عملیاتی چون ذخیره در db یا ... را می توانیم انجام دهیم و مقدار متغییر joinPoint نیز یکسری اطلاعات مانند نام متد، نام کلاس و ...که باعث شده این aspect اجرا شود را در اختیار ما می گذارد .
@After("@annotation(SystemAspect)") public void logExecutionTime(JoinPoint joinPoint) throws Throwable { MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); String actualClassName = methodSignature.getDeclaringTypeName(); String methodName = methodSignature.getName(); String returnTypeName =methodSignature .getReturnType().getName(); Object[] paramValues=joinPoint.getArgs(); }
مثال زیر نحوه ی استفاده از annoation است. اگر همه چیز درست پیش رفته باشه یه آیکون تقریبا صورتی رنگ در کنار متد قرار می گیرد که اگر بر روی این آیکون بزنید شما رو به متد مربوطه که قرار است بعد از این متد اجرا شود می برد.(متد logExecutionTime بالا)
در annotation که بالا تعریف شد، از هیچ value استفاده نشده و مثل یه marker عمل می کنه، ولی ما میتونیم value های متفاوتی تعریف کنیم و در زمان log گیری از اون ها استفاده کنیم .
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface SystemAspect { boolean localOnly() default false; boolean admin() default true; }
هم می تونیم در بالاسر متدی که این annotation رو صدا کردیم مقادیر admin و localOnly را مقدار دهی کنیم یا از همان مقادیر پیش فرض شان استفاده کنیم.
@SystemAspect(admin = false) public void testAspect(){...}
نحوه ی گرفتن مقدار این annotation ها در کلاس aspect هم مانند زیر است.
@After("@annotation(SystemAspect)") public void logExecutionTime(JoinPoint joinPoint) throws Throwable { MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); SystemAspect annotation = methodSignature.getMethod() .getAnnotation(SystemAspect.class); if (annotation.admin()) { ...... } }
منبع:
https://niels.nu/blog/2017/spring-boot-aop.html
https://www.baeldung.com/spring-aop-annotation