SpringAOP 切面编程原理解析

前言

我们在学习 SpringAOP 的时候一直听说是面向切面编程,还有经常听到说 aop 可以用来做日志、做接口调用统计、做分库分表动态切换等等,觉得很神奇,明明我们只使用 @Aspect 注解了一个独立的 class,怎么能够这么强大呢?

本篇试着从源码角度分析一下 aop 的原理,在开始之前我们提出几个问题,一边学习一边解答。

  • 被增强的类还是原来的类嘛?
  • 类什么时候被增强的?

我假定看本文的读者已经有基本的 springioc 相关的基础,最好也了解过 springaop 的源码,当然没有阅读过就需要大家更加集中精神。

SpringIOC 是对类的解耦,SpringAOP 就是对方法的解耦。牢记这句话

本人的 Spring 源码注释项目:https://github.com/Lingouzi/spring-framework.git

解析

Demo

老规矩,上 demo

// 来个接口
public interface Calculate {
   
     int div(int numA, int numB);
}
// 实现类
public class LybqCalculate implements Calculate {
   
	@Override
	public int div(int numA, int numB) {
   
		System.out.println("执行目标方法:div");
		return numA / numB;
	}
}
/**
 * 被 @Aspect 注解的 bean 就是一个切面,就是一个 Aspect
 */
@Aspect
@Order
public class LogAspect {
   
	
	/**
	 * 被 @Pointcut 注解的,就是一个切点,这个决定了我们要增强哪个方法。
	 * 【springaop 只能增强 springioc 容器管理下的 bean 中的方法。它和 aspectj 还是有区别的,springaop 只是实现了 aspectj 的部分思想】
	 */
	@Pointcut("execution(* top.ybq87.LybqCalculate.*(..))")
    public void pointCut() {
   
    }
	
	/**
	 * 各种通知,Advise,我们前面定义了切点,就拦截了 bean 的方法,那么要对这些方法做什么呢?就在通知方法这里进行
	 * @param joinPoint
	 * @throws Throwable
	 */
	@Before(value = "pointCut()")
    public void methodBefore(JoinPoint joinPoint) throws Throwable {
   
        String methodName = joinPoint.getSignature().getName();
        System.out.println("执行目标方法【" + methodName + "】的<前置通知>,入参" + Arrays.asList(joinPoint.getArgs()));
    }
    
    @After(value = "pointCut()")
    public void methodAfter(JoinPoint joinPoint) {
   
        String methodName = joinPoint.getSignature().getName();
        System.out.println("执行目标方法【" + methodName + "】的<后置通知>,入参" + Arrays.asList(joinPoint.getArgs()));
    }
    
    @AfterReturning(value = "pointCut()", returning = "result")
    public void methodReturning(JoinPoint joinPoint, Object result) {
   
        String methodName = joinPoint.getSignature().getName();
        System.out.println("执行目标方法【" + methodName + "】的<返回通知>,入参" + Arrays.asList(joinPoint.getArgs()) + ";返回值:" + result);
    }
    
    @AfterThrowing(value = "pointCut()")
    public void methodAfterThrowing(JoinPoint joinPoint) {
   
        String methodName = joinPoint.getSignature().getName();
        System.out.println("执行目标方法【" + methodName + "】的<异常通知>,入参" + Arrays.asList(joinPoint.getArgs()));
    }
    
    // 还有个 Around 方法,留给大家自己研究了
    
}
// 配置类,注入下实现类和我们的切面方法
@Configuration
@EnableAspectJAutoProxy
public class MainConfig {
   

    @Bean
    public Calculate calculate() {
   
        return new LybqCalculate();
    }

    @Bean
    public LogAspect logAspect() {
   
        return new LogAspect();
    }
}
// main 方法
public class MainClass {
   
    
    public static void main(String[] args) {
   
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MainConfig.class);
        Calculate calculate = (Calculate) ac.getBean("calculate");
        calculate.div(6, 2);
    }
}
// 跑起来的打印结果
执行目标方法【div】的<前置通知>,入参[6, 2]
执行目标方法:div
执行目标方法【div】的<后置通知>,入参[6, 2]
执行目标方法【div】的<返回通知>,入参[6, 2];返回值:3

如上,代码比较简单,完整的 demo 见我的spring 源码解析项目。

被增强的类还是原来的类嘛?

这个问题大家一定很奇怪,这里我再具体描述下:我们 mian 方法中使用的 calculate 对象还是你以为的那个calculate对象嘛?

断点打印看看

image-20200429133637257

这里我们看到这个类被标注为了 $Proxy说明这是一个代理类,也就是被增强的类了,不是原来的狗子了。

狗子你变了 2

SpringAOP 通过动态代理技术,对类和方法进行增强,其中主要有 2 种动态代理方式

JDK 动态代理、cglib 动态代理

类什么时候被增强的?

既然它变强了,那么一定是因为它秃了,明明这个方法只能打印执行目标方法:div,但是实际上却被一众小弟环绕,那么它怎么变秃的呢?

抄作业开始,【所以我说希望读者有一定的源码基础才好嘛】,看过源码的应该了解,bean 的实例化和初始化都是在finishBeanFactoryInitialization,进行的,而更进一步的初始化是在doCreateBean

AbstractApplicationContext#refresh
--AbstractApplicationContext#finishBeanFactoryInitialization
----DefaultListableBeanFactory#preInstantiateSingletons
------AbstractBeanFactory#doGetBean
--------AbstractAutowireCapableBeanFactory#createBean
----------AbstractAutowireCapableBeanFactory#doCreateBean

跟进到AbstractAutowireCapableBeanFactory#doCreateBeanexposedObject = initializeBean(beanName, exposedObject, mbd);打个断点,跳过其他 bean 的初始化,等到我们的calculate

image-20200429135729468

目前发现我们的calculate还是原来的狗子,而exposedObject也还是我们期待的那个对象。

step over

image-20200429140349755

明显看到这之后它变了。

关键方法 initializeBean(beanName, exposedObject, mbd)
protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {
   
    if (System.getSecurityManager() != null) {
   
        AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
   
            invokeAwareMethods(beanName, bean);
            return null;
        }, getAccessControlContext());
    }
    else {
   
        // bean 如果实现了 xxxAware 接口,那么这里进行方法的回调,不是本文重点
        invokeAwareMethods(beanName, bean);
    }

    Object wrappedBean = bean;
    if (mbd == null || !mbd.isSynthetic()) {
   
        // 后置处理器,@PostConstuct 注解的方法
        wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
    }

    try {
   
        /**
		 * 调用 bean 配置中的 init-method="xxx"
		 */
        invokeInitMethods(beanName, wrappedBean, mbd);
    }
    catch (Throwable ex) {
   
        throw new BeanCreationException(
            (mbd != null ? mbd.getResourceDescription() : null),
            beanName, "Invocation of init method failed", ex);
    }
    if (mbd == null || !mbd.isSynthetic(</
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值