一  基本理解

  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切面编程的更多相关文章

  1. Spring基础篇——DI和AOP初识

    前言 作为从事java开发的码农,Spring的重要性不言而喻,你可能每天都在和Spring框架打交道.Spring恰如其名的,给java应用程序的开发带了春天般的舒爽感觉.Spring,可以说是任何 ...

  2. Spring基础篇——Spring容器和应用上下文理解

    上文说到,有了Spring之后,通过依赖注入的方式,我们的业务代码不用自己管理关联对象的生命周期.业务代码只需要按照业务本身的流程,走啊走啊,走到哪里,需要另外的对象来协助了,就给Spring说,我想 ...

  3. Spring AOP 切面编程记录日志和接口执行时间

    最近客户现在提出系统访问非常慢,需要优化提升访问速度,在排查了nginx.tomcat内存和服务器负载之后,判断是数据库查询速度慢,进一步排查发现是因为部分视图和表查询特别慢导致了整个系统的响应时间特 ...

  4. spring框架(2)— 面相切面编程AOP

    spring框架(2)— 面相切面编程AOP AOP(Aspect Oriented Programming),即面向切面编程. 可以说是OOP(Object Oriented Programming ...

  5. Spring MVC通过AOP切面编程 来拦截controller 实现日志的写入

    首选需要参考的是:[参考]http://www.cnblogs.com/guokai870510826/p/5977948.html    http://www.cnblogs.com/guokai8 ...

  6. SpringBoot2.0 基础案例(11):配置AOP切面编程,解决日志记录业务

    本文源码 GitHub地址:知了一笑 https://github.com/cicadasmile/spring-boot-base 一.AOP切面编程 1.什么是AOP编程 在软件业,AOP为Asp ...

  7. Spring Boot 自定义注解,AOP 切面统一打印出入参请求日志

    其实,小哈在之前就出过一篇关于如何使用 AOP 切面统一打印请求日志的文章,那为什么还要再出一篇呢?没东西写了? 哈哈,当然不是!原因是当时的实现方案还是存在缺陷的,原因如下: 不够灵活,由于是以所有 ...

  8. 十:SpringBoot-配置AOP切面编程,解决日志记录业务

    SpringBoot-配置AOP切面编程,解决日志记录业务 1.AOP切面编程 1.1 AOP编程特点 1.2 AOP中术语和图解 2.SpringBoot整合AOP 2.1 核心依赖 2.2 编写日 ...

  9. AOP切面编程在android上的应用

    代码地址如下:http://www.demodashi.com/demo/12563.html 前言 切面编程一直是一个热点的话题,这篇文章讲讲一个第三方aop库在android上的应用.第三方AOP ...

随机推荐

  1. 团队第六次 # scrum meeting

    github 本此会议项目由PM召开,召开时间为4-10日晚上9点 召开时长20分钟 任务表格 袁勤 负责协调前后端 https://github.com/buaa-2016/phyweb/issue ...

  2. input file禁用手机本地文件选择,只允许拍照上传图片

    <input type="file" accept="image/*" capture="camera"> 会有个问题,上传的图 ...

  3. python3百度设置高级搜索例子

    #=======================================#作者:邓沛友#2018.12.16=============================coding:utf-8f ...

  4. 嵌入式linux——时钟(三)

    今天写第一篇,S3C2440的时钟,配置好时钟系统,各个模块才能正常有效的工作,为了了解始终系统,必须要阅读芯片手册,尽量看英文版的,这样还能捎带着增加一下阅读英语计数文档的能力. 概览 在2440数 ...

  5. Bootstrap字体无法显示

    下载的font文件没有放进你的项目文件里.

  6. rest_famework 增删改查初第二阶段(中级,此阶段是优化初级阶段的代码)的使用

    url: re_path('authors/$', views.AuthorView.as_view()),re_path('authors/(\d+)/$', views.AuthorDetailV ...

  7. [HTML]音乐自动播放(兼容微信)

    文件下载:音乐自动播放(兼容微信).zip   <!DOCTYPE html> <html> <head> <meta charset="utf-8 ...

  8. Git综合使用命令行和gui工具小结

    使用Git的时候,综合使用命令行和gui工具,可以把Git用的最舒服,因此这里总结下使用gui和命令行的一些对应操作, gui中拉取:git pull origin dev_branch gui中推送 ...

  9. js 字符串截取函数substr,substring,slice之间的差异

    js 字符串的截取,主要有三个函数,一般使用三个函数:substr,substring,slice. 而这三个函数是不完全一样的,平时很难记住,在这里做下笔记,下次遇到的时候,直接从这里参考,调用合适 ...

  10. springboot+vue前后端分离,nginx代理配置 tomcat 部署war包详细配置

    1.做一个小系统,使用了springboot+vue 基础框架参考这哥们的,直接拿过来用,链接https://github.com/smallsnail-wh/interest 前期的开发环境搭建就不 ...