若是你有幸能看到java
tiny-spring
是爲了學習Spring的而開發的,能夠認爲是一個Spring的精簡版。Spring的代碼不少,層次複雜,閱讀起來費勁。我嘗試從使用功能的角度出發,參考Spring的實現,一步一步構建,最終完成一個精簡版的Spring。 有人把程序員與畫家作比較,畫家有門基本功叫臨摹,tiny-spring能夠算是一個程序的臨摹版本-從本身的需求出發,進行程序設計,同時對著名項目進行參考。git
AOP分爲配置(Pointcut,Advice),織入(Weave)兩部分工做,固然還有一部分是將AOP整合到整個容器的生命週期中。程序員
7.step7-使用JDK動態代理實現AOP織入github
織入(weave)相對簡單,咱們先從它開始。Spring AOP的織入點是AopProxy,它包含一個方法Object getProxy()來獲取代理後的對象。spring
在Spring AOP中,我以爲最重要的兩個角色,就是咱們熟悉的MethodInterceptor和MethodInvocation(這兩個角色都是AOP聯盟的標準),它們分別對應AOP中兩個基本角色:Advice和Joinpoint。Advice定義了在切點指定的邏輯,而Joinpoint則表明切點express
切點通知器相關接口app
public interface MethodInterceptor extends Interceptor {
Object invoke(MethodInvocation invocation) throws Throwable;
}
複製代碼
Spring的AOP只支持方法級別的調用,因此其實在AopProxy裏,咱們只須要將MethodInterceptor放入對象的方法調用便可。框架
使用jdk動態代理ide
public class JdkDynamicAopProxy extends AbstractAopProxy implements InvocationHandler {
public JdkDynamicAopProxy(AdvisedSupport advised ) {
super(advised);
}
@Override
public Object getProxy() {
return Proxy.newProxyInstance(getClass().getClassLoader(),advised.getTargetSource().getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//提取攔截的方法
MethodInterceptor methodInterceptor = (MethodInterceptor) advised.getMethodInterceptor();
//比較傳入的方法和對象的方法是否一致,若是一致則調用傳入的方法,
if (advised.getMethodMatcher() != null
&& advised.getMethodMatcher().matches(method, advised.getTargetSource().getTarget().getClass())) {
//這裏應該是先調用攔截的方法,而後調用原始對象的方法。可是通常括號裏的東西不是優先嗎?括號裏面好像就只有賦值操做而已。
return methodInterceptor.invoke(new ReflectiveMethodInvocation(advised.getTargetSource().getTarget(),method, args));
} else {
return method.invoke(advised.getTargetSource().getTarget(), args);
}
}
複製代碼
咱們稱被代理對象爲TargetSource,而AdvisedSupport就是保存TargetSource和MethodInterceptor的元數據對象。這一步咱們先實現一個基於JDK動態代理的JdkDynamicAopProxy,它能夠對接口進行代理。因而咱們就有了基本的織入功能。學習
@Test
public void testInterceptor() throws Exception {
// --------- helloWorldService without AOP
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("tinyioc.xml");
HelloWorldService helloWorldService = (HelloWorldService) applicationContext.getBean("helloWorldService");
helloWorldService.helloWorld();
// --------- helloWorldService with AOP
// 1. 設置被代理對象(Joinpoint)
AdvisedSupport advisedSupport = new AdvisedSupport();
TargetSource targetSource = new TargetSource(helloWorldService, HelloWorldServiceImpl.class,
HelloWorldService.class);
advisedSupport.setTargetSource(targetSource);
// 2. 設置攔截器(Advice)
TimerInterceptor timerInterceptor = new TimerInterceptor();
advisedSupport.setMethodInterceptor(timerInterceptor);
// 3. 建立代理(Proxy)
JdkDynamicAopProxy jdkDynamicAopProxy = new JdkDynamicAopProxy(advisedSupport);
HelloWorldService helloWorldServiceProxy = (HelloWorldService) jdkDynamicAopProxy.getProxy();
// 4. 基於AOP的調用
helloWorldServiceProxy.helloWorld();
}
複製代碼
git checkout step-8-invite-pointcut-and-aspectj
複製代碼
完成了織入以後,咱們要考慮另一個問題:對什麼類以及什麼方法進行AOP?對於「在哪切」這一問題的定義,咱們又叫作「Pointcut」。Spring中關於Pointcut包含兩個角色:ClassFilter
和MethodMatcher
,分別是對類和方法作匹配。Pointcut有不少種定義方法,例如類名匹配、正則匹配等,可是應用比較普遍的應該是和AspectJ
表達式的方式。
AspectJ
是一個「對Java的AOP加強」。它最先是實際上是一門語言,咱們跟寫Java代碼同樣寫它,而後靜態編譯以後,就有了AOP的功能。下面是一段AspectJ代碼:
aspect PointObserving {
private Vector Point.observers = new Vector();
public static void addObserver(Point p, Screen s) {
p.observers.add(s);
}
public static void removeObserver(Point p, Screen s) {
p.observers.remove(s);
}
...
}
複製代碼
這種方式無疑過重了,爲了AOP,還要適應一種語言?因此如今使用也很少,可是它的Pointcut
表達式被Spring借鑑了過來。因而咱們實現了一個AspectJExpressionPointcut
:
@Test
public void testMethodInterceptor() throws Exception {
String expression = "execution(* us.codecraft.tinyioc.*.*(..))";
AspectJExpressionPointcut aspectJExpressionPointcut = new AspectJExpressionPointcut();
aspectJExpressionPointcut.setExpression(expression);
boolean matches = aspectJExpressionPointcut.getMethodMatcher().matches(HelloWorldServiceImpl.class.getDeclaredMethod("helloWorld"),HelloWorldServiceImpl.class);
Assert.assertTrue(matches);
}
複製代碼
public class AspectJExpressionPointcutTest {
@Test
public void testClassFilter() throws Exception {
String expression = "execution(* us.codecraft.tinyioc.*.*(..))";
AspectJExpressionPointcut aspectJExpressionPointcut = new AspectJExpressionPointcut();
aspectJExpressionPointcut.setExpression(expression);
boolean matches = aspectJExpressionPointcut.getClassFilter().matches(HelloWorldService.class);
Assert.assertTrue(matches);
}
@Test
public void testMethodInterceptor() throws Exception {
String expression = "execution(* us.codecraft.tinyioc.*.*(..))";
AspectJExpressionPointcut aspectJExpressionPointcut = new AspectJExpressionPointcut();
aspectJExpressionPointcut.setExpression(expression);
boolean matches = aspectJExpressionPointcut.getMethodMatcher().matches(HelloWorldServiceImpl.class.getDeclaredMethod("helloWorld"),HelloWorldServiceImpl.class);
Assert.assertTrue(matches);
}
複製代碼
git checkout step-9-auto-create-aop-proxy
複製代碼
萬事俱備,只欠東風!如今咱們有了Pointcut和Weave技術,一個AOP已經算是完成了,可是它尚未結合到Spring中去。怎麼進行結合呢?Spring給了一個巧妙的答案:使用BeanPostProcessor
。
BeanPostProcessor是BeanFactory提供的,在Bean初始化過程當中進行擴展的接口。只要你的Bean實現了BeanPostProcessor
接口,那麼Spring在初始化時,會優先找到它們,而且在Bean的初始化過程當中,調用這個接口,從而實現對BeanFactory核心無侵入的擴展。
那麼咱們的AOP是怎麼實現的呢?咱們知道,在AOP的xml配置中,咱們會寫這樣一句話:
<aop:aspectj-autoproxy/>
複製代碼
它其實至關於:
<bean id="autoProxyCreator" class="org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator"></bean>
複製代碼
AspectJAwareAdvisorAutoProxyCreator
就是AspectJ方式實現織入的核心。它實際上是一個BeanPostProcessor。在這裏它會掃描全部Pointcut,並對bean作織入。
爲了簡化xml配置,我在tiny-spring中直接使用Bean的方式,而不是用aop前綴進行配置:
<bean id="autoProxyCreator" class="us.codecraft.tinyioc.aop.AspectJAwareAdvisorAutoProxyCreator"></bean>
<bean id="timeInterceptor" class="us.codecraft.tinyioc.aop.TimerInterceptor"></bean>
<bean id="aspectjAspect" class="us.codecraft.tinyioc.aop.AspectJExpressionPointcutAdvisor">
<property name="advice" ref="timeInterceptor"></property>
<property name="expression" value="execution(* us.codecraft.tinyioc.*.*(..))"></property>
</bean>
複製代碼
TimerInterceptor
實現了MethodInterceptor
(實際上Spring中還有Advice
這樣一個角色,爲了簡單,就直接用MethodInterceptor了)。
至此,一個AOP基本完工。
git checkout step-10-invite-cglib-and-aopproxy-factory
複製代碼
前面的JDK動態代理只能對接口進行代理,對於類則無能爲力。這裏咱們須要一些字節碼操做技術。這方面大概有幾種選擇:ASM
,CGLib
和javassist
,後二者是對ASM
的封裝。Spring中使用了CGLib。
在這一步,咱們還要定義一個工廠類ProxyFactory
,用於根據TargetSource類型自動建立代理,這樣就須要在調用者代碼中去進行判斷。
另外咱們實現了Cglib2AopProxy
,使用方式和JdkDynamicAopProxy
是徹底相同的。
public class Cglib2AopProxy extends AbstractAopProxy {
public Cglib2AopProxy(AdvisedSupport advised) {
super(advised);
}
//經過cglib類庫建立了一個代理類的實例
@Override
public Object getProxy() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(advised.getTargetSource().getTargetClass());
enhancer.setInterfaces(advised.getTargetSource().getInterfaces());
//設置代理類的通知方法,至關於設置攔截器方法
enhancer.setCallback(new DynamicAdvisedInterceptor(advised));
Object enhanced = enhancer.create();
return enhanced;
}
//方法攔截器
private static class DynamicAdvisedInterceptor implements MethodInterceptor {
private AdvisedSupport advised;
private org.aopalliance.intercept.MethodInterceptor delegateMethodInterceptor;
private DynamicAdvisedInterceptor(AdvisedSupport advised) {
this.advised = advised;
this.delegateMethodInterceptor = advised.getMethodInterceptor();
}
//調用代理類的方法(代理類與原始類是父子關係,還有一種是兄弟關係,調用實質是調用原始類的方法)
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
if (advised.getMethodMatcher() == null
|| advised.getMethodMatcher().matches(method, advised.getTargetSource().getTargetClass())) {
//這裏也應該是先調用攔截方法,而後調用原始對象的方法
return delegateMethodInterceptor.invoke(new CglibMethodInvocation(advised.getTargetSource().getTarget(), method, args, proxy));
}
return new CglibMethodInvocation(advised.getTargetSource().getTarget(), method, args, proxy).proceed();
}
}
private static class CglibMethodInvocation extends ReflectiveMethodInvocation {
private final MethodProxy methodProxy;
public CglibMethodInvocation(Object target, Method method, Object[] args, MethodProxy methodProxy) {
super(target, method, args);
this.methodProxy = methodProxy;
}
@Override
public Object proceed() throws Throwable {
return this.methodProxy.invoke(this.target, this.arguments);
}
}
複製代碼
有一個細節是CGLib建立的代理是沒有注入屬性的, Spring的解決方式是:CGLib僅做代理,任何屬性都保存在TargetSource中,使用MethodInterceptor=>TargetSource的方式進行調用。
至此,AOP部分完工。
一、經過AspectJ表達式處理AOP的主要類和關係
二、使用cglib動態代理
三、兩種動態代理
一層一層的封裝,合理運用組合、繼承類或接口來賦予、加強類的相應功能
接口的運用:
模板方法模式以及hook方法的應用: 例如: 在AbstractBeanFactory中規範了bean的加載,實例化,初始化,獲取的過程。AutowireCapableBeanFactory裏實現了hook方法(applyPropertyValues方法),該方法在AbstractBeanFactory#initializeBean方法中調用,AbstractBeanFactory中有默認的hook方法空實現。
工廠方法模式的應用:例如:BeanFactory#getBean,由子類決定怎樣去獲取bean並在獲取時進行相關操做。工廠方法把實例化推遲到子類。
外觀(門面)模式的運用:ClassPathXmlApplicationContext對 Resouce 、 BeanFactory、BeanDefinition 進行了功能的封裝,解決 根據地址獲取資源經過 IoC 容器註冊bean定義並實例化,初始化bean的問題,並提供簡單運用他們的方法。
代理模式的運用:
單例模式的運用:
策略模式: 這裏有個想法,看ClassPathXMLApplicationContext構造方法能夠知道是默認用自動裝配的策略,在這裏能夠另外本身寫個類繼承AbstractBeanFactory,重寫applyPropertyValues方法實現裝配策略,在初始化的時候就能夠選擇不一樣的裝配策略了。