本文源码较多,讲述一些个人对spring中AOP编程的一个源码分析理解,只代表个人理解,希望能和大家进行交流,有什么错误也渴求指点!!!接下来进入正题

AOP的实现,我认为简单的说就是利用代理模式,对目标方法所在的类进行封装代理。请求目标方法时,是直接请求代理对象,再根据用户指定的通知(切点),在代理对象中进行操作,到了该使用目标方法的时候,调用代理对象中包装的真正目标方法完成,以实现面向切面编程,以下对两个问题进行一个分析:

  • 代理对象什么时候被创建
  • 切面类我们定义的切点信息是怎么加载的
    • 找到在何处进行扫描
    • 探究如何进行扫描

代理对象是什么时候被创建的?

这里是通过name来获取bean的情况,注意使用type的方式来获取bean进行调试,就会和本文有所出入

现在从Main方法出发

//1.创建IOC容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//2.从IOC中获取bean实例
ArithmeticCalculator arithmeticCalculator = (ArithmeticCalculator) ctx.getBean("arithmeticCalculatorImpl");
  • 我们进入getBean的代码查看,发现bean是直接在一个名为singletonObjects的concurrentHashMap(ioc容器)中取出来的,取出时就已经是一个proxy对象了,可以证明,是在初始化实例的时候就创建了

  • 我们知道spring会在程序启动的时候,初始化ioc容器,而对于我们指定的切面类,在初始化实例时,就将找到我们指定的类,对其进行创建代理,代理对象创建后,放置到ioc容器中

  • 具体的流程是在初始化到我们指定的对象时,他会先创建出一个未代理的实例

    该实例会经过一个applyBeanPostProcessorsAfterInitialization方法,7个spring的后置处理器进行遍历,如果该类符合某个后置处理器的条件,则会被后置处理器加载,而我们aop的类会被(AnnotationAwareAspectJAutoProxyCreator)后置处理器处理,如下为7个后置处理器

  • 进入到该后置处理器我们会发现,他会通过判断这个类是否注释了切面编程的标记,如果注释了则进行处理,也就是我们的注解起了作用,这里是经过了AnnotationAwareAspectJAutoProxyCreator后置处理器的处理,以下是AnnotationAwareAspectJAutoProxyCreator处理器的applyBeanPostProcessorsAfterInitialization初始化方法

    //applyBeanPostProcessorsAfterInitialization方法的源码
    public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName) throws BeansException {
    Object result = existingBean; Object current;
    for(Iterator var4 = this.getBeanPostProcessors().iterator(); var4.hasNext(); result = current) {
    BeanPostProcessor processor = (BeanPostProcessor)var4.next();
    //processor=AnnotationAwareAspectJAutoProxyCreator时,会进入postProcessAfterInitialization方法
    current = processor.postProcessAfterInitialization(result, beanName);
    if (current == null) {
    return result;
    }
    }
    return result;
    }
    //进入AnnotationAwareAspectJAutoProxyCreator后置处理器的操作,通过调试我们看到,在wrapIfNecessary方法中进行了代理
    public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
    if (bean != null) {
    Object cacheKey = this.getCacheKey(bean.getClass(), beanName);
    //判断是否需要代理
    if (this.earlyProxyReferences.remove(cacheKey) != bean) {
    //代理
    return this.wrapIfNecessary(bean, beanName, cacheKey);
    }
    }
    //该方法是真正对proxy进行代理的方法
    protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
    return bean;
    } else if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
    return bean;
    } else if (!this.isInfrastructureClass(bean.getClass()) && !this.shouldSkip(bean.getClass(), beanName)) {
    //获取切点信息
    Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, (TargetSource)null);
    if (specificInterceptors != DO_NOT_PROXY) {
    this.advisedBeans.put(cacheKey, Boolean.TRUE);
    //创建了代理对象
    Object proxy = this.createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
    this.proxyTypes.put(cacheKey, proxy.getClass());
    //返回
    return proxy;
    } else {
    this.advisedBeans.put(cacheKey, Boolean.FALSE);
    return bean;
    }
    } else {
    this.advisedBeans.put(cacheKey, Boolean.FALSE);
    return bean;
    }
    }
  • 后置处理器的处理方式,是在一个(postProcessAfterInitialization)方法中进行的处理,通过判断指定是使用jdbc的动态代理创建proxy还是通过cglib,处理后返回到变量中,完成后添加到ioc容器中,完成初始化,之后调用时都是已经代理过的实例,就可以进行切面编程了

//最终是调用了DefaultAopProxyFactory类的createAopProxy来实现代理
//可以看到这里根据一些配置条件来判断我们要创建的代理是jdk动态代理还是cglib
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (!config.isOptimize() && !config.isProxyTargetClass() && !this.hasNoUserSuppliedProxyInterfaces(config)) {
//创建返回
return new JdkDynamicAopProxy(config);
} else {
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.");
} else {
//创建返回
return (AopProxy)(!targetClass.isInterface() && !Proxy.isProxyClass(targetClass) ? new ObjenesisCglibAopProxy(config) : new JdkDynamicAopProxy(config));
}
}
}

也就是说,spring在初始化容器的时候,会为@Aspect注解的类进行一个代理操作,而其中主要起作用的是7个后置处理器中的AnnotationAwareAspectJAutoProxyCreator处理器对其进行了代理

切点信息是如何获取、封装,然后存储在List中的

先找到他在何处进行的扫描

以上的创建代理过程中,我们知道了如下代码是获得了切点信息的一个语句

//该方法中调用的时返回值为List<Advisor>的方法,在返回的时候将其转化为Object[]类型,所以以下代码的返回值就编程List<Advisor>,也说明了Advisor就是封装了pointcut信息的对象
Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, (TargetSource)null);

观察advisorsCache,可以知道,这是一个map集合,显然他以我们@AspectJ注解的类名为key,切点注解的信息集合为值

进入其中,往里追代码,追到以下位置

protected List<Advisor> findCandidateAdvisors() {
List<Advisor> advisors = super.findCandidateAdvisors();
if (this.aspectJAdvisorsBuilder != null) {
//我们可以看到添加Advisor于buildAspectJAdvisors()有关
advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
}

继续往里追,我们看到在buildAspectJAdvisors方法中有这么一句代码

  List<Advisor> cachedAdvisors = (List)this.advisorsCache.get(aspectName);

我们发现只是从advisorsCache这个map中取出来的,也就是在这之前advisorsCache就已经赋值了,于是我们应该追溯advisorsCache对象的初始化,这里利用一个小技巧,当我们要追溯一个map或者其他集合的构建时,可以通过查找关键字即advisorsCache.put这样的方式来追溯到位置,于是我们发现同样在buildAspectJAdvisors方法中我们能看到此处有对advisorsCache的初始化,代码分析如下

//BeanFactoryAspectJAdvisorsBuilder类中的
public List<Advisor> buildAspectJAdvisors() {
//aspectBeanNames拥有我们注释了AspectJ注解的类名
List<String> aspectNames = this.aspectBeanNames;
//我们发现第一获取时,aspectNames为空,会进入一个线程安全的代码块进行扫描,猜测这是一个双重检验实现懒加载,需要的时候才去扫描注解
if (aspectNames == null) {
synchronized(this) {
aspectNames = this.aspectBeanNames;
if (aspectNames == null) {
List<Advisor> advisors = new ArrayList();
List<String> aspectNames = new ArrayList();
//调用了工具来得到所有的beanNames
String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, Object.class, true, false);
String[] var18 = beanNames;
int var19 = beanNames.length;
//遍历所有的bean
for(int var7 = 0; var7 < var19; ++var7) {
String beanName = var18[var7];
//isEligibleBean -- 具备条件
//protected boolean isEligibleBean(String beanName) {
// return AnnotationAwareAspectJAutoProxyCreator.this.isEligibleAspectBean(beanName);
// }
if (this.isEligibleBean(beanName)) {
Class<?> beanType = this.beanFactory.getType(beanName);
//this.advisorFactory.isAspect(beanType)校验是否有这个注解
// public boolean isAspect(Class<?> clazz) {
//return this.hasAspectAnnotation(clazz) && !this.compiledByAjc(clazz);
//}
if (beanType != null && this.advisorFactory.isAspect(beanType)) {
//将类名存储
aspectNames.add(beanName);
//AspectMetadata应该是判断该beanName对应的类有没有一个注解@Aspect,主要是在AspectMetadata的AjType对象的getPerClause()中进行一个判断,如果是true,则new PerClauseImpl(PerClauseKind.SINGLETON)) 返回,则可以通过以下的if判断
AspectMetadata amd = new AspectMetadata(beanType, beanName);
if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {
//MetadataAwareAspectInstanceFactory就是把beanFactory和当前得到的beanName封装在一起
MetadataAwareAspectInstanceFactory factory = new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
//此处得到Advisor结果集,Advisor主要是封装了每个方法对应的连接点
List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
if (this.beanFactory.isSingleton(beanName)) {
//满足条件之后添加到advisorsCache中,
this.advisorsCache.put(beanName, classAdvisors);
} else {
this.aspectFactoryCache.put(beanName, factory);
}
//同时添加到advisors中,满足第一次调用输出
advisors.addAll(classAdvisors);
} else {
if (this.beanFactory.isSingleton(beanName)) {
throw new IllegalArgumentException("Bean with name '" + beanName + "' is a singleton, but aspect instantiation model is not singleton");
} MetadataAwareAspectInstanceFactory factory = new PrototypeAspectInstanceFactory(this.beanFactory, beanName);
this.aspectFactoryCache.put(beanName, factory);
advisors.addAll(this.advisorFactory.getAdvisors(factory));
}}}}
this.aspectBeanNames = aspectNames;
//返回
return advisors;
}
}
if (aspectNames.isEmpty()) {
return Collections.emptyList();
//如果不为空
} else {
//创建一个封装了切点信息的advisor对象集合
List<Advisor> advisors = new ArrayList();
Iterator var3 = aspectNames.iterator(); while(var3.hasNext()) {
String aspectName = (String)var3.next();
//只要不为空,在advisorsCache就能拿到aspectName对应的List<Advisor>,也就是advisorsCache这个map中已经存好了我们的pointcut,只是将他赋给advisors然后返回而已,也即是我们上一步寻找切点信息来源时的代码
List<Advisor> cachedAdvisors = (List)this.advisorsCache.get(aspectName);
if (cachedAdvisors != null) {
advisors.addAll(cachedAdvisors);
} else {
MetadataAwareAspectInstanceFactory factory = (MetadataAwareAspectInstanceFactory)this.aspectFactoryCache.get(aspectName);
advisors.addAll(this.advisorFactory.getAdvisors(factory));
}
}
return advisors;
}
}

也就是说在buildAspectJAdvisors()处,spring是使用了synchronized代码块以及双重校验来保证关于切点信息扫描的代码块在任何情况下都只执行一次,也就是线程安全的,而且在此处是一个懒加载的效果,第一次调用就会进入代码块执行扫描并返回结果,第二次就直接从cachedAdvisors获取了,完成的是扫描所有的bean,会对我们@Aspect注解进行识别,解析,将类中每个切点的类型和切点的目标和方法封装在一起,得到多个切点对象,然后返回

以上为我认为的spring扫描切点信息的源码位置

接下来看看他是怎么扫描的

为了方便以下代码的阅读,我先将封装结构贴在这里,以下为Advisor对切点信息的封装结构:一个Advisor封装的是一个切点,一个bean有多个Advisor

接下来进入代码分析,由上可以知道Advisor对象是封装pointcut信息的,那么我们就追进getAdvisors()方法来看看

//获取Advisor结果集,摘取部分代码我们来看看
public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) {
……………………
//var6为该bean的所有方法,循环得到方法上的注解信息
while(var6.hasNext()) {
Method method = (Method)var6.next();
//传入方法得对象
Advisor advisor = this.getAdvisor(method, lazySingletonAspectInstanceFactory, advisors.size(), aspectName);
if (advisor != null) {
advisors.add(advisor);
}
……………………
}
public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrderInAspect, String aspectName) {
//猜测这是一个判断,检查的或许和Aspect有关
this.validate(aspectInstanceFactory.getAspectMetadata().getAspectClass());
//可以看出来,getPointcut方法就是封装切点信息的,进入这个方法
AspectJExpressionPointcut expressionPointcut = this.getPointcut(candidateAdviceMethod, aspectInstanceFactory.getAspectMetadata().getAspectClass());
return expressionPointcut == null ? null : new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod, this, aspectInstanceFactory, declarationOrderInAspect, aspectName);
}
	private AspectJExpressionPointcut getPointcut(Method candidateAdviceMethod, Class<?> candidateAspectClass) {
//findAspectJAnnotationOnMethod得到方法上的注解信息,也就是我们指定的连接点的处理方法,在这个方法中将aspectJ支持的切面通知类型逐个遍历,看看method中的注解是哪一个,并提取出他的信息,内部代码为:
// A result = AnnotationUtils.findAnnotation(method, toLookFor);
// return result != null ? new AbstractAspectJAdvisorFactory.AspectJAnnotation(result) : null;
// result为 method.getDeclaredAnnotation(annotationType)的返回值,即result封装的是我们在类上的A切点注解,将其作为参数构建出AspectJAnnotation对象并返回,也就是说AspectJAnnotation对象中就包含了切点注解的信息
// 而进入AspectJAnnotation对象的构造器中,我们发觉调用this.pointcutExpression = this.resolveExpression(annotation);完成对excution表达式的解析,内部代码比较容易看懂,这里不做记录
AspectJAnnotation<?> aspectJAnnotation =
AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
if (aspectJAnnotation == null) {
return null;
} else {
AspectJExpressionPointcut ajexp = new AspectJExpressionPointcut(candidateAspectClass, new String[0], new Class[0]);
//在这里设置了pointcut信息,execution表达式在创建aspectJAnnotation的时候已经赋值了
ajexp.setExpression(aspectJAnnotation.getPointcutExpression());
if (this.beanFactory != null) {
ajexp.setBeanFactory(this.beanFactory);
}
//返回包含了一个切点的AspectJExpressionPointcut对象
return ajexp;
}
}

受本人水平有限,关于扫描只能分析到这里,之后会继续更新,有错误希望大家可以评论指点,谢谢大佬~~

基于个人理解的springAOP部分源码分析,内含较多源码,慎入的更多相关文章

  1. 死磕 java集合之CopyOnWriteArraySet源码分析——内含巧妙设计

    问题 (1)CopyOnWriteArraySet是用Map实现的吗? (2)CopyOnWriteArraySet是有序的吗? (3)CopyOnWriteArraySet是并发安全的吗? (4)C ...

  2. [转] jQuery源码分析-如何做jQuery源码分析

    jQuery源码分析系列(持续更新) jQuery的源码有些晦涩难懂,本文分享一些我看源码的方法,每一个模块我基本按照这样的顺序去学习. 当我读到难度的书或者源码时,会和<如何阅读一本书> ...

  3. Redis源码分析:serverCron - redis源码笔记

    [redis源码分析]http://blog.csdn.net/column/details/redis-source.html   Redis源代码重要目录 dict.c:也是很重要的两个文件,主要 ...

  4. 序列化器中钩子函数源码分析、many关键字源码分析

    局部钩子和全局钩子源码分析(2星) # 入口是 ser.is_valid(),是BaseSerializer的方法 # 最核心的代码 self._validated_data = self.run_v ...

  5. k8s client-go源码分析 informer源码分析(5)-Controller&Processor源码分析

    client-go之Controller&Processor源码分析 1.controller与Processor概述 Controller Controller从DeltaFIFO中pop ...

  6. SpringMVC流程源码分析及DispatcherServlet核心源码

    一.源码分析前还是需要一张流程图作为指导,如下: 二.简单介绍以及源码定位 DispatcherServlet其实就是一个HttpServlet,他是HttpServlet的子类,所以它和普通的Htt ...

  7. MyBatis源码分析-IDEA新建MyBatis源码工程

    MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生Map使用简 ...

  8. nova创建虚拟机源码分析系列之五 nova源码分发实现

    前面讲了很多nova restful的功能,无非是为本篇博文分析做铺垫.本节说明nova创建虚拟机的请求发送到openstack之后,nova是如何处理该条URL的请求,分析到处理的类. nova对于 ...

  9. Java源码分析系列之HttpServletRequest源码分析

    从源码当中 我们可以 得知,HttpServletRequest其实 实际上 并 不是一个类,它只是一个标准,一个 接口而已,它的 父类是ServletRequest. 认证方式 public int ...

随机推荐

  1. 服务消费者(Ribbon)

    上一篇文章,简单概述了服务注册与发现,在微服务架构中,业务都会被拆分成一个独立的服务,服务之间的通讯是基于http restful的,Ribbon可以很好地控制HTTP和TCP客户端的行为,Sprin ...

  2. 打包发布 Qt Quick/Widgets 程序

    使用的QT自带的部署工具(windeployqt.exe,路径QT安装路径),版本替换debug/release Qt Quick "C:\Qt\Qt5.8.0\5.8\mingw53_32 ...

  3. 记录下 rhel 7 安装MySQL 并重置root密码

    注意官方是很不提倡用root的. 下载并安装MySQL 最新的rpm地址 https://dev.mysql.com/downloads/repo/yum/ #wget https://repo.my ...

  4. 常用API - 字符串

    String类 java.lang.String类代表字符串 Java 程序中的所有字符串字面值(如 "abc" )都作为此类的实例实现. 特点 字符串的内容不可变!! 因为 St ...

  5. PreparedStatement 防止sql注入 练习

    使用的数据库 MariaDB 10.5.4版本   端口1054     数据库为jt_db,表 为user 数据库的建表和插入相关数据代码: create table user( id int pr ...

  6. beautiful numbers树形dp or 数位dp

    题目找链接 题意: 如果数a能被a中的每一位数整除(0除掉),则称a是一个beautiful number,求一个区间内的beautiful numbers的个数. 分析: 首先,很显然,l到r的所有 ...

  7. Jmeter(十五) - 从入门到精通 - JMeter导入自定义的Jar包(详解教程)

    1.简介 原计划这一篇是介绍前置处理器的基础知识的,结果由于许多小伙伴或者童鞋们在微信和博客园的短消息中留言问如何引入自己定义的Jar包呢???我一一回复告诉他们和引入插件的Jar包一样的道理,一通百 ...

  8. LoadLibraryA与GetProcAddress介绍

    0x00 函数原型 HMODULE LoadLibraryA(     LPCTSTR lpLibFileName//模块的的的名字 ) FARPROC GetProcAddress( HMODULE ...

  9. 选择困难症必看!云服务器如何选择操作系统,Windows和Linux哪个更好?

    在购买云服务器时,会有一个必选的配置,就是操作系统的选择,如何选择操作系统?操作系统选择错了怎么办?这是不少用户会遇到的问题,今天我们就来教大家如何选择操作系统,以及操作系统选择错了,该怎么切换. W ...

  10. Mysql常用sql语句(24)- delete 删除数据

    测试必备的Mysql常用sql语句系列 https://www.cnblogs.com/poloyy/category/1683347.html 前言 delete 也属于DML语句(数据操纵语句) ...