Spring AOP简单介绍
什么是AOP
AOP(Aspect Oriented Programing)面向切面编程,相比较oop面向对象编程来说,Aop关注的不再是程序代码中某个类,某些方法,而考虑的更多的是一种面到面的切入,即层与层之间的一种切入,所以称之为切面。即通过AOP,可以在不修改方法的前提下,在其前后等插入新的功能。
AOP通常用于日志记录、性能统计、安全控制、事务处理等方面,实现公共功能性的重复使用。
AOP其底层是使用动态代理实现的(JDK和CGLIB)
AOP的主要优点如下:
- 降低模块与模块之间的耦合度,提高业务代码的聚合度。(高内聚低耦合)
- 提高了代码的复用性。
- 提高系统的扩展性。(高版本兼容低版本)
- 可以在不影响原有的功能基础上添加新的功能。
AOP基本概念
- Joinpoint(连接点):被拦截到的每个方法,spring aop的一个连接点即代表一个方法的执行。
- Pointcut(切入点):对连接点进行拦截的定义(匹配规则定义规定拦截哪些方法,对哪些方法进行处理),spring有专门的表达式语言定义。
- Advice(通知):拦截到每一个方法后所要做的操作。
- Aspect(切面):切入点与通知的结合,决定了切面的定义,切入点定义了要拦截哪些类的哪些方法,通知则定义了拦截过方法后要做什么,切面则是横切关注点的抽象,与类相似,类是对物体特征的抽象,切面则是横切关注点抽象。
- Target(目标对象):被代理的目标对象。
- Weave(织入):将切面应用到目标对象并生成代理对象的这个过程即为织入。
通知类型
-
Before
(前置通知):执行方法前通知 -
AfterReturning
(返回通知):方法正常结束返回后的通知 -
AfterThrowing
(异常抛出通知):抛出异常时通知 -
After
(最终通知):无论方法是否发生异常,均会执行该通知。在返回通知前执行 -
Around
(环绕通知):包围一个连接点的通知,这是最强大的一种通知类型,可以完成上面几种通知的功能。
实现AOP
首先,我们需要导入相关依赖,如下
<!-- spring AOP -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
然后需要在spring配置文件中添加命名空间
xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
例如
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
实现AOP主要有两种方式,一种是通过注解实现,另一种通过xml实现
通过注解实现AOP
首先要在配置文件中开启自动代理,如下
<!-- 开启自动代理 -->
<aop:aspectj-autoproxy/>
定义切面类,所有的通知注解里都要指定切入点,如下
@Component// 交给IOC容器实例化
@Aspect// 声明当前类是一个切面类
public class LogCut {
// 定义切入点,指定example包及其子包下的所有方法
@Pointcut("execution(* org.example..*.*(..))")
public void cut() {
}
// 前置通知
@Before("cut()")
public void before() {
System.out.println("前置通知");
}
// 返回通知
@AfterReturning("cut()")
public void afterReturning() {
System.out.println("返回通知");
}
// 异常抛出通知,throwing指定接受异常的形参名
@AfterThrowing(value = "cut()", throwing = "e")
public void afterThrowing(Exception e) {
System.out.println("异常抛出通知");
System.out.println(e.getMessage());
}
// 最终通知
@After("cut()")
public void after() {
System.out.println("最终通知");
}
}
接下来只要执行example包下的任一方法,都会进行切面,在对应的位置执行对应的方法。
若要使用环绕通知,如下
@Component// 交给IOC容器实例化
@Aspect// 声明当前类是一个切面类
public class LogCut {
// 定义切入点,指定example包及其子包下的所有方法
@Pointcut("execution(* org.example..*.*(..))")
public void cut() {
}
@Around("cut()")
public Object around(ProceedingJoinPoint pjp){
System.out.println("前置通知");
Object object = null;
try {
object = pjp.proceed();// 执行所调用的方法
System.out.println("返回通知");
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("异常抛出通知");
} finally {
System.out.println("最终通知");
}
return object;
}
}
通过XML实现AOP
若使用xml实现,我们只需要将注解都去除后,在配置文件中添加aop配置即可,例如
<!-- 配置aop -->
<aop:config>
<aop:aspect ref="logCut">
<!-- 定义aop切入点 -->
<aop:pointcut id="cut" expression="execution(* org.example..*.*(..))"/>
<!-- 前置通知 -->
<aop:before method="before" pointcut-ref="cut"/>
<!-- 异常抛出通知 -->
<aop:after-throwing method="afterThrowing" throwing="e" pointcut-ref="cut"/>
<!-- 环绕通知 -->
<aop:around method="around" pointcut-ref="cut"/>
</aop:aspect>
</aop:config>
切入点规则定义
规则如下
execution(方法修饰符 包.类.方法(参数))
其中,除了参数使用..
表示所有参数,其他都使用*
作为通配符。在包中,使用..
表示该包下的类及其子包下的类
例如:
- 拦截所有方法:
execution(* *(..))
- 拦截所有公共的set方法:
execution(public set*(..))
- 拦截com.xxx包下所有类的所有的方法:
execution(* com.xxx.*.*(..))
- 拦截com.xxx包及其子包下所有类的所有的方法:
execution(* com.xxx..*.*(..))