1. 简介
1.1 AOP概述
AOP(Aspect Orient Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程(OOP)的一种补充和完善(锦上添花)。实际项目中我们通常将面向对象理解为一个静态过程(例如一个系统有多少个模块,一个模块有哪些对象,对象有哪些属性),面向切面理解为一个动态过程(例如,在对象运行时动态织入一些扩展功能或控制对象执行)。如图所示:

AOP 与 OOP 字面意思相近,但其实两者完全是面向不同领域的设计思想。实际项目中我们通常将面向对象理解为一个静态过程(例如一个系统有多少个模块,一个模块有哪些对象,对象有哪些属性),面向切面的运行期代理方式,理解为一个动态过程,可以在对象运行时动态织入一些扩展功能或控制对象执行。
1.2.AOP的作用?
- 代码重用:将一些通用的功能(比如日志记录、安全控制等)抽象出来,形成可重用的模块;
- 降低代码耦合度:将不同的关注点分离开来,这可以避免代码之间的紧耦合;
- 简化开发:使开发人员将关注点从业务逻辑中分离出来,使得开发更加简单明了;
- 提高系统可扩展性:在系统需求变化时,只需要修改AOP模块而不是修改业务逻辑,这可以使得系统更加易于扩展和维护:
1.3. AOP原理分析
假如现在有一个业务对象,这个对象已经实现了一些核心业务,但是我们希望在核心业务的基础上在添加一些拓展业务,而且要求不能对目标业务对象中的实现进行修改(遵循OCP原则-对扩展开放,对修改关闭),请问如何实现?
我们可以这样,自己为目标类创建子类或者为目标类创建兄弟类,对目标业务进行功能拓展。这种方式是可以去实现的,但是假如需要进行业务拓展的类有很多,我们每个类都要基于目标类型进行子类或兄弟类的创建,工作量会比较大,那怎么办呢?
AOP可以在系统启动时为目标类型创建子类或兄弟类型对象,这样的对象我们通常会称之为动态代理对象.如图所示:

其中,为目标类型(XxxServiceImpl)创建其代理对象方式有两种:
第一种:借助JDK官方API(Proxy)为目标对象类型创建其兄弟类型对象.
第二种:借助CGLIB库(Enhancer)为目标对象类型创建其子类类型对象.
2. AOP快速入门
业务描述
在项目中定义一个日志切面(LogAspect),通过切面中的通知方法(添加扩展业务的方法)为目标业务对象做记录日志的功能增强。
- 第1步: 创建SpringBoot工程 _05springaop
-
第2步: 在项目中添加aop应用依赖,代码如下:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
说明:基于此依赖
spring可以整合AspectJ框架快速完成AOP的基本实现。AspectJ是一个面向切面的框架,他定义了AOP的一些语法,有一个专门的字节码生成器来生成遵守java规范的class文件(了解)。
-
第3步: 准备被代理的目标资源
-
接口:
cn.tedu._05springaop.aop.Calculatorpackage cn.tedu._05springaop.aop; public interface Calculator { int add(int m, int n); int sub(int m, int n); int mul(int m, int n); int div(int m, int n); } -
实现类:
cn.tedu._05springaop.aop.CalculatorImplpackage cn.tedu._05springaop.aop; import org.springframework.stereotype.Component; @Component public class CalculatorImpl implements Calculator{ @Override public int add(int m, int n) { int result = m + n; System.out.println("方法内部:" + result); return result; } @Override public int sub(int m, int n) { int result = m - n; System.out.println("方法内部:" + result); return result; } @Override public int mul(int m, int n) { int result = m * n; System.out.println("方法内部:" + result); return result; } @Override public int div(int m, int n) { int result = m / n; System.out.println("方法内部:" + result); return result; } }
-
-
第4步: 创建切面类并配置 LogAspect
用于进行切入点的定义,功能增强方法的定义,在方法内部做日志业务增强,关键代码如下:
package cn.tedu._05springaop.aop; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; /** * Aspect注解:表示这个类是一个切面类 */ @Aspect @Component public class LogAspect { @Before("execution(public int cn.tedu._05springaop.aop.CalculatorImpl.*(..))") public void beforeMethod(JoinPoint joinPoint) { System.out.println("LogAspect类中:前置通知,方法开始记录日志了..."); } } -
第5步: 在测试类中进行测试
@SpringBootTest class ApplicationTests { @Test void contextLoads() { ApplicationContext context = new AnnotationConfigApplicationContext("cn.tedu._05springaop"); Calculator calculator = context.getBean(Calculator.class); int addResult = calculator.add(10, 20); } }

切面对象设计
Spring中通过代理对象(Proxy)调用切面对象(Aspect)的方法为目标对象(Target Service Obect)做功能增强,这个时候就需要我们定义一个切面对象,在切面对象中定义通知方法(这里的方法中我们要写扩展业务),为目标业务进行功能拓展。
3. AOP核心组件分析
3.1. AOP切入点表达式
对于AOP中的切入点表达式可以分成两大类,分别为粗粒度和细粒度表达式:
- 粗粒度的切入点表达式,可以作用于类中所有方法,不能精确指定某个方法。
-
bean("bean的名字"),这里表示bean中的所有方法都是切入点方法。例如bean("calculatorImpl")
@Before("bean('calculatorImpl')") -
within("包名.类名表达式")指定包中指定类或所有类内部的方法都是切入点方法,例如within("cn.tedu._05springaop.aop.*")
-
calculatorImpl在装配CalculatorImpl类时,Spring会自动生成一个小写字母开头的名称(如果前两个字母都是大写(DBean),那就是原类名),当然也可以指定@Component(Value="calculator")
- 细粒度的切入点表达式,可以精确于类中指定的某个方法。
-
execution(权限标识符 方法返回值 方法所在包名.类名.方法名(参数列表))@Before("execution(public int cn.tedu._05springaop.aop.CalculatorImpl.add(int,int)))@Before("execution(* * cn.tedu..*.*(..))")
-
3.2. 切入点表达式语法
- 权限标识符: * 表示权限任意
- 方法返回值: * 表示返回值不限;
- 包名部分: .. 表示包的层次和深度任意;
- 类名部分: * 表示类名任意;
- 方法名部分:* 表示方法名任意;
- 方法参数列表部分:(..) 表示参数列表任意;
3.3. AOP通知方法
在AOP切面中可以有多个通知方法,这些方法可以使用指定注解进行描述:
-
@Before前置通知(此注解描述的方法,会在目标业务执行之前执行) -
@After后置通知(目标业务方法执行结束侯执行,无论是否出现了异常) -
@AfterReturning返回通知(目标业务方法没有异常时执行) -
@AfterThrowing异常通知(目标业务方法抛出异常时执行) -
@Around环绕通知(最重要,可以在此方法内部调用目标业务执行链,优先级最高)
3.4 示例
分析由如上描述的通知方法分别在什么时候执行。
package cn.tedu._05springaop.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/**
* Aspect注解:表示这个类是一个切面类
*/
@Aspect
@Component
public class LogAspect {
/*
@Pointcut注解:定义切入点表达式方法,指定哪些方法需要被增强
*/
@Pointcut("execution(int cn.tedu._05springaop.aop.CalculatorImpl.*(..))")
public void doTime(){}
/**
* 1.Before注解: 前置通知,调用目标方法[add() sub() mul() div()]之前需要执行的方法;
* 2.参数为:切入点表达式,指定哪些方法需要被拦截植入扩展业务逻辑;
*/
@Before("doTime()")
public void beforeMethod(JoinPoint joinPoint) {
/*
getSignature(): 获取连接点签名信息;
getName(): 获取连接点名称;
getArgs(): 获取连接点参数;
*/
String methodName = joinPoint.getSignature().getName();
String args = Arrays.toString(joinPoint.getArgs());
System.out.println("LogAspect类中:前置通知,方法开始记录日志了..." + methodName + ":" + args);
}
/**
* After:
* 1.后置通知,目标方法执行结束[正常结束 || 异常结束]后执行此方法;
* 2.后置通知没有权限获取目标方法的返回值;
*/
@After("doTime()")
public void aftherMethod(JoinPoint joinPoint){
System.out.println("LogAspect类中:后置通知,方法开始记录日志了...");
}
/**
* AfterReturning
* 1.返回通知:目标方法正常结束后执行,异常结束则不会执行;
* 2.返回通知有权限获取目标方法的返回值;
*/
@AfterReturning(value = "doTime()", returning = "result")
public void afterReturningMethod(JoinPoint joinPoint, Object result){
System.out.println("LogAspect类中:返回通知,方法开始记录日志了...");
}
/**
* AfterThrowing
* 1.异常通知: 目标方法抛出异常时执行;
* 2.异常通知有权限拿到目标方法抛出的异常对象;
*/
@AfterThrowing(value = "doTime()", throwing = "ex")
public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex){
System.out.println("LogAspect类中:异常通知,方法开始记录日志了...");
}
/**
* Around
* 1.环绕通知: 等价于 前置通知+后置通知+返回通知+异常通知;
* 2.环绕通知: 有权限执行目标方法!!! 通常使用 try ... catch ... finally 包裹;
*/
@Around("doTime()")
public Object aroundMethod(ProceedingJoinPoint joinPoint){
Object result = null;
try {
System.out.println("环绕通知:目标对象方法执行之前");
//目标对象方法的执行
result = joinPoint.proceed();
System.out.println("环绕通知:目标对象方法执行之后");
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("环绕通知:目标对象方法出现异常");
} finally {
System.out.println("环绕通知:目标对象方法执行完毕");
}
return result;
}
}
3.5. 切面优先级
当有多个切面时,假如你希望这些切面方法的执行有先后顺序,需要通过@Order注解指定切面优先级,例如
@Order(10) //数字越大优先级越低
@Aspect
@Component
public class TimeAspect{
//.....
}
