AOP是什么?

面向切面编程

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

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

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

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

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

一个场景

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

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

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

package com.tianmaying.aopdemo;

public class Boss {

    private BookingService bookingService;

    public Boss() {
this.bookingService = new QunarBookingService();
} //... public void goSomewhere() {
long start = System.currentTimeMillis(); //订机票
boolean status = bookingService.bookFlight(); //查看耗时
long duration = System.http://tianmaying.com/tutorial/spring-ioc#/1() - start;
System.out.println(String.format("time for booking flight is %d seconds", duration)); //记录日志
if (status) {
System.out.println("booking flight succeeded!");
} else {
System.out.println("booking flight failed!");
}
}
}

我们看到,在订机票的同时,还要处理查看耗时和记录日志,关注的事情太多了,头大啊。而且项目大了之后,除了订机票之外,很多业务功能都要写类似的代码。让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相关的依赖:

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.5</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.5</version>
</dependency>

LogAspect.java

package com.tianmaying.aopdemo.aspect;

import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component; @Aspect //1
@Component
public class LogAspect { @Pointcut("execution(* com.tianmaying.aopdemo..*.bookFlight(..))") //2
private void logPointCut() {
} @AfterReturning(pointcut = "logPointCut()", returning = "retVal") //3
public void logBookingStatus(boolean retVal) { //4
if (retVal) {
System.out.println("booking flight succeeded!");
} else {
System.out.println("booking flight failed!");
}
}
}
  • 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:

package com.tianmaying.aopdemo.aspect;

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

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

package com.tianmaying.aopdemo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; @Component
public class SmartBoss {
private BookingService bookingService; //... public void goSomewhere() {
bookingService.bookFlight();
}
}

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

回到定义

  • 切面(Aspect):指的就是通用功能的代码实现,比如我们上面演示的时间记录切面,日志切面,它们都是普通的Java类:TimeRecordingAspectLogAspect
  • 目标对象(Target):要被织入切面的对象,例子中的CtripBookingService,有了AOP,它们可以专注于核心业务逻辑代码了!
  • 通知(Advice):切面是一个类,而通知就是类里的方法以及这个方法如何织入到目标方法的方式(用@AfterReturning@Around标注的方法)。我们的例子中只展示了两类通知,根据织入到目标方法方式的不同,一共可以分为5种:

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

总结

  • 关注点分离的思想
  • 理解AOP的3W方法
  • 切面、目标对象、切入点、通知和织入的概念

[Spring入门学习笔记][Spring的AOP原理]的更多相关文章

  1. [spring入门学习笔记][spring的IoC原理]

    什么叫IoC 控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度.其中最常见的方式叫做依赖注入(Dependency ...

  2. [Spring入门学习笔记][Spring Boot]

    什么是Spring Boot Spring Boot正是在这样的一个背景下被抽象出来的开发框架,它本身并不提供Spring框架的核心特性以及扩展功能,只是用于快速.敏捷地开发新一代基于Spring框架 ...

  3. Spring入门学习笔记(3)——事件处理类

    目录 Spring中的事件处理 Spring内建事件 监听Context事件 Example 自定义Spring事件 Spring中的事件处理 ApplicationContext 是Spring的核 ...

  4. Spring Cloud学习笔记--Spring Boot初次搭建

    1. Spring Boot简介 初次接触Spring的时候,我感觉这是一个很难接触的框架,因为其庞杂的配置文件,我最不喜欢的就是xml文件,这种文件的可读性很不好.所以很久以来我的Spring学习都 ...

  5. Spring Boot学习笔记——Spring Boot与MyBatis的集成(项目示例)

    1.准备数据库环境 # 创建数据库 CREATE DATABASE IF NOT EXISTS zifeiydb DEFAULT CHARSET utf8 COLLATE utf8_general_c ...

  6. [Spring入门学习笔记][静态资源]

    遗留问题 在上一节课的作业中,我们一定遇到了一点问题——虽然将页面内容正确的返回给了浏览器,但是浏览器显示的样式却是不正确的,这是因为在HTML的\标签中我们这样引入了CSS资源: <link ...

  7. [Spring入门学习笔记][创建网站URL]

    设计网站的URL 现代的Web站点都会设计一套拥有明确意义,方便用户记忆的URL,不论是域名还是路径,以天码营为例: http://tianmaying.com/courses表示网站下所有的课程列表 ...

  8. Spring入门学习笔记(1)

    目录 Spring好处 依赖注入 面向面编程(AOP) Spring Framework Core Container Web Miscellaneous 编写第一个程序 IoC容器 Spring B ...

  9. Spring入门学习笔记(4)——JDBC的使用

    目录 Spring JDBC框架概览 JdbcTemplate类 配置数据源 数据访问对象(Data Access Object,DAO) 执行SQL命令 Spring JDBC框架概览 使用传统的J ...

随机推荐

  1. Asus 安装 windows 7

    尊敬的华硕用户您好, 您是不是要让S400从usb和光驱启动呢.可以按如下步骤操作,1.开机的时候长按F2键进入BIOS界面,通过方向键进入[Boot]菜单,通过方向键选择[Lunch CSM]选项, ...

  2. 用C++写出hanoi

    汉诺塔(港台:河內塔)是根据一个传说形成的數學问题有三根杆子A,B,C.A杆上有N个(N>1)穿孔圆盘,盘的尺寸由下到上依次变小.要求按下列规则将所有圆盘移至C杆:-每次只能移动一个圆盘-大的盘 ...

  3. git教程 入门

    快速上传已有代码到github 如何将最新代码上传到github,这里讲本地已有项目文件的情况(假如本地有一个helloworld的工程目录,目录中有很多项目文件.),步骤如下: 前提:已安装git客 ...

  4. forward 和redirect的区别

    1.从地址栏显示来说 forward是服务器请求资源,服务器直接访问目标地址的URL,把那个URL的响应内容读取过来,然后把这些内容再发给浏览器.浏览器根本不知道服务器发送的内容从哪里来的,所以它的地 ...

  5. crontab经验

    1.基本格式  第1列分钟1-59  第2列小时1-23(0表示子夜)  第3列日1-31  第4列月1-12  第5列星期0-6(0表示星期天)  第6列要运行的命令 2.关于日志 (1)基本日志位 ...

  6. js 16进制字符串互转

    /** * 16进制转换为字符串 * @param hex * @returns {*} */ function hexToString(hex) { var tmp = ''; if (hex.le ...

  7. 迷宫问题python实现(靠右手摸墙)

    大家好,我是小鸭酱,博客地址为:http://www.cnblogs.com/xiaoyajiang 这是大二时候的数学模型毕业课程设计,我选择了自己研究盲人穿越迷宫的问题.当然后来再在网上查了这个问 ...

  8. IOS 面试 --- 动画 block

    1 谈谈对Block 的理解?并写出一个使用Block执行UIVew动画? 答案:Block是可以获取其他函数局部变量的匿名函数,其不但方便开发,并且可以大幅提高应用的执行效率(多核心CPU可直接处理 ...

  9. 管理Activity 用户在主界面按两次回退退出系统

    1:定义一个用于管理Activity的类. /* * 用于管理Activity */ public class SysApp extends Application{ private List< ...

  10. CoFun 1616 数字游戏

    Description 一个数x可以按以下规则生成数字: 1.将任意两位交换,若交换的数字为a和b,生成的代价为((a and b)+(a xor b))*2 . 例如134可以生成431,因为431 ...