Spring AOP 深度剖析與實(shí)踐
在當(dāng)今復(fù)雜多變的軟件開(kāi)發(fā)領(lǐng)域,Spring 框架無(wú)疑是一顆璀璨的明星。而其中的 Spring AOP(面向切面編程)更是以其獨(dú)特的魅力和強(qiáng)大的功能,為開(kāi)發(fā)者們打開(kāi)了一扇通往全新編程境界的大門。
當(dāng)我們深入探索 Spring AOP 的世界,就仿佛置身于一個(gè)充滿無(wú)限可能的編程宇宙之中。它猶如一把神奇的鑰匙,能夠巧妙地解開(kāi)代碼結(jié)構(gòu)中的復(fù)雜癥結(jié),讓我們可以從全新的切面視角去審視和構(gòu)建軟件。無(wú)論是實(shí)現(xiàn)系統(tǒng)級(jí)的橫切關(guān)注點(diǎn),如日志記錄、事務(wù)管理、安全控制等,還是對(duì)業(yè)務(wù)邏輯進(jìn)行精細(xì)化的分離與整合,Spring AOP 都展現(xiàn)出了無(wú)與倫比的適應(yīng)性和靈活性。它并非僅僅是一種技術(shù)手段,更是一種編程理念的革新,為我們帶來(lái)了高效、簡(jiǎn)潔且極具擴(kuò)展性的開(kāi)發(fā)方式。讓我們一同踏上這趟精彩的 Spring AOP 之旅,去領(lǐng)略它所蘊(yùn)含的奧秘與力量。
一、詳解Spring對(duì)AOP的設(shè)計(jì)與實(shí)現(xiàn)
1.對(duì)AOP的理解
AOP(Aspect-Oriented Programming:面向切面編程),它實(shí)際做的就是將業(yè)務(wù)和一些非業(yè)務(wù)進(jìn)行拆解,降低彼此業(yè)務(wù)模塊與非業(yè)務(wù)模塊的耦合度,便于后續(xù)的擴(kuò)展維護(hù)。例如權(quán)限校驗(yàn)、日志管理、事務(wù)處理等都可以使用AOP實(shí)現(xiàn)。而Spring就是基于動(dòng)態(tài)代理實(shí)現(xiàn)AOP的。如果被代理的類有實(shí)現(xiàn)接口的話,就會(huì)基于JDK Proxy完成代理的創(chuàng)建,反之就是通過(guò)Cglib完成代理創(chuàng)建,當(dāng)然你也可以強(qiáng)制使用Cglib。
2.什么是AOP中切點(diǎn)、切面、通知
AOP中有很多核心術(shù)語(yǔ),分別是:
- 目標(biāo)(Target): 這就被代理的對(duì)象,例如我們希望對(duì)UserService每個(gè)方法進(jìn)行增強(qiáng)(在不動(dòng)它的代碼情況下增加一些非業(yè)務(wù)的動(dòng)作),那么這個(gè)UserService就是目標(biāo)。
- 代理(Proxy): 就是給你被代理后的對(duì)象的廠商,例如我們上面說(shuō)過(guò)希望對(duì)UserService每個(gè)方法進(jìn)行增強(qiáng),那么給用戶返回增強(qiáng)后的對(duì)象的類就是代理類。
- 連接點(diǎn)(JoinPoint):目標(biāo)對(duì)象,每一個(gè)可能可以被增強(qiáng)的方法都可以稱為連接點(diǎn),盡管它最后可能不會(huì)被增強(qiáng)。
- 切入點(diǎn)(Pointcut): 連接點(diǎn)中即能夠應(yīng)用通知的位置。
- 通知(Advice): 不要被表面的語(yǔ)義誤導(dǎo),通知并不是告知某人的意思,通知的意思是攔截對(duì)象后,做的增強(qiáng)操作,也就是攔截后要執(zhí)行什么代碼。
- 切面(Aspect): 切入點(diǎn)(Pointcut)+通知(Advice)。
- 織入(Weaving):把通知的動(dòng)作融入到對(duì)象中,生成代理對(duì)象的過(guò)程就叫做織入。
3.Spring AOP和AspectJ AOP的區(qū)別
Spring AOP屬于運(yùn)行時(shí)增強(qiáng),基于代理(Proxying)實(shí)現(xiàn)的。而AspectJ AOP屬于編譯時(shí)增強(qiáng),基于字節(jié)碼操作(Bytecode Manipulation)實(shí)現(xiàn)的。
在《精通spring4.x》一書(shū)中,我們可以知道,jdk生成的代理對(duì)象性能遠(yuǎn)遠(yuǎn)差于cglib生成代理對(duì)象,但cglib創(chuàng)建代理對(duì)象花費(fèi)的時(shí)間卻遠(yuǎn)遠(yuǎn)高于jdk代理創(chuàng)建的對(duì)象。所以在spring框架的使用中,如果是單例的bean需要實(shí)現(xiàn)aop等操作,我們建議是使用cglib動(dòng)態(tài)代理技術(shù):
4.AspectJ 通知類型
- Before(前置通知): 目標(biāo)對(duì)象方法調(diào)用前觸發(fā)增強(qiáng)。
- After (后置通知):目標(biāo)對(duì)象方法調(diào)用后進(jìn)行增強(qiáng)。
- AfterReturning(返回通知):目標(biāo)對(duì)象方法執(zhí)行結(jié)束,返回值時(shí)進(jìn)行增強(qiáng)。
- AfterThrowing(異常通知):目標(biāo)對(duì)象方法執(zhí)行報(bào)錯(cuò)并拋出時(shí)做的增強(qiáng)。
- Around(環(huán)繞通知):這個(gè)比較常用了,目標(biāo)對(duì)象方法調(diào)用前后我們可以做各種增強(qiáng)操作,甚至不調(diào)用對(duì)象的方法都能做到。
5.多個(gè)切面執(zhí)行順序我們?nèi)绾未_定
答: 有兩種方式:
- 注解法:使用@Order注解來(lái)決定切面bean的執(zhí)行順序。
// 值越小優(yōu)先級(jí)越高
@Order(1)
@Component
@Aspect
public class LoggingAspect implements Ordered {
- 繼承接口法:implements Ordered接口
@Component
@Aspect
public class LoggingAspect implements Ordered {
// ....
@Override
public int getOrder() {
// 返回值越小優(yōu)先級(jí)越高
return 1;
}
}
6.AOP操作在bean生命周期的那個(gè)階段實(shí)現(xiàn)
在bean初始化前后也就我們常說(shuō)的BPP階段完成AOP類的緩存以及通知器創(chuàng)建。在bean初始化后,根據(jù)需要結(jié)合通知器完成代理類的改造。
7.動(dòng)態(tài)代理是什么
是在運(yùn)行期間,創(chuàng)建目標(biāo)對(duì)象的代理對(duì)象,目標(biāo)對(duì)象不變,我們通過(guò)對(duì)方法動(dòng)態(tài)攔截,進(jìn)行前置或者后置等各種增強(qiáng)操作。AOP中就有CGLIB動(dòng)態(tài)代理和JDK動(dòng)態(tài)代理技術(shù)。
8.動(dòng)態(tài)代理的創(chuàng)建過(guò)程
AOP提供了一個(gè)默認(rèn)工廠根據(jù)類是否有繼承接口或者是否就是目標(biāo)類決定創(chuàng)建的策略。然后根據(jù)不同的策略決定代理類的創(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);
//生成代理對(duì)象并返回
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
以下便是cglib的創(chuàng)建策略:
@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
.......
try {
.......
//將當(dāng)前類信息通過(guò)enhancer 生成代理對(duì)象
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();
}
//返回最終生成的代理對(duì)象
return createProxyClassAndInstance(enhancer, callbacks);
}
........
}
catch (Throwable ex) {
......
}
}
二、詳解CGLIB代理
1.Spring AOP和Cglib的關(guān)系
CGLIB是一個(gè)強(qiáng)大、高性能的代碼生成包。使用ASM操作字節(jié)碼,動(dòng)態(tài)生成代理,對(duì)方法進(jìn)行增強(qiáng)。,它廣泛的被許多AOP框架使用,為他們提供方法的攔截。
例如我們希望對(duì)某個(gè)service進(jìn)行日志打印,基于CGLIB我們可以這樣實(shí)現(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) {
//設(shè)置被代理的目標(biāo)類
this.target = target;
// 創(chuàng)建加強(qiáng)器設(shè)置代理類以及回調(diào),當(dāng)代理類被調(diào)用時(shí),callback就會(huì)去調(diào)用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("調(diào)用被代理對(duì)象的方法,代理對(duì)象:[{}],代理方法:[{}]", o.getClass().getName(), method.getName());
Object result = methodProxy.invokeSuper(o, args);
logger.info("代理調(diào)用結(jié)束,返回結(jié)果:[{}]", String.valueOf(result));
return null;
}
}
測(cè)試代碼
public class Main {
public static void main(String[] args) {
CglibProxy<UserServiceImpl> cglibProxy = new CglibProxy();
UserServiceImpl targetClass =cglibProxy.getTargetClass(new UserServiceImpl());
targetClass.findUserList();
}
}
2.Cglib代理流程是是什么樣的
如下圖所示,整體來(lái)說(shuō)就是基于enhancer去配置被代理類的各種參數(shù),然后生成代理類:
注意:final方法無(wú)法被代理,因?yàn)樗豢杀蛔宇惛采w。
3.Spring中的Cglib代理流程
源碼如下,我們可以看出和我們寫的實(shí)例代碼是差不多的。
@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
.....
try {
//獲取當(dāng)前類信息獲取生成代理對(duì)象
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));
// 獲取當(dāng)前類中的方法
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);
// 生成代理對(duì)象
return createProxyClassAndInstance(enhancer, callbacks);
}
catch (CodeGenerationException | IllegalArgumentException ex) {
.....
}
catch (Throwable ex) {
.....
}
}
三、詳解JDK代理
1. JDK代理示例
答:這個(gè)是jdk自帶的一種代理,我們只需繼承InvocationHandler即可實(shí)現(xiàn)。但是前提是這個(gè)類必須繼承某些接口才能使用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("代理方法被調(diào)用,類名稱[{}],方法名稱[{}]", target.getClass().getName(), method.getName());
Object result = method.invoke(target, args);
logger.info("代理方法調(diào)用結(jié)束,返回結(jié)果:[{}]", String.valueOf(result));
return result;
};
proxy = (UserService) Proxy.newProxyInstance(loader, interfaces, handler);
return (T) proxy;
}
}
測(cè)試代碼:
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在下面這樣一段參數(shù),或者在上述main方法加這個(gè)代碼:
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
然后我們通過(guò)debug可以發(fā)現(xiàn)它回步入這段代碼,其中他會(huì)生成一個(gè)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對(duì)象 */
// 將Object類中hashCode、equals、toString方法包裝成ProxyMethod對(duì)象
addProxyMethod(hashCodeMethod, Object.class);
addProxyMethod(equalsMethod, Object.class);
addProxyMethod(toStringMethod, Object.class);
// 將代理類接口方法包裝成ProxyMethod對(duì)象
for (Class<?> intf : interfaces) {
for (Method m : intf.getMethods()) {
addProxyMethod(m, intf);
}
}
//......
/* 第二步:為代理類組裝字段,構(gòu)造函數(shù),方法,static初始化塊等 */
try {
// 添加構(gòu)造函數(shù),參數(shù)是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 以及通過(guò)我們的的代理類的InvocationHandler 調(diào)用這些方法:
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);
}
//......
//動(dòng)態(tài)代理了findUserList方法,后續(xù)調(diào)用時(shí)本質(zhì)上是通過(guò)代理類的method對(duì)原有方法進(jìn)行調(diào)用,即我們的InvocationHandler所實(shí)現(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代理的實(shí)現(xiàn)
JdkDynamicAopProxy源碼如下,可以看到本質(zhì)上也是通過(guò)傳入:
- 類加載器
- 接口類型,通過(guò)proxiedInterfaces獲取對(duì)應(yīng)接口到代理緩存中獲取要生成的代理類型
- 對(duì)應(yīng)的InvocationHandler 進(jìn)行邏輯增強(qiáng)。
@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
//......
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}