摘要:何为简单点来定义就是切面,是一种编程范式。定义一个切面的载体定义一个切点定义一个为,并指定对应的切点一个注册配置类,启动容器,初始化时期获取对象,获取对象时期,并进行打印好了,这样我们整体的代理就已经完成。
问题:Spring AOP代理中的运行时期,是在初始化时期织入还是获取对象时期织入?
织入就是代理的过程,指目标对象进行封装转换成代理,实现了代理,就可以运用各种代理的场景模式。何为AOP
简单点来定义就是切面,是一种编程范式。与OOP对比,它是面向切面,为何需要切面,在开发中,我们的系统从上到下定义的模块中的过程中会产生一些横切性的问题,这些横切性的问题和我们的主业务逻辑关系不大,假如不进行AOP,会散落在代码的各个地方,造成难以维护。AOP的编程思想就是把业务逻辑和横切的问题进行分离,从而达到解耦的目的,使代码的重用性、侵入性低、开发效率高。
AOP使用场景日志记录;记录调用方法的入参和结果返参。
用户的权限验证;验证用户的权限放到AOP中,与主业务进行解耦。
性能监控;监控程序运行方法的耗时,找出项目的瓶颈。
事务管理;控制Spring事务,Mysql事务等。
AOP概念点 AOP和Spring AOP的关系在这里问题中,也有一个类似的一对IOC和DI(dependency injection)的关系,AOP可以理解是一种编程目标,Spring AOP就是这个实现这个目标的一种手段。同理IOC也是一种编程目标,DI就是它的一个手段。
SpringAOP和AspectJ是什么关系在Spring官网可以看到,AOP的实现提供了两种支持分别为@AspectJ、Schema-based AOP。其实在Spring2.5版本时,Spring自己实现了一套AOP开发的规范和语言,但是这一套规范比较复杂,可读性差。之后,Spring借用了AspectJ编程风格,才有了@AspectJ的方式支持,那么何为编程风格。
Annotation注解方式;对应@AspectJ
JavaConfig;对应Schema-based AOP
SpringAOP和AspectJ的详细对比,在之后的章节会在进行更加详细的说明,将会在他们的背景、织入方法、性能做介绍。
Spring AOP的应用阅读官网,是我们学习一个新知识的最好途径,这个就是Spring AOP的核心概念点,跟进它们的重要性,我做了重新的排序,以便好理解,这些会为我们后续的源码分析起到作用。
Aspect:切面;使用@Aspect注解的Java类来实现,集合了所有的切点,做为切点的一个载体,做一个比喻就像是我们的一个数据库。
Tips:这个要实现的话,一定要交给Spirng IOC去管理,也就是需要加入@Component。
Pointcut:切点;表示为所有Join point的集合,就像是数据库中一个表。
Join point:连接点;俗称为目标对象,具体来说就是servlet中的method,就像是数据库表中的记录。
Advice:通知;这个就是before、after、After throwing、After (finally)。
Weaving:把代理逻辑加入到目标对象上的过程叫做织入。
target:目标对象、原始对象。
aop Proxy:代理对象 包含了原始对象的代码和增加后的代码的那个对象。
TipsSpringAOP源码分析
这个应用点,有很多的知识点可以让我们去挖掘,比如Pointcut中execution、within的区别,我相信你去针对性搜索或者官网都未必能有好的解释,稍后会再专门挑一个文章做重点的使用介绍;
为了回答我们的一开始的问题,前面的几个章节我们做了一些简单的概念介绍做为铺垫,那么接下来我们回归正题,正面去切入问题。以码说话,我们以最简洁的思路把AOP实现,我们先上代码。
项目结构介绍
项目目录结构,比较简单,5个主要的文件;
pom.xml核心代码;spring-content是核心jar,已经包含了spring所有的基础jar,aspectjweaver是为了实现AOP。
AppConfig.java;定义一个Annotation,做为我们Spirng IOC容器的启动类。
package com.will.config; @Configuration @ComponentScan("com.will") @EnableAspectJAutoProxy(proxyTargetClass = false) public class AppConfig { }
WilAspect.java ;按照官网首推的方式(@AspectJ support),实现AOP代理。
package com.will.config; /** * 定义一个切面的载体 */ @Aspect @Component public class WilAspect { /** * 定义一个切点 */ @Pointcut("execution(* com.will.dao.*.*(..))") public void pointCutExecution(){ } /** * 定义一个Advice为Before,并指定对应的切点 * @param joinPoint */ @Before("pointCutExecution()") public void before(JoinPoint joinPoint){ System.out.println("proxy-before"); } }
Dao.java
package com.will.dao; public interface Dao { public void query(); }
UserDao.java
package com.will.dao; import org.springframework.stereotype.Component; @Component public class UserDao implements Dao { public void query() { System.out.println("query user"); } }
Test.java
package com.will.test; import com.will.config.AppConfig; import com.will.dao.Dao; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class Test { public static void main(String[] args) { /** * new一个注册配置类,启动IOC容器,初始化时期; */ AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(AppConfig.class); /** * 获取Dao对象,获取对象时期,并进行query打印 */ Dao dao = annotationConfigApplicationContext.getBean(Dao.class); dao.query(); annotationConfigApplicationContext.start(); } }
好了,这样我们整体的AOP代理就已经完成。
问题分析测试究竟是哪个时期进行对象织入的,比如Test类中,究竟是第一行还是第二行进行织入的,我们只能通过源码进行分析,假如是你,你会进行如何的分析源码解读。
Spring的代码非常优秀,同时也非常复杂,那是一个大项目,里面进行了很多的代码封装,那么的代码你三天三夜也读不完,甚至于你都不清楚哪一行的该留意的,哪一行是起到关键性作用的,这里教几个小技巧。
看方法返回类型;假如是void返回类型的,看都不看跳过。返回结果是对象,比如T果断进行去进行跟踪。
假设法;就当前场景,我们大胆假设是第二行进行的织入。
借助好的IDE;IDEA可以帮我们做很多的事情,它的debug模式中的条件断点、调用链(堆栈)会帮助到我们。
假设法源码分析debug模式StepInfo(F5)后,进入 AbstractApplicationContext.getBean方法,这个是Spring应用上下文中最重要的一个类,这个抽象类中提供了几乎ApplicationContext的所有操作。这里第一个语句返回void,我们可以直接忽略,看下面的关键性代码。
继续debug后,会进入到 DefaultListableBeanFactory 类中,看如下代码
return new NamedBeanHolder<>(beanName, getBean (beanName, requiredType, args));
在该语句中,这个可以理解为 DefaultListableBeanFactory 容器,帮我们获取相应的Bean。
进入到AbstractBeanFactory类的doGetBean方法之后,我们运行完。
Object sharedInstance = getSingleton(beanName);
语句之后,看到 sharedInstance 对象打印出&Proxyxxx ,说明在getSingleton 方法的时候就已经获取到了对象,所以需要跟踪进入到 getSingleton 方法中,继续探究。
不方便不方便我们进行问题追踪到这个步骤之后,我需要引入IDEA的条件断点,不方便我们进行问题追踪因为Spring会初始化很多的Bean,我们再ObjectsharedInstance=getSingleton(beanName);加入条件断点语句。
继续debug进入到DefaultSingletonBeanRegistry的getSingleton方法。
我们观察下执行完ObjectsingletonObject=this.singletonObjects.get(beanName); 之后的singletonObject已经变成为&ProxyUserDao,这个时候Spring最关键的一行代码出现了,请注意这个this.singletonObjects。
this.singletonObjects就是相当IOC容器,反之IOC容器就是一个线程安全的线程安全的HashMap,里面存放着我们需要Bean。
我们来看下singletonObjects存放着的数据,里面就有我们的UserDao类。
这就说明,我们的初始化的时期进行织入的,上图也有整个Debug模式的调用链。
源码深层次探索通过上一个环节已经得知是在第一行进行初始化的,但是它在初始化的时候是什么时候完成织入的,抱着求知的心态我们继续求证。
还是那个问题,那么多的代码,我的切入点在哪里?
既然singletonObjects是容器,存放我们的Bean,那么找到关键性代码在哪里进行存放(put方法)就可以了。于是我们通过搜索定位到了。
我们通过debug模式的条件断点和debug调用链模式,就可以进行探索。
这个时候借助上图中的调用链,我们把思路放到放到IDEA帮我定位到的两个方法代码上。
DefaultSingletonBeanRegistry.getSingleton
我们一步步断点,得知,当运行完singletonObject=singletonFactory.getObject();之后,singletonObject已经获得了代理。
至此我们知道,代理对象的获取关键在于singletonFactory对象,于是又定位到了AbstractBeanFactorydoGetBean方法,发现singletonFactory参数是由createBean方法创造的。这个就是Spring中IOC容器最核心的地方了,这个代码的模式也值得我们去学习。
sharedInstance = getSingleton(beanName, () -> { try { return createBean(beanName, mbd, args); } catch (BeansException ex) { // Explicitly remove instance from singleton cache: It might have been put there // eagerly by the creation process, to allow for circular reference resolution. // Also remove any beans that received a temporary reference to the bean. destroySingleton(beanName); throw ex; } });
这个第二个参数是用到了jdk8中的lambda,这一段的含义是就是为了传参,重点看下 createBean(beanName,mbd,args);代码。随着断点,我们进入到这个类方法里面。
AbstractAutowireCapableBeanFactory.createBean中的;
ObjectbeanInstance=doCreateBean(beanName,mbdToUse,args)方法;
doCreateBean方法中,做了简化。
Initialize the bean instance. Object exposedObject = bean; try { populateBean(beanName, mbd, instanceWrapper); exposedObject = initializeBean(beanName, exposedObject, mbd); } ... return exposedObject;
当运行完 exposedObject=initializeBean(beanName,exposedObject,mbd);之后,我们看到exposedObject已经是一个代理对象,并执行返回。这一行代码就是取判断对象要不要执行代理,要的话就去初始化代理对象,不需要直接返回。后面的initializeBean方法是涉及代理对象生成的逻辑(JDK、Cglib),后续会有一个专门的章节进行详细介绍。
总结通过源码分析,我们得知,Spring AOP的代理对象的织入时期是在运行Spring初始化的时候就已经完成的织入,并且也分析了Spring是如何完成的织入。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/73659.html
摘要:在写完容器源码分析系列文章中的最后一篇后,没敢懈怠,趁热打铁,花了天时间阅读了方面的源码。从今天开始,我将对部分的源码分析系列文章进行更新。全称是,即面向切面的编程,是一种开发理念。在中,切面只是一个概念,并没有一个具体的接口或类与此对应。 1. 简介 前一段时间,我学习了 Spring IOC 容器方面的源码,并写了数篇文章对此进行讲解。在写完 Spring IOC 容器源码分析系列...
摘要:一以及术语是的简称,被译为面向切面编程。切面由切点和增强组成,他包括了连接点定义和横切逻辑代码的定义,就是负责实施切面的框架。五使用来定义纯粹的切面使用方法也非常简单,使用的标签。采用动态代理和动态代理技术在运行期间织入。 引言 AOP是软件开发思想发展到一定阶段的产物,AOP的出现并不是为了代替OOP,仅作为OOP的有益补充,在下面的例子中这个概念将会得到印证。AOP的应用场合是受限...
摘要:关于依赖注入注入的注解提供的注解不仅仅是对象,还有在构造器上,还能用在属性的方法上。与之相反,的限定符能够在所有可选的上进行缩小范围的操作,最终能够达到只有一个满足所规定的限制条件。注解是使用限定符的主要方式。 本文首发于泊浮目的专栏:https://segmentfault.com/blog... Spring致力于提供一种方法管理你的业务对象。在大量Java EE的应用中,随处可...
摘要:如果依赖靠构造器方式注入,则无法处理,直接会报循环依赖异常。光继承这个接口还不够,继承这个接口只能获取,要想让生效,还需要拿到切面对象包含和才行。有了目标对象,所有的切面类,此时就可以为生成代理对象了。 Spring 是一个轻量级的 J2EE 开源框架,其目标是降低企业级应用开发难度,提高企业级应用开发效率。在日程开发中,我们会经常使用 Spring 框架去构建应用。所以作为一个经常使...
摘要:值得一提的是由于采用动态创建子类的方式生成代理对象,所以不能对目标类中的方法进行代理。动态代理中生成的代理类是子类,调试的时候可以看到,打开源码可看到实现了和也就实现方法。 前面讲到了动态代理的底层原理,接下来我们来看一下aop的动态代理.Spring AOP使用了两种代理机制:一种是基于JDK的动态代理,一种是基于CGLib的动态代理. ①JDK动态代理:使用JDK创建代理有一个限制...
阅读 3188·2021-11-24 10:30
阅读 1312·2021-09-30 09:56
阅读 2384·2021-09-07 10:20
阅读 2596·2021-08-27 13:10
阅读 697·2019-08-30 11:11
阅读 2049·2019-08-29 12:13
阅读 757·2019-08-26 12:24
阅读 2896·2019-08-26 12:20