Spring-AOP原理

1. 介绍

1.1 什么是AOP

AOP (Aspect Orient Programming),直译过来就是面向切面编程。AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。面向对象编程将程序抽象成各个层次的对象,而面向切面编程是将程序抽象成各个切面。

从该图可以很形象地看出,所谓切面,相当于应用对象间的横切点,我们可以将其单独抽象为单独的模块。

1.2 为什么需要AOP

 想象下面的场景,开发中在多个模块间有某段重复的代码,我们通常是怎么处理的?显然,没有人会靠“复制粘贴”吧。在传统的面向过程编程中,我们也会将这段代码,抽象成一个方法,然后在需要的地方分别调用这个方法,这样当这段代码需要修改时,我们只需要改变这个方法就可以了。然而需求总是变化的,有一天,新增了一个需求,需要再多出做修改,我们需要再抽象出一个方法,然后再在需要的地方分别调用这个方法,又或者我们不需要这个方法了,我们还是得删除掉每一处调用该方法的地方。实际上涉及到多个地方具有相同的修改的问题我们都可以通过 AOP 来解决。
 

1.3 如何织入(Weaving)

在不改变原有代码的前提下,对功能进行增强。相当于定义了如何进行代理。

1.4 什么是切面(Aspect/Advisor)

切面 = 通知 + 切点。表示指定的时间点对指定的地方进行额外的操作。感觉就像切点与通知的封装类。

1.5 什么是切点(Point Cut) 

指定的时间,可以理解进入通知这个大门的门票,也就是条件是什么。

1.6 什么是通知(Advice)

定义增强的功能的实现。

(1) Advice的时机

  • Before——在方法调用之前调用通知
  • After——在方法完成之后调用通知,无论方法执行成功与否
  • After-returning——在方法执行成功之后调用通知
  • After-throwing——在方法抛出异常后进行通知
  • Around——通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为

(2) Advice类图

 

2. 源码

2.1 通知如何实现

所有的通知实现都是继承这个钩子类。

// org.aopalliance.aop.Advice
public interface Advice {

}

2.2 切点如何实现

 定义哪些类哪些方法可以执行

// org.springframework.aop.Pointcut
public interface Pointcut {

	/**
	 * Return the ClassFilter for this pointcut.
	 * @return the ClassFilter (never {@code null})
	 */
	ClassFilter getClassFilter();

	/**
	 * Return the MethodMatcher for this pointcut.
	 * @return the MethodMatcher (never {@code null})
	 */
	MethodMatcher getMethodMatcher();


	/**
	 * Canonical Pointcut instance that always matches.
	 */
	Pointcut TRUE = TruePointcut.INSTANCE;

}

2.3  切面

可以看出切面就是切点和通知的组合。

// org.springframework.aop.Advisor
public interface Advisor {
	
	Advice EMPTY_ADVICE = new Advice() {};
	
	Advice getAdvice();

	boolean isPerInstance();
}
// org.springframework.aop.PointcutAdvisor
public interface PointcutAdvisor extends Advisor {

	Pointcut getPointcut();

}

2.4 如何织入

对类和方法进行增强就使用代理模式来增强,在JAVA中有JDK代理和Cglib代理。Spring作为框架,有两种时机可以进行织入。一是在启动容器初始化bean的时候就进行增强,然后容器中存放的不是bean的实例,而是bean的代理实例。二是在每一次使用bean的时候判断一次是否需要增强,需要就对其增强,然后返回bean的代理实例。这两种方法很明显第一种比较友好,只是让容器的启动时间稍微长了一点,而第二种在运行时判断,会使得用户的体验变差。

我们的框架初始化bean是在BeanFactory中进行,还包括bean的实例化,参数注入以及将bean放入容器中等。很明显对bean的增强应该是在bean实例化完成并在还没有放进容器中的时候。那么也就是在BeanFactory的doGetBean方法中了。这里有一个小问题在于,doGetBean方法做的事情已经够多了,继续往里加入代码无疑会使得代码大爆炸,很难维护也不易扩展。为了解决这个问题这里我们可以使用观察者模式来解决这一问题,将doGetBean方法中每一个过程都作为一个观察者存在,当我们需要添加功能时既可以添加一个观察者然后注入,这样不会对已有代码做出改变。

BeanFactory充当subject角色,BeanPostProcessor则充当observer的角色,BeanFactory监听BeanPostProcessor,我们可以将功能抽出为一个BeanPostProcessor,将其注册到BeanFactory中,这样既不会使得BeanFactory中代码过多,同时也比较容易做到了功能的解耦,假设我们不需要某一个功能,那么直接解除绑定即可而不需要任何其他操作

2.4.1 Spring AOP如何实现

(1) @EnableAspectJAutoProxy

@EnableAspectJAutoProxy主要的功能是给IOC容器中注册一个AnnotationAwareAspectJAutoProxyCreator的BeanDefinition,以便后续Bean实例化的时候注册Bean。

// org.springframework.aop.config.AopConfigUtils#registerOrEscalateApcAsRequired
private static BeanDefinition registerOrEscalateApcAsRequired(Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source) {
        Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
        if (registry.containsBeanDefinition("org.springframework.aop.config.internalAutoProxyCreator")) {
            BeanDefinition apcDefinition = registry.getBeanDefinition("org.springframework.aop.config.internalAutoProxyCreator");
            if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
                int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
                int requiredPriority = findPriorityForClass(cls);
                if (currentPriority < requiredPriority) {
                    apcDefinition.setBeanClassName(cls.getName());
                }
            }

            return null;
        } else {
            // 注册BeanDefinition
            RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
            beanDefinition.setSource(source);
            beanDefinition.getPropertyValues().add("order", -2147483648);
            beanDefinition.setRole(2);
            registry.registerBeanDefinition("org.springframework.aop.config.internalAutoProxyCreator", beanDefinition);
            return beanDefinition;
        }
    }

(2)BeanPostProcesor的设计

AnnotationAwareAspectJAutoProxyCreator继承InstantiationAwareBeanPostProcessor,其父类AbstractAutoProxyCreator实现了postProcessBeforeInstantiation方法(每个Bean在实例化之前都会执行),利用代理类增强了普通Bean。

(3)AOP实例化增强Bean的前置处理器

    // org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessBeforeInstantiation
	public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {
        // 获取缓存key,其实就是 BeanName
		Object cacheKey = getCacheKey(beanClass, beanName);
        // targetSourcedBeans是自定义实现的Bean,AOP这里的化我觉得不是关键的地方吧
		if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {
            //判断当前bean是否在advisedBeans中(advisedBeans保存了所有需要增强的bean)
			if (this.advisedBeans.containsKey(cacheKey)) {
				return null;
			}
            //1. 判断是不是 基础类型,也就说判断是不是 实现了这些接口 Advice、Pointcut、Advisor、AopInfrastructureBean、
            //2. 还会判断是不是 有没有 @Aspect 注解
            //3. 是否需要跳过----获取候选的增强器(切面里面的通知方法)【List<Advisor> candidateAdvisors】,如果增强器是 AspectJPointcutAdvisor 类型的,则返回true(封装的通知方法的增强器是 InstantiationModelAwarePointcutAdvisor类型).这里会遍历所有@Aspect下所有的方法,将其转换成切面Advisor(InstantiationModelAwarePointcutAdvisorImpl)。
            // 这段逻辑是处理如果是基础类、切面类、原始类 都不会进行代理
			if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
                // 这里其实是将 切面 Bean 的BeanName 缓存起来,代表这个 Bean 不进行 增强操作
				this.advisedBeans.put(cacheKey, Boolean.FALSE);
				return null;
			}
		}

		// ...

		return null;
	}

 (4)AOP实例化增强Bean的后置处理器

    // org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessAfterInitialization
    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;
	}
    // org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#wrapIfNecessary
    protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
		if (StringUtils.hasLength(beanName) && 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;
		}

		// 注解@Aspect的类,会将其下跟AOP相关的方法转换成切面类(PointCut),例如@Before、@After等等,@Before这些会转换成PointcutAdvisor。然后每个Bean会根据@PointCut的条件进行匹配
		Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
		if (specificInterceptors != DO_NOT_PROXY) {
            // 不为空的话,我们就需要增强这个Bean,创建代理类返回
			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;
	}

(5) 创建代理类

// org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#createProxy
protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
			@Nullable 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[] 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());
	}

 

    // org.springframework.aop.framework.DefaultAopProxyFactory#createAopProxy
    public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
		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 {
			return new JdkDynamicAopProxy(config);
		}
	}

(6) 方法调用

创建完代理类后,当遇到符合AOP方法调用的时候,JDK代理类会调用invoke方法,CGlib代理类会调用getProxy方法(最终还是利用MethodInterceptor调用invoke方法)。下面是以Cglib代理类为例,后面就开始套娃调用模式,开始执行已经排序完毕的Advisor。

    // org.springframework.aop.framework.CglibAopProxy.FixedChainStaticTargetInterceptor#intercept
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
			MethodInvocation invocation = new CglibMethodInvocation(
					proxy, this.target, method, args, this.targetClass, this.adviceChain, methodProxy);
			// If we get here, we need to create a MethodInvocation.
			Object retVal = invocation.proceed();
			retVal = processReturnType(proxy, this.target, method, retVal);
			return retVal;
		}
    // org.springframework.aop.framework.ReflectiveMethodInvocation#proceed
    public Object proceed() throws Throwable {
		// We start with an index of -1 and increment early.
		if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
			return invokeJoinpoint();
		}
        // 每次递归的时候,数组下标都会加1来遍历
		Object interceptorOrInterceptionAdvice =
				this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
		if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
			// Evaluate dynamic method matcher here: static part will already have
			// been evaluated and found to match.
			InterceptorAndDynamicMethodMatcher dm =
					(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
			Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
            // 会根据@PointCut注解匹配条件,匹配到了就执行Advice通知
			if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {
				return dm.interceptor.invoke(this);
			}
			else {
				// Dynamic matching failed.
				// Skip this interceptor and invoke the next in the chain.
				return proceed();
			}
		}
		else {
			// It's an interceptor, so we just invoke it: The pointcut will have
			// been evaluated statically before this object was constructed.
			return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
		}
	}

 

(7)以@After为例

通过try finally方式,递归调用。因为是@After注解,所以等后续Advisor执行完毕后,调用@After的通知方法来进行增强。

    // org.springframework.aop.aspectj.AspectJAfterAdvice#invoke
    public Object invoke(MethodInvocation mi) throws Throwable {
		try {
			return mi.proceed();
		}
		finally {
			invokeAdviceMethod(getJoinPointMatch(), null, null);
		}
	}

3. FAQ

3.1 JDK代理和Cglib代理有什么区别

Java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理;而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。JDK动态代理只能对实现了接口的类生成代理,而不能针对类;CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法。因为是继承,所以该类或方法最好不要声明成final 。@EnableAspectJAutoProxy(proxyTargetClass = true)可以强制使用CGLIB实现AOP。

  • 如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP ;
  • 如果目标对象实现了接口,可以强制使用CGLIB实现AOP ;
  • 如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换;

3.1.1 优缺点

 (1) 使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类。

(2) 在对JDK动态代理与CGlib动态代理的代码实验中看,1W次执行下,JDK7及8的动态代理性能比CGlib要好20%左右。

3.2 这里创建代理类的过程有点疑问, @EnableAspectJAutoProxy(proxyTargetClass = true)已经标明创建代理类全部使用CGlib了,为什么这里没有这个判断

如果没有添加proxyTargetClass=true,则代理类全部使用JDK代理类。、该配置的设置导致config.isProxyTargetClass为true。方法快内表示目前是接口的话则使用JDK代理类,否则使用CGlib代理类。

    // org.springframework.aop.framework.DefaultAopProxyFactory#createAopProxy
    public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
		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.");
			}
			if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
				return new JdkDynamicAopProxy(config);
			}
			return new ObjenesisCglibAopProxy(config);
		}
		else {
			return new JdkDynamicAopProxy(config);
		}
	}

 3.3 没有加@EnableAspectJAutoProxy也能实现AOP功能

下面方法是应用启动的时候一定会执行的,只不过我们可以通过注解来获取额外的配置,这样配置会比较灵活。

    // org.springframework.context.annotation.AspectJAutoProxyRegistrar#registerBeanDefinitions
    public void registerBeanDefinitions(
			AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

		AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);

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

 

4. 参考资料

spring源码解析之AOP原理

Spring的两种动态代理:Jdk和Cglib 的区别和实现

Spring系列之AOP的原理及手动实现

从@EnableAspectJAutoProxy分析Spring AOP原理

@Pointcut 的 12 种用法,你知道几种?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值