1. 程式人生 > 其它 >Spring原始碼分析之AOP使用和分析

Spring原始碼分析之AOP使用和分析

前言

AOP(Aspect Oriented Programming)面向切面程式設計,通過在執行期對切入點(如指定類的指定方法)建立代理物件,來完成對業務功能的增強,適用於日誌監聽,事務處理等場景。SpringAOP是在IOC容器的基礎上實現的。

AOP的各種概念

  • 通知(Advice):
    定義在連線點處的行為,圍繞方法呼叫而注入,如列印日誌行為
  • 切入點(Pointcut):
    確定在哪些連線點處應用通知,如包含指定註解的方法
  • 通知器(Advisor):
    組合通知和切入點,在什麼地方注入什麼行為
  • 連線點(JoinPoint):
    被攔截的方法
  • 切面(Aspect):
    在Spring中可以看做一系列Advisor的封裝。

AspectJ介紹

AspectJ是AOP程式設計的完美解決方案,可以使用特定的編譯器實現在編譯期向程式碼中植入相應的Advice行為。Spring的AOP實現使用了AspectJ的2點功能:

  1. 其中一些註解如@Aspect,@Pointcut等
  2. 通過AspectJ來解析Pointcut表示式,如@Pointcut("execution(public * abc.sayHi())"),判斷一個業務類的業務方法是否會被攔截。

這裡我們使用一個例子來更好的瞭解AspectJ。首先引入依賴

<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjrt</artifactId>
  <version>1.8.13</version>
</dependency>
<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
  <version>1.8.13</version>
</dependency>

程式碼如下

import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;
import org.aspectj.weaver.tools.PointcutExpression;
import org.aspectj.weaver.tools.PointcutParameter;
import org.aspectj.weaver.tools.PointcutParser;
import org.aspectj.weaver.tools.PointcutPrimitive;

public class TestAspectJ {

  private static final Set<PointcutPrimitive> SUPPORTED_PRIMITIVES = new HashSet<>();

  static {
    SUPPORTED_PRIMITIVES.add(PointcutPrimitive.EXECUTION);
    SUPPORTED_PRIMITIVES.add(PointcutPrimitive.ARGS);
    SUPPORTED_PRIMITIVES.add(PointcutPrimitive.REFERENCE);
    SUPPORTED_PRIMITIVES.add(PointcutPrimitive.THIS);
    SUPPORTED_PRIMITIVES.add(PointcutPrimitive.TARGET);
    SUPPORTED_PRIMITIVES.add(PointcutPrimitive.WITHIN);
    SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ANNOTATION);
    SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_WITHIN);
    SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ARGS);
    SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_TARGET);
  }

  public static void main(String[] args) throws NoSuchMethodException {
    ClassLoader classLoader = TestAspectJ.class.getClassLoader();
    //建立一個解析器
    PointcutParser parser = PointcutParser
        .getPointcutParserSupportingSpecifiedPrimitivesAndUsingSpecifiedClassLoaderForResolution(
            SUPPORTED_PRIMITIVES, classLoader);
    PointcutParameter[] parameters = {};
    //解析出切入點表示式,這裡的方法路徑替換為當前SmsService的sayHi()的實際路徑
    String expression = "execution(public * com.imooc.sourcecode.java.spring.aop.test1.TestAspectJ.SmsService.sayHi())";
    PointcutExpression pointcutExpression = parser
        .parsePointcutExpression(expression, null, parameters);
    //判斷SmsService類是否會被攔截
    boolean match = pointcutExpression.couldMatchJoinPointsInType(SmsService.class);
    System.out.println(match);//true
    Method smsServiceToStringMethod = SmsService.class.getMethod("toString");
    //判斷SmsService類的toString()方法是否會被攔截
    match = pointcutExpression.matchesMethodExecution(smsServiceToStringMethod).alwaysMatches();
    System.out.println(match);//false
    Method smsServiceSayHiMethod = SmsService.class.getMethod("sayHi");
    //判斷SmsService類的sayHi()方法是否會被攔截
    match = pointcutExpression.matchesMethodExecution(smsServiceSayHiMethod).alwaysMatches();
    System.out.println(match);//true
  }

  public static class SmsService {
    public void sayHi() {
      System.out.println("SmsService.sayHi()");
    }
  }

}

我們也可以使用@Pointcut註解來定義表示式

import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.weaver.tools.PointcutExpression;
import org.aspectj.weaver.tools.PointcutParameter;
import org.aspectj.weaver.tools.PointcutParser;
import org.aspectj.weaver.tools.PointcutPrimitive;

public class TestAspectJ2 {

  private static final Set<PointcutPrimitive> SUPPORTED_PRIMITIVES = new HashSet<>();

  static {
    SUPPORTED_PRIMITIVES.add(PointcutPrimitive.EXECUTION);
    SUPPORTED_PRIMITIVES.add(PointcutPrimitive.ARGS);
    SUPPORTED_PRIMITIVES.add(PointcutPrimitive.REFERENCE);
    SUPPORTED_PRIMITIVES.add(PointcutPrimitive.THIS);
    SUPPORTED_PRIMITIVES.add(PointcutPrimitive.TARGET);
    SUPPORTED_PRIMITIVES.add(PointcutPrimitive.WITHIN);
    SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ANNOTATION);
    SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_WITHIN);
    SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ARGS);
    SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_TARGET);
  }

  public static void main(String[] args) throws NoSuchMethodException {
    ClassLoader classLoader = TestAspectJ2.class.getClassLoader();
    //建立一個解析器
    PointcutParser parser = PointcutParser
        .getPointcutParserSupportingSpecifiedPrimitivesAndUsingSpecifiedClassLoaderForResolution(
            SUPPORTED_PRIMITIVES, classLoader);
    PointcutParameter[] parameters = {};
    //解析出切入點表示式
    PointcutExpression pointcutExpression = parser
        .parsePointcutExpression("webLog()", LogAspect.class, parameters);
    //判斷SmsService類是否會被攔截
    boolean match = pointcutExpression.couldMatchJoinPointsInType(SmsService.class);
    System.out.println(match);//true
    Method smsServiceToStringMethod = SmsService.class.getMethod("toString");
    //判斷SmsService類的toString()方法是否會被攔截
    match = pointcutExpression.matchesMethodExecution(smsServiceToStringMethod).alwaysMatches();
    System.out.println(match);//false
    Method smsServiceSayHiMethod = SmsService.class.getMethod("sayHi");
    //判斷SmsService類的sayHi()方法是否會被攔截
    match = pointcutExpression.matchesMethodExecution(smsServiceSayHiMethod).alwaysMatches();
    System.out.println(match);//true
  }

  public class LogAspect {
    //這裡的方法路徑替換為當前SmsService的sayHi()的實際路徑
    @Pointcut("execution(public * com.imooc.sourcecode.java.spring.aop.test1.TestAspectJ2.SmsService.sayHi())")
    public void webLog() {
    }
  }
  public static class SmsService {
    public void sayHi() {
      System.out.println("SmsService.sayHi()");
    }
  }

}

我們解析表示式時傳入了LogAspect的Class,所以AspectJ會在Class中尋找包含@Pointcut註解的方法,查詢方法名稱和我們需要的一致的具體表達式。
PointcutExpression物件提供了couldMatchJoinPointsInType()方法來判斷當前切入點是否匹配給定Class,
提供了matchesMethodExecution()方法來判斷當前切入點是否匹配給定的Method。
Spring的AOP實現就是使用這兩個方法來判斷是否能夠對指定業務類建立動態代理的,具體可以看AspectJExpressionPointcut,它就是@Pointcut註解的具體實現類。
更多關於Pointcut表示式的介紹,可以檢視spring aop中pointcut表示式完整版
更多關於AspectJ的介紹,可以檢視AspectJ 使用介紹

SpringAOP使用

import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.Pointcut;
import org.springframework.aop.PointcutAdvisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.framework.ProxyFactory;

public class TestProxyFactoryAOP {
  public static void main(String[] args) {
    //建立代理工廠
    ProxyFactory proxyFactory = new ProxyFactory();
    //目標物件(被代理物件)
    proxyFactory.setTarget(new SmsService());
    //實現介面,可以有多個
    proxyFactory.addInterface(IService.class);
    //通知器,包含通知和切入點,可以有多個
    proxyFactory.addAdvisor(new MyAdvisor(createPointcut()));
    //建立代理物件
    IService proxy = (IService) proxyFactory
        .getProxy(TestProxyFactoryAOP.class.getClassLoader());
    System.out.println(proxy.getClass());
    proxy.hi();//被攔截
    System.out.println("============");
    proxy.hello();//沒有被攔截
  }
  private static Pointcut createPointcut() {
    AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
    //這裡替換為SmsService的hi()方法具體路徑
    String expression = "execution(public * com.imooc.sourcecode.java.spring.aop.test2.TestProxyFactoryAOP.SmsService.hi())";
    pointcut.setExpression(expression);
    return pointcut;
  }
  public static class MyAdvisor implements PointcutAdvisor {
    private Pointcut pointcut;
    public MyAdvisor(Pointcut pointcut) {
      this.pointcut = pointcut;
    }
    @Override
    public Pointcut getPointcut() {
      return pointcut;
    }
    @Override
    public Advice getAdvice() {
      return new MyMethodInterceptor();
    }
    @Override
    public boolean isPerInstance() {
      return true;
    }
  }
  public static class MyMethodInterceptor implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
      System.out.println("before invoke");
      Object result = invocation.proceed();
      System.out.println("after invoke");
      return result;
    }
  }
  public static class SmsService implements IService {
    @Override
    public void hi() {
      System.out.println("SmsService.hi()");
    }
    @Override
    public void hello() {
      System.out.println("SmsService.hello()");
    }
  }
  public interface IService {
    void hi();
    void hello();
  }
}

定義一個介面IService和它的實現類SmsService,建立一個通知(Advice)物件和切入點(Pointcut)物件,組合成一個通知器(Advisor)物件,
通過ProxyFactory這個類根據目標物件和要實現的介面列表,以及最重要的通知器列表來建立一個代理物件。底層是使用JDK動態代理和CGLIB來建立代理物件。
關於JDK動態代理,可以檢視jdk實現動態代理
關於CGLIB,可以檢視CGLIB實現動態代理

原始碼分析

核心邏輯有兩個,一個是建立代理物件,一個是代理方法的執行。先分析建立代理物件,進入ProxyFactory的getProxy()方法。

建立代理物件

public Object getProxy(@Nullable ClassLoader classLoader) {
		return createAopProxy().getProxy(classLoader);
	}
protected final synchronized AopProxy createAopProxy() {
		if (!this.active) {
			activate();
		}
                //根據代理工廠來選擇是使用JDK還是CGLIB
		return getAopProxyFactory().createAopProxy(this);
	}

預設的AopProxyFactory實現類為DefaultAopProxyFactory。

@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
                //如果設定了optimize標識或者設定了proxyTargetClass標識或者沒有要實現的介面
		if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
			Class<?> targetClass = config.getTargetClass();
			if (targetClass == null) {
				throw new AopConfigException("TargetSource cannot determine target class: " +
						"Either an interface or a target is required for proxy creation.");
			}
                        //如果目標型別為介面,使用JDK動態代理
			if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
				return new JdkDynamicAopProxy(config);
			}
                        //使用CGLIB
			return new ObjenesisCglibAopProxy(config);
		}
		else {
                        //使用JDK動態代理
			return new JdkDynamicAopProxy(config);
		}
	}

總結起來就是

  • 如果設定了optimize標識或者設定了proxyTargetClass標識或者沒有要實現的介面且目標物件Class不是介面,就使用CGLIB。
  • 其他情況都是JDK動態代理。

因為我們添加了IService介面,所以使用JDK動態代理。進入JdkDynamicAopProxy的getProxy()方法。

@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
	        //獲取被代理物件的介面列表,這裡不僅包含我們自己的IService介面,Spring還增加了3個介面(SpringProxy,Advised,DecoratingProxy)
		Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
                //判斷我們的介面有沒有包含equals()和hashCode()方法,如果有,那我們的目標物件一定實現了這兩個方法,就不需要交給代理物件來處理了
		findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
                //建立代理物件,InvocationHandler引數傳的是this
		return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
	}

代理方法執行

建立代理物件的InvocationHandler就是JdkDynamicAopProxy自身,所以進入它的invoke()方法

@Override
@Nullable
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		Object oldProxy = null;
		boolean setProxyContext = false;

		TargetSource targetSource = this.advised.targetSource;
		Object target = null;

		try {
                        //處理equals()方法
			if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
				return equals(args[0]);
			}
                        //處理hashCode()方法
			else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
				return hashCode();
			}
                        //處理Spring增加的DecoratingProxy介面的實現
			else if (method.getDeclaringClass() == DecoratingProxy.class) {
				return AopProxyUtils.ultimateTargetClass(this.advised);
			}
                        //處理Spring增加的Advised介面的實現,實際委託給AdvisedSupport(這裡是ProxyFactory)來處理
			else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
					method.getDeclaringClass().isAssignableFrom(Advised.class)) {
				return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
			}
			Object retVal;
                        //如果設定了exposeProxy標識(可以通過@EnableAspectJAutoProxy註解的exposeProxy屬性來設定),將建立的代理物件暴露出去
                        //我們在業務方法中可以通過AopContext的currentProxy()方法獲取代理物件
			if (this.advised.exposeProxy) {
				oldProxy = AopContext.setCurrentProxy(proxy);
				setProxyContext = true;
			}
			target = targetSource.getTarget();
			Class<?> targetClass = (target != null ? target.getClass() : null);

			//獲取攔截器鏈,就是從所有的Advisor中過濾出匹配目標類和目標方法的Advisor
			List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

			//如果沒有匹配的Advisor,直接通過反射執行業務方法
			if (chain.isEmpty()) {
                                //如果沒有匹配的Advisor,直接通過反射執行業務方法
				Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
				retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
			}
			else {
				//將攔截器鏈和被攔截的方法封裝到一個物件中,由它來具體執行
				MethodInvocation invocation =
						new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
				//開始執行
				retVal = invocation.proceed();
			}
			return retVal;
		}
	}

核心為獲取攔截器鏈和執行攔截器鏈,先分析獲取,進入AdvisedSupport的getInterceptorsAndDynamicInterceptionAdvice()方法。

public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, @Nullable Class<?> targetClass) {
                //增加了一層快取,先從快取中取
		MethodCacheKey cacheKey = new MethodCacheKey(method);
		List<Object> cached = this.methodCache.get(cacheKey);
		if (cached == null) {
                        //實際獲取
			cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
					this, method, targetClass);
			this.methodCache.put(cacheKey, cached);
		}
		return cached;
	}

繼續跟進去,這裡AdvisorChainFactory的具體實現類為DefaultAdvisorChainFactory

@Override
public List<Object> getInterceptorsAndDynamicInterceptionAdvice(
			Advised config, Method method, @Nullable Class<?> targetClass) {

		AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();
                //這裡就是我們自己定義MyAdvisor物件
		Advisor[] advisors = config.getAdvisors();
		List<Object> interceptorList = new ArrayList<>(advisors.length);
		Class<?> actualClass = (targetClass != null ? targetClass : method.getDeclaringClass());
		Boolean hasIntroductions = null;

		for (Advisor advisor : advisors) {
                        //包含切入點的通知器
			if (advisor instanceof PointcutAdvisor) {
				// Add it conditionally.
				PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
                                //判斷實際業務類Class是否匹配切入點,就是SmsService類是否匹配切入點表示式execution(public * xxx())
				if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) {
					MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();
					boolean match;
                                        //判斷給定的方法是否匹配切入點,這裡的方法就是SmsService類的hi()或hello()方法
					match = mm.matches(method, actualClass);
					if (match) {
                                                //這裡的攔截器就是我們自己定義的MyMethodInterceptor物件
						MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
						interceptorList.addAll(Arrays.asList(interceptors));
					}
				}
			}
		}

		return interceptorList;
	}

關於Pointcut是如何判斷給定類和給定方法是否匹配的,我們的例子中使用的是AspectJExpressionPointcut類,內部也是呼叫AspectJ的PointcutParser解析器來處理的,
就像上面AspectJ介紹中的示例程式碼一樣。
至此我們已經獲取到了一個匹配指定類指定方法的攔截器鏈,下面就開始執行攔截器鏈了。進入ReflectiveMethodInvocation的proceed()方法。

@Override
@Nullable
public Object proceed() throws Throwable {
		//內部使用一個索引,記錄當前執行的攔截器,如果攔截器全部執行完了,就執行業務方法
		if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
			return invokeJoinpoint();
		}

		Object interceptorOrInterceptionAdvice =
				this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
		if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
			//暫時先不管
		}
		else {
			//執行攔截器,這裡就是我們配置的MyMethodInterceptor類
			return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
		}
	}

再看一下我們定義的MyMethodInterceptor類

public static class MyMethodInterceptor implements MethodInterceptor {

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
      System.out.println("before invoke");
      Object result = invocation.proceed();
      System.out.println("after invoke");
      return result;
    }
  }

我們在攔截器中又呼叫了MethodInvocation的proceed()方法,所有就又開始了其他攔截器的執行,這是一個遞迴的呼叫,類似於Servlet的過濾器鏈的執行過程。

專案中使用

定義切面

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class LogAspect {

  @Pointcut("execution(public * com.imooc.sourcecode.java.spring.aop.SmsService.sayHi())")
  public void webLog() {
  }

  /**
   * 進入連線點之前.
   *
   * @param joinPoint JoinPoint
   */
  @Before("webLog()")
  public void doBefore(JoinPoint joinPoint) {
    // 記錄下請求內容
    System.out.println("doBefore: " + joinPoint.getTarget());
  }
}

配置啟用AOP

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@EnableAspectJAutoProxy
@ComponentScan
public class BeanConfig {
}

我們在專案中可以使用註解的方式來定義Pointcut和Advice,Spring會幫我們建立對應的Pointcut物件和MethodInterceptor物件,然後組合成Advisor,
核心為@EnableAspectJAutoProxy註解,它會向IOC容器注入AnnotationAwareAspectJAutoProxyCreator,這是一個BeanPostProcessor,
AnnotationAwareAspectJAutoProxyCreator實現了postProcessAfterInitialization()方法,在Bean初始化之後,來判斷是否需要建立代理物件。

@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
		if (bean != null) {
			Object cacheKey = getCacheKey(bean.getClass(), beanName);
			if (this.earlyProxyReferences.remove(cacheKey) != bean) {
                                //核心,是否需要建立代理物件
				return wrapIfNecessary(bean, beanName, cacheKey);
			}
		}
		return bean;
	}

會查詢IOC容器中所有包含@Aspect註解的Bean物件,解析@Pointcut及@Before等註解,封裝成Advisor物件列表。
解析出的Advisor列表儲存在BeanFactoryAspectJAdvisorsBuilder類中。

public class BeanFactoryAspectJAdvisorsBuilder {
        //儲存所有包含@Aspect註解的Bean名稱的快取
	@Nullable
	private volatile List<String> aspectBeanNames;
        //儲存Bean名稱和Advisor列表的對應關係的快取
	private final Map<String, List<Advisor>> advisorsCache = new ConcurrentHashMap<>();
}

只會解析一次,下次直接從快取中取。

分析總結

SpringAOP的實現原理:

  1. 根據@Aspect註解解析出所有Advisor(通知器),包含Pointcut(切入點)和Advice(通知)。
  2. 在Bean初始化(屬性裝配已經完成,初始化方法也已經呼叫)之後從所有的Advisor中過濾出可以匹配的Advisor。
  3. 如果有匹配的Advisor,就使用JDK或CGLIB建立代理物件。
  4. 執行業務方法,被代理物件所攔截
  5. 代理物件會從所有的Advisor中過濾出可以匹配的Advisor,封裝成過濾器鏈,依次執行。

參考

AspectJ 使用介紹
Spring AOP 使用介紹,從前世到今生
spring aop中pointcut表示式完整版