spring-AOP(一)實現原理

暱稱又又被用了發表於2018-11-19

spring內部使用了jdk動態代理、cglib(通過生成位元組碼的方式,繼承目標類擴充套件目標類功能)兩種方式實現了AOP框架。本篇先詳細介紹spring內部的AOP概念實體、之後介紹spring AOP的使用方式和原理

實現原理

spring內部使用了jdk動態代理、cglib這兩種機制構建了整個AOP框架的基礎

JDK動態代理

我們可以通過反射技術,為需要代理的目標物件,創造一個代理類出來,並且在代理類中執行我們所需要的邏輯,如:統計方法執行時間、列印日誌。
相對於cglib技術,JDK動態代理存在兩個比較明顯的缺點:

  1. 目標物件必須是通過實現介面的類,才能建立代理
  2. 在執行時,使用反射進行擴充套件目標物件功能,效能會略低於cglib位元組碼技術的實現方式
// 一個需要進行代理的介面
public interface Greeting {

    void sayHi(String name);

    void sayByte(String name);
}

// 介面實現類,即目標物件。
// 我們需要在不改變該實現類程式碼的基礎上,在執行介面方法時,進行一些額外的功能
public class GreetingImpl implements Greeting {

    @Override
    public void sayHi(String name) {
        System.out.println(name);
    }

    @Override
    public void sayByte(String name) {
        System.out.println(name);
    }
}

// 實現一個InvocationHandler,用於執行額外功能,並且呼叫目標物件的方法
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * created by luhuancheng on 2018/11/17
 * @author luhuancheng
 */
public class LogInvocationHandler implements InvocationHandler {

    private Object target;

    public LogInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before execute"); // 在執行目標物件方法之前,列印日誌
        Object result = method.invoke(target, args); // 執行目標物件的方法
        System.out.println("After execute"); // 在執行目標物件方法之後,列印日誌

        return result;
    }
}

// 建立動態代理
public class DynamicProxy {
    public static void main(String[] args) {

        Greeting greeting = new GreetingImpl();
        Object proxyInstance = Proxy.newProxyInstance(DynamicProxy.class.getClassLoader(), new Class[]{Greeting.class}, new LogInvocationHandler(greeting));
        Greeting proxy = (Greeting) proxyInstance;
        proxy.sayHi("luhuancheng");
        proxy.sayByte("luhuancheng");
    }
}
複製程式碼

cglib位元組碼技術

當我們要代理的目標物件,並不是由一個介面實現時。我們無法通過JDK動態代理來進行代理物件的建立,這時候就需要cglib這種位元組碼技術的登場了。

// 需要被代理的目標類,該類沒有實現任何一個介面
public class Requestable {
    public void request() {
        System.out.println("request in Requestable without implementint any interface");
    }
}

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

// 實現cglib的方法攔截器介面
public class RequestableCallback implements MethodInterceptor {

    private static final String INTERCEPTOR_METHOD_NAME = "request";

    @Override
    public Object intercept(Object target, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        if (INTERCEPTOR_METHOD_NAME.equals(method.getName())) {
            System.out.println("Before execute");
            Object result = methodProxy.invokeSuper(target, args);
            System.out.println("After execute");
            return result;
        }
        return null;
    }
}

import net.sf.cglib.proxy.Enhancer;

// cglib代理實現-動態位元組碼生成技術擴充套件物件行為(對目標物件進行繼承擴充套件)
public class CglibProxy {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Requestable.class);
        enhancer.setCallback(new RequestableCallback());

        Requestable proxy = (Requestable) enhancer.create();
        proxy.request();
    }
}

複製程式碼

AOP概念實體

JoinPoint

JoinPoint,可以理解為執行切面動作的一個時機。如:構造方法呼叫時,欄位設定時,方法呼叫時,方法執行時。但在spring AOP中,僅支援方法執行時的JoinPoint。

類圖

spring-AOP(一)實現原理

Pointcut

Pointcut,是我們在開發AOP功能時,定義的一個幫助我們捕捉系統中的相應JoinPoint的規則。

類圖

spring-AOP(一)實現原理

  1. ClassFilter, 用於匹配被執行織入操作的物件。如果型別對於我們要捕獲的JoinPoint無關的話,可以使用TruePointcut類
  2. MethodMatcher,用於匹配被執行織入操作的方法 。從大的分類可以分為兩類:StaticMethodMatcher(不關心Pointcut具體引數)、DynamicMethodMatcher(關係Pointcut具體引數)
  3. StaticMethodMatcherPointcut繼承了StaticMethodMatcher而且實現介面Pointcut,其自身可以作為Pointcut和MethodMatcher
  4. NameMatchMethodPointcut,根據指定的方法名和JoinPoint方法名進行匹配
  5. JdkRegexpMethodPointcut,使用正規表示式與JoinPoint方法名進行匹配
  6. AnnotationMatchingPointcut,根據目標類中是否存在指定型別的註解來匹配JoinPoint
  7. ComposablePointcut,可以進行邏輯運算的Pointcut
  8. DynamicMethodMatcherPointcut,繼承了DynamicMethodMatcher而且實現介面Pointcut,其自身可以作為Pointcut和MethodMatcher

Advice

Advice,定義了AOP功能中的橫切邏輯

類圖

spring-AOP(一)實現原理

BeforeAdvice

橫切邏輯將在相應的JoinPoint執行之前執行

AfterAdvice

橫切邏輯將在相應的JoinPoint執行之後執行。該介面又派生了兩個子介面ThrowsAdvice、AfterReturningAdvice

ThrowsAdvice

橫切邏輯將在相應的JoinPoint執行異常時執行

// ThrowsAdvice是一個沒有定義方法的標記介面,但是在橫切邏輯執行時,
// spring AOP內部會通過反射的方式,檢測ThrowsAdvice實現類的方法。
// 我們可以定義以下三個方法,分別處理不同的異常
public class ExceptionBarrierThrowsAdvice implements ThrowsAdvice {

    public void afterThrowing(Throwable t) {
        // 普通異常處理邏輯
    }

    public void afterThrowing(RuntimeException e) {
        // 執行時異常處理邏輯
    }

    public void afterThrowing(Method method, Object[] args, Object target, ApplicationException e) {
        // 處理應用程式生成的異常
    }

}

/**
 * 業務異常類
 */
class ApplicationException extends RuntimeException {

}
複製程式碼
AfterReturningAdvice

橫切邏輯將在相應的JoinPoint正常執行返回時執行

MethodInterceptor

作為Spring AOP的環繞方法(Around Advice),可以攔截相應的JoinPoint方法執行,從而在JoinPoint執行的前、正常返回、執行後這三個地方進行橫切邏輯的切入

public class PerformanceMethodInterceptor implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        long start = System.currentTimeMillis();
        try {
            // 呼叫目標方法
            return invocation.proceed();
        } finally {
            System.out.println(String.format("cost time: %d", System.currentTimeMillis() - start));
        }
    }
}
複製程式碼

IntroductionInterceptor

Spring AOP框架中實現Introduction的介面。Introduction功能可以在不改變類的定義的情況下,為目標類增加新的介面或屬性

Advisor(對應AOP世界中的切面,Aspect。)

Advisor,代表了spring中的Aspect。
分為兩大類:PointcutAdvisor、IntroductionAdvisor

類圖

spring-AOP(一)實現原理

PointcutAdvisor

PointcutAdvisor,預設存在三個實現類DefaultPointcutAdvisor、NameMatchMethodPointcutAdvisor、RegexpMethodPointcutAdvisor

DefaultPointcutAdvisor

DefaultPointcutAdvisor作為PointcutAdvisor比較通用的一個實現類,我們可以為其設定Pointcut、Advice。虛擬碼如下:

Pointcut pointcut = ...
Advice advice = ...
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
advisor.setPointcut(pointcut);
advisor.setAdvice(advice);
複製程式碼
NameMatchMethodPointcutAdvisor

NameMatchMethodPointcutAdvisor,其內部持有一個NameMatchMethodPointcut例項作為pointcut。可以通過setMappedName(String mappedName)或者setMappedNames(String... mappedNames)操作pointcut,設定匹配的Pointcut方法名

RegexpMethodPointcutAdvisor

RegexpMethodPointcutAdvisor,其內部持有一個AbstractRegexpMethodPointcut實現類的例項pointcut(預設為JdkRegexpMethodPointcut),使用setPattern(String pattern)或者setPatterns(String... patterns)設定正規表示式用於匹配JoinPoint方法名

DefaultBeanFactoryPointcutAdvisor

DefaultBeanFactoryPointcutAdvisor,間接繼承了BeanFactoryAware,其內部持有beanfactory。在指定advice時,可以通過方法setAdviceBeanName(String adviceBeanName)指定advice在beanfactory中的唯一name。之後在需要Advice時,將從beanfactory中獲取,減少了容器啟動初期Advice和Advisor之間的耦合

IntroductionAdvisor

DefaultIntroductionAdvisor

DefaultIntroductionAdvisor作為唯一的Introduction型別的Advice,只能使用於Introduction場景。

spring AOP織入原理

在spring AOP中,可以通過ProxyFactory、ProxyFactoryBean、AbstractAutoProxyCreator(及其實現類)來執行織入

ProxyFactory

我們來看看ProxyFactory這個最基本的織入器是如何工作的。其步驟大致分為以下幾步:

  1. 將目標物件傳入構造器,例項化一個ProxyFactory
  2. 呼叫ProxyFactory相關方法,設定所需的引數。如:是否使用cglib優化setOptimize(true)、代理類setProxyTargetClass(true)
  3. 指定要織入的介面(這個步驟可選,程式碼內部會根據目標物件檢測到介面)
  4. 例項化切面(即Advisor),設定切面邏輯(將Advice實現類例項設定進Advisor實現類中)
  5. 呼叫ProxyFactory.getProxy()獲取代理類
  6. 執行代理類
基於介面
/**
* 基於介面的織入 - JDK動態代理
*/
private static void forInterface() {
    // 1. 需要被攔截的介面實現類
    ITask task = new TaskImpl();
    // 2. 例項化一個執行織入操作的ProxyFactory
    ProxyFactory weaver = new ProxyFactory(task);
    // 我們也可以讓基於介面的織入,使用cglib的方式
    weaver.setProxyTargetClass(true);
    weaver.setOptimize(true);
    // 3. 指定需要織入的介面
    weaver.setInterfaces(ITask.class);
    // 4. 定義切面
    NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
    advisor.setMappedName("execute");
    // 5. 指定切面邏輯
    advisor.setAdvice(new PerformanceMethodInterceptor());
    // 6. 將切面新增到ProxyFactory例項中
    weaver.addAdvisor(advisor);
    // 7. 執行織入,返回織入後的代理例項
    ITask proxyObject = (ITask) weaver.getProxy();
    // 8. 呼叫介面,此時的執行邏輯中織入了切面邏輯
    proxyObject.execute();
    System.out.println(proxyObject);
    System.out.println(task);
}
複製程式碼
基於類
/**
* 基於類的織入 - CGLIB
*/
private static void forClass() {
    // 1. 例項化一個執行織入操作的ProxyFactory,作為織入器
    ProxyFactory weaver = new ProxyFactory(new Excutable());
    // 2. 例項化Advisor(切面)
    NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
    advisor.setMappedName("execute");
    // 3. 為Advisor設定切面邏輯
    advisor.setAdvice(new PerformanceMethodInterceptor());
    // 4. 為織入器設定切面
    weaver.addAdvisor(advisor);
    // 5. 執行織入
    Excutable proxyObject = (Excutable) weaver.getProxy();
    // 6. 呼叫介面,此時的執行邏輯中織入了切面邏輯
    proxyObject.execute(); // 使用cglib,第一次執行需要動態生成位元組碼,效率比動態代理低。
    proxyObject.execute(); // 再次使用cglib,直接執行第一次呼叫生成的位元組碼,效率比動態代理高
    System.out.println(proxyObject.getClass());
}
複製程式碼
Introduction織入
private static void forIntroduction() {
    ProxyFactory weaver = new ProxyFactory(new DevloperImpl());
    weaver.setInterfaces(IDevloper.class, ITester.class);
    TesterFeatureIntroductionInterceptor advice = new TesterFeatureIntroductionInterceptor();
    weaver.addAdvice(advice);
//        DefaultIntroductionAdvisor advisor = new DefaultIntroductionAdvisor(advice);
//        weaver.addAdvisor(advisor);
    Object proxy = weaver.getProxy();
    ((ITester) proxy).test();
    ((IDevloper) proxy).develSoftware();
}
複製程式碼

ProxyFactory內部機制

類圖

spring-AOP(一)實現原理

AopProxy

AopProxy對使用不同實現機制的代理進行了抽象。提供兩個介面用於生成代理物件

Object getProxy();
Object getProxy(ClassLoader classLoader);
複製程式碼
ProxyConfig

提供了5個屬性用於配置控制代理物件生成的過程

private boolean proxyTargetClass = false;

private boolean optimize = false;

boolean opaque = false;

boolean exposeProxy = false;

private boolean frozen = false;
複製程式碼
Advised

提供介面用於配置生成代理的必要配置資訊,比如Advice、Advisor等

AopProxyFactory

作為AopProxy的抽象工廠

DefaultAopProxyFactory

AopProxyFactory預設實現,通過介面根據AdvisedSupport提供的配置資訊建立代理

AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException;
複製程式碼
ProxyFactory

作為基本的織入器,繼承了ProxyCreatorSupport(為了統一管理生成不同型別的AopProxy,將生成邏輯抽象到了這個類中)。ProxyCreatorSupport持有了一個AopProxyFactory型別的例項,預設為DefaultAopProxyFactory

主流程

spring-AOP(一)實現原理

ProxyFactoryBean(容器內織入)

使用ProxyFactory我們可以獨立於spring的IOC容器來使用spring AOP框架。但是便於我們管理Pointcut、Advice等相關的bean,我們一般利用IOC容器來進行管理。在IOC容器中,使用ProxyFactoryBean來進行織入

類圖

spring-AOP(一)實現原理
可以看出ProxyFactoryBean實現了介面FactoryBean,其實現如下:

public class ProxyFactoryBean extends ProxyCreatorSupport
		implements FactoryBean<Object>, BeanClassLoaderAware, BeanFactoryAware {
    
    private synchronized void initializeAdvisorChain() throws AopConfigException, BeansException {
		if (this.advisorChainInitialized) {
			return;
		}

        // 當攔截器名稱列表不為空時,初始化advisor chain
		if (!ObjectUtils.isEmpty(this.interceptorNames)) {
            // 需要根據interceptorName從beanfactory中取到對應advisor的例項,所以beanfactory不能為null
			if (this.beanFactory == null) {
				throw new IllegalStateException("No BeanFactory available anymore (probably due to serialization) " +
						"- cannot resolve interceptor names " + Arrays.asList(this.interceptorNames));
			}

			// Globals can't be last unless we specified a targetSource using the property...
			if (this.interceptorNames[this.interceptorNames.length - 1].endsWith(GLOBAL_SUFFIX) &&
					this.targetName == null && this.targetSource == EMPTY_TARGET_SOURCE) {
				throw new AopConfigException("Target required after globals");
			}

			// Materialize interceptor chain from bean names.
			for (String name : this.interceptorNames) {
				if (logger.isTraceEnabled()) {
					logger.trace("Configuring advisor or advice '" + name + "'");
				}
                // 如果name以符號"*"結尾,則從beanfactory中獲取beanname為name(去掉*)開頭的所有Advisor、Interceptor型別的bean,註冊為Advisor
				if (name.endsWith(GLOBAL_SUFFIX)) {
					if (!(this.beanFactory instanceof ListableBeanFactory)) {
						throw new AopConfigException(
								"Can only use global advisors or interceptors with a ListableBeanFactory");
					}
					addGlobalAdvisor((ListableBeanFactory) this.beanFactory,
							name.substring(0, name.length() - GLOBAL_SUFFIX.length()));
				}
                // 普通的name(即非*號匹配的bean),則直接從beanfactory獲取,新增到advisor chain中
				else {
					// If we get here, we need to add a named interceptor.
					// We must check if it's a singleton or prototype.
					Object advice;
					if (this.singleton || this.beanFactory.isSingleton(name)) {
						// Add the real Advisor/Advice to the chain.
						advice = this.beanFactory.getBean(name);
					}
					else {
						// It's a prototype Advice or Advisor: replace with a prototype.
						// Avoid unnecessary creation of prototype bean just for advisor chain initialization.
						advice = new PrototypePlaceholderAdvisor(name);
					}
					addAdvisorOnChainCreation(advice, name);
				}
			}
		}
        // 標記已初始化
		this.advisorChainInitialized = true;
	}

    private synchronized Object getSingletonInstance() {
		if (this.singletonInstance == null) {
            // 根據targetName從beanfactory中獲取目標物件
			this.targetSource = freshTargetSource();
            
			if (this.autodetectInterfaces && getProxiedInterfaces().length == 0 && !isProxyTargetClass()) {
				// Rely on AOP infrastructure to tell us what interfaces to proxy.
				Class<?> targetClass = getTargetClass();
				if (targetClass == null) {
					throw new FactoryBeanNotInitializedException("Cannot determine target class for proxy");
				}
                // 自動識別目標物件的介面,設定到interfaces屬性中
				setInterfaces(ClassUtils.getAllInterfacesForClass(targetClass, this.proxyClassLoader));
			}
			// Initialize the shared singleton instance.
			super.setFrozen(this.freezeProxy);
            // 生成代理物件
			this.singletonInstance = getProxy(createAopProxy());
		}
		return this.singletonInstance;
	}

    @Override
    public Object getObject() throws BeansException {
        // 初始化advisor chain
        initializeAdvisorChain();
        
        if (isSingleton()) {
            // 獲取單例的代理
            return getSingletonInstance();
        }
        else {
            if (this.targetName == null) {
                logger.warn("Using non-singleton proxies with singleton targets is often undesirable. " +
                        "Enable prototype proxies by setting the 'targetName' property.");
            }
            // 獲取原型的代理(每次都會重新生成代理物件)
            return newPrototypeInstance();
        }
    }
}

複製程式碼

使用AbstractAutoProxyCreator實現類(自動織入)

spring-AOP(一)實現原理
從類圖可以看出,所有的AutoProxyCreator都間接實現了介面InstantiationAwareBeanPostProcessor。而InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation方法提供了一個機會,讓註冊到beanfactory中的bean在例項化之前,可以有一個建立例項的嵌入邏輯。具體可以看下springIOC容器例項化bean例項的過程分析
spring-AOP(一)實現原理
從類圖可以看出,所有的AutoProxyCreator都間接實現了介面InstantiationAwareBeanPostProcessor。而InstantiationAwareBeanPostProcessor#postProcessAfterInitialization方法提供了一個機會,完成代理物件的建立,並快取到容器中,供後續使用

BeanNameAutoProxyCreator

使用BeanNameAutoProxyCreator的虛擬碼

TargetClass target1 = new TargetClass();
TargetClass target2 = new TargetClass();

BeanNameAutoProxyCreator autoProxyCreator = new BeanNameAutoProxyCreator();
// 指定目標例項的beanname
autoProxyCreator.setBeanNames("target1", "target2");
// 指定Advice(切面邏輯)
autoProxyCreator.setInterceptorNames("a interceptor name");

// 完成以上配置的BeanNameAutoProxyCreator,註冊到IOC容器時,將自動完成對target1、target2兩個bean進行織入切面邏輯
複製程式碼

總結

總結一下:

  1. 我們瞭解了spring aop中AOP實體的實現類:JoinPoint、Pointcut、Advice、Advisor(Aspect)等
  2. 使用ProxyFactory在容器外進行切面邏輯的織入
  3. 使用ProxyFactoryBean在容器管理Advice、Advisor例項bean的基礎上,進行切面邏輯的織入
  4. 使用AbstractAutoProxyCreator的實現類,通過一些配置可以實現在容器啟動時,自動生成代理類。免去了手動生成代理類的過程

以下內容就是spring aop框架的實現原理,可以看到建立aop的過程相當的繁瑣,並且如果使用這種方式來建立代理類,織入切面邏輯的話,存在大量的模板程式碼。在spring2.0中,使用了一種全新的方法來簡化我們開發AOP的流程。我們在下篇文章進行分析吧

相關文章