1.Spring AOP的基本概念
1.1 AOP 的概念
AOP (Aspect-Oriented Programming) 即面向切面编程,它与OOP (Object-OrientedProgramming,面向对象编程)相辅相成, 提供了与OOP不同的抽象软件结构的视角。在OOP中,以类作为程序的基本单元,而AOP中的基本单元是Aspect (切面)。Struts2的拦截器设计就是基于AOP的思想,是个比较经典的应用。
在业务处理代码中通常有日志记录、性能统计、安全控制、事务处理、异常处理等操作。尽管使用OOP可以通过封装或继承的方式达到代码的重用,但仍然有同样的代码分散。在各个方法中。因此,采用00P处理日志记录等操作不仅增加了开发者的工作量,而且提高了升级维护的困难。为了解决此类问题,AOP 思想应运而生。AOP采取横向抽取机制,即将分散在各个方法中的重复代码提取出来,然后在程序编译或运行阶段将这些抽取出来的代码应用到需要执行的地方。这种横向抽取机制采用传统的OOP是无法办到的,因为00P实现的是父子关系的纵向重用。但是AOP不是OOP的替代品,而是OOP的补充,它们相辅相成。
在AOP中,横向抽取机制的类与切面的关系如图所示:
如图,通过切面Aspect分别在业务类1和业务类2中加入了一系列操作。
1.2 AOP的术语
切面
切面(Aspect)是指封装 横切到系统功能(例如事务处理)的类。
连接点
连接点(Joinpoint)是指程序运行中的一些时间点,例如方法的调用或异常的抛出。
切入点
切入点(Poincut) 是指需要处理的连接点。在Spring AOP中,所有的方法执行都是连接点,而切入 点是个描述信息, 它修饰的是连接点,通过切入点确定哪些连接点需要被处理。
- 通知
通知(Advice)是由切面添加到特定的连接点(满足切入点规则)的一段代码,即在定义好的切入点处 所要执行的程序代码,可以将其理解为切面开启后切面的方法,因此通知是切面的具体实现,
- 引入
引入(Introduction) 允许在现有的实现类中添加自定义的方法和属性。
- 目标对象
目标对象(Target Object)是指所有被通知的对象。如果AOP框架使用运行时代理的方式(动态的AOP)来实现切面,那么通知对象总是一个代理对象。 - 代理
代理(Proxy)是通知应用到目标对象之后被动态创建的对象。
- 织入
织入(Weaving) 是将切面代码插入到目标对象上,从而生成代理对象的过程。根据不同的实现技术,AOP 织入有3种方式:编译期织入,需要有特殊的Java编译器;类装载期织入,需要有特殊的类装载器;动态代理织入,在运行期为目标类添加通如生成子类的方式。Spring AOP框架默认采用动态代理织入,而AspetJ(基于Java 语言的AOP概架) 采用编译器织入和类装载期织入。
2. 动态代理
2.1 JDK动态代理
JDK动态代理是java.lang.reflect.*包提供的方式,它必须借助一个接口才能产生代理对象。因此,对于使用业务接口的类,Spring 默认使用JDK动态代理实现AOP.下面通过一个实例演示如何使用JDK动态代理实现Spring AOP,具体步骤如下:
- Userdao.java
1 | package JDk; |
- UserdaoImpl.java
1 | package JDk; |
- Myaspect.java
1 | package JDk; |
- jdkproxy.java
1 | package JDk; |
- jdktest.java
1 | package JDk; |
运行结果:
模拟权限控制
模拟异常处理
保存
模拟日志记录
性能检测
============
模拟权限控制
模拟异常处理
修改
模拟日志记录
性能检测
============
模拟权限控制
模拟异常处理
删除
模拟日志记录
性能检测
2.2 CGLIB动态代理
CGLIB (Code Generation Library)是一个高性能开源的代码生成包,采用非常底层的字节码技术,对指定的目标类生成一个子类, 并对子类进行增强。在Spring Core包中已经集成了CGLIB所需要的JAR包,不需要另外导入JAR包。下面通过一个实例演示CGLIB动态代理的实现过程,具体步骤如下:
Testdao.java
1 | package CGLIB; |
CGLiBproxy.java
1 | package CGLIB; |
CGLIBProxyTest.java
1 | package CGLIB; |
运行结果:
模拟权限控制
模拟异常处理
保存
模拟日志记录
性能检测
=====================
模拟权限控制
模拟异常处理
修改
模拟日志记录
性能检测
=====================
模拟权限控制
模拟异常处理
删除
模拟日志记录
性能检测
3. 基于xml配置实现AspectJ
1:定义UserController
1 | public class UserController { |
2:定义切面类 实现功能:保存用户操作记录
1 | import org.aspectj.lang.JoinPoint; |
3:配置xml
1 | <!-- 配置Bean --> |
4:测试打印
1 |
|
五:纯注解方式实现AspectJ
1:定义UserController,同上
2:定义切面类
1 | import org.aspectj.lang.JoinPoint; |
3:注解配置类
1 | import org.springframework.context.annotation.Bean; |
或者使用xml
1 | <!-- 开启Spring支持AspectJ注解 --> |
4:测试打印
1:注解配置类对应的测试方法
1 |
|
2:xml配置对应的测试类
1 |
|
注意一:JoinPoint joinPoint在方法的参数列表中,必须放在第一位,否则会报错
注意二:@AfterReturning和@AfterThrowing必须配置对应的returning和throwing参数
Spring AOP 最热门面试题及答案
内容大纲:
- 描述一下Spring AOP?
- 在Spring AOP中关注点(concern)和横切关注点(cross-cutting concern)有什么不同?
- AOP有哪些可用的实现?
- Spring中有哪些不同的通知类型(advice types)?
- Spring AOP 代理是什么?
- 引介(Introduction)是什么?
- 连接点(Joint Point)和切入点(Point Cut)是什么?
- 织入(Weaving)是什么?
描述一下Spring AOP
Spring AOP(Aspect Oriented Programming,面向切面编程)是OOPs(面向对象编程)的补充,它也提供了模块化。在面向对象编程中,关键的单元是对象,AOP的关键单元是切面,或者说关注点(可以简单地理解为你程序中的独立模块)。一些切面可能有集中的代码,但是有些可能被分散或者混杂在一起,例如日志或者事务。这些分散的切面被称为横切关注点。一个横切关注点是一个可以影响到整个应用的关注点,而且应该被尽量地集中到代码的一个地方,例如事务管理、权限、日志、安全等。
AOP让你可以使用简单可插拔的配置,在实际逻辑执行之前、之后或周围动态添加横切关注点。这让代码在当下和将来都变得易于维护。如果你是使用XML来使用切面的话,要添加或删除关注点,你不用重新编译完整的源代码,而仅仅需要修改配置文件就可以了。
Spring AOP通过以下两种方式来使用。但是最广泛使用的方式是Spring AspectJ 注解风格(Spring AspectJ Annotation Style)
- 使用AspectJ 注解风格
- 使用Spring XML 配置风格
在Spring AOP中关注点和横切关注点有什么不同?
关注点是我们想在应用的模块中实现的行为。关注点可以被定义为:我们想实现以解决特定业务问题的方法。比如,在所有电子商务应用中,不同的关注点(或者模块)可能是库存管理、航运管理、用户管理等。
横切关注点是贯穿整个应用程序的关注点。像日志、安全和数据转换,它们在应用的每一个模块都是必须的,所以他们是一种横切关注点。
AOP有哪些可用的实现?
基于Java的主要AOP实现有:
- AspectJ
- Spring AOP
- JBoss AOP
在维基百科上你可以找到一个AOP实现的大列表。
Spring中有哪些不同的通知类型
通知(advice)是你在你的程序中想要应用在其他模块中的横切关注点的实现。Advice主要有以下5种类型:
- 前置通知(Before Advice): 在连接点之前执行的Advice,不过除非它抛出异常,否则没有能力中断执行流。使用
@Before
注解使用这个Advice。 - 返回之后通知(After Retuning Advice): 在连接点正常结束之后执行的Advice。例如,如果一个方法没有抛出异常正常返回。通过
@AfterReturning
关注使用它。 - 抛出(异常)后执行通知(After Throwing Advice): 如果一个方法通过抛出异常来退出的话,这个Advice就会被执行。通用
@AfterThrowing
注解来使用。 - 后置通知(After Advice): 无论连接点是通过什么方式退出的(正常返回或者抛出异常)都会执行在结束后执行这些Advice。通过
@After
注解使用。 - 围绕通知(Around Advice): 围绕连接点执行的Advice,就你一个方法调用。这是最强大的Advice。通过
@Around
注解使用。
Spring AOP 代理是什么?
代理是使用非常广泛的设计模式。简单来说,代理是一个看其他像另一个对象的对象,但它添加了一些特殊的功能。
Spring AOP是基于代理实现的。AOP 代理是一个由 AOP 框架创建的用于在运行时实现切面协议的对象。
Spring AOP默认为 AOP 代理使用标准的 JDK 动态代理。这使得任何接口(或者接口的集合)可以被代理。Spring AOP 也可以使用 CGLIB 代理。这对代理类而不是接口是必须的。
如果业务对象没有实现任何接口那么默认使用CGLIB。
引介(Introduction)是什么?
引介让一个切面可以声明被通知的对象实现了任何他们没有真正实现的额外接口,而且为这些对象提供接口的实现
使用 @DeclareParaents
注解来生成一个引介。
更多详情,请参考 官方文档
连接点(Joint Point)和切入点(Point cut)是什么?
连接点是程序执行的一个点。例如,一个方法的执行或者一个异常的处理。在 Spring AOP 中,一个连接点总是代表一个方法执行。举例来说,所有定义在你的 EmpoyeeManager
接口中的方法都可以被认为是一个连接点,如果你在这些方法上使用横切关注点的话。
切入点(切入点)是一个匹配连接点的断言或者表达式。Advice 与切入点表达式相关联,并在切入点匹配的任何连接点处运行(比如,表达式 execution(* EmployeeManager.getEmployeeById(...))
可以匹配 EmployeeManager
接口的 getEmployeeById()
)。由切入点表达式匹配的连接点的概念是 AOP 的核心。Spring 默认使用 AspectJ 切入点表达式语言。
什么是织入(weaving)?
Spring AOP 框架仅支持有限的几个 AspectJ 切入点的类型,它允许将切面运用到在 IoC 容器中声明的 bean 上。如果你想使用额外的切入点类型或者将切面应用到在 Spring IoC 容器外部创建的类,那么你必须在你的 Spring 程序中使用 AspectJ 框架,并且使用它的织入特性。
织入是将切面与外部的应用类型或者类连接起来以创建通知对象(adviced object)的过程。这可以在编译时(比如使用 AspectJ 编译器)、加载时或者运行时完成。Spring AOP 跟其他纯 Java AOP 框架一样,只在运行时执行织入。在协议上,AspectJ 框架支持编译时和加载时织入。
AspectJ 编译时织入是通过一个叫做 ajc 特殊的 AspectJ 编译器完成的。它可以将切面织入到你的 Java 源码文件中,然后输出织入后的二进制 class 文件。它也可以将切面织入你的编译后的 class 文件或者 Jar 文件。这个过程叫做后编译时织入(post-compile-time weaving)。在 Spring IoC 容器中声明你的类之前,你可以为它们运行编译时和后编译时织入。Spring 完全没有被包含到织入的过程中。更多关于编译时和后编译时织入的信息,请查阅 AspectJ 文档。
AspectJ 加载时织入(load-time weaving, LTW)在目标类被类加载器加载到JVM时触发。对于一个被织入的对象,需要一个特殊的类加载器来增强目标类的字节码。AspectJ 和 Spring 都提供了加载时织入器以为类加载添加加载时织入的能力。你只需要简单的配置就可以打开这个加载时织入器。