面試官:拋開Spring來說,如何自己實現(xiàn)Spring AOP?
哈嘍,大家好,我是了不起。
作為一名Java程序員,面向切面編程這種編程思想,應該是我們?nèi)粘>幋a中常應用的編程思想。
這種編程范式,旨在提高代碼的模塊化程度。在AOP中,特定類型的問題被定義為“切面”,例如日志、事務管理或安全性等,這些切面可以在不改變核心業(yè)務邏輯的情況下,被插入程序的不同部分。對于提高代碼的優(yōu)雅,減少冗余度特別有用。
雖然Spring框架中的Spring AOP是Java社區(qū)中最著名的AOP實現(xiàn),但為了完全理解這種思想,我們可以不依賴Spring來實現(xiàn)AOP功能。
1、AOP 核心概念
1.1 切面(Aspects)
切面是AOP的核心,它將橫切關(guān)注點(如日志、事務處理等)與主業(yè)務邏輯分離。一個切面定義了何時(何處)和如何執(zhí)行這些橫切關(guān)注點。
1.2 連接點(Join Points)
連接點是應用執(zhí)行過程中能夠插入切面的點。在Java中,這通常是方法的調(diào)用。
1.3 通知(Advice)
通知定義了切面具體要執(zhí)行的操作。主要類型包括前置通知(before)、后置通知(after)、環(huán)繞通知(around)、拋出異常時通知(after throwing)和返回時通知(after returning)。
1.4 切點(Pointcuts)
切點定義了在哪些連接點執(zhí)行切面代碼。它是一組表達式,用于匹配特定的連接點。
2、使用Java動態(tài)代理
Java動態(tài)代理是一種在運行時創(chuàng)建代理對象的方法,代理對象可以在調(diào)用實際對象的方法前后執(zhí)行額外的操作。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 簡單的AOP實現(xiàn)
public class SimpleAOP {
// 獲取代理對象
public static Object getProxy(Object target, Advice advice) {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
advice.beforeMethod(method);
Object result = method.invoke(target, args);
advice.afterMethod(method);
return result;
}
}
);
}
// 通知接口
public interface Advice {
void beforeMethod(Method method);
void afterMethod(Method method);
}
}
在上述代碼中,getProxy 方法創(chuàng)建了一個代理對象,該對象在每次方法調(diào)用前后執(zhí)行定義在 Advice接口中的操作。
3、字節(jié)碼操作
字節(jié)碼操作是更高級但復雜的AOP實現(xiàn)方式。這涉及在類加載到JVM時修改其字節(jié)碼,插入額外的代碼。
3.1 使用ASM或ByteBuddy
- ASM:一種低級字節(jié)碼操作庫,提供了對字節(jié)碼的細粒度控制。
- ByteBuddy:相比ASM,ByteBuddy提供了更簡潔的API,適合那些不需要深入字節(jié)碼細節(jié)的場景。
下面我以 ByteBuddy 為例,展示一下如何使用ByteBuddy來實現(xiàn)一個基本的AOP功能:在方法執(zhí)行前后添加日志。
①、添加ByteBuddy依賴到你的項目中。如果你使用Maven,可以在pom.xml文件中加入以下依賴:
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.11.22</version>
</dependency>
②、使用ByteBuddy來創(chuàng)建一個代理類,這個類在方法執(zhí)行前后打印日志:
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.implementation.FixedValue;
import net.bytebuddy.matcher.ElementMatchers;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import java.lang.reflect.Modifier;
public class AOPExample {
public static void main(String[] args) throws Exception {
DynamicType.Unloaded<Object> dynamicType = new ByteBuddy()
.subclass(Object.class)
.method(ElementMatchers.named("toString"))
.intercept(MethodDelegation.to(LoggerInterceptor.class))
.make();
Class<?> dynamicTypeLoaded = dynamicType
.load(AOPExample.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
.getLoaded();
Object dynamicObject = dynamicTypeLoaded.newInstance();
System.out.println(dynamicObject.toString());
}
public static class LoggerInterceptor {
public static String intercept() {
System.out.println("Method intercepted before execution");
String result = "Hello from intercepted method";
System.out.println("Method intercepted after execution");
return result;
}
}
}
在上述代碼中,我們創(chuàng)建了一個代理類,它覆蓋了toString方法。方法被調(diào)用時,我們的LoggerInterceptor類將被調(diào)用。在LoggerInterceptor類中,我們在方法執(zhí)行前后添加了日志。