Spring AOP

01.07.2026 | Muallif: Jaxongir a.k.a | Kategoriya: Spring Boot | 24 daqiqa o'qish

Spring AOP quyidagilarni o‘z ichiga oladi: aspect, advice, pointcut, join point, @Aspect, @Around, @Before, @After, pointcut expressionlar, JDK dynamic proxy vs CGLIB, self-invocation problem, va custom annotation bilan AOP.


1. Spring AOP nima?

AOP - Aspect-Oriented Programming.

Oddiy qilib aytganda:

AOP - asosiy biznes logikaga tegmasdan, uning atrofiga qo‘shimcha logika qo‘shish usuli.

Masalan, sizda service bor:

@Service
public class PaymentService {

    public void pay() {
        System.out.println("Payment processing...");
    }
}

Endi har bir method ishlaganda:

  • log yozish;

  • execution time o‘lchash;

  • transaction ochish/yopish;

  • security tekshirish;

  • cache ishlatish;

  • audit qilish;

kerak bo‘lsa, har bir method ichiga bularni yozish yomon amaliyot.

Yomon ko‘rinish:

public void pay() {
    long start = System.currentTimeMillis();

    System.out.println("Security check");
    System.out.println("Transaction start");

    System.out.println("Payment processing...");

    System.out.println("Transaction commit");
    System.out.println("Execution time: " + (System.currentTimeMillis() - start));
}

Bu kodni iflos qiladi.

AOP bilan esa asosiy method toza qoladi:

public void pay() {
    System.out.println("Payment processing...");
}

Qo‘shimcha logika alohida joyda yoziladi.


2. AOP qayerlarda ishlatiladi?

Spring ichida juda ko‘p muhim annotationlar AOP/proxy mexanizmi orqali ishlaydi:

Annotation

Ichkarida nima qiladi?

@Transactional

Method oldidan transaction ochadi, oxirida commit/rollback qiladi

@Cacheable

Method natijasini cache qiladi

@Async

Methodni boshqa threadda ishlatadi

@PreAuthorize

Methoddan oldin permission tekshiradi

Custom annotation

O‘zingiz xohlagan cross-cutting logic yozasiz

Cross-cutting concern - ko‘p joyda takrorlanadigan texnik logika.

Masalan:

Controller
Service
Repository

hammasida logging kerak bo‘lishi mumkin. Bu logging biznes logika emas, lekin ko‘p joyga "kesib o‘tib" kiradi. Shuning uchun cross-cutting concern deyiladi.


3. AOP asosiy terminlari

3.1 Aspect

Aspect - qo‘shimcha logika yozilgan class.

Masalan, logging aspect:

@Aspect
@Component
public class LoggingAspect {
}

Bu class biznes logika emas. U boshqa methodlar atrofida ishlaydigan texnik logika.


3.2 Advice

Advice - qachon va nima bajarilishini bildiradi.

Masalan:

@Before("execution(* com.example.service.*.*(..))")
public void beforeMethod() {
    System.out.println("Method started");
}

Bu yerda @Before - advice.

Advice turlari:

Advice

Qachon ishlaydi?

@Before

Methoddan oldin

@After

Method tugagandan keyin, exception bo‘lsa ham

@AfterReturning

Method muvaffaqiyatli return qilgandan keyin

@AfterThrowing

Method exception tashlaganda

@Around

Method oldidan ham, keyin ham; methodni boshqaradi

Eng kuchlisi - @Around.


3.3 Pointcut

Pointcut - qaysi methodlarga AOP qo‘llanishini bildiruvchi shart.

Masalan:

execution(* com.example.service.*.*(..))

Ma’nosi:

com.example.service package ichidagi barcha classlarning barcha methodlariga qo‘llanadi

3.4 Join Point

Join Point - AOP ulanishi mumkin bo‘lgan nuqta.

Spring AOP’da join point odatda method execution.

Masalan:

paymentService.pay();

pay() methodining chaqirilishi - join point.


4. Kerakli dependency

Spring Boot projectda AOP ishlatish uchun:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

Gradle:

implementation 'org.springframework.boot:spring-boot-starter-aop'

Spring Boot starter qo‘shilgandan keyin @Aspect classlar avtomatik ishlaydi.


5. Oddiy @Before misol

Service:

@Service
public class OrderService {

    public void createOrder() {
        System.out.println("Order created");
    }
}

Aspect:

@Aspect
@Component
public class LoggingAspect {

    @Before("execution(* com.example.service.OrderService.createOrder(..))")
    public void beforeCreateOrder() {
        System.out.println("Before createOrder");
    }
}

Controller yoki boshqa joydan chaqirsak:

orderService.createOrder();

Natija:

Before createOrder
Order created

6. execution pointcut syntax

Eng ko‘p ishlatiladigan pointcut:

execution(modifier returnType package.class.method(arguments))

Masalan:

execution(public String com.example.UserService.getName(Long))

Ma’nosi:

public String getName(Long id)

methodiga tushadi.

Lekin odatda wildcard ishlatiladi.


6.1 Barcha service methodlar

@Before("execution(* com.example.service.*.*(..))")
public void log() {
}

Tushuntirish:

Qism

Ma’nosi

*

return type istalgan

com.example.service.*

service packagedagi istalgan class

.*

istalgan method

(..)

istalgan argumentlar


6.2 Barcha package va subpackage

@Before("execution(* com.example..service..*(..))")
public void log() {
}

Bu chuqurroq package strukturalarni ham qamrab oladi.


6.3 Faqat create bilan boshlanadigan methodlar

@Before("execution(* com.example.service.*.create*(..))")
public void logCreateMethods() {
}

Mos keladi:

createUser()
createOrder()
createPayment()

7. @AfterReturning

Method muvaffaqiyatli tugaganda ishlaydi.

@Aspect
@Component
public class ResultLoggingAspect {

    @AfterReturning(
            pointcut = "execution(* com.example.service.UserService.getUser(..))",
            returning = "result"
    )
    public void logResult(Object result) {
        System.out.println("Returned result: " + result);
    }
}

Service:

@Service
public class UserService {

    public String getUser(Long id) {
        return "Ali";
    }
}

Natija:

Returned result: Ali

8. @AfterThrowing

Method exception tashlasa ishlaydi.

@Aspect
@Component
public class ErrorLoggingAspect {

    @AfterThrowing(
            pointcut = "execution(* com.example.service.*.*(..))",
            throwing = "ex"
    )
    public void logError(Exception ex) {
        System.out.println("Exception: " + ex.getMessage());
    }
}

Service:

@Service
public class PaymentService {

    public void pay() {
        throw new RuntimeException("Payment failed");
    }
}

Natija:

Exception: Payment failed

Bu logging, audit, monitoring uchun foydali.


9. @Around - eng muhim advice

@Around methodni to‘liq o‘rab oladi.

@Aspect
@Component
public class PerformanceAspect {

    @Around("execution(* com.example.service.*.*(..))")
    public Object measureTime(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();

        Object result = joinPoint.proceed();

        long end = System.currentTimeMillis();

        System.out.println(joinPoint.getSignature()
                + " took " + (end - start) + " ms");

        return result;
    }
}

Bu yerda:

joinPoint.proceed();

asl methodni ishga tushiradi.

Agar proceed() chaqirilmasa, original method umuman ishlamaydi.

Yomon:

@Around("execution(* com.example.service.*.*(..))")
public Object broken(ProceedingJoinPoint joinPoint) throws Throwable {
    System.out.println("Before");
    return null;
}

Bu methodni bloklab qo‘yadi.

To‘g‘ri:

@Around("execution(* com.example.service.*.*(..))")
public Object correct(ProceedingJoinPoint joinPoint) throws Throwable {
    System.out.println("Before");
    Object result = joinPoint.proceed();
    System.out.println("After");
    return result;
}

10. Method argumentlarini olish

@Around("execution(* com.example.service.UserService.findById(..))")
public Object logArgs(ProceedingJoinPoint joinPoint) throws Throwable {
    Object[] args = joinPoint.getArgs();

    System.out.println("Arguments: " + Arrays.toString(args));

    return joinPoint.proceed();
}

Service:

public User findById(Long id) {
    return userRepository.findById(id).orElseThrow();
}

Agar id = 10 bo‘lsa:

Arguments: [10]

11. Method nomini olish

@Around("execution(* com.example.service.*.*(..))")
public Object logMethodName(ProceedingJoinPoint joinPoint) throws Throwable {
    String methodName = joinPoint.getSignature().getName();

    System.out.println("Method: " + methodName);

    return joinPoint.proceed();
}

12. Custom annotation bilan AOP

Bu productionda juda foydali usul.

Masalan, faqat kerakli methodlarda performance o‘lchamoqchisiz.

Annotation yaratamiz:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecutionTime {
}

Service:

@Service
public class ReportService {

    @LogExecutionTime
    public void generateReport() {
        System.out.println("Generating report...");
    }
}

Aspect:

@Aspect
@Component
public class ExecutionTimeAspect {

    @Around("@annotation(LogExecutionTime)")
    public Object logTime(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();

        Object result = joinPoint.proceed();

        long end = System.currentTimeMillis();

        System.out.println(joinPoint.getSignature().getName()
                + " executed in " + (end - start) + " ms");

        return result;
    }
}

Endi faqat @LogExecutionTime qo‘yilgan methodlarda ishlaydi.

Bu yondashuv ancha toza:

@LogExecutionTime
public void heavyOperation() {
}

13. Custom annotation ichida parametr ishlatish

Annotation:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AuditAction {
    String value();
}

Service:

@Service
public class UserService {

    @AuditAction("USER_CREATED")
    public void createUser() {
        System.out.println("User created");
    }
}

Aspect:

@Aspect
@Component
public class AuditAspect {

    @Around("@annotation(auditAction)")
    public Object audit(
            ProceedingJoinPoint joinPoint,
            AuditAction auditAction
    ) throws Throwable {

        System.out.println("Audit started: " + auditAction.value());

        Object result = joinPoint.proceed();

        System.out.println("Audit finished: " + auditAction.value());

        return result;
    }
}

Natija:

Audit started: USER_CREATED
User created
Audit finished: USER_CREATED

Real projectda buni:

  • audit log;

  • admin action tracking;

  • user activity logging;

  • security-sensitive operation monitoring;

uchun ishlatish mumkin.


14. Spring AOP proxy orqali ishlaydi

Spring AOP asosan proxy-based.

Ya’ni Spring original objectni to‘g‘ridan-to‘g‘ri bermaydi. Uni proxy bilan o‘rab beradi.

Client
  ↓
Proxy object
  ↓
Real service object

Masalan:

@Service
public class PaymentService {

    @Transactional
    public void pay() {
    }
}

Spring taxminan shunday qiladi:

paymentService proxy
    ↓
transaction begin
    ↓
real paymentService.pay()
    ↓
transaction commit/rollback

15. JDK Dynamic Proxy vs CGLIB

Spring proxy yaratishda ikki xil yo‘l ishlatadi.

15.1 JDK Dynamic Proxy

Agar class interface implement qilgan bo‘lsa, Spring interface asosida proxy yaratishi mumkin.

public interface PaymentService {
    void pay();
}
@Service
public class PaymentServiceImpl implements PaymentService {

    public void pay() {
        System.out.println("Pay");
    }
}

Proxy interface orqali ishlaydi:

PaymentService interface proxy

15.2 CGLIB Proxy

Agar interface bo‘lmasa, Spring classning subclassini yaratadi.

@Service
public class PaymentService {

    public void pay() {
        System.out.println("Pay");
    }
}

CGLIB taxminan shunday proxy yaratadi:

class PaymentServiceProxy extends PaymentService {
}

Shuning uchun final class yoki final method bilan ehtiyot bo‘lish kerak.

Yomon:

@Service
public final class PaymentService {
}

Yoki:

@Service
public class PaymentService {

    @Transactional
    public final void pay() {
    }
}

CGLIB final methodni override qila olmaydi. Natijada AOP ishlamasligi mumkin.


16. Self-invocation problem

Bu Spring AOP’dagi eng mashhur muammo.

@Service
public class UserService {

    public void register() {
        saveUser();
    }

    @Transactional
    public void saveUser() {
        System.out.println("Saving user");
    }
}

Tashqaridan chaqirilsa:

userService.saveUser();

proxy orqali o‘tadi va @Transactional ishlaydi.

Lekin ichkaridan chaqirilsa:

register() -> saveUser()

proxy chetlab o‘tiladi.

External call:
Controller → Proxy → Transaction → saveUser()

Self call:
UserService.register() → this.saveUser()

this.saveUser() proxy emas. Shuning uchun AOP ishlamaydi.


17. Self-invocation yechimlari

17.1 Eng toza yechim: methodni boshqa servicega chiqarish

@Service
public class UserRegistrationService {

    private final UserPersistenceService persistenceService;

    public UserRegistrationService(UserPersistenceService persistenceService) {
        this.persistenceService = persistenceService;
    }

    public void register() {
        persistenceService.saveUser();
    }
}
@Service
public class UserPersistenceService {

    @Transactional
    public void saveUser() {
        System.out.println("Saving user");
    }
}

Bu eng yaxshi yondashuv, chunki responsibility ham ajraladi.


17.2 Self proxy inject qilish

@Service
public class UserService {

    private final UserService self;

    public UserService(@Lazy UserService self) {
        this.self = self;
    }

    public void register() {
        self.saveUser();
    }

    @Transactional
    public void saveUser() {
        System.out.println("Saving user");
    }
}

Bu ishlaydi, lekin dizayn jihatdan unchalik toza emas.


17.3 AopContext.currentProxy()

((UserService) AopContext.currentProxy()).saveUser();

Buning uchun config kerak:

@EnableAspectJAutoProxy(exposeProxy = true)

Bu usul odatda tavsiya etilmaydi. Kod Spring AOP’ga juda qattiq bog‘lanib qoladi.


18. AOP va private method

Spring AOP private methodlarga ishlamaydi.

Yomon:

@Service
public class UserService {

    @Transactional
    private void saveUser() {
    }
}

Sabab:

  • proxy tashqaridan kelgan public/protected/package method chaqiruvlarini ushlaydi;

  • private method class ichki detali;

  • proxy private methodni intercept qila olmaydi.

Amalda AOP uchun methodlar ko‘pincha public bo‘lishi kerak.


19. AOP va controller/service/repository

AOP’ni hamma joyga uraverish kerak emas.

Yaxshi joylar:

Joy

Misol

Service layer

transaction, audit, business logging

Controller layer

request logging, rate limit annotation

Integration layer

retry, external API monitoring

Sensitive operationlar

permission/audit

Yomon joylar:

  • har bir getter/setter;

  • entity classlar;

  • private helper methodlar;

  • juda ko‘p mayda methodlar;

  • performance-critical hot path.

AOP qulay, lekin ortiqcha ishlatilsa debug qilish qiyinlashadi.


20. Real project: request logging misol

@Aspect
@Component
public class ControllerLoggingAspect {

    @Around("within(@org.springframework.web.bind.annotation.RestController *)")
    public Object logController(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();

        try {
            return joinPoint.proceed();
        } finally {
            long duration = System.currentTimeMillis() - start;

            System.out.println("Controller method: "
                    + joinPoint.getSignature()
                    + " took " + duration + " ms");
        }
    }
}

Bu barcha @RestController class methodlarini o‘rab oladi.

Lekin productionda System.out.println emas, logger ishlatiladi:

private static final Logger log =
        LoggerFactory.getLogger(ControllerLoggingAspect.class);

21. Real project: audit annotation

Annotation:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Auditable {
    String action();
}

Service:

@Service
public class ProductService {

    @Auditable(action = "PRODUCT_CREATED")
    public void createProduct() {
        System.out.println("Product created");
    }
}

Aspect:

@Aspect
@Component
public class AuditAspect {

    private static final Logger log =
            LoggerFactory.getLogger(AuditAspect.class);

    @Around("@annotation(auditable)")
    public Object audit(
            ProceedingJoinPoint joinPoint,
            Auditable auditable
    ) throws Throwable {

        String method = joinPoint.getSignature().toShortString();

        log.info("Audit start. action={}, method={}",
                auditable.action(), method);

        try {
            Object result = joinPoint.proceed();

            log.info("Audit success. action={}, method={}",
                    auditable.action(), method);

            return result;
        } catch (Exception ex) {
            log.error("Audit failed. action={}, method={}, error={}",
                    auditable.action(), method, ex.getMessage());

            throw ex;
        }
    }
}

Bu real backendda foydali bo‘ladi.


22. @Transactional ham AOP misoli

Siz yozasiz:

@Transactional
public void transferMoney() {
    withdraw();
    deposit();
}

Spring ichkarida taxminan shunday qiladi:

try {
    transactionManager.begin();

    transferMoney();

    transactionManager.commit();
} catch (Exception e) {
    transactionManager.rollback();
    throw e;
}

Shuning uchun @Transactional ham AOP/proxy mexanizmiga tayanadi.


23. AOP ishlamay qoladigan holatlar

Eng ko‘p uchraydiganlari:

Muammo

Sabab

Self-invocation

Method shu class ichidan chaqirilgan

Private method

Proxy private methodni ushlamaydi

Final class/method

CGLIB override qila olmaydi

Bean Spring containerda emas

new bilan o‘zingiz yaratgansiz

Pointcut noto‘g‘ri

Method expressionga tushmayapti

Dependency yo‘q

spring-boot-starter-aop qo‘shilmagan

Aspect bean emas

@Component yo‘q

@Aspect yo‘q

Class aspect sifatida tanilmaydi


24. AOP debug qilish

Agar aspect ishlamayotgan bo‘lsa, tekshiring:

1. Dependency bormi?

spring-boot-starter-aop

2. Aspect classda ikkalasi ham bormi?

@Aspect
@Component
public class MyAspect {
}

3. Method Spring bean ichidami?

Yomon:

UserService service = new UserService();
service.save();

Bu Spring bean emas. AOP ishlamaydi.

Yaxshi:

@Autowired
private UserService userService;

Yoki constructor injection.

4. Method tashqaridan chaqirilyaptimi?

Controller → Service proxy → Method

bo‘lsa ishlaydi.

Service ichida this.method()

bo‘lsa ishlamasligi mumkin.


25. Best practice

1. AOP’ni texnik/logging/audit kabi cross-cutting logika uchun ishlating

Yaxshi:

@Auditable(action = "ORDER_CANCELLED")
public void cancelOrder() {
}

Yomon:

@Around(...)
public void calculateBusinessRule() {
}

Biznes qoidani yashirin aspectga chiqarish kodni tushunarsiz qiladi.


2. @Aroundda exceptionni yutib yubormang

Yomon:

try {
    return joinPoint.proceed();
} catch (Exception e) {
    return null;
}

Bu buglarni yashiradi.

Yaxshi:

try {
    return joinPoint.proceed();
} catch (Exception e) {
    log.error("Error", e);
    throw e;
}

3. Pointcutni juda keng qilmang

Yomon:

@Around("execution(* com.example..*(..))")

Bu butun projectga tushib ketadi.

Yaxshi:

@Around("execution(* com.example.order.service.*.*(..))")

Yoki custom annotation ishlating:

@Around("@annotation(Auditable)")

4. Aspect ichida og‘ir ish qilmaslik

Yomon:

@Around("@annotation(Auditable)")
public Object audit(ProceedingJoinPoint jp) {
    // remote API call
    // heavy DB query
}

Yaxshiroq:

  • event publish qilish;

  • async audit queue;

  • Kafka/RabbitMQga event yuborish;

  • minimal log yozish.


26. Kichik amaliy loyiha

Vazifa

Service method execution timeni o‘lchaydigan custom annotation yozing.

1. Annotation

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TrackTime {
}

2. Aspect

@Aspect
@Component
public class TrackTimeAspect {

    private static final Logger log =
            LoggerFactory.getLogger(TrackTimeAspect.class);

    @Around("@annotation(TrackTime)")
    public Object track(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.nanoTime();

        try {
            return joinPoint.proceed();
        } finally {
            long duration = System.nanoTime() - start;

            log.info("{} took {} ms",
                    joinPoint.getSignature().toShortString(),
                    duration / 1_000_000);
        }
    }
}

3. Service

@Service
public class ReportService {

    @TrackTime
    public String generate() {
        return "Report generated";
    }
}

Natija logda:

ReportService.generate() took 4 ms

27. Interview savollar

Savol 1: Spring AOP nima?

Javob:

Spring AOP cross-cutting concernlarni, masalan logging, transaction, security, cache kabi logikalarni biznes kodga aralashtirmasdan qo‘shish mexanizmi. Spring AOP asosan proxy orqali ishlaydi.


Savol 2: Advice turlari qaysilar?

Javob:

@Before, @After, @AfterReturning, @AfterThrowing, @Around. Eng kuchlisi @Around, chunki u methodni ishga tushirishdan oldin va keyin logika qo‘shadi, hatto methodni chaqirmasligi ham mumkin.


Savol 3: Pointcut nima?

Javob:

Pointcut - qaysi methodlarga aspect qo‘llanishini belgilaydigan expression. Masalan execution(* com.example.service.*.*(..)).


Savol 4: Spring AOP private methodga ishlaydimi?

Javob:

Yo‘q. Spring AOP proxy-based bo‘lgani uchun private methodlarni intercept qila olmaydi. Odatda AOP public methodlarda ishlatiladi.


Savol 5: Self-invocation nima?

Javob:

Bir class ichida bir method boshqa AOP annotationli methodni this.method() orqali chaqirsa, chaqiruv proxy orqali o‘tmaydi. Shuning uchun @Transactional, @Cacheable, custom aspect ishlamasligi mumkin.


Savol 6: JDK Dynamic Proxy va CGLIB farqi nima?

Javob:

JDK Dynamic Proxy interface asosida proxy yaratadi. CGLIB esa classdan subclass yaratadi. Interface bo‘lsa JDK proxy ishlashi mumkin, interface bo‘lmasa Spring CGLIB ishlatadi. Final class/methodlarda CGLIB muammo berishi mumkin.


28. Qisqa xulosa

Spring AOP - Java developer uchun juda muhim mavzu.

Asosiy eslab qolish kerak bo‘lganlar:

Tushuncha

Qisqa ma’no

Aspect

Qo‘shimcha logika yozilgan class

Advice

Qachon ishlashini bildiradi

Pointcut

Qaysi methodlarga tushishini bildiradi

Join point

Method chaqirilish nuqtasi

@Around

Eng kuchli advice

Proxy

Spring AOP’ning asosi

Self-invocation

Ichki method chaqiruvida AOP ishlamasligi

Custom annotation

Productionda toza va nazoratli AOP ishlatish usuli

Eng muhim fikr:

Spring AOP biznes kodni toza saqlash uchun kerak. Lekin haddan tashqari ishlatilsa, kod oqimini yashirib yuboradi va debugni qiyinlashtiradi.