转自

https://www.tianmaying.com/tutorial/spring-aop

AOP是什么?

软件工程有一个基本原则叫做“关注点分离”(Concern Separation),通俗的理解就是不同的问题交给不同的部分去解决,每部分专注于解决自己的问题。这年头互联网也天天强调要专注嘛!

这其实也是一种“分治”或者“分类”的思想,人解决复杂问题的能力是有限的,所以为了控制复杂性,我们解决问题时通常都要对问题进行拆解,拆解的同时建立各部分之间的关系,各个击破之后整个问题也迎刃而解了。人类的思考,复杂系统的设计,计算机的算法,都能印证这一思想。额,扯远了,这跟AOP有神马关系?

面向切面编程(Aspect Oriented Programming,AOP)其实就是一种关注点分离的技术,在软件工程领域一度是非常火的研究领域。我们软件开发时经常提一个词叫做“业务逻辑”或者“业务功能”,我们的代码主要就是实现某种特定的业务逻辑。但是我们往往不能专注于业务逻辑,比如我们写业务逻辑代码的同时,还要写事务管理、缓存、日志等等通用化的功能,而且每个业务功能都要和这些通用功能混在一起,痛苦!所以,为了将业务功能的关注点和通用化功能的关注点分离开来,就出现了AOP技术。这些通用化功能的代码实现,对应的就是我们说的切面(Aspect)。

业务功能代码和切面代码分开之后,责任明确,开发者就能各自专注解决问题了,代码可以优雅的组织了,设计更加高内聚低耦合了(终极目标啊!)。但是请注意,代码分开的同时,我们如何保证功能的完整性呢? 你的业务功能依然需要有事务和日志等特性,即切面最终需要合并(专业术语叫做织入, Weave)到业务功能中。怎么做到呢? 这里就涉及AOP的底层技术啦,有三种方式:

  1. 编译时织入:在代码编译时,把切面代码融合进来,生成完整功能的Java字节码,这就需要特殊的Java编译器了,AspectJ属于这一类
  2. 类加载时织入:在Java字节码加载时,把切面的字节码融合进来,这就需要特殊的类加载器,AspectJ和AspectWerkz实现了类加载时织入
  3. 运行时织入:在运行时,通过动态代理的方式,调用切面代码增强业务功能,Spring采用的正是这种方式。动态代理会有性能上的开销,但是好处就是不需要神马特殊的编译器和类加载器啦,按照写普通Java程序的方式来就行了!

一个场景

接下来上例子!David对土豪老板定机票的例子比较满意,所以决定继续沿用这个例子。

Boss在订机票时,我们希望能够记录订机票这个操作所消耗的时间,同时记录日志(这里我们简单的在控制台打印预定成功的信息)。

我们来看普通青年的做法吧:

  1. package com.tianmaying.aopdemo;
  2. public class Boss {
  3. private BookingService bookingService;
  4. public Boss() {
  5. this.bookingService = new QunarBookingService();
  6. }
  7. //...
  8. public void goSomewhere() {
  9. long start = System.currentTimeMillis();
  10. //订机票
  11. boolean status = bookingService.bookFlight();
  12. //查看耗时
  13. long duration = System.currentTimeMillis() - start;
  14. System.out.println(String.format("time for booking flight is %d seconds", duration));
  15. //记录日志
  16. if (status) {
  17. System.out.println("booking flight succeeded!");
  18. } else {
  19. System.out.println("booking flight failed!");
  20. }
  21. }
  22. }

我们看到,在订机票的同时,还要处理查看耗时和记录日志,关注的事情太多了,头大啊。而且项目大了之后,除了订机票之外,很多业务功能都要写类似的代码。让AOP来拯救我们吧!

使用AOP的场景

相比在IoC例子中的代码,我们让BookingServicebookFlight()方法返回一个boolean值,表示是否预定成功。这样我们可以演示如何获取被切方法的返回值。

通过AOP我们怎么做呢,David今天送出独门秘籍,告诉你通过3W方法(What-Where-When)来理解AOP。

  • What:What当然指的时切面啦!首先我们将记录消耗时间和记录日志这两个功能的代码分离出来,我们可以做成两个切面,命名为TimeRecordingAspectLogAspect
  • Where:切面的织入发生在哪呢?切面针对的目标对象(Target)是SmartBoss(区别于Boss)!这里还有有一个很关键的概念叫做切入点(Pointcut),在这个场景中就是指在SmartBoss调用什么方法的时候的时候应用切面。显然,我们希望增强的是bookFlight()方法,即在bookFlight方法调用的地方,我们加入时间记录和日志。
  • When: 什么时候织入呢?这涉及到织入的时机问题,我们可以在bookFlight()执行前织入,执行后织入,或者执行前后同时切入。When的概念用专业术语来说叫做通知(Advice)。

了解了3W之后,来看看代码吧,先上LogAspect:

插入一段,POM文件不要忘记了引入Spring AOP相关的依赖:

  1. <dependency>
  2. <groupId>org.springframework</groupId>
  3. <artifactId>spring-context</artifactId>
  4. <version>4.2.0.RELEASE</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.springframework</groupId>
  8. <artifactId>spring-aop</artifactId>
  9. <version>4.2.0.RELEASE</version>
  10. </dependency>
  11. <dependency>
  12. <groupId>org.aspectj</groupId>
  13. <artifactId>aspectjweaver</artifactId>
  14. <version>1.8.5</version>
  15. </dependency>
  16. <dependency>
  17. <groupId>org.aspectj</groupId>
  18. <artifactId>aspectjrt</artifactId>
  19. <version>1.8.5</version>
  20. </dependency>

LogAspect

  1. package com.tianmaying.aopdemo.aspect;
  2. import org.aspectj.lang.annotation.AfterReturning;
  3. import org.aspectj.lang.annotation.Aspect;
  4. import org.aspectj.lang.annotation.Pointcut;
  5. import org.springframework.stereotype.Component;
  6. @Aspect //1
  7. @Component
  8. public class LogAspect {
  9. @Pointcut("execution(* com.tianmaying.aopdemo..*.bookFlight(..))") //2
  10. private void logPointCut() {
  11. }
  12. @AfterReturning(pointcut = "logPointCut()", returning = "retVal") //3
  13. public void logBookingStatus(boolean retVal) { //4
  14. if (retVal) {
  15. System.out.println("booking flight succeeded!");
  16. } else {
  17. System.out.println("booking flight failed!");
  18. }
  19. }
  20. }

我们看这段代码:

1通过一个@Apsect标注,表示LogAspect是一个切面,解决了What问题。2通过定义一个标注了@Pointcut的方法,定义了Where的问题,"execution(* com.tianmaying.aopdemo..*.bookFlight(..))"表示在com.tianmaying.aopdemo包或者子包种调用名称为bookFlight的地方就是切入点!定义Pioncut的语法这里不详解了,David这里要告诉你的时它的作用:解决Where的问题!3通过一个@AfterReturning标注表示在bookFlight()调用之后将切面织入,这是一个AfterReturning类型的Advice,注意这里可以通过returning属性获取bookFlight()的返回值。4这里定义了实现切面功能的代码,经过这么一番闪转腾挪,最后写日志的代码跑到这里来了!

再来看TimeRecordingAspect:

TimeRecordingAspect:

  1. package com.tianmaying.aopdemo.aspect;
  2. import org.aspectj.lang.ProceedingJoinPoint;
  3. import org.aspectj.lang.annotation.Around;
  4. import org.aspectj.lang.annotation.Aspect;
  5. import org.aspectj.lang.annotation.Pointcut;
  6. import org.springframework.stereotype.Component;
  7. @Aspect
  8. @Component
  9. public class TimeRecordingAspect {
  10. @Pointcut("execution(* com.tianmaying.aopdemo..*.bookFlight(..))")
  11. private void timeRecordingPointCut() {
  12. }
  13. @Around("timeRecordingPointCut()") //1
  14. public Object recordTime(ProceedingJoinPoint pjp) throws Throwable { //2
  15. long start = System.currentTimeMillis();
  16. Object retVal = pjp.proceed(); // 3
  17. long duration = System.currentTimeMillis() - start;
  18. System.out.println(String.format(
  19. "time for booking flight is %d seconds", duration));
  20. return retVal;
  21. }
  22. }
  • LogAspect不同,因为要计算bookFlight()的耗时,我们必须在调用前后到切入代码,才能算出来这之间的时间差。因此,在1处,我们定义的是一个Around类型的Advice。
  • 2处是实现AroundAdvice的方法,其方法的参数和返回值是固定写法。
  • 3处也是固定写法,表示对目标方法(即bookFlight())的调用,注意不要漏了,漏掉的话原方法就不会被调用了,通常情况下肯定不是你想要的结果!

回头再看SmartBoss的代码,比Boss简单多了,goSomewhere()方法中只剩下一条语句,酷的掉渣啊,只关注订机票,其他的事情都F**k Off吧!

  1. package com.tianmaying.aopdemo;
  2. import org.springframework.beans.factory.annotation.Autowired;
  3. import org.springframework.stereotype.Component;
  4. @Component
  5. public class SmartBoss {
  6. private BookingService bookingService;
  7. //...
  8. public void goSomewhere() {
  9. bookingService.bookFlight();
  10. }
  11. }

当然,要让代码Run起来,还需要在App类中加上@EnableAspectJAutoProxy标注,这样Spring启动时就去去扫描AOP相关的标注,在创建对象时帮我们去执行织入过程!

回到定义

例子讲完!现在我们再来逐一看看AOP中的那些名词定义,这个时候理解这些概念,你应该不会觉得冷冰冰了,应该要有一种"Ya!"的感觉啦!

  • 切面(Aspect):指的就是通用功能的代码实现,比如我们上面演示的时间记录切面,日志切面,它们都是普通的Java类:TimeRecordingAspectLogAspect

  • 目标对象(Target):要被织入切面的对象,例子中的CtripBookingService,有了AOP,它们可以专注于核心业务逻辑代码了!

  • 切入点(Pointcut):定义通知应该切入到什么地方,Spring支持的切入点就是方法调用,切入点的定义可以使用正则表达式,用以描述什么类型的方法调用。@Pointcut就是用来定义切入点的。

  • 通知(Advice):切面是一个类,而通知就是类里的方法以及这个方法如何织入到目标方法的方式(用@AfterReturning@Around标注的方法)。我们的例子中只展示了两类通知,根据织入到目标方法方式的不同,一共可以分为5种:

    • 前置通知(Before)
    • 后置通知(AfterReturning)
    • 异常通知(AfterThrowing)
    • 最终通知(After)
    • 环绕通知(Around)
  • 织入(Weaving):AOP实现的过程,即将切面应用到目标对象,从而创建一个新的代理对象的过程,对于Spring来说,就是初始化Context中的对象时,完成织入操作。

现在你应该理解Spring中AOP的关键知识和核心原理了,剩下的就是在David给你的3W框架下,去学习每一部分的知识了,比如不同类型通知的写法,PointCut的各种类型的定义方法,切面和目标对象之间参数传递,等等。当然,最关键的还是赶紧实践中用起来!

Spring的AOP原理的更多相关文章

  1. spring ioc aop 原理

    spring ioc aop 原理 spring ioc aop 的原理 spring的IoC容器是spring的核心,spring AOP是spring框架的重要组成部分. 在传统的程序设计中,当调 ...

  2. Spring之AOP原理、代码、使用详解(XML配置方式)

    Spring 的两大核心,一是IOC,另一个是AOP,本博客从原理.AOP代码以及AOP使用三个方向来讲AOP.先给出一张AOP相关的结构图,可以放大查看. 一.Spring AOP 接口设计 1.P ...

  3. Spring系列.AOP原理简析

    Spring AOP使用简介 Spring的两大核心功能是IOC和AOP.当我们使用Spring的AOP功能时是很方便的.只需要进行下面的配置即可. @Component @Aspect public ...

  4. Spring中AOP原理,源码学习笔记

    一.AOP(面向切面编程):通过预编译和运行期动态代理的方式在不改变代码的情况下给程序动态的添加一些功能.利用AOP可以对应用程序的各个部分进行隔离,在Spring中AOP主要用来分离业务逻辑和系统级 ...

  5. [Spring入门学习笔记][Spring的AOP原理]

    AOP是什么? 面向切面编程 软件工程有一个基本原则叫做“关注点分离”(Concern Separation),通俗的理解就是不同的问题交给不同的部分去解决,每部分专注于解决自己的问题.这年头互联网也 ...

  6. 【转】spring的AOP原理,使用场景是什么?

    什么是AOP AOP(Aspect-OrientedProgramming,面向方面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善.OOP引入 ...

  7. spring中aop原理

  8. Spring Cloud Hystrix原理篇(十一)

    一.Hystrix处理流程 Hystrix流程图如下: Hystrix整个工作流如下: 构造一个 HystrixCommand或HystrixObservableCommand对象,用于封装请求,并在 ...

  9. Java轻量级业务层框架Spring两大核心IOC和AOP原理

    IoC(Inversion of Control): IOC的基本概念是:不创建对象,但是描述创建它们的方式.在代码中不直接与对象和服务连接,但在配置文件中描述哪一个组件需要哪一项服务.容器负责将这些 ...

随机推荐

  1. vscode安装依赖报错 TypeError: zipfile.readEntry is not a function

    错误原因是npm的版本太高,需要把5.x的版本换回4.x的 npm install npm@4 -g 或者 cnpm install npm@4 -g 详见:https://github.com/Mi ...

  2. 15 Vue计算属性和侦听器

    计算属性 模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的. 在模板中放入太多的逻辑会让模板过重且难以维护.例如: split = 字符中间空格分割, reverse= 反转 join('' ...

  3. docker学习(四)

    一.Docker数据管理 在容器中管理数据主要有两种方式:1.数据卷(Data volumes)2.数据卷容器(Data volume containers) 1.数据卷数据卷是一个可供一个或多个容器 ...

  4. PHP 调用 shell

    可以使用的命令: popenfpassthrushell_execexecsystem 1.popen resource popen ( string command, string mode ) 打 ...

  5. Planet.Earth.II.EP05.2016

  6. [other] Div

    https://www.luogu.org/problemnew/show/U16765 解法一 随机输出一组合法解. 复杂度 O(1) 预计得分 10~??? 解法二 看完题目基本能想到大力贪心,通 ...

  7. QoS in RoCE (zz)

    QoS in RoCE 首页分类标签留言关于订阅2018-03-22 | 分类 Network  | 标签 RDMA  RoCE  ECN  PFC Overview TCP/IP协议栈满足不了现代I ...

  8. django项目部署上线 nginx + uwsgi

    一.安装python3 安装步骤:https://www.cnblogs.com/zhangqigao/p/11661875.html 二.修改django中的配置文件 修改settings.py ( ...

  9. 10月清北学堂培训 Day 4

    今天是钟皓曦老师的讲授~ 今天的题比昨天的难好多,呜~ T1 我们需要找到一个能量传递最多的异构体就好了: 整体答案由花时间最多的异构体决定: 现在的问题就是这么确定一个异构体在花费时间最优的情况下所 ...

  10. Ubuntu 14.04 源

    清华源https://mirrors.tuna.tsinghua.edu.cn/help/ubuntu/ ubuntu 14.04 官方源 # deb cdrom:[Ubuntu 14.04.4 LT ...