আপনি যদি কিছুদিন ধরে Spring Boot অ্যাপ্লিকেশন তৈরি করে থাকেন, তাহলে নিশ্চয়ই লক্ষ্য করেছেন যে কিছু কাজ — যেমন logging, security check, transaction management, এবং performance monitoring — প্রায় প্রতিটি লেয়ারে বারবার লিখতে হয়। একই boilerplate কোড বারবার লেখার ফলে আপনার মূল business logic অগোছালো হয়ে যায়। এই সমস্যার সমাধানই হলো Aspect-Oriented Programming (AOP)।
এই গাইডে আমরা Spring Boot-এ AOP কী, কীভাবে কাজ করে, প্রধান annotations এবং বাস্তব উদাহরণ নিয়ে বিস্তারিত আলোচনা করব।
AOP কী?
Aspect-Oriented Programming (AOP) হলো একটি programming paradigm যা আপনাকে cross-cutting concerns গুলোকে মূল business logic থেকে আলাদা করতে দেয়। Cross-cutting concern হলো এমন functionality যা অ্যাপ্লিকেশনের একাধিক লেয়ারে বিস্তৃত — যেমন logging, security, caching, error handling, এবং transaction management।
এই logic গুলো ডজন ডজন class-এ ছড়িয়ে দেওয়ার বদলে, AOP আপনাকে সেগুলো একটি নির্দিষ্ট module-এ সংজ্ঞায়িত করতে দেয় যাকে বলা হয় Aspect। এরপর এটি declaratively যেখানে দরকার সেখানে প্রয়োগ করা যায়।
Spring AOP প্রক্সি-ভিত্তিক এবং Spring IoC container-এর সাথে নির্বিঘ্নে কাজ করে।
AOP-এর মূল ধারণাসমূহ
কোডে যাওয়ার আগে গুরুত্বপূর্ণ পরিভাষাগুলো জেনে নেওয়া যাক:
| ধারণা | বিবরণ |
|---|---|
| Aspect | Cross-cutting concern ধারণকারী module (যেমন: logging aspect) |
| Join Point | Execution-এর যে বিন্দুতে aspect প্রয়োগ করা যায় (যেমন: method execution) |
| Advice | Join point-এ aspect কর্তৃক গৃহীত পদক্ষেপ (যে কোড আসলে চলে) |
| Pointcut | Join point-গুলো নির্বাচন করে — কোথায় advice প্রযোজ্য তা নির্ধারণ করে |
| Weaving | Aspects-কে অন্য অবজেক্টের সাথে সংযুক্ত করার প্রক্রিয়া |
| Target Object | এক বা একাধিক aspect দ্বারা advised অবজেক্ট |
| Proxy | Spring AOP কর্তৃক তৈরি অবজেক্ট যা aspect চুক্তি বাস্তবায়ন করে |
Spring Boot-এ AOP সেটআপ
Spring Boot-এ AOP সেটআপ অত্যন্ত সহজ। pom.xml-এ নিচের dependency যোগ করুন:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
ব্যস! Spring Boot AopAutoConfiguration-এর মাধ্যমে স্বয়ংক্রিয়ভাবে AOP কনফিগার করে। @EnableAspectJAutoProxy annotation আলাদাভাবে যোগ করার প্রয়োজন নেই (যদিও চাইলে যোগ করতে পারেন)।
Advice-এর প্রকারভেদ
Spring AOP পাঁচ ধরনের advice সাপোর্ট করে, প্রতিটি target method-এর সাথে সম্পর্কিত ভিন্ন সময়ে চলে:
১. @Before — Method-এর আগে চলে
@Aspect
@Component
public class LoggingAspect {
private static final Logger log = LoggerFactory.getLogger(LoggingAspect.class);
@Before("execution(* com.example.service.*.*(..))")
public void logBeforeMethod(JoinPoint joinPoint) {
log.info("Method call হচ্ছে: {}", joinPoint.getSignature().getName());
}
}
Pre-condition check, method entry logging, বা security validation-এর জন্য ব্যবহার করুন।
২. @After — Method-এর পরে চলে (সবসময়)
@After("execution(* com.example.service.*.*(..))")
public void logAfterMethod(JoinPoint joinPoint) {
log.info("Method execute হয়েছে: {}", joinPoint.getSignature().getName());
}
Method সফলভাবে শেষ হোক বা exception throw করুক — এটি সবসময় চলে। এটি finally block-এর মতো কাজ করে।
৩. @AfterReturning — সফল সম্পন্নের পরে চলে
@AfterReturning(
pointcut = "execution(* com.example.service.UserService.findUser(..))",
returning = "result"
)
public void logAfterReturning(JoinPoint joinPoint, Object result) {
log.info("Method {} এর return value: {}",
joinPoint.getSignature().getName(), result);
}
Method-এর return value inspect বা log করতে এটি ব্যবহার করুন।
৪. @AfterThrowing — Exception throw হলে চলে
@AfterThrowing(
pointcut = "execution(* com.example.service.*.*(..))" ,
throwing = "exception"
)
public void logAfterThrowing(JoinPoint joinPoint, Throwable exception) {
log.error("Method {}-এ Exception: {}",
joinPoint.getSignature().getName(), exception.getMessage());
}
Centralized exception logging এবং monitoring-এর জন্য চমৎকার।
৫. @Around — সবচেয়ে শক্তিশালী Advice
@Around পুরো method execution-কে wrap করে। আপনার সম্পূর্ণ নিয়ন্ত্রণ থাকে — arguments পরিবর্তন করতে পারেন, return value বদলাতে পারেন, এমনকি method টি সম্পূর্ণ skip করতে পারেন।
@Around("execution(* com.example.service.*.*(..))")
public Object measureExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = joinPoint.proceed(); // আসল method execute করুন
long duration = System.currentTimeMillis() - startTime;
log.info("Method {} চলেছে {} ms",
joinPoint.getSignature().getName(), duration);
return result;
}
গুরুত্বপূর্ণ:
@Around-এjoinPoint.proceed()অবশ্যই call করতে হবে। না হলে আসল method execute হবে না — প্রায়ই কোনো error ছাড়াই, যা খুব বিভ্রান্তিকর bug তৈরি করে।
Pointcut Expression লেখা
Pointcut expression হলো AOP-এর প্রাণ — এটি নির্ধারণ করে কোন method গুলো intercept হবে। Spring AOP AspectJ pointcut expression language ব্যবহার করে।
execution Designator
সবচেয়ে বেশি ব্যবহৃত pointcut designator:
execution([modifier] return-type [class].method-name(params) [throws])
উদাহরণ:
// UserService-এর সকল method
"execution(* com.example.service.UserService.*(..))"
// service package-এর সকল class-এর সকল method
"execution(* com.example.service.*.*(..)) "
// শুধু String return করা public method
"execution(public String com.example..*.*(..)) "
// যেকোনো সংখ্যক argument সহ method
"execution(* com.example.service.*.*(..)) "
@annotation Designator
নির্দিষ্ট annotation বহনকারী method টার্গেট করুন — custom aspect-এর জন্য অত্যন্ত শক্তিশালী:
// @Loggable দিয়ে annotate করা সকল method intercept করুন
"@annotation(com.example.annotation.Loggable)"
within Designator
// service package-এর সকল class-এর সকল method
"within(com.example.service.*)"
&&, ||, ! দিয়ে Pointcut সংযুক্ত করা
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceMethods() {}
@Pointcut("execution(* com.example.repository.*.*(..))")
public void repositoryMethods() {}
@Pointcut("serviceMethods() || repositoryMethods()")
public void serviceOrRepository() {}
বাস্তব জীবনের Use Cases
Use Case ১: Custom Annotation দিয়ে Method-Level Logging
Annotation তৈরি করুন:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Loggable {
String value() default "";
}
Aspect তৈরি করুন:
@Aspect
@Component
public class LoggingAspect {
private static final Logger log = LoggerFactory.getLogger(LoggingAspect.class);
@Around("@annotation(loggable)")
public Object logExecution(ProceedingJoinPoint joinPoint,
Loggable loggable) throws Throwable {
String methodName = joinPoint.getSignature().getName();
log.info("[শুরু] {} - {}", methodName, loggable.value());
try {
Object result = joinPoint.proceed();
log.info("[শেষ] {} সফলভাবে সম্পন্ন হয়েছে", methodName);
return result;
} catch (Exception e) {
log.error("[ত্রুটি] {} ব্যর্থ হয়েছে: {}", methodName, e.getMessage());
throw e;
}
}
}
যেকোনো জায়গায় ব্যবহার করুন:
@Service
public class OrderService {
@Loggable("নতুন অর্ডার প্রক্রিয়া করা হচ্ছে")
public Order createOrder(OrderRequest request) {
// business logic এখানে
}
}
Use Case ২: Performance Monitoring
@Aspect
@Component
public class PerformanceAspect {
private static final Logger log = LoggerFactory.getLogger(PerformanceAspect.class);
@Around("execution(* com.example.service.*.*(..))")
public Object trackPerformance(ProceedingJoinPoint joinPoint) throws Throwable {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
Object result = joinPoint.proceed();
stopWatch.stop();
if (stopWatch.getTotalTimeMillis() > 1000) {
log.warn("ধীর Method সনাক্ত: {} সময় নিয়েছে {} ms",
joinPoint.getSignature().toShortString(),
stopWatch.getTotalTimeMillis());
}
return result;
}
}
Use Case ৩: Role-Based Security Check
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresRole {
String value();
}
@Aspect
@Component
@RequiredArgsConstructor
public class SecurityAspect {
private final SecurityContext securityContext;
@Before("@annotation(requiresRole)")
public void checkRole(JoinPoint joinPoint, RequiresRole requiresRole) {
String requiredRole = requiresRole.value();
if (!securityContext.getCurrentUser().hasRole(requiredRole)) {
throw new AccessDeniedException(
"অ্যাক্সেস অস্বীকৃত। প্রয়োজনীয় role: " + requiredRole
);
}
}
}
Spring AOP বনাম AspectJ: পার্থক্য কী?
| বৈশিষ্ট্য | Spring AOP | AspectJ |
|---|---|---|
| Weaving | Runtime (proxy-based) | Compile-time বা load-time |
| Scope | শুধু Spring-managed bean | যেকোনো Java class |
| Performance | সামান্য ধীর (proxy overhead) | দ্রুত |
| Setup | সহজ, Spring-এ অন্তর্নির্মিত | AspectJ compiler/agent প্রয়োজন |
| ব্যবহার | বেশিরভাগ enterprise অ্যাপ | Non-Spring code বা fine-grained |
বেশিরভাগ Spring Boot অ্যাপ্লিকেশনের জন্য Spring AOP-ই সঠিক পছন্দ। শুধুমাত্র non-Spring-managed অবজেক্ট intercept করতে হলে বা compile-time weaving দরকার হলে full AspectJ ব্যবহার করুন।
সাধারণ ভুলগুলো এড়িয়ে চলুন
১. Self-invocation AOP trigger করে না
Spring AOP প্রক্সির মাধ্যমে কাজ করে, তাই একই class-এর মধ্যে this দিয়ে method call করলে প্রক্সি bypass হয় এবং advice কাজ করে না। ApplicationContext থেকে প্রক্সি নিন অথবা আলাদা bean-এ refactor করুন।
২. Private method intercept হয় না
Spring AOP শুধুমাত্র public method intercept করতে পারে। কখনো private method-এ @Around কাজ করবে বলে আশা করবেন না।
৩. @Around-এ joinPoint.proceed() ভুলবেন না
@Around advice-এ proceed() call করতে ভুলে গেলে আসল method execute হবে না — প্রায়ই কোনো error ছাড়াই, যা অত্যন্ত বিভ্রান্তিকর bug তৈরি করে।
৪. অতিরিক্ত বিস্তৃত Pointcut Expression
execution(* *.*(..)) এর মতো সব কিছুকে match করে এমন pointcut performance মারাত্মকভাবে নষ্ট করে। সবসময় যতটা সম্ভব নির্দিষ্ট থাকুন।
সারসংক্ষেপ
Spring Boot-এ AOP হলো সেই feature যা একবার ভালোভাবে বুঝলে আপনার অ্যাপ্লিকেশন architecture সম্পূর্ণ বদলে যায়। Service class-গুলোকে logging, security এবং monitoring কোড দিয়ে ভারাক্রান্ত করার বদলে সেই concerns গুলো clean, reusable aspect-এ externalize করুন।
সংক্ষিপ্ত পুনরাবৃত্তি:
- Aspect declare করতে
@Aspect+@Componentব্যবহার করুন - সঠিক advice type বেছে নিন:
@Before,@After,@AfterReturning,@AfterThrowing, অথবা@Around - Pointcut expression আয়ত্ত করুন — বিশেষত
execution()এবং@annotation() - Expressive, declarative API তৈরিতে custom annotation ব্যবহার করুন
- Proxy-এর সীমাবদ্ধতা মনে রাখুন (self-invocation নয়, private method নয়)
AOP আপনার কোড পরিষ্কার রাখে, concerns আলাদা রাখে এবং architecture maintainable রাখে। ছোট থেকে শুরু করুন — আপনার পরবর্তী প্রজেক্টে একটি logging aspect যোগ করুন এবং দেখুন আপনার service layer কতটা পরিষ্কার হয়ে যায়।