基于个人理解的springAOP部分源码分析,内含较多源码,慎入
本文源码较多,讲述一些个人对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部分源码分析,内含较多源码,慎入的更多相关文章
- 死磕 java集合之CopyOnWriteArraySet源码分析——内含巧妙设计
问题 (1)CopyOnWriteArraySet是用Map实现的吗? (2)CopyOnWriteArraySet是有序的吗? (3)CopyOnWriteArraySet是并发安全的吗? (4)C ...
- [转] jQuery源码分析-如何做jQuery源码分析
jQuery源码分析系列(持续更新) jQuery的源码有些晦涩难懂,本文分享一些我看源码的方法,每一个模块我基本按照这样的顺序去学习. 当我读到难度的书或者源码时,会和<如何阅读一本书> ...
- Redis源码分析:serverCron - redis源码笔记
[redis源码分析]http://blog.csdn.net/column/details/redis-source.html Redis源代码重要目录 dict.c:也是很重要的两个文件,主要 ...
- 序列化器中钩子函数源码分析、many关键字源码分析
局部钩子和全局钩子源码分析(2星) # 入口是 ser.is_valid(),是BaseSerializer的方法 # 最核心的代码 self._validated_data = self.run_v ...
- k8s client-go源码分析 informer源码分析(5)-Controller&Processor源码分析
client-go之Controller&Processor源码分析 1.controller与Processor概述 Controller Controller从DeltaFIFO中pop ...
- SpringMVC流程源码分析及DispatcherServlet核心源码
一.源码分析前还是需要一张流程图作为指导,如下: 二.简单介绍以及源码定位 DispatcherServlet其实就是一个HttpServlet,他是HttpServlet的子类,所以它和普通的Htt ...
- MyBatis源码分析-IDEA新建MyBatis源码工程
MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生Map使用简 ...
- nova创建虚拟机源码分析系列之五 nova源码分发实现
前面讲了很多nova restful的功能,无非是为本篇博文分析做铺垫.本节说明nova创建虚拟机的请求发送到openstack之后,nova是如何处理该条URL的请求,分析到处理的类. nova对于 ...
- Java源码分析系列之HttpServletRequest源码分析
从源码当中 我们可以 得知,HttpServletRequest其实 实际上 并 不是一个类,它只是一个标准,一个 接口而已,它的 父类是ServletRequest. 认证方式 public int ...
随机推荐
- linux下 解释 终端命令 ls -al或者ls -li 输出的信息
$ ls -al drwxr-xr-x. wjshan0808 wjshan0808 Sep : .cache $ ls -li ...
- Android 伤敌一千自损八百之萤石摄像头集成(三)
说一下萤石原生播放 先上代码 private MyOrientationDetector mOrientationDetector; @Override protected void onCreate ...
- 确定比赛名次 UDU-1285 + 逃生 UDU 4857 拓扑排序(找不同)
确定比赛名次 题目大意 有N个比赛队(1<=N<=500),编号依次为1,2,3,....,N进行比赛,比赛结束后,裁判委员会要将所有参赛队伍从前往后依次排名,但现在裁判委员会不能直接获得 ...
- springmvc返回接口中long型,页面接收时值却变了
Java序列化JSON时long型数值不准确 现象 项目中用到了唯一ID生成器.生成出的ID时long型的(比如说4616189619433466044).通过某个rest接口中返回json数据后,发 ...
- day36 解决粘包问题
目录 一.tcp粘包问题出现的原因 二.解决粘包问题low的办法 三.egon式解决粘包问题 四.实现并发 1 tcp 2 udp 一.tcp粘包问题出现的原因 前引: tcp的客户端与服务端进行通信 ...
- eShopOnContainers 知多少[11]:服务间通信之gRPC
引言 最近翻看最新3.0 eShopOncontainers源码,发现其在架构选型中补充了 gRPC 进行服务间通信.那就索性也写一篇,作为系列的补充. gRPC 老规矩,先来理一下gRPC的基本概念 ...
- 06 Vue生命周期钩子
生命周期钩子 表示一个vue实例从创建到销毁的这个过程,将这个过程的一些时间节点赋予了对应的钩子函数 钩子函数: 满足特点条件被回调的方法 new Vue({ el: "#app" ...
- 爬虫页面解析 lxml 简单教程
一.与字符串的相互转换 1.字符串转变为etree 对象 import lxml.html tree = lxml.html.fromstring(content) # content 字符串对象 2 ...
- conda install 失败 http404
最近conda install keras出现各种问题,显示配置问你,配置了清华中科大的源,都不行 估计原因是:配置各种源太多,最后全部删除只留一个清华源,成功 暴力方法直接删除C:\Users\Ad ...
- keyring源码加密解密函数分析
Encrypt the page data contents. Page type can't be FIL_PAGE_ENCRYPTED, FIL_PAGE_COMPRESSED_AND_ENCRY ...