Spring AOP 深度剖析與實踐
在當今復雜多變的軟件開發(fā)領域,Spring 框架無疑是一顆璀璨的明星。而其中的 Spring AOP(面向切面編程)更是以其獨特的魅力和強大的功能,為開發(fā)者們打開了一扇通往全新編程境界的大門。
當我們深入探索 Spring AOP 的世界,就仿佛置身于一個充滿無限可能的編程宇宙之中。它猶如一把神奇的鑰匙,能夠巧妙地解開代碼結構中的復雜癥結,讓我們可以從全新的切面視角去審視和構建軟件。無論是實現(xiàn)系統(tǒng)級的橫切關注點,如日志記錄、事務管理、安全控制等,還是對業(yè)務邏輯進行精細化的分離與整合,Spring AOP 都展現(xiàn)出了無與倫比的適應性和靈活性。它并非僅僅是一種技術手段,更是一種編程理念的革新,為我們帶來了高效、簡潔且極具擴展性的開發(fā)方式。讓我們一同踏上這趟精彩的 Spring AOP 之旅,去領略它所蘊含的奧秘與力量。
一、詳解Spring對AOP的設計與實現(xiàn)
1.對AOP的理解
AOP(Aspect-Oriented Programming:面向切面編程),它實際做的就是將業(yè)務和一些非業(yè)務進行拆解,降低彼此業(yè)務模塊與非業(yè)務模塊的耦合度,便于后續(xù)的擴展維護。例如權限校驗、日志管理、事務處理等都可以使用AOP實現(xiàn)。而Spring就是基于動態(tài)代理實現(xiàn)AOP的。如果被代理的類有實現(xiàn)接口的話,就會基于JDK Proxy完成代理的創(chuàng)建,反之就是通過Cglib完成代理創(chuàng)建,當然你也可以強制使用Cglib。
2.什么是AOP中切點、切面、通知
AOP中有很多核心術語,分別是:
- 目標(Target): 這就被代理的對象,例如我們希望對UserService每個方法進行增強(在不動它的代碼情況下增加一些非業(yè)務的動作),那么這個UserService就是目標。
- 代理(Proxy): 就是給你被代理后的對象的廠商,例如我們上面說過希望對UserService每個方法進行增強,那么給用戶返回增強后的對象的類就是代理類。
- 連接點(JoinPoint):目標對象,每一個可能可以被增強的方法都可以稱為連接點,盡管它最后可能不會被增強。
- 切入點(Pointcut): 連接點中即能夠應用通知的位置。
- 通知(Advice): 不要被表面的語義誤導,通知并不是告知某人的意思,通知的意思是攔截對象后,做的增強操作,也就是攔截后要執(zhí)行什么代碼。
- 切面(Aspect): 切入點(Pointcut)+通知(Advice)。
- 織入(Weaving):把通知的動作融入到對象中,生成代理對象的過程就叫做織入。
3.Spring AOP和AspectJ AOP的區(qū)別
Spring AOP屬于運行時增強,基于代理(Proxying)實現(xiàn)的。而AspectJ AOP屬于編譯時增強,基于字節(jié)碼操作(Bytecode Manipulation)實現(xiàn)的。
在《精通spring4.x》一書中,我們可以知道,jdk生成的代理對象性能遠遠差于cglib生成代理對象,但cglib創(chuàng)建代理對象花費的時間卻遠遠高于jdk代理創(chuàng)建的對象。所以在spring框架的使用中,如果是單例的bean需要實現(xiàn)aop等操作,我們建議是使用cglib動態(tài)代理技術:
4.AspectJ 通知類型
- Before(前置通知): 目標對象方法調用前觸發(fā)增強。
- After (后置通知):目標對象方法調用后進行增強。
- AfterReturning(返回通知):目標對象方法執(zhí)行結束,返回值時進行增強。
- AfterThrowing(異常通知):目標對象方法執(zhí)行報錯并拋出時做的增強。
- Around(環(huán)繞通知):這個比較常用了,目標對象方法調用前后我們可以做各種增強操作,甚至不調用對象的方法都能做到。
5.多個切面執(zhí)行順序我們如何確定
答: 有兩種方式:
- 注解法:使用@Order注解來決定切面bean的執(zhí)行順序。
// 值越小優(yōu)先級越高
@Order(1)
@Component
@Aspect
public class LoggingAspect implements Ordered {
- 繼承接口法:implements Ordered接口
@Component
@Aspect
public class LoggingAspect implements Ordered {
// ....
@Override
public int getOrder() {
// 返回值越小優(yōu)先級越高
return 1;
}
}
6.AOP操作在bean生命周期的那個階段實現(xiàn)
在bean初始化前后也就我們常說的BPP階段完成AOP類的緩存以及通知器創(chuàng)建。在bean初始化后,根據需要結合通知器完成代理類的改造。
7.動態(tài)代理是什么
是在運行期間,創(chuàng)建目標對象的代理對象,目標對象不變,我們通過對方法動態(tài)攔截,進行前置或者后置等各種增強操作。AOP中就有CGLIB動態(tài)代理和JDK動態(tài)代理技術。
8.動態(tài)代理的創(chuàng)建過程
AOP提供了一個默認工廠根據類是否有繼承接口或者是否就是目標類決定創(chuàng)建的策略。然后根據不同的策略決定代理類的創(chuàng)建。
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
//如果是接口則走JdkDynamicAopProxy反之走ObjenesisCglibAopProxy
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
以下便是jdk代理的創(chuàng)建策略:
@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
.........
//獲取被代理的類的接口
Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
//生成代理對象并返回
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
以下便是cglib的創(chuàng)建策略:
@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
.......
try {
.......
//將當前類信息通過enhancer 生成代理對象
Enhancer enhancer = createEnhancer();
if (classLoader != null) {
enhancer.setClassLoader(classLoader);
if (classLoader instanceof SmartClassLoader &&
((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
enhancer.setUseCache(false);
}
}
enhancer.setSuperclass(proxySuperClass);
enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(classLoader));
Callback[] callbacks = getCallbacks(rootClass);
Class<?>[] types = new Class<?>[callbacks.length];
for (int x = 0; x < types.length; x++) {
types[x] = callbacks[x].getClass();
}
//返回最終生成的代理對象
return createProxyClassAndInstance(enhancer, callbacks);
}
........
}
catch (Throwable ex) {
......
}
}
二、詳解CGLIB代理
1.Spring AOP和Cglib的關系
CGLIB是一個強大、高性能的代碼生成包。使用ASM操作字節(jié)碼,動態(tài)生成代理,對方法進行增強。,它廣泛的被許多AOP框架使用,為他們提供方法的攔截。
例如我們希望對某個service進行日志打印,基于CGLIB我們可以這樣實現(xiàn):
前置步驟引入依賴:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
首先創(chuàng)建用戶類
public class User {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public User() {
}
public User(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
service類
public class UserServiceImpl {
public List<User> findUserList() {
return Collections.singletonList(new User("xiaoming", 18));
}
}
代理類
public class CglibProxy<T> implements MethodInterceptor {
private static Logger logger = LoggerFactory.getLogger(CglibProxy.class);
private Object target;
public T getTargetClass(Object target) {
//設置被代理的目標類
this.target = target;
// 創(chuàng)建加強器設置代理類以及回調,當代理類被調用時,callback就會去調用intercept
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.target.getClass());
enhancer.setCallback(this);
//返回代理類
return (T) enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
logger.info("調用被代理對象的方法,代理對象:[{}],代理方法:[{}]", o.getClass().getName(), method.getName());
Object result = methodProxy.invokeSuper(o, args);
logger.info("代理調用結束,返回結果:[{}]", String.valueOf(result));
return null;
}
}
測試代碼
public class Main {
public static void main(String[] args) {
CglibProxy<UserServiceImpl> cglibProxy = new CglibProxy();
UserServiceImpl targetClass =cglibProxy.getTargetClass(new UserServiceImpl());
targetClass.findUserList();
}
}
2.Cglib代理流程是是什么樣的
如下圖所示,整體來說就是基于enhancer去配置被代理類的各種參數,然后生成代理類:
注意:final方法無法被代理,因為它不可被子類覆蓋。
3.Spring中的Cglib代理流程
源碼如下,我們可以看出和我們寫的實例代碼是差不多的。
@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
.....
try {
//獲取當前類信息獲取生成代理對象
Enhancer enhancer = createEnhancer();
if (classLoader != null) {
enhancer.setClassLoader(classLoader);
if (classLoader instanceof SmartClassLoader &&
((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
enhancer.setUseCache(false);
}
}
enhancer.setSuperclass(proxySuperClass);
enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(classLoader));
// 獲取當前類中的方法
Callback[] callbacks = getCallbacks(rootClass);
Class<?>[] types = new Class<?>[callbacks.length];
for (int x = 0; x < types.length; x++) {
types[x] = callbacks[x].getClass();
}
enhancer.setCallbackFilter(new ProxyCallbackFilter(
this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
enhancer.setCallbackTypes(types);
// 生成代理對象
return createProxyClassAndInstance(enhancer, callbacks);
}
catch (CodeGenerationException | IllegalArgumentException ex) {
.....
}
catch (Throwable ex) {
.....
}
}
三、詳解JDK代理
1. JDK代理示例
答:這個是jdk自帶的一種代理,我們只需繼承InvocationHandler即可實現(xiàn)。但是前提是這個類必須繼承某些接口才能使用jdk代理。
首先我們定義接口,User類沿用上述的:
public interface UserService {
List<User> findUserList();
}
修改UserServiceImpl:
public class UserServiceImpl implements UserService{
@Override
public List<User> findUserList() {
return Collections.singletonList(new User("xiaoming", 18));
}
}
jdk代理類:
public class JDKProxy<T> {
private static Logger logger = LoggerFactory.getLogger(JDKProxy.class);
private Object target;
public JDKProxy(Object target) {
this.target = target;
}
public T getTargetObj() {
UserService proxy;
ClassLoader loader = target.getClass().getClassLoader();
Class[] interfaces = new Class[]{UserService.class};
InvocationHandler handler = (p, method, args) -> {
logger.info("代理方法被調用,類名稱[{}],方法名稱[{}]", target.getClass().getName(), method.getName());
Object result = method.invoke(target, args);
logger.info("代理方法調用結束,返回結果:[{}]", String.valueOf(result));
return result;
};
proxy = (UserService) Proxy.newProxyInstance(loader, interfaces, handler);
return (T) proxy;
}
}
測試代碼:
public class Main {
public static void main(String[] args) {
JDKProxy<UserService> jdkProxy=new JDKProxy<>(new UserServiceImpl());
UserService userService = jdkProxy.getTargetObj();
System.out.println(userService.findUserList());
}
}
2. JDK代理的工作流程
我們不妨在jvm在下面這樣一段參數,或者在上述main方法加這個代碼:
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
然后我們通過debug可以發(fā)現(xiàn)它回步入這段代碼,其中他會生成一個ClassFile,方法名為generateClassFile:
public static byte[] generateProxyClass(final String name,
Class<?>[] interfaces,
int accessFlags)
{
ProxyGenerator gen = new ProxyGenerator(name, interfaces, accessFlags);
final byte[] classFile = gen.generateClassFile();
...
}
而代理方法做的事情,整體如下所示,可以看到它整體做的就是拿著被代理類的 各種方法封裝成ProxyMethod方法,然后寫入class文件中:
/**
* Generate a class file for the proxy class. This method drives the
* class file generation process.
*/
private byte[] generateClassFile() {
/* 第一步:將所有方法包裝成ProxyMethod對象 */
// 將Object類中hashCode、equals、toString方法包裝成ProxyMethod對象
addProxyMethod(hashCodeMethod, Object.class);
addProxyMethod(equalsMethod, Object.class);
addProxyMethod(toStringMethod, Object.class);
// 將代理類接口方法包裝成ProxyMethod對象
for (Class<?> intf : interfaces) {
for (Method m : intf.getMethods()) {
addProxyMethod(m, intf);
}
}
//......
/* 第二步:為代理類組裝字段,構造函數,方法,static初始化塊等 */
try {
// 添加構造函數,參數是InvocationHandler
methods.add(generateConstructor());
// 代理方法
for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
for (ProxyMethod pm : sigmethods) {
// 字段
fields.add(new FieldInfo(pm.methodFieldName,
"Ljava/lang/reflect/Method;",
ACC_PRIVATE | ACC_STATIC));
// 上述ProxyMethod中的方法
methods.add(pm.generateMethod());
}
}
// static初始化塊
methods.add(generateStaticInitializer());
} catch (IOException e) {
throw new InternalError("unexpected I/O Exception", e);
}
//......
/* 第三步:寫入class文件 */
//......
try {
//......
dout.writeShort(0); // (no ClassFile attributes for proxy classes)
} catch (IOException e) {
throw new InternalError("unexpected I/O Exception", e);
}
return bout.toByteArray();
}
看看上文命令下創(chuàng)建class,可以看到它 implements UserService 以及通過我們的的代理類的InvocationHandler 調用這些方法:
public final class $Proxy0 extends Proxy implements UserService {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
//......
//動態(tài)代理了findUserList方法,后續(xù)調用時本質上是通過代理類的method對原有方法進行調用,即我們的InvocationHandler所實現(xiàn)的邏輯
public final List findUserList() throws {
try {
return (List)super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
//......
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
//初始化我們的代理方法類method
m3 = Class.forName("com.pdai.aop.jdkProxy.UserService").getMethod("findUserList");
//......
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
3.Spring AOP中JDK代理的實現(xiàn)
JdkDynamicAopProxy源碼如下,可以看到本質上也是通過傳入:
- 類加載器
- 接口類型,通過proxiedInterfaces獲取對應接口到代理緩存中獲取要生成的代理類型
- 對應的InvocationHandler 進行邏輯增強。
@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
//......
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}