AOP概述
- 在我們的日常開發中,除了正常業務邏輯外,還可能經常會需要在業務邏輯的特定位置加入日志,以便于調試和問題分析。但是這種插入日志的邏輯和業務邏輯間并不存在連續性和依賴性,這種邏輯侵入隨著項目的不斷發展,會導致項目越來越來臃腫,同時也更加難以管理。為了解決這個問題,優秀的前輩們推出了AOP(面向切面編程)理念以及很多優秀的AOP框架,其中比較有代表性的就AspectJ,AspectJ通過擴展java語言實現了AOP框架。當然我們的spring框架也實現了自己的AOP框架(Spring AOP),并在spring 2.0后完美的集成了ApectJ。
- AOP有很多種實現方式,在最早的時候AOP的實現還是通過靜態代理的方式實現,例如AspectJ通過自己的ajc編譯器在程序編譯階段編譯生成靜態代理類的Class文件以完成相關切面邏輯,不過這種方式不夠靈活,每次有新的接口或邏輯需要使用切面功能都需要修改AspectJ中的切面配置,然后重新啟動項目,無疑這是很煩的。隨著技術的發展,我們有了更多更好的技術實現,這里介紹兩只中常用AOP實現技術,也是我們spring中使用的:
(1)jdk動態代理:
java在jdk 1.3后引入了jdk動態代理,可以在運行期間為實現了某個接口的對象動態的是生成代理對象,它是在運行期間通過反射實現的,靈活性很好了,但是較編譯生成Class文件的實現方式,性能差了些,另外所有需要進行代理的對象都必須要實現某個接口,這是動態代理最大的硬傷了。
(2)cglib動態字節碼增強:
大家都知道,jvm的類加載器并不在乎class文件如何生成,只要是滿足規范的class文件都能加載運行。一般我們的java應用程序都是通過javac編譯生成class文件,但是只要我們滿足class文件規范,我們完全可以使用cglib等字節碼工具在程序運行期間動態的構建Class文件。在我們從本地的class文件加載類文件并構建對象時,可以使用cglib動態的生成該對象的代理對象。
靜態代理與動態代理
AOP的實現是基于代理模式的,所以我們需要通過了解代理模式來學習AOP,接下來我來介紹下靜態代理和動態代理的實現。
1. 靜態代理:
我們可以看下下面的代碼:
public interface Subject {String getName();
}public class RealSubject implements Subject {public String getName() {return ”東哥真帥!“;}
}public class Proxy implements Subject {private RealSubject realSubject;public Proxy(Subject realSubject){this.realSubject = realSubject;}public String getName() {return realSubject.getName() + ”東哥真是666!“;}
}public class Main {public static void main(String[] args) {Subject realSubject = new RealSubject();Subject proxy = new Proxy(realSubject);proxy. getName();
}
我們可以看到真的是灰常簡單,代理對象和原始對象都實現了Subject,代理對象引用了原始對象,并在接口調用時利用了realSubject的getName()方法,只不過在realSubject返回數據的基礎上加了些文字,沒錯這就是最簡單的靜態代理。但這種模式存在一個致命的問題:getName()函數可能并不僅僅只有Subject接口實現了,其他接口可能也實現了這個函數,例如我再有個Topic接口,它也有這個getName()方法。那么當我的切面需要切到所有實現getName的函數時,我們還需要在為Topic接口實現一套類似上面的代碼,這要搞下去的話累都累死了,為了解決這個問題,我們引出了動態代理。我們接下來看下基于jdk和cglib實現的動態代理。
2. 動態代理:
(1)jdk動態代理代碼:
public class RequestInvocationHandler implements InvocationHandler {private Object target;public RequestInvocationHandler(Object target){this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {if(method.getName().equals("getName")){return method.invoke(target, args) + "東哥真是666!";}return null;}}Subject subject = (Subject) Proxy.newProxyInstance(ProxyRunner.class.getClassLoader(),new Class[]{Subject.class},new RequestInvocationHandler(new RealSubject()));subject.getName();Topic topic = (Topic) Proxy.newProxyInstance(ProxyRunner.class.getClassLoader(),new Class[]{Topic.class},new RequestInvocationHandler(new RealTopic()));topic.getName();
jdk動態代理的實現主要依賴于java.lang.reflect.Proxy類和java.lang.reflect.InvocationHandler接口。我們可以看出jdk動態代理是靠實現特定接口,我們在平時開發中不可能所有的對象都實現特定的接口,cglib為我們解決了這個問題。
(2)cglib實現動態代理代碼:
public class RequestInvocationHandler implements MethodInterceptor {@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {if (method.getName().equals("getName")) {return methodProxy.invokeSuper(o, objects) + "東哥真是666!";}return null;}}Enhancer enhancer = new Enhancer();enhancer.setSuperclass(Subject.class);enhancer.setCallback(new RequestInvocationHandler());Subject proxy = (Subject) enhancer.create();proxy.getName();
我們可以看到cglib通過繼承目標對象,并覆寫父類方法來實現動態代理,通過字節碼生成技術在運行期間動態的自動幫我們構建代理對象。由于cglib采用繼承覆寫父類方法實現,所以也存在一定局限,當父類的方法為final、static、private類型時,cglib無法對方法進行代理,只能直接調用目標對象的方法。
AOP組件介紹
在具體了解整個AOP的運行流程前,我們先來看下AOP中幾個比較重要的組件,當然這些術語都是參照ApectJ中的概念。
組件名 | 概念 |
---|---|
joinpoint | AOP功能模塊需要織入的地方, 也有人稱之為系統執行點,在ApectJ中有很多執行點類型,例如方法調用、方法執行、字段設置、類初始化等,但是在Spring Aop目前僅支持方法調用類型的切入點,不過已經能夠我們大部分的業務開發了。 |
pointcut | pointcut就是joinpoint的表述方式,通過pointcut的表達式我們就可以尋找到滿足條件joinpoint。 |
advice | advice是切入邏輯的載體,簡單的說就是我們要織入的邏輯代碼,他有很多種類型,包括Before Advice、After Advice等等,不過我們最常用的還是Around Advice,它同時包含了其他幾種的功能。 |
aspect(advisor) | aspect是將joinpoint和pointcut模塊化封裝的AOP概念實體,在我們SpringAop中也稱為advisor,且一般一個advisor僅包含一個joinpoint和一個pointcut。 |
weaver | weaver就是織入器,負責將邏輯織入到joinpoint的具體執行者。 |
target object | 被織入的對象 |
Spring AOP織入原理
我們先來看下偽代碼:
// 新建織入者weaver
ProxyFactory weaver = new ProxyFactory(new Executable());// 新建根據接口名匹配的advisor(aspect)
NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
// 設置pointcut,在這里是需要織入的接口名,"execute"相當于pointcut
advisor.setMappedName("execute");
// 設置Advice,ExecutableAdvice中包含了代碼邏輯
advisor.setAdvice(new ExecutableAdvice());// 將advisor傳遞給weaver
weaver.addAdvisor(advisor);
// weaver通過ProxyFactor獲取代理類
Executable proxyObject = weaver.getProxy();proxyObject.execute();
上面的偽代碼實際上已經揭示了AOP的完整流程:
(1)通過advisor包裝pointcut和advice數據
(2)然后weaver利用advisor內的數據來獲取代理對象
通過上面的邏輯分析,我們可以知道,對于SpringAOP的實現,最重要的有兩點即pointcut和advice數據的獲取和代理對象的生成,那我們接下來就分為這兩部分著重講解下。
(一)pointcut和advice數據的獲取
其實上面的偽代碼是spring早期的實現方式,如上面代碼所示,當時的pointcut只能是方法名或者對象類型(支持正則表達式),而advice需要通過實現特定的接口來實現,advisor也是通過繼承特定的系統類實現的,最后還需要將bean的信息以xml的形式保存起來以支持IoC。
當然現在沒那么麻煩了,我們看下目前我們的使用方式(基于注解的代理):
@Aspect
@Component
class Aspect {@Pointcut("@annotation(com.xxx.xxx.xxx)")public void servicePointcut() {}@Around("servicePointcut()")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {xxxxxxxxxxxxxxx;xxxxxxxxxxxxxxx;return xxx;}
}
現在我們有注解支持并且集成了AspectJ,可以使用AspectJ表達式,但底層的實現原理還是一樣的。我們現在使用的weaver在注入到容器后,容器會自動尋找容器內所有標識了@Aspect的advisor(aspect)對象,獲取到advisor(aspect)對象后,會通過反射獲取@Pointcut內的表達式,然后通過AspectJ的類庫解析生成pointcut對象,最后ProxyFactory便可以利用這些數據生成代理對象了,其實原理還是一樣的,只是加了層封裝,更加方便了我們的業務開發。
(二)代理對象的生成
接下來就是最重點的部分了,其實也很簡單,需要大家有足夠的IoC知識,可以看下我的IoC原理文章:深入了解spring IoC
在獲取到了pointcut和advice數據后,weaver便開始了代理對象的構造了。我們在上面的代碼中可以看到weaver的類型是ProxyFactory,ProxyFactory是最簡單基本的一個織入器實現類,目前我們最經常使用的織入器實現類是AnnotationAwareAspectJAutoProxyCreator,不過實現原理都是一樣的:都是基于BeanPostProcessor。我們在IoC原理中介紹過BeanPostProcessor,BeanPostProcessor可以干預Bean的實例化過程,它有點類似于責任鏈,任何一個bean的構建都需要經過這條鏈,只有執行完所有BeanPostProcessor后才會返回實例化的對象。AOP實現了BeanPostProcessor,他在目標對象實例化后利用反射或cglib生成了目標對象的動態代理對象(實現邏輯見上面:jdk動態代理代碼實現/cglib實現動態代理),然后直接將代理對象返回給了使用方。這樣使用方使用的便是經過動態代理后的對象,便實現了AOP的功能。很簡單吧!🙃🙃🙃🙃