AOP අත්දැකීම් | Advanced AOP Customization & Performance Monitoring - SC Guide

AOP අත්දැකීම් | Advanced AOP Customization & Performance Monitoring - SC Guide

AOP වල සූක්‍ෂ්ම විදියට වැඩ කරමු: Custom Aspects, Pointcuts, Advice Types එක්ක Performance Monitoring SC Guide

මචංලා, මේ දවස්වල code කරනකොට ඔලුවට එන ප්‍රශ්නයක් තමයි code එක maintain කරන්නේ කොහොමද? එකම logic එක තැන් ගොඩක නැතිව, පිරිසිදුව තියාගන්නේ කොහොමද? කියන එක. ගොඩක් වෙලාවට අපි දකින දෙයක් තමයි Logging, Security, Transaction Management වගේ දේවල් අපේ core business logic එකත් එක්කම පැටලිලා තියෙනවා. මේවා තමයි අපි "Cross-Cutting Concerns" කියලා හඳුන්වන්නේ. මේවා වෙනම handle කරන්න බැරිද? අනිවාර්යයෙන්ම පුළුවන්! ඒකට තියෙන සුපිරිම විසඳුමක් තමයි Aspect-Oriented Programming (AOP) කියන්නේ.

අද අපි බලමු AOP කියන්නේ මොකක්ද කියල පොඩ්ඩක් මතක් කරගෙන, ඊටත් එහා ගිහින් "Advanced AOP Customization" ගැන. ඒ කියන්නේ, අපිට ඕන විදියටම custom Aspects හදාගන්න විදිය, Pointcuts හරියටම පාවිච්චි කරන විදිය, සහ විවිධ Advice types වල නියම භාවිතයන් ගැන. අන්තිමටම, අපි එකටම Performance Monitoring කරන්න පුළුවන් Aspect එකක් හදලා බලමු. එහෙනම්, වැඩි කතා නැතුව වැඩේට බහිමු!

AOP කියන්නේ මොකක්ද? පොඩ්ඩක් මතක් කරගමු.

සරලවම කිව්වොත් AOP කියන්නේ අපේ code base එකේ තියෙන cross-cutting concerns (උදා: logging, security, caching, transaction management) core business logic එකෙන් වෙන් කරලා, වෙනම modules විදියට හසුරුවන්න පුළුවන් methodology එකක්. මේකෙන් code එකේ readability එක වැඩි වෙනවා වගේම, maintain කරන්නත් හරිම ලේසියි. ඒ වගේම, එකම logic එක තැන් ගොඩක duplicate වෙන එකත් වළක්වා ගන්න පුළුවන්.

AOP වලදී අපිට වැදගත් වෙන ප්‍රධාන Concepts ටිකක් තියෙනවා:

  • Aspect: මේක තමයි අපේ cross-cutting concern එක encapsulate කරගෙන ඉන්නේ. උදාහරණයක් විදියට, "ලොග් කරන වැඩේ" (logging) කියන එකම Aspect එකක් විදියට ගන්න පුළුවන්. Aspect එකක් ඇතුළේ Pointcuts සහ Advices තියෙනවා.
  • Join Point: අපේ application එකේ execution path එකේදී Aspect එකට "join" වෙන්න පුළුවන් තැනක්. Method call එකක්, exception thrown කරන තැනක් වගේ දේවල් Join Points වෙන්න පුළුවන්.
  • Pointcut: මේක තමයි Join Points තෝරගන්න පාවිච්චි කරන expression එක. "මේ class එකේ මේ method එක execute වෙනකොට" වගේ දේවල් Pointcut එකකින් define කරන්න පුළුවන්.
  • Advice: Pointcut එකකින් අඳුරගත්ත Join Point එකේදී Aspect එකෙන් කරන්න ඕන action එක. උදා: "log කරන්න", "අවසර තියෙනවද බලන්න".
  • Weaving: Aspect එක Join Points වලට integrate කරන process එක. මේක compile-time, load-time, හෝ runtime වෙන්න පුළුවන්.

මේ Concepts මතක තියාගමු, මොකද Advanced Customization වලදී මේවා ගැන හොඳ අවබෝධයක් තියෙන්න ඕන.

Custom Aspects හදමු: අපේම AOP විසඳුම්.

දැන් අපි බලමු කොහොමද අපිට ඕන විදියටම custom Aspects හදන්නේ කියලා. Spring AOP වගේ framework එකක් පාවිච්චි කරනවා නම්, මේක හරිම ලේසියි. අපි පොඩි Logger Aspect එකක් හදමු.

Step 1: Aspect එක නිර්වචනය කිරීම

Aspect එකක් කියන්නේ සාමාන්‍ය Java class එකක් වගේ තමයි. ඒත්, ඒක Aspect එකක් කියලා Spring framework එකට කියන්න අපි @Aspect annotation එක පාවිච්චි කරනවා. ඒ වගේම, මේක Spring component එකක් විදියට recognize කරන්න @Component annotation එකත් දාමු.


import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Aspect // මේක Aspect එකක් බව කියනවා
@Component // Spring component එකක් බව කියනවා
public class MyCustomLoggerAspect {

    private static final Logger logger = LoggerFactory.getLogger(MyCustomLoggerAspect.class);

    // මෙතන තමයි අපේ Pointcuts සහ Advices එකතු කරන්නේ

}

මේ MyCustomLoggerAspect එක ඇතුළේ තමයි අපේ logging logic එක තියෙන්නේ. මේකේ තියෙන Advices තීරණය කරන්නේ මොන Joint Point එකේදී, මොන වගේ action එකක්ද කරන්න ඕන කියලා.

Pointcut එහෙමත් නැත්නම් කපන තැන් හරියටම අඳුරගමු.

AOP වලදී Pointcut කියන්නේ හරියට target lock කරනවා වගේ වැඩක්. අපිට ඕන method එක, class එක, නැත්නම් package එක හරියටම pinpoint කරන්න මේක පාවිච්චි කරනවා. Pointcut expressions හරහා තමයි මේක කරන්නේ. පොඩ්ඩක් සංකීර්ණ වුණත්, මේක තේරුම් ගත්තොත් AOP වල ලොකු වැඩක් කරගන්න පුළුවන්.

Common Pointcut Designators:

@annotation(): මේක හරිම බලගතුයි. අපිට custom annotation එකක් හදලා, ඒකෙන් annotate කරපු methods හෝ classes වලට match කරන්න පුළුවන්.


// @Loggable කියන annotation එක තියෙන ඕනම method එකකට
@annotation(com.example.annotations.Loggable)
        

args(): මේකෙන් method arguments වලට match කරන්න පුළුවන්.


// String type එකේ argument එකක් ගන්න ඕනම method එකකට
args(String)
        

// String argument එකක් සහ ඕනම තව argument එකක් ගන්න methods වලට
args(String, ..)
        

within(): මේකෙන් යම්කිසි package එකක්, class එකක් හෝ එහි sub-packages වලට අදාළ Join Points match කරන්න පුළුවන්.


// com.example.service package එකේ තියෙන ඕනම class එකක ඕනම method එකකට
within(com.example.service.*)
        

// com.example.service package එකේ සහ ඒක ඇතුළේ තියෙන sub-packages වලට
within(com.example.service..*)
        

execution(): මේක තමයි වැඩිපුරම පාවිච්චි කරන එක. Methods execution වලට match කරන්න පාවිච්චි කරනවා.


// public access modifier තියෙන, com.example.service package එකේ තියෙන ඕනම class එකක ඕනම method එකකට
execution(public * com.example.service.*.*(..))
        

* කියන්නේ ඕනම return type එකක්, (..) කියන්නේ ඕනම arguments ගානක්.


// com.example.service.UserService class එකේ getAllUsers() method එකට
execution(* com.example.service.UserService.getAllUsers())
        

Pointcut Expressions Combine කරමු:

අපිට && (AND), || (OR), ! (NOT) වගේ logical operators පාවිච්චි කරලා Pointcuts combine කරන්න පුළුවන්.


// com.example.service package එකේ තියෙන, "get" වලින් පටන් ගන්න ඕනම method එකකට
// ඒ වගේම ඒ method එකට @Loggable annotation එක තියෙන්නත් ඕන.
@Pointcut("execution(* com.example.service.*.get*(..)) && @annotation(com.example.annotations.Loggable)")
public void serviceGetMethodsAnnotatedWithLoggable() {}

ඔන්න ඔහොම තමයි Pointcut Expressions හදන්නේ. මේවා හොඳට practice කරන්න ඕන, මොකද වැරදි Pointcut එකක් දුන්නොත්, අපි බලාපොරොත්තු නොවන තැන්වලටත් Aspect එක apply වෙන්න පුළුවන්, නැත්නම් අවශ්‍ය තැනට apply නොවෙන්නත් පුළුවන්.

Advice Types හරියටම පාවිච්චි කරමු.

Pointcut එකකින් Join Point එකක් අඳුරගත්තට පස්සේ, ඒ Join Point එකේදී කරන්න ඕන action එක define කරන්නේ Advice එකකින්. AOP වල ප්‍රධාන Advice types පහක් තියෙනවා:

@Around Advice:මේක තමයි AOP වල තියෙන වඩාත්ම බලගතු සහ සංකීර්ණ Advice එක. මේකෙන් Target method එක execute වෙන්න කලින් සහ පස්සේ දෙකටම intervene කරන්න පුළුවන්. ඒ වගේම, Target method එක execute කරනවද නැද්ද කියලත් තීරණය කරන්න පුළුවන්. මේකේදී අපිට ProceedingJoinPoint object එක ලැබෙනවා. ඒකේ තියෙන proceed() method එක call කරල තමයි Target method එක execute කරන්නේ.


import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;

// ... (other imports and class definition)

@Around("execution(* com.example.service.*.*(..))")
public Object profile(ProceedingJoinPoint joinPoint) throws Throwable {
    long start = System.currentTimeMillis();
    Object result = joinPoint.proceed(); // Target method එක execute කරනවා
    long end = System.currentTimeMillis();
    logger.info("Method: {} executed in {} ms", joinPoint.getSignature().toShortString(), (end - start));
    return result;
}
        

පාවිච්චි කරන අවස්ථා: Performance monitoring, Caching, Transaction management, Retries.

@After (Finally) Advice:Target method එක execute වෙලා ඉවර වුණාට පස්සේ (result එක return කළත්, exception එකක් throw කළත්) මේ Advice එක සෑම විටම execute වෙනවා.


@After("execution(* com.example.service.*.*(..))")
public void logAfter(JoinPoint joinPoint) {
    logger.info("After (finally) method: {}", joinPoint.getSignature().toShortString());
}
        

පාවිච්චි කරන අවස්ථා: Resource release (closing database connections, file handles), Auditing.

@AfterThrowing Advice:Target method එකකින් exception එකක් throw වුණොත් මේ Advice එක execute වෙනවා. මේකෙන් thrown exception එකට access ගන්න පුළුවන්.


@AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "error")
public void logAfterThrowing(JoinPoint joinPoint, Throwable error) {
    logger.error("Exception in method: {} with message: {}", joinPoint.getSignature().toShortString(), error.getMessage());
}
        

පාවිච්චි කරන අවස්ථා: Error logging, Exception handling, Resource cleanup.

@AfterReturning Advice:Target method එක successfully execute වෙලා return වුණාට පස්සේ මේ Advice එක execute වෙනවා. මේකෙන් return value එකටත් access ගන්න පුළුවන්.


@AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result")
public void logAfterReturning(JoinPoint joinPoint, Object result) {
    logger.info("After method: {} returning: {}", joinPoint.getSignature().toShortString(), result);
}
        

පාවිච්චි කරන අවස්ථා: Logging the result, Caching the result, Post-processing data.

@Before Advice:Target method එක execute වෙන්න කලින් මේ Advice එක execute වෙනවා. මේකෙන් JoinPoint object එකට access ගන්න පුළුවන්. ඒකෙන් method arguments, target object වගේ දේවල් ගන්න පුළුවන්.


@Before("execution(* com.example.service.*.*(..))")
public void logBefore(JoinPoint joinPoint) {
    logger.info("Before method: {} with args: {}", joinPoint.getSignature().toShortString(), Arrays.toString(joinPoint.getArgs()));
}
        

පාවිච්චි කරන අවස්ථා: Input validation, Security checks, Transaction initiation.

මේ Advice types ගැන හොඳට තේරුම් අරන් හිටියොත්, ඕනම cross-cutting concern එකක් ලස්සනට AOP හරහා implement කරන්න පුළුවන්. මතක තියාගන්න @Around Advice එක ගොඩක් බලගතු වුණත්, ඒක ප්‍රවේශමෙන් පාවිච්චි කරන්න ඕන, මොකද ඒකෙන් original method execution flow එකට බාධා වෙන්න පුළුවන්.

Practical Exercise: Performance Monitoring Aspect එකක් හදමු.

දැන් අපි ඉගෙන ගත්ත දේවල් පාවිච්චි කරලා, අපේ application එකේ methods වල performance monitor කරන්න පුළුවන් Aspect එකක් හදමු. මේකෙන් අපිට system එකේ slow වෙන තැන් පහසුවෙන් අඳුරගන්න පුළුවන් වෙනවා.

Goal:

අපිට ඕන, අපි specify කරන methods වල execution time එක measure කරලා log කරන්න. මේක කරන්න අපි custom annotation එකක් සහ @Around Advice එකක් පාවිච්චි කරනවා.

Step 1: Custom Annotation එක නිර්මාණය කිරීම

මුලින්ම අපි @TrackPerformance කියන custom annotation එක හදමු. මේකෙන් අපිට monitoring කරන්න ඕන methods identify කරන්න පුළුවන්.


package com.example.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD) // මේ annotation එක methods වලට විතරයි apply කරන්න පුළුවන්
@Retention(RetentionPolicy.RUNTIME) // මේක runtime එකේදීත් තියාගන්න ඕන
public @interface TrackPerformance {
    String value() default ""; // monitoring කරන method එකට නමක් දෙන්න පුළුවන් optional field එකක්
}

මේ @TrackPerformance annotation එකෙන් අපේ methods annotate කළාම, Aspect එකට පුළුවන් ඒ methods අඳුරගෙන performance monitoring කරන්න.

Step 2: Performance Monitoring Aspect එක නිර්මාණය කිරීම

දැන් අපි PerformanceMonitoringAspect එක හදමු. මේකේ @Around Advice එකක් තියෙනවා, ඒක @TrackPerformance annotation එක තියෙන methods වලට apply වෙනවා.


package com.example.aspects;

import com.example.annotations.TrackPerformance;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class PerformanceMonitoringAspect {

    private static final Logger logger = LoggerFactory.getLogger(PerformanceMonitoringAspect.class);

    @Around("@annotation(trackPerformance)") // @TrackPerformance annotation එක තියෙන ඕනම method එකකට මේ Advice එක apply වෙනවා.
    public Object measureMethodExecutionTime(ProceedingJoinPoint joinPoint, TrackPerformance trackPerformance) throws Throwable {
        long startTime = System.nanoTime(); // method එක execute වෙන්න කලින් වෙලාව ගන්නවා

        Object result = null;
        try {
            result = joinPoint.proceed(); // target method එක execute කරනවා
        } finally {
            long endTime = System.nanoTime(); // method එක execute වෙලා ඉවර වුණාට පස්සේ වෙලාව ගන්නවා
            long duration = (endTime - startTime) / 1_000_000; // milliseconds වලට convert කරනවා

            MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
            String className = methodSignature.getDeclaringType().getSimpleName();
            String methodName = methodSignature.getName();
            String annotationValue = trackPerformance.value(); // annotation එකේ value එක ගන්නවා

            if (annotationValue.isEmpty()) {
                logger.info("Performance Log: {}.{} executed in {} ms", className, methodName, duration);
            } else {
                logger.info("Performance Log [{}]: {}.{} executed in {} ms", annotationValue, className, methodName, duration);
            }
        }
        return result;
    }
}

මේ Aspect එකේදී අපි කරන්නේ මොකක්ද?

  1. @Around("@annotation(trackPerformance)") කියන Pointcut එකෙන් කියනවා, @TrackPerformance annotation එක තියෙන ඕනම method එකක් execute වෙනකොට මේ Advice එක activate කරන්න කියලා. trackPerformance කියන parameter එකෙන් අපිට annotation object එකටම access ගන්න පුළුවන්.
  2. System.nanoTime() පාවිච්චි කරලා method එක execute වෙන්න කලින් සහ පස්සේ වෙලාව record කරනවා.
  3. joinPoint.proceed() කියන method එකෙන් තමයි actual target method එක execute කරන්නේ.
  4. `finally` block එක ඇතුළේ අපි duration එක calculate කරලා, Method Signature එකත් එක්ක log කරනවා.
  5. @TrackPerformance annotation එකේ value() field එක පාවිච්චි කරලා customized log message එකක් දෙන්නත් පුළුවන් විදියට හදලා තියෙනවා.

Step 3: Usage Example

දැන් අපිට ඕන කරන ඕනම service method එකකට මේ @TrackPerformance annotation එක දාන්න පුළුවන්. උදාහරණයක් විදියට:


package com.example.service;

import com.example.annotations.TrackPerformance;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.List;

@Service
public class UserService {

    @TrackPerformance("Fetching All Users") // අපේ custom annotation එක මෙතන
    public List<String> getAllUsers() {
        // Simulate some long-running operation
        try {
            Thread.sleep(1500); // 1.5 seconds delay
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        return Arrays.asList("Alice", "Bob", "Charlie");
    }

    @TrackPerformance
    public String getUserById(Long id) {
        // Simulate some short operation
        try {
            Thread.sleep(200); // 0.2 seconds delay
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        return "User_" + id;
    }
}

මේ UserService එකේ methods call කරනකොට, අපේ PerformanceMonitoringAspect එක activate වෙලා, ඒ methods execute වෙන්න ගත්ත කාලය log කරයි. Output එක ආසන්න වශයෙන් මෙහෙම වෙයි:


Performance Log [Fetching All Users]: UserService.getAllUsers executed in 1502 ms
Performance Log: UserService.getUserById executed in 201 ms

ෆට්ට නේද? දැන් අපිට ඕන නම් මේ log එක වෙනම database එකකට දාන්න පුළුවන්, නැත්නම් monitoring tool එකකට integrate කරන්නත් පුළුවන්.

මේක තවත් වැඩිදියුණු කරන්න පුළුවන්. උදාහරණයක් විදියට, යම්කිසි method එකක execution time එක threshold එකක් පැන්නොත් alert කරන්න, නැත්නම් වෙනම metrics system එකකට data යවන්න වගේ දේවල් කරන්න පුළුවන්. මේක තමයි AOP වල තියෙන ලස්සන. අපේ core business logic එක clean එකේ තියාගෙනම, මේ වගේ cross-cutting concerns වෙනම handle කරන්න පුළුවන්.

නිගමනය: AOP එක්ක වැඩේ ගොඩ

හරි, මචංලා! අපි අද AOP වල ගැඹුරු තැන් ටිකක් කතා කළා. Aspect කියන්නේ මොකක්ද, Pointcuts හරියටම පාවිච්චි කරන්නේ කොහොමද, Advice types වල වෙනස්කම් සහ ඒවා භාවිතා කරන අවස්ථා වගේම, Performance Monitoring Aspect එකක් පවා හදාගත්තා. මේ AOP කියන concept එක හරියට තේරුම් අරන් පාවිච්චි කළොත්, අපේ code base එකේ maintainability එක, modularity එක සහ readability එක පුදුමාකාර විදියට වැඩි කරගන්න පුළුවන්.

අපි දැක්කා Performance Monitoring Aspect එකක් හදපු විදිය. ඒකෙන් අපේ application එකේ bottleneck වෙන්න පුළුවන් තැන් පහසුවෙන් අඳුරගන්න පුළුවන් වෙනවා. මේ වගේම Logging, Security, Caching වගේ ඕනම cross-cutting concern එකක් AOP හරහා ලස්සනට manage කරන්න පුළුවන්.

ඔයාලත් මේක try කරලා බලන්න. ඔයාලගේ project එකකදී මේ වගේ දෙයක් implement කරලා බලන්න. අනිවාර්යයෙන්ම ඒකෙන් ඔයාලගේ code base එකට ලොකු වටිනාකමක් එකතු වෙයි. ප්‍රශ්න තියෙනවා නම්, comment section එකේ අහන්න. මේ වගේ තව tips & tricks ඕනෙද?

එහෙනම්, ඊලඟ ලිපියෙන් හම්බවෙමු! Code කරන එක නවත්තන්න එපා!