帆影cc 发表于 2021-8-25 14:19:27

Spring Aop 源码笔记和源码阅读个人技巧分享

概述

用过spring 框架的小伙伴都知道,aop对spring 的重要性,学习源码必不可少,文本记录一些源码跟踪源码技巧学习心得,需要纠错和改正的请在下方留言


aop 原理大抵分析

这个网上一搜一大堆,重复论述没故意义,说下我个人理解,关键两个字 代理
什么叫代理,和银行一样,你干啥都要经过人家的手,如许只要你钱有变动银行都知道了
spring 也是一样,假设有个类
public class A{        void test(){                system.out.println("test run");        }}我们需要在A中的test() 做个切面
此时spring 会生成一个代理后对象,这个对象才是你现实真正调用的,如许你调用方法时不就形成了切面
可以如许理解
class Proxy$1{        A a;        test(){                a.test();        }}这个也可以看做是静态代理
如果有多个代理,那么就可能如下图所示


https://p26.toutiaoimg.com/large/pgc-image/ae9afbc5c181424e8af61c768458a814


但是这个代码又不是写死的,所以代理就不能是静态的,又称动态代理
spring 中动态生成代理类的方式有两种

[*]cglib


[*]jdk代理
两者具体怎样使用,这个本文不做论述
准备demo

首先是一个spring 入口
@ComponentScan("bean")@EnableAspectJAutoProxypublic class Application {        public static void main(String[] args) {                AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);                ComService bean = context.getBean(ComService.class);                bean.testAop();        }}被代理的对象
@Servicepublic class ComService {        public void testAop(){                System.out.println("===== ComService ====");        }}切面定义
@Aspect@Componentpublic class Aop {        @Pointcut("execution(public * bean.service.ComService.testAop())")        public void businessService() {}        @Around("businessService()")        public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {                // start stopwatch                Object retVal = pjp.proceed();                System.out.println(pjp.getSignature().getName() + " === 切面");                // stop stopwatch                return retVal;        }}源码分析

其实网上如许源码解析的帖子很多,但是都有共同点,都是上来直接告诉你这代码在哪看,然后贴上一些他们的理解注释,但是作为一个刚刚学习spring 或者对spring 源码研究不深地开发小伙伴来说,我想知道你是咋找到这代码的,本文分享我一步一步找到关键源码的地方,可能技巧很low,但是挺实用,大部分框架源码我如果不事先看博客都是这么找到的
这里主要回答两个题目

[*]什么时间解析的切面
项目中我们表示切面是通过在类上加入注解 `@Aspect` 实现的,
那么肯定有代码是解析这个注解的
最简单的方式直接看看哪里调用就完了,我是直接下载了spring 的源码,如许一点就行了


https://p9.toutiaoimg.com/large/pgc-image/cf92f264defc478581d8e3d4781334ae


可以看到 `org.aspectj.internal.lang.reflect.AjTypeImpl#getPerClause` 有get操作
然后再看看这个方法哪里有被调用


https://p9.toutiaoimg.com/large/pgc-image/dd3b6e80fefb402e82da8e122e3eac6b


到这步可以在一些你以为可能的方法中打断点,看看会不会进来(方法很low但是对我如许的小白我以为还挺实用)
// 代码简写了List aspectNames = this.aspectBeanNames;// 这个方法不只调用一次if (aspectNames == null) {List advisors = new ArrayList();aspectNames = new ArrayList();String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(    this.beanFactory, Object.class, true, false);for (String beanName : beanNames) {    ....    // 判断是否有切面注解    // 上文猜测找的类不是这里调用方法,所以找这个代码有点侥幸    if (this.advisorFactory.isAspect(beanType)) {      aspectNames.add(beanName);      AspectMetadata amd = new AspectMetadata(beanType, beanName);      // 这里就会调用我之前猜测的方法      // 下面都会调用一个方法   advisors.addAll(this.advisorFactory.getAdvisors(factory));      if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {       ...      }      else {      ...      }    }}this.aspectBeanNames = aspectNames;return advisors;}怎样生成的代理类

这里找源码有些讨巧,前段时间刚刚学习了下spring bean生命周期的源码,所以直接在`DefaultSingletonBeanRegistry#getSingleton()`
debug,写个断点条件


https://p9.toutiaoimg.com/large/pgc-image/68c5fd8ce2154efc800e003d9bed5c81


然后一点点放行代码,看着返回值obj如果发生了变革那就找到了
`org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean`
到这方法中对象已经初步创建了


https://p26.toutiaoimg.com/large/pgc-image/2eaddc634dd14b6e9eb0bcf674587b89




https://p6.toutiaoimg.com/large/pgc-image/980427c3c41b48e6a1ca98fed04b7403


可以看到 `AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsAfterInitialization` 这行代码之后
这个对象已经不是原来的对象了
找到之后就开始慢慢分析了
Object result = existingBean;for (BeanPostProcessor processor : getBeanPostProcessors()) {        Object current = processor.postProcessAfterInitialization(result, beanName);        if (current == null) {                return result;        }        result = current;}同理也是打断点
可以找到 `AbstractAutoProxyCreator#postProcessAfterInitialization` --> `AbstractAutoProxyCreator#wrapIfNecessary`
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {...        // 创建切面代理        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;}一直往下 `ProxyFactory#getProxy`
// createAopProxy() 这个方法返回了 实现了 AopProxy 对象        return createAopProxy().getProxy(classLoader);`DefaultAopProxyFactory#createAopProxy` 这里决定了生成代理类的方式
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {if (!IN_NATIVE_IMAGE &&      // 是否优化      (config.isOptimize() ||      // 是否直接代理以及是接口       config.isProxyTargetClass() ||       // 判断是否是实现了 org.springframework.aop.SpringProxy 接口的类       hasNoUserSuppliedProxyInterfaces(config))) {    // 需要被代理的类    Class targetClass = config.getTargetClass();    if (targetClass == null) {      throw new AopConfigException();    }    // 如果是接口    if (targetClass.isInterface()         // 自己是否是代理类      || Proxy.isProxyClass(targetClass)) {      return new JdkDynamicAopProxy(config);    }    return new ObjenesisCglibAopProxy(config);}else {    return new JdkDynamicAopProxy(config);}}简单说就是 如果被代理的类是个接口、或者自己就是个代理类那么用jdk代理方式,否则就是Cglib代理
因为jdk只能代理接口
不管是jdk代理方式还是Cglib代理方式,目的都是生层代理类
下面就想知道如果有多个切面怎么办,
`org.springframework.aop.framework.JdkDynamicAopProxy#invoke`


https://p3.toutiaoimg.com/large/pgc-image/17204d34307f4c61b39bdc76bb9ea243


`org.springframework.aop.framework.CglibAopProxy#getProxy(java.lang.ClassLoader)`


https://p3.toutiaoimg.com/large/pgc-image/ce4f8d75ba04466c8ccff4b1bf9bd7fe


在上面的代码中debug 一下就知道一个bean有哪些方法在切面中
感悟

知道了这个aop大抵的流程之后,开发中我们碰到题目就可以有个大概的思路,比如说常见的aop失效题目
大抵概括有两个方面
- 切面没有解析到
- 代理类没有生成
大部分题目都可以通过debug解决,前提是你得知道在哪里debug
最后

欢迎大家在下方留言你们的源码阅读技巧,我也想学习下

通往大数据的地铁 发表于 2021-8-25 19:26:47

转发了

用户名已经存在惹 发表于 2021-8-27 01:12:51

转发了
页: [1]
查看完整版本: Spring Aop 源码笔记和源码阅读个人技巧分享