AOC介绍
说明:
(1) 【Spring AOP面向切面编程技术】:Spring提供的一种可插拔的组件技术;
(2) 【Spring AOP面向切面编程技术】:对于程序来说,相当于是一个额外增加的外挂;
(3) 本篇博客,仅仅介绍了Spring AOP的概念性内容,不涉及具体细节;即本篇博客的目的,仅仅是理解Spring AOP的大体作用;
【Spring AOP面向切面编程】部分,包括以下三部分内容;
【Spring AOP面向切面编程技术】结合场景的分析
1.【Spring AOP面向切面编程】的一个引入案例
(1)传统的做法:
说明阐述:
(1)如上图所示,,某个项目中有两个模块,可以认为这个两个模块是相对独立的;假如,此时要给这两个模块添加权限过滤的功能,即,只有拥有权限的用户才能够访问执行对应的模块;
(2)传统的做法,在两个模块的代码中去增加有关权限控制的代码(自然,增加的权限控制代码的位置,是在软件模块的实际代码前面;因为,只有执行了权限控制代码,并且发现权限OK的情况下,才会去执行软件模块中具体的业务代码);
(3)传统的做法,自然可以实现上述需求。但是,假如后来我们又不需要权限过滤的功能了,此时应该怎么办?:按照传统的做法是,需要修改源代码,把权限过滤的代码给去掉;
(4)可以感觉到,传统的做法,需要频繁修改业务逻辑源代码,而且同一个权限过滤代码需要在不同的模块中都写一遍;挺不好的;
(5)和【传统的做法】相比,【Spring AOP面向切面编程技术】是一种更好的解决策略;
(2)基于【Spring AOP面向切面编程技术】的做法:
说明阐述:
(0)所谓面向切面编程,是指在执行前或者执行后,都可以去额外的增加扩展功能;这个扩展功能就称之为切面;
(1)如上图,在软件模块执行前,增加了权限过滤的切面;如果一个用户拥有访问模块A或模块B的权限,那么就可以向下执行模块A或模块B;如果一个用户没有权限,那么这个权限切面,就会把用户挡在外边,不能去访问模块A或模块B;即,此时,对于这个权限切面来说,其起的作用就是拦截功能;
(2)如上图,在程序运行完之后,可以增加日志切面;日志切面是对当前软件运行过程中【什么时间运行的、参数是什么、结果是什么】作记录的,方便程序的调试和跟踪;
(3)权限切面和日志切面,对于模块A或模块B来说都是额外附带的外挂;模块A或模块B在运行时,也感觉不到权限切面或日志切面的存在;
(4)假如后来我们不需要权限过滤的功能和日志功能了,此时应该怎么办?:只需要在配置文件中进行简单的调整,就能把权限切面和日志切面,从当前系统中给移除;
2.【Spring AOP面向切面编程】分析
(1) 【Spring AOP面向切面编程技术】有点类似于日常浏览器中安装的各种插件;比如,QQ浏览器中的翻译插件,该插件可以实时将网页中的英文转换为中文;如果以后我们不需要这个功能了,就可以把这个插件给卸掉;在这个过程中,对于网页本身来说,其是感受不到翻译插件的存在的。即,之所以举翻译插件的例子,其目的就是为了说明如下内容:对于【Spring AOP面向切面编程技术】,可以将其看成是【在原有业务模块软件功能的基础上,额外的增加了一些插件】在实际开发时,这些插件可以根据需要与否,随时拔插;
(2) 为什么【Spring AOP面向切面编程技术】编写的这些外挂式的扩展空能,被称为Aspect(切面)嘞?:如下图,这些【Spring AOP面向切面编程技术】的东西,就像是横切面一样,穿插在了原始的软件模块的运行过程中;其为原始的业务代码提供了额外的扩展。所以将【Spring AOP技术,称为面向切面编程技术】。(个人感觉,之所称之为切面,似乎可以这样理解下:是因为基于Spring AOP开发的程序,没有陷入到程序原本的逻辑代码的泥淖中,换句话说其和程序原本的逻辑代码耦合性几乎为零;基于Spring AOP开发的程序,横行在工程中,哪儿需要这个功能其就可以横扫到哪儿,横扫到哪儿就在哪儿起作用,)
Spring AOP简介
(1) AOP将通用的、与业务无关的功能抽象分装为切面类,再通过配置的方式加入到当前系统中;
(2) 【Spring AOP面向切面编程技术】真正的目的是,在不修改源代码的情况下,对程序行为进行扩展;
初识SpringAOP
说明:
(1) 【Spring AOP面向切面编程】具体的使用场景、使用习惯等内容,还需要慢慢积累;因为目前觉得,究竟什么时候使用这个技术,对于哪些或哪类功能适合使用这个技术,还不太清除。这是个需要后面加强和提升的能力!
(2) 本篇博客的【一:准备一个…】,【二:需求说明】,【三:正式编码…】部分都是些准备工作,不是重点啦,可以快速浏览过去;【四:正式实现…】是本博客的核心;
(3) AOP必须在IoC的基础上才能起作用!!!
(4) 本篇博客仅仅是一个入门性质的介绍,目的是AOP初体验;关于AOP的详细内容,在后面介绍;
一:准备一个工程,演示用
为了演示,导入示例工程s01(这个工程在WorkSpace的aop目录下):
其中预置了readme.md,EmployeeDao,UserDao,EmployeeService,EmployeeDao;
(1)readme.md文档:
(2)EmployeeDao类:
说明:
(1) 这个类模拟了,操作数据中的Employee员工表; 其中添加了一个示意性的方法,向Employee员工表插入一条数据;
(3)UserDao类:
说明:
(1) 这个类模拟了,操作数据中User用户表; 其中添加了一个示意性的方法,向User用户表插入一条数据;
(4)EmployeeService类:
说明:
(1) EmployeeService主要是处理员工入职的逻辑,其中会调用EmployeeDao中的方法;
(2) 然后,其中的EmployeeDao属性,也生成了get和set方法;
(5)UserService类:
说明:
(1) 代码分析
(2) 然后,其中的UserDao属性,也生成了get和set方法;
二:需求说明
(1)需求:
无论是Service类中的方法,还是Dao类中的方法,希望在方法执行前,打印出各自执行的时间。通过这些时间信息,可以了解到在一天之中的哪个时段,是应用程序负载最高的时刻。
(2)直接想法(这也是传统的做法):
问题分析:
(1)工程中的类很多,类中的方法更多;一个一个的去添加打印时间的代码,很麻烦,效率很低;
(2)这种方式,为了添加打印时间的方法,而直接去修改源代码;万一某天不需要打印时间的功能了,难道还要手动去修改源代码去删除这些打印时间的代码吗?
(3)即,上面的方式中,【实现打印时间功能的代码】和【程序中原有的代码】深度耦合!
(4)而且,上面为了实现【打印时间功能】,其实现效率是很低的;而且也很容易出错;
所以,如果对Spring比较了解的话,完全可以使用Spring中的AOP技术;利用Spring AOP来实现这个功能。
(3)利用【Spring AOP技术】去解决:
说明:
(1) 如上,使用【Spring AOP技术】去解决这个问题:像这种【不用修改源代码,而去对原有程序进行扩展的技术】,就是Spring AOP了;
三:正式编码前的准备
(1)pom.xml:引入所需依赖:【spring-context模块】,【aspectjweaver模块】
(1) 引入【spring-context模块】,这个过程会自动引入【spring-aop模块】
其中,有个问题,【spring-context】引入5.3.9版本会出错,这其中的原因还不清楚;(我估计,因为这个项目是别人构建的,可能在当前工程中已经设置了aop的一些内容,而这些已经设置的东西可能包含涉及版本的内容;
(2) 引入【spring-aop模块】的底层依赖【aspectjweaver模块】
(2)创建applicationContext.xml配置文件:引入【默认命名空间】,【context命名空间】,【aop命名空间】;配置IoC容器中的bean;
说明:
(1) 引入了以前没遇到过的【aop命名空间】;
(2) 配置IoC容器中的bean对象; (即UserDao类,EmployeeDao类,UserService类,EmployeeService类的对象啦)
(3)创建SpringApplication类:为了能让IoC容器运行起来;
说明:
(1) 运行结果:通过运行结果,能够知道,目前的工作一切顺利,没有什么问题;
四:正式实现:使用【Spring AOP技术】实现【二:需求说明】中的需求;
(1)创建容纳切面类的包:aspect包;添加针对方法的切面类:MethodAspect类;
MethodAspect类:
说明:
(1) 【MethodAspect切面类】中添加了【切面方法,printExecutionTime()方法】;
(2) 切面类内容分析;
补充:特地说明:能够感受到,对于【能跑通Spring AOP】这个事来说,【joinPoint.getTarget()…】和【joinPoint.getSignature()…】这两条语句并不是必需的。即,这儿我们之所以要添加这两条语句,纯粹是为了满足业务需求,因为我们在打印时间的时候,顺带打印类名和方法名的话会更好。即使如此,JoinPoint参数还是十分重要的,其可以获取目标类和目标方法的信息,能够预见,以后在实际开中,为了满足业务需求,肯定会大量的使用到JoinPoint参数的。
(3) 可以发现,这个目标类和目标方法,仅仅是定义了一个逻辑;目前看来,这个逻辑仅仅完成了打印【时间+目标类名+目标方法名】的功能;即,目前看来,这个目标类和目标方法,不为特定的其他类或文件服务,即这个MethodAspect切面类,似乎可以看成是一个面向大众服务的Utils类;
至此,切面类(及其中的切面方法)写好之后,Spring AOP并不知道,【MethodAspect切面类】中添的【printExecutionTime()切面方法】究竟应用到哪些类的哪些方法上啊?(即,在切面类中我们定义了【打印时间的方法】,,,那么,我们究竟是想在工程中的哪些类的哪些方法执行前或执行后打印时间嘞?)为此,就要在applicationContext.xml中配置了;
(2)配置applicationContext.xml:配置究竟在【哪些类的哪些方法上】应用【MethodAspect切面类】;
我们接下来,需要在applicationContext.xml中,利用aop配置,说明这个MethodAspect切面类作用的范围是什么;
说明:
(1) AOP底层运行,也是需要在IoC容器中的;所以,对于MethodAspect切面类,也是需要实例化个对象到IoC容器中的;
(2) 接下来,就是使用【aop命名空间】中的标签,去配置一些东西了;
即基本内容是:【通过IoC配置的方式,配置切面类的bean】 ,【定义切点,划定一个类和方法的范围】,【指定,在刚在我们划定的范围上,作用哪个切面类】
(3)运行效果:还是沿用上面的SpringApplication入口类,没有作任何更改;
说明:
(1) 运行效果:
(2) 如果,我们又不需要打印时间了,此时的做法很简单:
由此,可以感觉到【Spring AOP】特别像一个插件,需要用的时候,就在applicationContext.xml中配置上;不需要用了,就把配置信息删除或者注释掉;即插即用,很方便;
(4)Summary;
●【Spring AOP】的整个运行过程,是建立在【Spring IoC】基础上的;没有IoC就没有AOP;
● 【Spring AOP】的使用过程:在pom.xml中引入依赖 → 在IoC容器中配置bean → 编写切面类和切面方法 → 在applicationContext.xml中配置切面类的bean → 配置切点 → 将【切点中划定的类和方法】和【切面类和切面方法】做一个匹配;
● 切面类和切面方法中,定义的是,对原先bean扩展的功能(扩展二字,理解的还不够);如下图所示:
● 这一条十分重要: 经过实测:那些将要被作用切面类或切面方法的类,都需要在IoC容器中配置对应的bean;否则【Spring AOP】会不起作用的;其基本思想还是,AOP必须在IoC的基础上才能起作用;
比如下面的错误例子:
即,还是那句话,【AOP必须在IoC的基础上才能起作用】;
AOC关键概念
说明: 上文初体验已经介绍过了,所以本篇博客的内容可能看起来稍显重复和啰嗦;
一:【Spring AOP】和【AspectJ】的关系
说明:
(1) AspectJ是Eclipse提供的;AspectJ有一套完整的体系,可以在运行时实现【AOP面向切面编程】的理念;
(2) 【Spring AOP】在底层依赖【AspectJweaver】实现了【类和方法的匹配】;
(3) 但是,【Spring AOP】并不是所有的东西都是使用AspectJ来做的;上面使用AspectJWeaver圈定了范围后,接下来【使用切面类的对目标类进行功能扩展】的任务,在底层就是Spring AOP自己实现的,其核心原理是代理模式;
二:【Spring AOP】几个关键概念
说明:
(1) Aspect:切面本质上就是一个标准的类;然后,如果有多个切面类的话,单个切面类最好实现某一个功能,即切面类最好也要遵守单一职责原则;
(2) Target Class/Method:目标类/目标方法;指的是【真正要执行、与业务相关的类和方法】:对于切面,就是对这些目标方法进行扩展和增强的; (这一项,决定了做什么事情。 )
(3) PointCut:切点(切入点);execution表达式,划定切面作用的范围;每一个切点,我们都需要给其设置一个id;(这一项,决定了在什么地方。 )
(4) JoinPoint:在切面运行过程中,JointPoint包含了目标类/目标方法的元数据对象;(元数据对象就是,描述这些目标类和目标方法的信息)
(5) Advice:通知;这个决定了在什么时间执行切面类中的切面方法;( 这一项,决定了在什么时间。 )
三:AOP配置过程
说明:
(1)第一步: 依赖AspectJ:
(2)第二步: 实现切面类/方法:
(3)第三步: 配置Aspect Bean:即配置切面类
(4)第四步: 定义PointCut切点:划定切面类的作用范围
(5)第五步: 配置Advice通知;
补充:applicationContext.xml中的【命名空间和对应约束】的来源:
即,Spring官网文档的地址为:【Core Technologies】
JoinPoint连接点对象
说明: (1) 一定注意,【这儿有一个类A,然后我们定义了一个切面类B,然后通过AOP配置,把切面类B作用到A类上;;那么A类一定要在IoC容器中配置bean】;还是那句话,AOP必须在IoC的基础,才OK;
(2) 强调说明:【Object[] getArgs():获取目标方法的参数】在程序调试和观测时,是非常重要的;
一:JoinPoint连接对象简介
JoinPoint连接点,用于获取目标类/目标方法的相关信息;
(1) Object getTarget():获取 IoC容器内的目标对象;得到了目标对象,就可以得到对应的类;(由这一条,再一次印证,AOP必须以IoC为基础)
(2) Signature getSignature() :获取目标对象中要执行的目标方法;
(3) Object[] getArgs():获取目标方法的参数;
二:案例
MethodAspect切面类;
说明:
(1) JoinPoint常用的三个方法示例;
SpringApplication入口测试类;
说明:
(1) UserService类的generateRandomPassword()方法,如下所示:
(2) 运行结果
强调说明:【Object[] getArgs():获取目标方法的参数】在程序调试和观测时,是非常重要的;
因为,在项目管理过程中,很多线上的项目,在进行跟踪调试时,如果想知道某个方法输入的参数什么,那么就可以在当前系统中增加一个这样的切面,然后通过log4j或者logback这样的日志组件,输出这些信息;
自然,在这个日志输出的过程中要保证安全,像用户名和密码这些参数就不能直接输出。然后,有关日志的内容可以参考【MyBatis进阶一:MyBatis日志管理;(【如何输出日志到日志文件中】待补充……)】中的内容,但仅仅这篇博客是不够的,在后续要专门去研究一下在复杂系统如何管理日志;
PointCut切点表达式
一:切点表达式分析;
说明:(根据上图中的红色箭头,依次描述)
(1) 【public】:方法修饰符;因为,大部分目标方法都是public修饰的,所以public可以省略不写;不写public的时候,该位置的默认值就是public;
(2) 【*】:方法如果不需要返回值,就写void;如果需要返回值,就写对应的返回值类型;如果需不需要返回值都可以,就写*通配符;
(3) 【com.imooc】:切点所描述的范围;这儿意思是,只能是com.imooc包下的;
(4) 【..】:包通配符;这儿意思是,只要隶属于com.imooc包下的类,无论是如com.imooc.UserDao类,还是如com.imooc.aop.UserDao的类都在范围内;
(5) 【*】:对应了类名;*通配符,表示任意类都可以;
(6) 【*】:*通配符,表示任意方法;
(7) 【..】:参数通配符,表示去匹配任意形式、不限个数、不限类型的方法;(如,方法重构时,就可以设置这个参数,来指定特定的方法)(在这儿,*代表一个参数;(,)表示匹配有两个参数的方法)
上面PointCut表达式整体的意思是:去匹配com.imooc包下的,所有类的所有public修饰的方法;
二:切点表达式案例;
SpringApplication入口类;
案例1
运行结果: UserService类和UserDao类,都被切面类作用了;
案例2:只圈定Service类;(这是日常开发中,最常用的形式)
运行结果: 只有UserService类被切面类作用了;
案例3:public可以去掉
运行结果: 去掉public后,没什么影响;
案例4:只圈定没有返回值的方法
运行结果:可以看到,有返回值的方法,没有被切面类作用;
案例5:只去圈定有返回值,且返回值类型为String的方法
运行结果:可以看到,有返回值且返回值类型为String的方法,被切面类作用;
案例6:圈定create开头的方法
运行结果:结果如预期;
案例7:圈定指定格式的参数的方法:只圈定无参数的方法
运行结果:结果如预期;
*案例8:圈定指定格式的参数的方法:只捕获有两个参数的方法;(代表一个参数)
运行结果:如预期;
案例9:圈定指定格式的参数的方法:只捕获有两个参数的方法,而且第一参数需要是String类型的;
运行结果:如预期;