Implementasi Konsep AOP Dengan Spring
Di artikel sebelumnya telah dijelaskan mengenai konsep dasar dari Aspect Oriented Programming (AOP). Pada artikel kali ini kita akan melihat bagaimana Spring Framework mengimplementasikan konsep AOP dengan cara yang elegan dan seamless. Semua source code pada artikel ini dapat diakses disini.
Contoh Kasus
Sebagai contoh kasus kita akan membuat sebuah class sederhana yaitu BankAccount
yang memiliki beberapa fitur seperti deposit()
, withdraw()
, transfer()
. Fitur-fitur ini nantinya akan disisipkan dengan fungsi seperti logging, monitoring, maupun security checking baik sebelum, maupun sesudah eksekusi. Fungsi-fungsi ini disebut dengan advice atau dalam konteks AOP disebut dengan aspect. Sedangkan fitur-fitur yang akan diterapkan(embed) oleh advice disebut joint point.
public class BankAccount {
public BankAccount(String owner, String accountNumber) {
}
public BigDecimal deposit(BigDecimal amount) {
System.out.println("executing deposit");
balance = balance.add(amount);
return balance;
}
...
}
Dependency
Spring sendiri sudah menyediakan library untuk handle AOP yang terdapat pada library spring-context
, namun memiliki fungsi yang terbatas dimana hanya mendukung menggunakan xml config saja. Jika kita ingin menggunakan annotation config, Spring mendukung penggunaan library external seperti aspectj
yang nanti akan kita bahas di tulisan berikutnya.
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
...
</dependencies>
Saya juga tidak menggunakan Spring secara langsung melainkan dibungkus menggunakan Spring Boot.
Advice
Advice merupakan elemen utama dari konsep AOP, dimana advice diterapkan diberbagai layer aplikasi atau istilah keren-nya adalah cross-cutting concerns. Sebagai contoh fungsi logging dalam suatu aplikasi biasanya diterapkan diberbagai layer, setiap fitur yang dieksekusi akan menjalankan fungsi logging. Oleh karena itu fungsi logging disini disebut advice atau dalam konteks AOP disebut aspect itu sendiri. Satu advice bisa diterapkan sebelum atau sesudah proses eksekusi joint point. Berikut contoh implementasi pada Spring:
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class LogBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
String className = target.getClass().getName();
String methodName = method.getName();
System.out.println("[LOG BEFORE] Executing method " + methodName + "() " +
"of class " + className + " with following parameters.");
for(Object parameter : args){
System.out.println("[LOG BEFORE] " + parameter.getClass().getName() + " = " + parameter.toString());
}
}
}
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
public class LogAfterAdvice implements AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
String className = target.getClass().getName();
String methodName = method.getName();
System.out.println("[LOG AFTER] Returning from method " + methodName +
"() of class " + className + " with " + returnValue.toString());
}
}
Class yang diimplementasikan menggunakan interface MethodBeforeAdvice
akan dijalankan sebelum target/joint point dieksekusi. Sedangkan class yang diimplementasikan menggunakan interface AfterReturningAdvice
akan dijalankan setelah target dieksekusi. Pada interface AfterReturningAdvice
kita bisa mengambil object hasil return (jika memiliki return value) dari method target yang bisa didapatkan melalui parameter awal dari method afterReturning()
. Selain itu kita juga bisa menambahkan lagi beberapa advice sesuai keinginan kita.
Daftarkan class-class diatas pada config.xml
agar Spring membuatkan object-object nya untuk kita.
<beans>
<bean id="logBeforeAdvice" class="com.hiwijaya.aop.advice.LogBeforeAdvice"/>
<bean id="logAfterAdvice" class="com.hiwijaya.aop.advice.LogAfterAdvice"/>
...
</beans>
Pointcut
Pada pointcut ini kita mendefinisikan advice mana saja yang akan diterapkan pada suatu target. Berikut contoh konfigurasinya:
<beans>
...
<bean id="logBeforePointcut" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
<property name="advice" ref="logBeforeAdvice" />
<property name="mappedNames">
<list>
<value>deposit</value>
<value>withdraw</value>
<value>transfer</value>
</list>
</property>
</bean>
<bean id="logAfterPointcut" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
<property name="advice" ref="logAfterAdvice" />
<property name="mappedNames">
<list>
<value>deposit</value>
<value>withdraw</value>
<value>transfer</value>
</list>
</property>
</bean>
...
</beans>
Pada konfigurasi diatas kita membuat 2 buah pointcut yaitu logBeforePointcut
dan logAfterPointcut
yang mana keduanya akan diterapkan pada 3 buah target yaitu deposit()
, withdraw()
, dan transfer()
.
Object Proxy
Langkah terakhir adalah mendefinisikan proxy. Proxy merupakan suatu object hasil dari proses weaving suatu framework yang dimana pada proses ini setiap aspect diterapkan pada target-target yang sudah didefinisikan pada pointcut.
<beans>
<bean id="account1" class="com.hiwijaya.aop.BankAccount">
<constructor-arg name="owner" value="Happy Indra Wijaya"/>
<constructor-arg name="accountNumber" value="1234567890"/>
</bean>
<bean id="bankAccountProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="account1"/>
<property name="interceptorNames">
<list>
<!-- The order is important -->
<value>logBeforePointcut</value>
<value>logAfterPointcut</value>
...
</list>
</property>
</bean>
</beans>
Ada kondisi dimana kita memiliki banyak advice yang diterapkan pada satu target. Misalkan kita memiliki 2 atau 3 advice before pada satu target, pertanyaannya adalah mana advice yang akan dieksekusi terlebih dahulu? Untuk menentukan urutan advice mana yang akan dieksekusi terlebih dahulu yaitu berdasarkan urutan item dari <property name="interceptorNames">
.
Run
Saatnya jalankan programnya!
public class Main {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("config.xml");
BankAccount account = context.getBean("bankAccountProxy", BankAccount.class);
account.deposit(new BigDecimal(1000000));
}
}
Jika kita perhatikan pada potongan program diatas, kita membuat object account
dari bean bankAccountProxy
yang dimana object tersebut sudah di-embed dengan advice/aspect melalui proses weaving. Berikut output dari program di atas:
[LOG BEFORE] Executing method deposit() of class com.hiwijaya.aop.BankAccount with following parameters
[LOG BEFORE] java.math.BigDecimal = 1000000
executing deposit
[LOG AFTER] Returning from method deposit() of class com.hiwijaya.aop.BankAccount with 1000000
Untuk kode program lengkap dari contoh diatas dapat diakses disini.
Ref: