Spring基础篇——Spring的AOP切面编程
一 基本理解
AOP,面向切面编程,作为Spring的核心思想之一,度娘上有太多的教程啊、解释啊,但博主还是要自己按照自己的思路和理解再来阐释一下。原因很简单,别人的思想终究是别人的,自己的理解才是自己的,尤其当用文字、代码来阐述一遍过后,理解层面上又似乎变得不一样了。
博主就不概念化解释AOP了,这里只简单说下为啥要使用这样一种编程思想和相关的AOP技术。其实很简单,就是为了业务模块间的解耦,尤其在现代的软件设计中强调高内聚、低耦合,要求我们的业务模块化,各个功能模块只关注自己的逻辑实现,而不用关注与主业务逻辑不相关的功能。然而,在面向对象的系统设计中,系统中不可或缺的一些功能如日志、事务是散布在应用各处与主逻辑代码高度耦合的,这让主业务代码变得相当冗余、难以复用。而在面向切面的编程思想中,我们是考虑将那些散布在应用多处的重复性代码抽离出来封装成模块化的功能类,一来让主业务逻辑更加专注、简单,二来模块化的日志、事务也便于复用和移植,这就是解耦的思想。但是,解耦并不等于断耦,抽离的功能最终还是要以某种方式"还"(qie)回去,否则应用的功能就不完善了。这里,"还"(qie)回去的技术就是AOP技术,而这种解耦的编程思想就是AOP的编程思想。在Java的生态中,提供AOP技术的框架也有不少,主要的运用就是Spring的AOP和Spring"借鉴"并包含进了自己的生态体系的 AspectJ的AOP。
二 核心概念
为便于理解阐述,博主先唠叨几句。上面的基本阐述中,我们知道,AOP要干的事情其实也很简单,就是要将对象编程中,抽离出来的模块代码(权限、日志、事务)还(qie)回去,但肯定不能是对象思维中的代码冗杂的组合,而是应该更加高明一些,最好能在原来的业务代码执行的过程中不知不觉的还(qie)回去——也就是说要在主业务逻辑执行的流程里,动态的添加(权限、日志、事务)代码抽离前干的那些事情。怎么能做到呢?用代理啊,亲!想想,我们对一个目标对象采用代理不就是为了在目标对象逻辑执行时候通过在代理对象中干点额外的事情吗?这样,虽然,原目标对象并没有增加任何额外的功能,通过代理的一番暗中骚操作,展示给调用者的就好像目标对象有了代理对象中的那些额外的功能一样。于是你也很好理解,为什么Spring的AOP中要用到动态代理了。好了,经过一番唠叨,我们再来看AOP的相关术语就要好理解得多——
1、横切关注点
如上描述,我们把日志、事务、权限等代码重复性极高却散布在应用程序各个地方的功能称为横切关注点。
2、连接点(Join Point)
被代理的目标对象在业务逻辑执行的过程中,可以被代理对象动态切入代理功能的一些时机节点,比如方法执行前、后,异常时,成功返回时等等。当然,这只是针对Spring来说的,因为Spring基于动态代理,只支持方法级别的AOP切入,实际上,AspectJ、JBoss等框架的AOP还能提供构造器以及更细粒度字段等的连接点支持。
3、通知(Advice)
如上描述,就是代理对象在什么时机要为目标对象额外增加的功能代码,因而很多教程资料上称之为 增强。请注意博主对通知的描述里有提到什么时机,这很好理解,你的代理对象要给目标对象增加额外功能,总得清楚要增加在哪些时机吧,所以,我们的通知按照功能切入的时机分为以下5个类型:
前置通知(Before):被代理对象目标方法被调用之前执行通知代码;
后置通知(After):被代理对象目标方法执行完成之后执行通知代码,不管方法是否成功执行(这相当于异常捕获中的finally块,总是会执行的意思,所以博主觉得如果将其命名为最终通知要更好理解些);
异常通知(After-throwing):被代理对象目标方法抛出异常后执行通知代码;
返回通知(After-returning):被代理对象目标方法成功执行后执行通知代码;
环绕通知(Around) :包裹被代理对象的目标方法,相当于结合了以上的所有通知类型。
4、切点(Pointcut)
被代理对象目标方法执行过程中真正的要执行通知代码的一个或多个连接点,这会通过切点表达式语言进行匹配。
6、切面(Aspect)
通知和切点的结合,切面完整的包含了代理对象对目标对象进行通知的三个基本要素:何时(前、后、异常、环绕、返回等),何地(切点),干什么(通知切入的功能)。
7、织入(Weaving)
将切面应用到被代理对象并创建代理对象的的过程。切面会在指定的连接点(切点)被织入到被代理对象的执行方法中。其实,被代理对象的生命周期中有多个时机(编译、类加载、运行)都可以进行织入,就 Spring 而言,是在被代理对象运行期进行代理对象的创建,织入切面逻辑的。
注:以上描述都是基于Spring 方法级别的AOP 来进行阐述
三 基础代码示例
说了那么多,还是上代码最简单直接。准备工作:
① 测试依赖的包及其版本(注:很多教程中都提到需要 aopalliance包,但是博主测试过程中并没有确认此包存在的必要性)
aspectjweaver-1.9.2.jar
commons-logging-1.2.jar
spring-aop-4.3.18.RELEASE.jar
spring-beans-4.3.18.RELEASE.jar
spring-context-4.3.18.RELEASE.jar
spring-core-4.3.18.RELEASE.jar
spring-expression-4.3.18.RELEASE.jar
spring-test-4.3.18.RELEASE.jar
② 定义两个基础模型类(如下),业务是:给只有打电话功能的手机动态的添加 拍照、玩游戏这样的非主业务功能。
//主业务功能
public class HuaWeiPhone {
public void ring() {
System.out.println("华为手机,产销第一");
}
} //额外添加的功能
public class Photograph { public void takePictures(){
System.out.println("华为手机,拍照牛批");
} public void playGames(){
System.out.println("华为手机,游戏玩得也这么畅快");
}
}
1、XML配置的方式
根据以上Java代码,进行非常简单的配置,就能看到动态的为手机增加了拍照功能的效果了——
<bean class="main.java.model.HuaWeiPhone"/>
<bean id="photograph" class="main.model.Photograph"/>
<aop:config>
<aop:pointcut id="ring" expression="execution(* main.model.HuaWeiPhone.ring(..))"/>
<aop:aspect ref="photograph">
<aop:before method="takePictures" pointcut-ref="ring"/>
<aop:after method="playGames" pointcut-ref="ring"/>
</aop:aspect>
</aop:config>
在Spring环境下测试类XML配置——
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:main/resource/applicationContext.xml")
public class SpringTest { @Autowired
HuaWeiPhone huaWeiPhone; @Test
public void testXml(){
huaWeiPhone.ring();
}
}
输出结果
2、Java注解的方式
需要先说明的是,Spring的基于注解的 AOP 实际上是借鉴吸收了AspectJ的功能,所以你会看到很多类似 AspectJ 框架的注解。在之前的模型类上通过添加相应的注解改造成一个切面——
@Aspect //将该类标注为一个AOP切面
@Component
public class Photograph { @Pointcut("execution(* main.model.HuaWeiPhone.ring(..))")
public void chenbenbuyi (){} @Before("chenbenbuyi()")
public void takePictures(){
System.out.println("华为手机,拍照牛批");
} @After("chenbenbuyi()")
public void playGames(){
System.out.println("华为手机,游戏玩得也这么畅快");
}
}
同样的,目标类(HuaWeiPhone)上也要添加@Componet注解将其交给Spring 容器管理。然后,如果是纯注解的话,还要一个配置类——
//配置注解扫描
@ComponentScan(basePackages = "main")
//启用AspectJ的自动代理功能
@EnableAspectJAutoProxy
public class JavaConfig {
}
最后,在Spring的环境下测试——
@RunWith(SpringJUnit4ClassRunner.class)
//@ContextConfiguration(locations = "classpath:main/resource/applicationContext.xml")
@ContextConfiguration(classes = JavaConfig.class)
public class SpringTest { @Autowired
HuaWeiPhone huaWeiPhone; @Test
public void testAnno(){
huaWeiPhone.ring();
}
}
结果同上,这里就不展示了。不过需要注意的是,不管什么配置方式,基于Spring 的AOP编程实现的前提都是要将通知对象和被通知方法交给Spring IOC容器管理,也就是要声明为Spring 容器中的Bean。
四 需求升级
在第三部分中,博主只是展示了最最简单的AOP功能实现,还有稍微复杂的技能点没有列出。比如,5种通知类型中的环绕通知呢?再比如,我的切面代码如果要传参数怎么办呢?接下来博主依次讲解。
① 关于环绕通知的运用
基于 二 中的阐述,5 种通知类型中 环绕通知 是功能最为强大,实际上,我们可以在环绕通知中个性化的定制出前置 、后置、异常和返回的通知类型,而如果单独的采用前置、后置等通知类型,如果业务涉及多线程对成员变量的修改,可能出现并发问题,所以环绕要比单独的使用另外的几种通知类型更加的安全。我们对上面的切面基于环绕通知进行修改,使之包含所有的通知类型的功能——
@Aspect
@Component
public class Photograph { @Pointcut("execution(* main.model.HuaWeiPhone.ring(..))")
public void chenbenbuyi (){} @Around("chenbenbuyi()")
public void surround(ProceedingJoinPoint joinPoint){
try {
System.out.println("目标方法执行前执行,我就是前置通知");
joinPoint.proceed();// ①
// int i =1/0; // ② 制造异常
System.out.println("正常返回,我就是返回通知");
} catch (Throwable e) {
System.out.println("出异常了,我就是异常通知");
}finally {
System.out.println("后置通知,我就是最终要执行的通知");
}
}
}
XML的配置和上面的其它通知类型一样,只不过元素标签为 <aop:around />而已。上面的打印语句的位置就对应了其它几种通知类型执行切面逻辑的时机。这里注意,环绕通知方法体中需要有 ProceedingJoinPoint 接口作为参数,在环绕通知中,通过执行该参数的 proceed() 方法来调用通知需要切入的目标方法。如果不执行 ① 处的调用,被通知方法实际上会被阻塞掉,所以你会看到,明明测试中执行了被通知的方法,实际却没有执行。该参数对象还可以获取方法签名、代理对象、目标对象等信息,可以自己测试着玩。
② 关于通知的传参问题
切面虽然是通用逻辑,但实际在切入不同的目标方的时候,可能还是希望通知方法根据被通知方法的不同(比如参数不同)而执行不一样的逻辑,这就要求我们的通知也能获取到被通知方法传入的参数。通过切点表达式,这也很容易办到。首先我们修改被通知的方法可以传参:
public void ring(String str) {
System.out.println("华为手机,产销第一");
int i =1/0;
}
然后切面中切点表达式和切面方法也做对应的修改——
@Aspect
@Component
public class Photograph {
/**
* Spring 借助于 AspectJ的切点表达式语言中的arg()表达式执行参数的传递工作
*/
@Pointcut("execution(* main.model.HuaWeiPhone.ring(String))&&args(name)")
public void chenbenbuyi (String name){} /**
* ① 在引用空标方法的切点表达式时同时也就要传入相应的参数
* ② 传入的参数形参名字必须和切点表达式中的相同
*/
@Before("chenbenbuyi(name)")
public void takePictures(String name){
System.out.println("喂喂,你好我是 "+ name);
} /**
* 对于异常通知,有专门的异常参数可以直接获取到被通知方法出现异常后信息的
*/
@AfterThrowing(pointcut = "chenbenbuyi(name)",throwing = "e")
public void excep(String name,Throwable e){
System.out.println("出异常了,异常信息是:"+e.getMessage());
}
}
XML中配置参数传递
<bean class="main.java.model.HuaWeiPhone"/>
<bean id="photograph" class="main.java.model.Photograph"/>
<aop:config>
<aop:pointcut id="ring" expression="execution(* main.java.model.HuaWeiPhone.ring(..)) and args(name)"/>
<aop:aspect ref="photograph">
<aop:before method="takePictures" pointcut-ref="ring" arg-names="name" />
<aop:after-throwing method="excep" throwing="e" arg-names="name,e" pointcut-ref="ring"/>
</aop:aspect>
</aop:config>
测试代码——
@RunWith(SpringJUnit4ClassRunner.class)
//@ContextConfiguration(locations = "classpath:main/resource/applicationContext.xml")
@ContextConfiguration(classes = JavaConfig.class)
public class SpringTest { @Autowired
HuaWeiPhone huaWeiPhone; @Test
public void testAnno(){
huaWeiPhone.ring("博客园 陈本布衣");
}
}
最终测试的执行结果——
注意点:
① XML配置中由于 &符号有特殊含义,所以 切点表达式中 连接形参名的时候就不能再使用注解中的 && ,而应该使用 and 代替,同样的如果有 或(|| )非 (!)操作,分别使用 or 和 not 代替。
② 注解和XML配置中切点表达式描述形参类型的地方博主采用了不同的方式,因为 .. 就表示任意类型,可以不用指明。
五 切点表达式常用图解
Spring基础篇——Spring的AOP切面编程的更多相关文章
- Spring基础篇——DI和AOP初识
前言 作为从事java开发的码农,Spring的重要性不言而喻,你可能每天都在和Spring框架打交道.Spring恰如其名的,给java应用程序的开发带了春天般的舒爽感觉.Spring,可以说是任何 ...
- Spring基础篇——Spring容器和应用上下文理解
上文说到,有了Spring之后,通过依赖注入的方式,我们的业务代码不用自己管理关联对象的生命周期.业务代码只需要按照业务本身的流程,走啊走啊,走到哪里,需要另外的对象来协助了,就给Spring说,我想 ...
- Spring AOP 切面编程记录日志和接口执行时间
最近客户现在提出系统访问非常慢,需要优化提升访问速度,在排查了nginx.tomcat内存和服务器负载之后,判断是数据库查询速度慢,进一步排查发现是因为部分视图和表查询特别慢导致了整个系统的响应时间特 ...
- spring框架(2)— 面相切面编程AOP
spring框架(2)— 面相切面编程AOP AOP(Aspect Oriented Programming),即面向切面编程. 可以说是OOP(Object Oriented Programming ...
- Spring MVC通过AOP切面编程 来拦截controller 实现日志的写入
首选需要参考的是:[参考]http://www.cnblogs.com/guokai870510826/p/5977948.html http://www.cnblogs.com/guokai8 ...
- SpringBoot2.0 基础案例(11):配置AOP切面编程,解决日志记录业务
本文源码 GitHub地址:知了一笑 https://github.com/cicadasmile/spring-boot-base 一.AOP切面编程 1.什么是AOP编程 在软件业,AOP为Asp ...
- Spring Boot 自定义注解,AOP 切面统一打印出入参请求日志
其实,小哈在之前就出过一篇关于如何使用 AOP 切面统一打印请求日志的文章,那为什么还要再出一篇呢?没东西写了? 哈哈,当然不是!原因是当时的实现方案还是存在缺陷的,原因如下: 不够灵活,由于是以所有 ...
- 十:SpringBoot-配置AOP切面编程,解决日志记录业务
SpringBoot-配置AOP切面编程,解决日志记录业务 1.AOP切面编程 1.1 AOP编程特点 1.2 AOP中术语和图解 2.SpringBoot整合AOP 2.1 核心依赖 2.2 编写日 ...
- AOP切面编程在android上的应用
代码地址如下:http://www.demodashi.com/demo/12563.html 前言 切面编程一直是一个热点的话题,这篇文章讲讲一个第三方aop库在android上的应用.第三方AOP ...
随机推荐
- MVC 5 调用存储过程参数配置方法-Procedure or function 'UP_***' expects parameter '@****', which was not supplied.
MVC 5 调用存储过程参数配置方法-Procedure or function 'UP_***' expects parameter '@****', which was not supplied. ...
- C# & JAVA:读写文件
using System; using System.IO; using System.Text; namespace ConsoleApplication4 { class Program { pu ...
- 关于如何使用ehcarts2加载svg矢量地图并自定义县级内部乡镇轮廓
项目需求:显示县级内部的乡镇一级地图的轮廓! 效果预览: 阻碍因素:echarts不提供县级以下乡镇级轮廓. 解决思路: 1.根据资料查找相关县的行政区域图(百度搜索),如本人所制作的浙江省宁波市宁海 ...
- 【Nginx】实现负载均衡
负载均衡是什么? 当一台服务器的单位时间内的访问量越大时,服务器压力就越大,大到超过自身承受能力时,服务器就会崩溃.为了避免服务器崩溃,让用户有更好的体验,我们通过负载均衡的方式来分担服务器压力. 我 ...
- MySQL实现分组取组内特定数据的功能
需求:在MySQL5.7环境下,查询下面表中,各个学科前两名的学生的成绩: 1.准备数据 窗机表以及向表中插入数据 创建一张表: DROP TABLE IF EXISTS `grade`; CREAT ...
- LeetCode 112. Path Sum 二叉树的路径和 C++
Given a binary tree and a sum, determine if the tree has a root-to-leaf path such that adding up all ...
- js 监听手机端键盘弹出和收起事件
//这里区分不同系统,可以参考之前的文档记录 https://www.cnblogs.com/wind-wang/p/10737110.html const ua = typeof window == ...
- tabpanel如何隐藏页签表头以及基本用法总结
tabpanel是extjs中一种比较常用的布局容器控件,也比较简单. ///1:相关的插件, var tabScrollerMenu = Ext.create("Ext.ux.TabScr ...
- 本地jar包添加至Maven仓库
Maven命令将本地的jar包方放到maven仓库中 //自定义本地的jar包在pom文件的参数 <dependency> <groupId>com.eee</group ...
- CentOS6.3上安装与配置nginx+php+mysql环境
1. 目前nginx采用是源码包安装的方式(yum安装失败),下载地址:http://nginx.org/en/download.html 我这里的安装包是:nginx-1.12.0.tar.gz 2 ...