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? |
|---|---|
| Method oldidan transaction ochadi, oxirida commit/rollback qiladi |
| Method natijasini cache qiladi |
| Methodni boshqa threadda ishlatadi |
| 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
Repositoryhammasida 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? |
|---|---|
| Methoddan oldin |
| Method tugagandan keyin, exception bo‘lsa ham |
| Method muvaffaqiyatli return qilgandan keyin |
| Method exception tashlaganda |
| 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‘llanadi3.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 created6. 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 |
| 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: Ali8. @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 failedBu 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_CREATEDReal 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 objectMasalan:
@Service
public class PaymentService {
@Transactional
public void pay() {
}
}Spring taxminan shunday qiladi:
paymentService proxy
↓
transaction begin
↓
real paymentService.pay()
↓
transaction commit/rollback15. 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 proxy15.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 |
|
Pointcut noto‘g‘ri | Method expressionga tushmayapti |
Dependency yo‘q |
|
Aspect bean emas |
|
| Class aspect sifatida tanilmaydi |
24. AOP debug qilish
Agar aspect ishlamayotgan bo‘lsa, tekshiring:
1. Dependency bormi?
spring-boot-starter-aop2. 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 → Methodbo‘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 ms27. 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 |
| 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.