spring-AOP(二)實現原理之AspectJ註解方式

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

在上一篇spring-AOP(一)實現原理我們瞭解瞭如何使用ProxyFactory來建立AOP代理物件,但其過程需要實現一些介面,並且需要一些比較複雜的配置。因此,在spring2.0之後,提供了一種較為便利的方式。 使用@Aspect註解宣告一個切面類,之後通過@EnableAspectJAutoProxy註解來註冊代理生成類AnnotationAwareAspectJAutoProxyCreator。下面我們來看一下其原理

spring中如何使用@AspectJ

織入方式

手動織入

  1. 首先需要定義一個Aspect
package com.luhc.springaop.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

/**
 * @author luhuancheng
 * @date 2018/11/20
 */
@Aspect
public class PerformanceTraceAspect {

    @Pointcut("execution(* *..*method1()) || execution(* *..*method2())")
    public void pointcutName(){}

    @Pointcut("@annotation(AnyJoinpointAnnotation)")
    public void matchPointcut(){}

    @Before("matchPointcut()")
    public void before() {
        System.out.println("+++++++++@annotation++++++++++");
    }

    @Around("pointcutName()")
    public Object performanceTrace(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();

        try {
            return joinPoint.proceed();
        } finally {
            System.out.println(String.format("cost time %s", System.currentTimeMillis() - start));
        }
    }

}
複製程式碼
  1. 通過AspectJProxyFactory手動織入
private static void manualWeaver() {
    // 手動織入
    AspectJProxyFactory weaver = new AspectJProxyFactory();
    weaver.setProxyTargetClass(true);
    // 宣告目標物件
    weaver.setTarget(new Foo());
    // 宣告切面
    weaver.addAspect(PerformanceTraceAspect.class);
    // 獲取代理
    Object proxy = weaver.getProxy();
    // 執行已經織入切面邏輯的方法
    ((Foo) proxy).method1(new FlyImpl());
    ((Foo) proxy).method2();
}
複製程式碼

自動織入

自動織入方式需要AnnotationAwareAspectJAutoProxyCreator類的支援,通過將AnnotationAwareAspectJAutoProxyCreator和需要的Aspect切面、以及目標物件宣告在IOC容器中,在容器啟動期間,AnnotationAwareAspectJAutoProxyCreator將自動為目標物件生成織入切面邏輯的代理物件

  1. 宣告配置
package com.luhc.springaop.aspect;

import org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author luhuancheng
 * @date 2018/11/21
 */
@Configuration
public class AspectConfigure {

    /**
     * 自動織入器
     * @return
     */
    @Bean
    public AnnotationAwareAspectJAutoProxyCreator proxyCreator() {
        AnnotationAwareAspectJAutoProxyCreator proxyCreator = new AnnotationAwareAspectJAutoProxyCreator();
        // 預設為false,如果目標物件未實現介面的話,其代理物件也是通過cglib生成
        proxyCreator.setProxyTargetClass(false);
        return proxyCreator;
    }

    /**
     * 未實現介面的目標物件
     * @return
     */
    @Bean
    public Foo foo() {
        return new Foo();
    }

    /**
     * 切面
     * @return
     */
    @Bean
    public PerformanceTraceAspect performanceTraceAspect() {
        return new PerformanceTraceAspect();
    }

}
複製程式碼
  1. 從IOC容器中取出織入切面後的代理物件
private static void autoWeaver() {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AspectConfigure.class);
    Foo foo = context.getBean(Foo.class);
    // 此時的foo物件,是以及經過AnnotationAwareAspectJAutoProxyCreator處理後的代理物件
    foo.method1(new FlyImpl());
    foo.method2();
}
複製程式碼

@AspectJ形式的Pointcut宣告方式

Spring AOP支援以下的Pointcut表示式

// 任意包下的具有任意引數、任意返回值的任意方法
// @Pointcut("execution(* *..*(..))")

// com.luhc.springaop的任意子包下的任意類,springAOP只支援方法級別的JoinPoint,因此這個表示式將匹配指定類所宣告的所有方法執行
// @Pointcut("within(com.luhc.springaop..*)")

// 匹配代理物件型別為Foo的所有方法級的JoinPoint
// @Pointcut("this(Foo)")

// 匹配目標物件型別為Fly的所有方法級的JoinPoint
// @Pointcut("target(Fly)")

// 匹配傳入引數型別為Fly和Foo的所有方法執行的JoinPoint,不關心方法在哪個類中定義
// @Pointcut("args(Fly,Foo)")

// @within  @target的區別在於@within是靜態匹配、@target是在執行時動態匹配
// 匹配所有被註解AnyJoinpointAnnotation標註了的類的所有方法級的JoinPoint
// @Pointcut("@within(AnyJoinpointAnnotation)")

// 匹配所有目標物件唄註解AnyJoinpointAnnotation標註了的類的所有方法級的JoinPoint
// @Pointcut("@target(AnyJoinpointAnnotation)")

// 匹配方法引數型別被註解AnyJoinpointAnnotation標註了的所有方法級的JoinPoint
// @Pointcut("@args(AnyJoinpointAnnotation)")

// 匹配方法被註解AnyJoinpointAnnotation標註了的所有方法級的JoinPoint
// @Pointcut("@annotation(AnyJoinpointAnnotation)")

// 可以使用 || 和 && 來表達pointcut之間的邏輯運算
// @Pointcut("execution(* *..*method1()) || execution(* *..*method2())")
複製程式碼

剖開@AspectJ在SpringAOP中的真相

@AspectJ形式的Pointcut

AnnotationAwareAspectJAutoProxyCreator通過反射獲取到@Pointcut註解的資訊,在內部例項化為AspectJExpressionPointcut物件。 AspectJExpressionPointcut實現了ClassFilter、MethodMatcher,其內部實現邏輯代理給了PointcutParser,最終生成為PointcutExpression(PointcutExpressionImpl實現類)例項

@AspectJ形式的Advice

在Advice定義中訪問Joinpoint處的方法引數

使用org.aspectj.lang.JoinPoint

將Advice方法的第一個引數宣告為org.aspectj.lang.JoinPoint型別,我們可以通過呼叫org.aspectj.lang.JoinPoint相關方法獲取需要的資料

@Before("matchPointcut()")
public void before(org.aspectj.lang.JoinPoint joinPoint) {
    // 獲取方法名
    System.out.println(joinPoint.getSignature().getName());
    System.out.println("+++++++++@annotation++++++++++");
}
複製程式碼

使用args標誌符繫結

// 可以同時使用識別符號和JoinPoint,但是JoinPoint必須放在第一個引數位置上
@Before("matchPointcut() && args(name)")
public void before(JoinPoint joinPoint, String name) {
    System.out.println("獲取到Joinpoint上的入參:" + name);
    System.out.println("獲取到Joinpoint的方法名: " + joinPoint.getSignature().getName());
}
複製程式碼

捕獲異常@AfterThrowing

@AfterThrowing(pointcut = "matchPointcut()", throwing = "e")
public void afterThrowing(JoinPoint joinPoint, RuntimeException e) {
    System.out.println("方法:" + joinPoint.getSignature().getName() + "發生異常:" + e.getMessage());
}
複製程式碼

捕獲返回值@AfterReturning

@AfterReturning(pointcut = "pointcutName()", returning = "result")
public void afterReturning(JoinPoint joinPoint, String result) {
    System.out.println("方法:" + joinPoint.getSignature().getName() + "獲得返回值:" + result);
}
複製程式碼

方法正常執行完成後(未丟擲異常)@After

@After("pointcutName()")
public void after(JoinPoint joinPoint) {
    System.out.println("方法:" + joinPoint.getSignature().getName() + ": 執行完畢");
}
複製程式碼

@Around環繞方法

@Around與其他幾個Advice註解不同,在@Around方法中,第一個引數必須為org.aspectj.lang.ProceedingJoinPoint

@Around("pointcutName()")
public Object performanceTrace(ProceedingJoinPoint joinPoint) throws Throwable {
    long start = System.currentTimeMillis();

    try {
        return joinPoint.proceed();
    } finally {
        System.out.println(String.format("cost time %s", System.currentTimeMillis() - start));
    }
}
複製程式碼

公開當前呼叫的代理物件

// 在目標物件方法中,通過此方法可以獲取當前目標物件的代理物件
AopContext.currentProxy() 
複製程式碼

@AspectJ形式的Spring AOP代理自動生成原理

一個例子

我們使用註解配置的一個spring aop的demo(新版本的spring中推薦使用註解來配置容器)

// 假設這是一個業務介面
public interface Fly {
    void fly();
}

// 業務介面實現
public class FlyImpl implements Fly {
    @Override
    public void fly() {
        System.out.println("++++++++++++++++ Fly ++++++++++++++++");
    }
}

// 宣告一個切面
@Aspect
public class PerformanceTraceAspect {

    // 匹配任意返回值、任意包下的、任意引數的fly方法。在這個demo中,將匹配到com.luhc.springaop.aspect.FlyImpl#fly這個方法
    @Pointcut("execution(* *..*fly(..))")
    public void pointcutName(){}

    // 宣告切入的前置邏輯
    @Before("pointcutName()")
    public void before(JoinPoint joinPoint) {
        // 可以通過JoinPoint獲取到pointcut方法的詳細資訊
        System.out.println("Before --> 獲取到Joinpoint的方法名: " + joinPoint.getSignature().getName());
    }

    // 宣告切入的後置邏輯
    @After("pointcutName()")
    public void after(JoinPoint joinPoint) {
        // 可以通過JoinPoint獲取到pointcut方法的詳細資訊
        System.out.println("After --> 獲取到Joinpoint的方法名: " + joinPoint.getSignature().getName());
    }

    // 宣告切入的環繞邏輯(即在方法執行前後切入邏輯)
    @Around("pointcutName()")
    public Object performanceTrace(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();

        try {
            // 呼叫執行鏈
            return joinPoint.proceed();
        } finally {
            System.out.println(String.format("cost time %s", System.currentTimeMillis() - start));
        }
    }
}

// 配置類
@Configuration
// 啟用AOP
@EnableAspectJAutoProxy
public class AspectConfigure {

    /**
     * 實現介面的目標物件
     * @return
     */
    @Bean
    public Fly fly() {
        return new FlyImpl();
    }

    /**
     * 切面
     * @return
     */
    @Bean
    public PerformanceTraceAspect performanceTraceAspect() {
        return new PerformanceTraceAspect();
    }

}

// 執行應用
public class AspectJDemo {
    // 使用配置類,初始化容器
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AspectConfigure.class);
    // 從容器中獲取業務介面(此時已經是被處理過的代理物件,即已經切入了切面邏輯)
    Fly fly = context.getBean(Fly.class);
    fly.fly();
}
複製程式碼

剖析demo執行機制

從@EnableAspectJAutoProxy註解開始解開神祕面紗

註解EnableAspectJAutoProxy的定義
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {

	// 是否使用cglib來生成代理
	boolean proxyTargetClass() default false;

	// 是否將代理繫結到ThreadLocal,後續在目標類中可以使用AopContext.currentProxy()來獲取代理物件
	boolean exposeProxy() default false;

}
複製程式碼
AspectJAutoProxyRegistrar配置類

重點在其元註解@Import上(有機會再分析一下關於spring的@Import註解匯入機制),其匯入了配置類AspectJAutoProxyRegistrar

class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {

	/**
	 * Register, escalate, and configure the AspectJ auto proxy creator based on the value
	 * of the @{@link EnableAspectJAutoProxy#proxyTargetClass()} attribute on the importing
	 * {@code @Configuration} class.
	 */
	@Override
	public void registerBeanDefinitions(
			AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        // 重點!!此處向容器注入了AnnotationAwareAspectJAutoProxyCreator類
		AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);

		AnnotationAttributes enableAspectJAutoProxy =
				AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
		if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
			AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
		}
		if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
			AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
		}
	}

}
複製程式碼
註冊過程
public abstract class AopConfigUtils {
    /**
	 * Stores the auto proxy creator classes in escalation order.
	 */
	private static final List<Class<?>> APC_PRIORITY_LIST = new ArrayList<Class<?>>();

	/**
	 * Setup the escalation list.
     * 在spring中,預設存在三個代理生成類。優先順序別從上到下排序,越往後優先順序越高
	 */
	static {
		APC_PRIORITY_LIST.add(InfrastructureAdvisorAutoProxyCreator.class);
		APC_PRIORITY_LIST.add(AspectJAwareAdvisorAutoProxyCreator.class);
		APC_PRIORITY_LIST.add(AnnotationAwareAspectJAutoProxyCreator.class); // 最高優先順序
	}

    private static BeanDefinition registerOrEscalateApcAsRequired(Class<?> cls, BeanDefinitionRegistry registry, Object source) {
		Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
		if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
			BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
			if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
                // 如果容器當前已經註冊了代理生成器類,則比較其與AnnotationAwareAspectJAutoProxyCreator的優先順序。取優先順序最高的那個作為代理生成器註冊在容器中。
                // 顯然AnnotationAwareAspectJAutoProxyCreator被註冊到容器中
				int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
				int requiredPriority = findPriorityForClass(cls);
				if (currentPriority < requiredPriority) {
					apcDefinition.setBeanClassName(cls.getName());
				}
			}
			return null;
		}
		RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
		beanDefinition.setSource(source);
		beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
		beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
		registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
		return beanDefinition;
	}
}
複製程式碼
註冊流程總結
  1. @EnableAspectJAutoProxy註解匯入了配置類AspectJAutoProxyRegistrar
  2. 配置類AspectJAutoProxyRegistrar呼叫了AopConfigUtils來註冊代理生成器AnnotationAwareAspectJAutoProxyCreator

AnnotationAwareAspectJAutoProxyCreator如何做到自動生成代理

類結構

spring-AOP(二)實現原理之AspectJ註解方式

主流程

spring-AOP(二)實現原理之AspectJ註解方式

原始碼分析
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
		implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {

    @Override
	public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
		Object cacheKey = getCacheKey(beanClass, beanName);

		if (beanName == null || !this.targetSourcedBeans.contains(beanName)) {
			if (this.advisedBeans.containsKey(cacheKey)) {
				return null;
			}
			if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
				this.advisedBeans.put(cacheKey, Boolean.FALSE);
				return null;
			}
		}

		// Create proxy here if we have a custom TargetSource.
		// Suppresses unnecessary default instantiation of the target bean:
		// The TargetSource will handle target instances in a custom fashion.
		if (beanName != null) {
			TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
			if (targetSource != null) {
				this.targetSourcedBeans.add(beanName);
				Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
				Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);
				this.proxyTypes.put(cacheKey, proxy.getClass());
				return proxy;
			}
		}

		return null;
	}

    // 生成代理物件
    @Override
	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		if (bean != null) {
			Object cacheKey = getCacheKey(bean.getClass(), beanName);
			if (!this.earlyProxyReferences.contains(cacheKey)) {
				return wrapIfNecessary(bean, beanName, cacheKey);
			}
		}
		return bean;
	}

    protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
		if (beanName != null && this.targetSourcedBeans.contains(beanName)) {
			return bean;
		}
        // 跳過基礎設施類
		if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
			return bean;
		}
        // 跳過基礎設施類
		if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
			this.advisedBeans.put(cacheKey, Boolean.FALSE);
			return bean;
		}

		// Create proxy if we have advice.
        // 獲取切面Advisor
		Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
		if (specificInterceptors != DO_NOT_PROXY) {
			this.advisedBeans.put(cacheKey, Boolean.TRUE);
			Object proxy = createProxy(
					bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
			this.proxyTypes.put(cacheKey, proxy.getClass());
			return proxy;
		}

		this.advisedBeans.put(cacheKey, Boolean.FALSE);
		return bean;
	}

    protected Object createProxy(
			Class<?> beanClass, String beanName, Object[] specificInterceptors, TargetSource targetSource) {

		if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
			AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
		}

		ProxyFactory proxyFactory = new ProxyFactory();
		proxyFactory.copyFrom(this);

		if (!proxyFactory.isProxyTargetClass()) {
			if (shouldProxyTargetClass(beanClass, beanName)) {
				proxyFactory.setProxyTargetClass(true);
			}
			else {
                // 解析目標物件介面
				evaluateProxyInterfaces(beanClass, proxyFactory);
			}
		}

        // 生成Advisor
		Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
		proxyFactory.addAdvisors(advisors);
		proxyFactory.setTargetSource(targetSource);
		customizeProxyFactory(proxyFactory);

		proxyFactory.setFrozen(this.freezeProxy);
		if (advisorsPreFiltered()) {
			proxyFactory.setPreFiltered(true);
		}

        // 生成代理
		return proxyFactory.getProxy(getProxyClassLoader());
	}

}
複製程式碼

總結

  1. 描述了@AspectJ形式的Spring AOP如何使用
  2. spring AOP可以使用的@AspectJ識別符號,如execution、within、this、target、@annotation等
  3. 描述了spring內部是如何使用@EnableAspectJAutoProxy註解來啟用Spring AOP功能的,以及代理生成器AnnotationAwareAspectJAutoProxyCreator的內部流程時如何根據@Aspect類,來自動生成代理的

相關文章