Nov 30, 2025
Aspect Oriented Programming (AOP) adalah paradigma pemrograman yang bertujuan untuk meningkatkan modularitas dengan memisahkan cross-cutting concern (masalah lintas bagian di aplikasi) dari inti logika aplikasi. Simple-nya memisahkan fungsionalitas yang umum seperti logging, caching, atau security terpisah dari logika bisnis aplikasi. Fungsionalitas tersebut dikumpulkan di suatu tempat kemudian disisipkan ke dalam logika bisnis aplikasi, tempat berkumpulnya fungsionalitas-fungsionalitas terebut disebut aspect. Dengan AOP, developer dapat menambahkan fungsionalitas ini ke kode yang sudah ada tanpa mengubah struktur kode aslinya. Bayangkan kita tidak harus nulis log.info(“start method”) di setiap method, AOP bisa otomatis tambahkan logging ke semua method tanpa mengubah isi kodenya. Ini bikin kode modular, mudah maintain, dan reusable.
Ada beberapa terminologi yang harus diketahui dalam AOP ini.
Poincut: sebuah ekspresi/kondisi penanda kapan fungsionalitas tersebut harus disisipkan, seperti pada method tertentu yang ada di dalam suatu package.
Join Point: titik spesifik dalam program di mana sebuah advice dapat disisipkan, seperti eksekusi sebuah metode.
Advice: fungsionalitas yang akan kita sisipkan jika suatu method tersebut memenuhi syarat poincut.
Aspect: tempat dimana kumpulan Advice dan Pointcut berada.
Selain Spring AOP, ada framework native bernama AspectJ untuk menyisipkan fungsionalitas khusus ke dalam object/method saat kompilasi. Perbedaannya dengan spring AOP, dia benar-benar di-compile dan dimasukkan secara langsung kepada object/method target, sedangkan Spring AOP dia sebenarnya menggunakan proxy pattern untuk menyisipkan logika advice-nya. Artikel ini tidak akan membahas AspectJ karena berfokus pada Spring AOP.
Seperti yang sudah dijelaskan, Spring AOP ini menggunakan Proxy yang berjalan di runtime, tidak seperti AspectJ yang di-compile terlebih dahulu. Karena hanya berjalan di runtime, aspect di Spring hanya dapat diterapkan pada Spring-managed bean (Object yang dikelola Spring). Cara kerjanya sebagai berikut
Ketika Spring mendeteksi sebuah bean yang perlu dikenai advice (target object), ia akan membuat objek Proxy di sekitarnya.
Ketika method pada target object dipanggil, pemanggilan tersebut sebenarnya melalui objek Proxy terlebih dahulu.
Proxy kemudian akan mencegat panggilan tersebut dan mengeksekusi advice yang relevan (misalnya @Before), lalu meneruskan panggilan ke method asli, dan akhirnya mengeksekusi advice sisa (misalnya @AfterReturning).
Dalam Spring AOP, pointcut paling sering didefinisikan menggunakan bahasa ekspresi AspectJ Pointcut Language. Seperti yang sudah dibahas, pointcut adalah sebuah ekspresi/kondisi dimana advice harus diterapkan. Berikut adalah ekspresi-ekspresi yang dapat digunakan dalam Spring AOP.
execution() akan mencocokkan eksekusi sebuah method tertentu. Berikut adalah struktur sintaksnya.
execution(modifier? return_type? declarating_type? name(args) throws?)
Tanda tanya (?) menandakan bagian optional.
Tanda bintang (*) adalah wildcard yang cocok dengan apapun
Titik dua (..) adalah wildcard yang cocok dengan nol atau lebih argumen
Berikut contoh-contoh expresi execution()
execution(* *(..)) : Semua eksekusi method, di kelas mana pun, dengan argumen apa pun, dan tipe kembalian apa pun. (Sangat luas!)
execution(public * *(..)) : Semua eksekusi method dengan modifier public, di kelas mana pun, dengan argumen apa pun, dan tipe kembalian apa pun.
execution(public * com.example.service..(..)) : Semua eksekusi method dengan modifier public, dengan return type apa pun, di dalam package com.example.service, di kelas mana pun, dan nama method apapun dengan argumen apa pun.
execution(public * com.example.service.UserService.*(..)) : Semua eksekusi method dengan modifier public, dengan return type apa pun, di dalam package com.example.service, di kelas UserService, dan nama method apapun dengan argumen apa pun.
execution(public String com.example.service..get(..)) : Semua eksekusi method dengan modifier public, dengan return type String, di dalam package com.example.service, di kelas mana pun, dan nama method yang diawali dengan get dengan argumen apa pun.
execution(public String com.example.service..(Long, ..)) : Semua eksekusi method dengan modifier public, dengan return type String, di dalam package com.example.service, di kelas mana pun, dan nama method apa pun dengan argumen pertama bertipe Long dan argumen-argumen lain setelahnya (atau tidak ada).
execution(public * com.example.repo...(..)) : Semua eksekusi method dengan modifier public, dengan return apa pun, di dalam semua sub-package dari com.repo (termasuk com.repo.order, com.repo.user, dll.), di kelas mana pun, dan nama method apa pun dengan argumen apa pun.
within() akan mencocokkan join point (eksekusi method) yang dideklarasikan di dalam tipe (kelas atau interface) yang ditentukan. Berikut contoh-contoh ekspresi within()
within(com.example.service.*) : Mencocokkan semua method yang dideklarasikan di semua kelas yang ada di dalam package com.example.service.
within(com.example.util..*) : Mencocokkan semua method yang dideklarasikan di semua kelas yang berada di package com.example.util dan semua sub-package-nya (com.example.util.db, com.example.util.date, dll.).
within(com.example.service.UserServiceImpl) : Mencocokkan semua method yang dideklarasikan di dalam kelas UserServiceImpl.
@annotation : Mencocokkan eksekusi method jika method tersebut memiliki anotasi tertentu. Contoh @annotation(com.example.CustomLog) ini akan match di semua method yang memiliki annotation CustomLog.
@within : Mencocokkan method jika kelas/tipe yang mendeklarasikannya memiliki anotasi tertentu. Contoh @within(org.springframework.stereotype.Service) ini akan match di semua method yang ada di kelas yang memiliki annotation Service.
@bean : Mencocokkan method pada bean yang memiliki ID atau nama tertentu. Contoh bean(userService), ini akan match semua public method pada bean bernama “userService”.
args() : Mencocokkan eksekusi method berdasarkan tipe argumen yang diterimanya. Urutan dan jumlah argumen itu penting. Contoh args(Long, ..) ini akan match dengan method apa pun yang menerima argumen pertama bertipe Long, diikuti oleh argumen apa pun (atau tidak ada) atau args(.., Long) ini akan match dengan method apa pun yang menerima argumen terakhir bertipe Long.
@args : mencocokkan eksekusi method jika tipe argumen memiliki anotasi tertentu. Contoh @args(com.contoh.annotation.ValidatableDTO, ..) ini akan match dengan method yang argumen pertamanya adalah objek yang kelasnya dianotasi dengan @ValidatableDTO
Aspect dideklarasikan dengan kelas dengan annotation @Aspect, kelas ini yang nantinya akan menampung poincut dan advice. Berikut cara mendeklarasikan Aspect.
@Aspect
@Component
public class LoggingAspect {
}
Ada lima jenis advice utama. Semua jenis ini dideklarasikan sebagai method biasa di dalam kelas @Aspect, dan ditandai dengan anotasi advice yang sesuai.
Advice ini akan dijalankan sebelum method target dieksekusi. Berikut contohnya.
@Before("execution(* com.example.service.*.*(..))") // isi dengan poincut
public void logBefore(JoinPoint joinPoint) {
// method ini akan dijalankan sebelum method target
System.out.println("ADVICE @Before: Before execution method in com.example.service");
}
Advice ini akan dijalankan setelah method target selesai, terlepas dari hasilnya (baik sukses atau melempar exception). Ini seperti blok finally. Berikut contohnya
@After("execution(* com.example.service.*.*(..))")
public void logAfterexecution(* com.example.service.*.*(..))JoinPoint joinPoint) {
// method ini akan dijalankan setelah method target
System.out.println("ADVICE @After: After execution method in com.example.service");
}
Advice ini akan dijalankan setelah method target selesai dengan sukses (mengembalikan nilai, tanpa melempar exception). Berikut contohnya
@AfterReturning(
pointcut = "execution(* com.example.service.*.*(..))",
returning = "result" // 'result' akan memegang nilai kembalian dari method target
)
public void logAfterReturning(JoinPoint joinPoint, Object result) {
// Logika dijalankan setelah method sukses dan mendapatkan hasilnya
System.out.println("ADVICE @AfterReturning: Method executed. Result: " + result);
}
Advice ini akan dijalankan setelah method target keluar dengan melempar exception. Berikut contohnya
@AfterThrowing(
pointcut = "execution(* com.example.service.*.*(..))",
throwing = "e" // 'e' akan memegang exception yang dilempar
)
public void logAfterThrowing(JoinPoint joinPoint, Throwable e) {
// Logika dijalankan setelah method melempar exception
System.out.println("ADVICE @AfterThrowing: Exception occured in method. Error: " + e.getMessage());
}
Advice ini seperti gabungan before dan after, akan dijalankan diantara sebelum dan sesudah sekaligus. Berikut contohnya.
@Around("execution(* com.example.service.*.*(..))")
public Object logAround(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
// 1. Logika sebelum method target
System.out.println("ADVICE @Around (Pre): executing...");
// **2. Eksekusi Method Target ASLI**
Object hasil = pjp.proceed();
// 3. Logika setelah method target
long end = System.currentTimeMillis();
System.out.println("ADVICE @Around (Post): Method executed in " + (end - start) + "ms.");
// 4. Mengembalikan hasil method target
return hasil;
}
Dalam mendeklarasikan advice, kita pasti ingin menerapkan advice yang sama pada satu pointcut, contohnya sebagai berikut.
@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.example.service.*.*(..))")
public void logBefore(JoinPoint joinPoint) {
System.out.println("Before service");
}
@After("execution(* com.example.service.*.*(..))")
public void logAfter(JoinPoint joinPoint) {
System.out.println("After service");
}
}
Kode seperti di atas pastinya akan menyusahkan kita dan membuat kode tidak rapi. Agar lebih rapi, kita bisa mendeklarasikan pointcut di method khusus:
@Aspect
@Component
public class LoggingAspect {
@PointCut("execution(* com.example.service.*.*(..))")
public void serviceMethods() {}
@Before("serviceMethods()")
public void logBefore(JoinPoint joinPoint) {
System.out.println("Before service");
}
@After("serviceMethods()")
public void logAfter(JoinPoint joinPoint) {
System.out.println("After service");
}
}
Beberapa pointcut juga bisa dikombinasikan menggunakan operator &&, ||, ! Contohnya sebagai berikut
@Aspect
@Component
public class LoggingAspect {
@Pointcut("within(com.app.service..*)")
public void inService() {}
@Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")
public void transactionalMethods() {}
@Before("inService() && transactionalMethods()")
public void log() {
System.out.println("Called a transactional service method");
}
}
Objek JoinPoint adalah interface yang disediakan oleh Spring AOP (bagian dari package org.aspectj.lang) yang memberikan informasi tentang join point yang sedang dieksekusi. Objek JointPoint dapat diakses di semua advice kecuali @Around
JoinPoint menyediakan method untuk mendapatkan representasi dari method yang sedang dieksekusi, dapat diambil dengan method getSignature() yang akan mengembalikan objek Signature.
Dari objek Signature yang dikembalikan, kita dapat memanggil beberapa method berikut.
getName() : nama method yang dieksekusi. Contoh nilai “saveUser”
getDeclaringTypeName() : mama kelas atau interface tempat method dideklarasikan. Contoh nilai “com.example.service.UserService”
toShortString() : representasi signature singkat. Contoh nilai “void saveUser(String)“
getTarget() : Mengembalikan objek target yang sebenarnya (implementasi asli UserServiceImpl) yang sedang dikenai advice.
getThis() : Mengembalikan objek proxy yang digunakan untuk memanggil method.
getKind() : Mendapatkan jenis join point (misalnya: “method-execution”)
Kita dapat mengambil argumen yang diteruskan ke method target. Untuk mendapatkan argumen-argumen nya dengan menggunakan getArgs() tipe yang akan dikembalikan adalah Object[]
ProceedingJoinPoint adalah subclass dari JoinPoint sehingga semua fitur JoinPoint bisa didapatkan oleh ProceedingJoinPoint, tetapi yang membedakan adalah adanya method proceed() yang digunakan untuk melanjutkan eksekusi method target.
contoh dasarnya adalah sebegai berikut
@Around("execution(* com.example.service.*.*(..))")
public Object measureTime(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
Object result = pjp.proceed(); // <-- menjalankan method target
long end = System.currentTimeMillis();
System.out.println("Time: " + (end - start));
return result;
}
Sebagai studi kasus sederhana kita akan gunakan kasus menambahkan logging dengan aspect pada kelas NotificationService, sebelum itu ada dependency yang harus kita tambahkan terlebih dahulu.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
struktur project sebelumnya adalah seperti ini
├── PracticeApplication.class
├── provider
│ ├── EmailNotifier.class
│ └── Notifier.class
└── service
└── NotificationService.class
kita akan coba membuat aspect untuk logging dari NotificationService
@Service
public class NotificationService {
private final Notifier notifier;
@Autowired
public NotificationService(Notifier notifier) {
this.notifier = notifier;
}
public void send(String message) {
notifier.notify(message);
}
}
untuk membuat aspect, bikin saja class aspect nya di mana saja, biar lebih rapi simpan saja di dalam forder aspect seperti ini.
├── aspect
│ └── LoggingAspect.class
├── PracticeApplication.class
├── provider
│ ├── EmailNotifier.class
│ └── Notifier.class
└── service
└── NotificationService.class
untuk membuat aspect tinggal bikin class dengan annotation Component dan Aspect
@Aspect
@Component
public class LoggingAspect {
}
sebagai studi kasus sederhana saja, kita akan bikin logging sebelum dan sesudah notification dikirim, juga dengan pengukuran berapa lama method tersebut dijalankan.
Pertama kita bikin dulu pointcut nya, karena NotificationService ini berada di dalam service kita bisa bikin pointcut seperti ini.
@Aspect
@Component
public class LoggingAspect {
@Pointcut("execution(public * com.example.practice.service.*.*(..))")
public void serviceMethods() {
}
}
pointcut di atas itu artinya semua method public yang ada di dalam package com.example.practice.service. Kalo sudah bikin pointcut seperti itu tinggal bikin saja advice-nya seperti ini.
@Aspect
@Component
public class LoggingAspect {
@Pointcut("execution(* com.example.practice.service.*.*(..))")
public void serviceMethods() {
}
@Before("serviceMethods()")
void beforeSendingNotification(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
System.out.println("Method: " + methodName);
System.out.println("Arguments: " + Arrays.toString(joinPoint.getArgs()));
}
@AfterReturning("serviceMethods()")
void afterSendingNotification(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
System.out.println("Method '" + methodName + "' executed successfully");
System.out.println("Arguments: " + Arrays.toString(joinPoint.getArgs()));
}
@Around("serviceMethods()")
Object aroundSendingNotification(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
Object proceed = proceedingJoinPoint.proceed();
long endTime = System.currentTimeMillis();
System.out.println("Execution: " + proceedingJoinPoint.getSignature().toShortString() + " = " + (endTime - startTime));
return proceed;
}
}
Aspect Oriented Programming (AOP) adalah paradigma pemrograman yang bertujuan untuk meningkatkan modularitas dengan memisahkan cross-cutting concern (masalah lintas bagian di aplikasi) dari inti logika aplikasi. AOP ini sangat berguna jika kita ingin menambahkan fungsional yang umum seperti logging atau caching kepada method tanpa harus mengubah isi method-nya. Ini akan membuat kode menjadi lebih bersih dan rapih, karena memisahkan logika logging dari logika inti.