Spring AOP
Spring AOP(面向切面编程)
AOP 的全称是“Aspect Oriented Programming”,即面向切面编程,和 OOP(面向对象编程)类似,也是一种编程思想。
AOP 采取横向抽取机制(动态代理),取代了传统纵向继承机制的重复性代码,其应用主要体现在事务处理、日志管理、权限控制、异常处理等方面。主要作用是分离功能性需求和非功能性需求,使开发人员可以集中处理某一个关注点或者横切逻辑,减少对业务代码的侵入,增强代码的可读性和可维护性。
简单的说,AOP 的作用就是保证开发者在不修改源代码的前提下,为系统中的业务组件添加某种通用功能。AOP 就是代理模式的典型应用。
目前最流行的 AOP 框架有两个,分别为 Spring AOP 和 AspectJ。
Spring AOP 是基于 AOP 编程模式的一个框架,它能够有效的减少系统间的重复代码,达到松耦合的目的。Spring AOP 使用纯 Java 实现,不需要专门的编译过程和类加载器,在运行期间通过代理方式向目标类植入增强的代码。有两种实现方式:基于接口的 JDK 动态代理和基于继承的 CGLIB 动态代理。
AspectJ 是一个基于 Java 语言的 AOP 框架,从 Spring 2.0 开始,Spring AOP 引入了对 AspectJ 的支持。AspectJ 扩展了 Java 语言,提供了一个专门的编译器,在编译时提供横向代码的植入。
为什么使用AOP
AOP 提供了一种可插入的方式,可以在实际逻辑之前、之后或周围添加其它关注点。比如一个类中有以下 10 个方法。
1 | class A{ |
以 m 开头的方法有 5 种,以 n 开头的方法有 2 种,以 p 开头的方法有 3 种。现在要求在以 m 开头的方法后添加发送通知功能。
在不使用 AOP 的情况下,我们必须修改以 m 开头的 5 种方法,在方法中调用发送通知的方法。
如果使用 AOP,我们不用在方法内调用发送通知的方法,只需要在类的方法中定义切入点,然后在 XML 文件中调用。如果需要删除或修改此功能,那么只需要在 XML 文件中进行更改。由此可以看出,使用 AOP 可以增强代码的可维护性。
AOP术语
为了更好地理解 AOP,我们需要了解一些它的相关术语。这些专业术语并不是 Spring 特有的,有些也同样适用于其它 AOP 框架,如 AspectJ。它们的含义如下表所示。
名称 | 说明 |
---|---|
Joinpoint(连接点) | 指那些被拦截到的点,在 Spring 中,指可以被动态代理拦截目标类的方法。 |
Pointcut(切入点) | 指要对哪些 Joinpoint 进行拦截,即被拦截的连接点。 |
Advice(通知) | 指拦截到 Joinpoint 之后要做的事情,即对切入点增强的内容。 |
Target(目标) | 指代理的目标对象。 |
Weaving(植入) | 指把增强代码应用到目标上,生成代理对象的过程。 |
Proxy(代理) | 指生成的代理对象。 |
Aspect(切面) | 切入点和通知的结合。 |
Advice 直译为通知,也有的资料翻译为“增强处理”,共有 5 种类型,如下表所示。
通知 | 说明 |
---|---|
before(前置通知) | 通知方法在目标方法调用之前执行 |
after(后置通知) | 通知方法在目标方法返回或异常后调用 |
after-returning(返回后通知) | 通知方法会在目标方法返回后调用 |
after-throwing(抛出异常通知) | 通知方法会在目标方法抛出异常后调用 |
around(环绕通知) | 通知方法会将目标方法封装起来 |
AOP 是 Spring 的核心之一,在 Spring 中经常会使用 AOP 来简化编程。在 Spring 框架中使用 AOP 主要有以下优势。
- 提供声明式企业服务,特别是作为 EJB 声明式服务的替代品。最重要的是,这种服务是声明式事务管理。
- 允许用户实现自定义切面。在某些不适合用 OOP 编程的场景中,采用 AOP 来补充。
- 可以对业务逻辑的各个部分进行隔离,从而使业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时也提高了开发效率。
Spring JDK动态代理
Spring JDK 动态代理需要实现 InvocationHandler 接口,重写 invoke 方法,客户端使用 Java.lang.reflect.Proxy 类产生动态代理类的对象。
示例
下面使用 Eclipse IDE 演示 JDK 动态代理,步骤如下:
- 创建 SpringDemo 项目,并在 src 目录下创建 net.biancheng 包。
- 在 net.biancheng 包下创建 UserManager(用户管理接口)、UserManagerImpl(用户管理接口实现类)、MyAspect(切面类)和 JdkProxy(动态代理类)。
- 运行 SpringDemo 项目。
UserManager 类代码如下。
1 | package net.biancheng; |
UserManagerImpl 类代码如下。
1 | package net.biancheng; |
MyAspect 类代码如下。
1 | package net.biancheng; |
MyAspect 类在切面中定义了两个增强的方法,分别为 myBefore 和 myAfter。
JdkProxy 类代码如下。
1 | package net.biancheng; |
运行结果如下。
1 | 方法执行之前 |
Spring CGLlB动态代理
JDK 动态代理使用起来非常简单,但是 JDK 动态代理的目标类必须要实现一个或多个接口,具有一定的局限性。如果不希望实现接口,可以使用 CGLIB代理。
CGLIB(Code Generation Library)是一个高性能开源的代码生成包,它被许多 AOP 框架所使用,其底层是通过使用一个小而快的字节码处理框架 ASM(Java 字节码操控框架)转换字节码并生成新的类。使用 CGLIB 需要导入 CGLIB 和 ASM 包,即 asm-x.x.jar 和 CGLIB-x.x.x.jar 。如果您已经导入了 Spring 的核心包 spring-core-x.x.x.RELEASE.jar,就不用再导入 asm-x.x.jar 和 cglib-x.x.x.jar 了。
Spring 核心包中包含 CGLIB 和 asm,也就是说 Spring 核心包已经集成了 CGLIB 所需要的包,所以在开发中不需要另外导入asm-x.x.jar 和 cglib-x.x.x.jar 包了。
示例
下面使用 Eclipse IDE 演示 CGLIB 动态代理的使用,步骤如下:
- 创建 SpringDemo 项目,并在 src 目录下创建 net.biancheng 包。
- 导入相关 JAR 包。
- 在 net.biancheng 包下创建 UserManager(用户管理接口)、UserManagerImpl(用户管理接口实现类)、MyAspect(切面类)和 CGLIBProxy(动态代理类)。
- 运行 SpringDemo 项目。
UserManager 类代码如下。
1 | package net.biancheng; |
UserManagerImpl 类代码如下。
1 | package net.biancheng; |
MyAspect 类代码如下。
1 | package net.biancheng; |
CglibProxy 类代码如下。
1 | package net.biancheng; |
运行结果如下。
1 | 方法执行之前 |
JDK代理和CGLIB代理的区别
JDK 动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用 InvokeHandler 来处理。而 CGLIB 动态代理是利用 ASM 开源包,加载代理对象类的 class 文件,通过修改其字节码生成子类来处理。
JDK 动态代理只能对实现了接口的类生成代理,而不能针对类。
CGLIB 是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法。因为是继承,所以该类或方法不能声明成 final 类型。
JDK动态代理特点
- 代理对象必须实现一个或多个接口
- 以接口的形式接收代理实例,而不是代理类
CGLIB动态代理特点
- 代理对象不能被 final 修饰
- 以类或接口形式接收代理实例
JDK与CGLIB动态代理的性能比较
生成代理实例性能:JDK > CGLIB
代理实例运行性能:JDK > CGLIB
来源——C语言中文网
g/)**