帆影cc 潜水
  • 1发帖数
  • 1主题数
  • 0关注数
  • 0粉丝
开启左侧

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

[复制链接]
帆影cc 发表于 2021-8-25 14:19:27 | 显示全部楼层 |阅读模式 打印 上一主题 下一主题
概述

用过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();        }}这个也可以看做是静态代理
如果有多个代理,那么就可能如下图所示



                               
登录/注册后可看大图



但是这个代码又不是写死的,所以代理就不能是静态的,又称动态代理
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 的源码,如许一点就行了



                               
登录/注册后可看大图



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



                               
登录/注册后可看大图



到这步可以在一些你以为可能的方法中打断点,看看会不会进来(方法很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,写个断点条件



                               
登录/注册后可看大图



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



                               
登录/注册后可看大图






                               
登录/注册后可看大图



可以看到 `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`



                               
登录/注册后可看大图



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



                               
登录/注册后可看大图



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

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

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

精彩评论2

通往大数据的地铁 发表于 2021-8-25 19:26:47 | 显示全部楼层
转发了
用户名已经存在惹 发表于 2021-8-27 01:12:51 | 显示全部楼层
转发了
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

猜你喜欢
在线客服邮箱
wxcy#wkgb.net

邮箱地址#换为@

Powered by 创意电子 ©2018-现在 专注资源实战分享源码下载站联盟商城