一般来说,AOP的切面我们一般用于记录日志或者在执行某个方法前后进行其他时间的加强处理,主要使用了动态代理模式,这里不再进行原理的讲解,只进行实际使用的分析。
1、首先,我们需要在pom文件中引入相关AOP的依赖:
<!--springAOP依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>2.定义切面类,这个名字你可以随意起,这俩我们就叫AopAspectGloLog吧
package com.example.rabbitmq.utils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.lang.reflect.Modifier;
@Component
@Aspect
public class AopAspectGloLog{
private final Logger logger= LoggerFactory.getLogger(AopAspectGloLog.class);
//描述需要的切入点
@Pointcut("execution(public * com.example.rabbitmq.app..*.*(..))")
//签名,可以理解是这个切入点的一个名称
public void controllerLog(){}
@Pointcut("execution(public * com.example.rabbitmq.app..*.*(..))")
public void unControllerLog(){}
@Before("unControllerLog()||controllerLog()")
public void logBeforeController(JoinPoint joinPoint){
System.out.println("目标方法名为:" + joinPoint.getSignature().getName());
System.out.println("目标方法所属类的简单类名:" + joinPoint.getSignature().getDeclaringType().getSimpleName());
System.out.println("目标方法所属类的类名:" + joinPoint.getSignature().getDeclaringTypeName());
System.out.println("目标方法声明类型:" + Modifier.toString(joinPoint.getSignature().getModifiers()));
//获取传入目标方法的参数
Object[] args = joinPoint.getArgs();
for (int i = 0; i < args.length; i++) {
System.out.println("第" + (i+1) + "个参数为:" + args[i]);
}
System.out.println("被代理的对象:" + joinPoint.getTarget());
System.out.println("代理对象自己:" + joinPoint.getThis());
}
}3.写好后,我们可以去切面位置的控制器里面访问一下试试,执行结果:

4.下面我们介绍一下里面的几个注解和参数:
- @Aspect和@Component
首先,这个@Aspect注释告诉Spring这是个切面类,然后@Compoment将转换成Spring容器中的bean或者是代理bean。 - @PointCut
这个注解包含两部分,PointCut表达式和PointCut签名。表达式是拿来确定切入点的位置的,说白了就是通过一些规则来确定,哪些方法是要增强的,也就是要拦截哪些方法。
关于PointCut里面的execution 的一些参数配置:
execution: 匹配连接点
within: 某个类里面
this: 指定AOP代理类的类型
target:指定目标对象的类型
args: 指定参数的类型
bean:指定特定的bean名称,可以使用通配符(Spring自带的)
@target: 带有指定注解的类型
@args: 指定运行时传的参数带有指定的注解
@within: 匹配使用指定注解的类
@annotation:指定方法所应用的注解注意,由于是动态代理的实现方法,所以不是所有的方法都能拦截得下来,对于JDK代理只有public的方法才能拦截得下来,对于CGLIB只有public和protected的方法才能拦截。
这里我们主要介绍execution的匹配方法,因为大多数时候都会用这个来定义pointcut:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern)
throws-pattern?)
execution(方法修饰符(可选) 返回类型 类路径 方法名 参数 异常模式(可选))除了返回类型,方法名还有参数之外,其他都是可选的
ret-type-pattern:可以为*表示任何返回值,全路径的类名等.
name-pattern:指定方法名,代表所以,set,代表以set开头的所有方法.
parameters pattern:指定方法参数(声明的类型), ()匹配没有参数; (…)代表任意多个参数; ()代表一个参数,但可以是任意型; (,String)代表第一个参数为任何值,第二个为String类型。
下面给几个例子:
execution(public * *(..))——表示匹配所有public方法
execution(* set*(..))——表示所有以“set”开头的方法
execution(* com.xyz.service.AccountService.*(..))——表示匹配所有AccountService接口的方法
execution(* com.xyz.service.*.*(..))——表示匹配service包下所有的方法
execution(* com.xyz.service..*.*(..))——表示匹配service包和它的子包下的方法然后是@PointCut的第二个部分,签名signature,也就是代码中的:
//这就是签名
public void uiControllerLog(){}像方法定义的这个Public void uiControllerLog(){}这个看起来像是方法定义的东西,就是签名,签名没有实际用处,只是用来标记一个Pointcut,可以理解成这个切入点的一个记号。
- @Before
这个是决定advice在切入点方法的什么地方执行的标签,这个注解的意思是在切入点方法执行之前执行我们定义的advice:
@Before("controllerLog() || uiControllerLog()") //在切入点的方法run之前要干的
public void logBeforeController(JoinPoint joinPoint) {}@Before注解括号里面写的是一个切入点,这里看见切入点表达式可以用逻辑符号&&,||,!来描述。 括号里面也可以内置切点表达式,也就是直接写成:
@Before("execution(public * com.stuPayment.uiController..*.*(..))"跟写成@Before(“uiControllerLog()”)的效果是一样的。
然后看到注解下面的方法,就是描述advice的,我们看到有个参数JoinPoint,这个东西代表着织入增强处理的连接点。JoinPoint包含了几个很有用的参数:
Object[] getArgs:返回目标方法的参数
Signature getSignature:返回目标方法的签名
Object getTarget:返回被织入增强处理的目标对象
Object getThis:返回AOP框架为目标对象生成的代理对象
除了注解@Around的方法外,其他都可以加这个JoinPoint作参数。@Around注解的方法的参数一定要是ProceedingJoinPoint,下面会介绍。
- @After
这个注解就是在切入的方法运行完之后把我们的advice增强加进去。一样方法中可以添加JoinPoint。
- @Around
这个注解可以简单地看作@Before和@After的结合。这个注解和其他的比比较特别,它的方法的参数一定要是ProceedingJoinPoint,这个对象是JoinPoint的子类。我们可以把这个看作是切入点的那个方法的替身,这个proceedingJoinPoint有个proceed()方法,相当于就是那切入点的那个方法执行,简单地说就是让目标方法执行,然后这个方法会返回一个对象,这个对象就是那个切入点所在位置的方法所返回的对象。
除了这个Proceed方法(很重要的方法),其他和那几个注解一样。
- @AfterReturning
顾名思义,这个注解是在目标方法正常完成后把增强处理织入。这个注解可以指定两个属性(之前的三个注解后面的括号只写一个@PointCut表达式,也就是只有一个属性),一个是和其他注解一样的PointCut表达式,也就是描述该advice在哪个接入点被织入;然后还可以有个returning属性,表明可以在Advice的方法中有目标方法返回值的形参。
@AfterReturning(returning = "returnOb", pointcut = "controllerLog() || uiControllerLog()")
public void doAfterReturning(JoinPoint joinPoint, Object returnOb) {
System.out.println("##################### the return of the method is : " + returnOb);
}浏览器发出一个请求后,打印结果:
##################### the return of the method is : login(这里是一个请求登录界面的请求,所以uicontroller返回一个String作为视图。)
- @AfterThrowing
异常抛出增强,在异常抛出后织入的增强。有点像上面的@AfterReturning,这个注解也是有两个属性,pointcut和throwing。
用法也和刚刚的那个returning差不多:
@AfterThrowing(pointcut = "controllerLog() || uiControllerLog()", throwing = "ex")
public void doAfterThrowing(JoinPoint joinPoint, Exception ex) {
String methodName = point.getSignature().getName();
List<Object> args = Arrays.asList(point.getArgs());
System.out.println("连接点方法为:" + methodName + ",参数为:" + args + ",异常为:" + ex);
}好了现在注解都介绍完了,这里还要提到上面用到的一个类:RequestContextHolder
//下面两个方法在没有使用JSF的项目中是没有区别的
RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
// RequestContextHolder.getRequestAttributes();
//从session里面获取对应的值
String str = (String) requestAttributes.getAttribute("name",RequestAttributes.SCOPE_SESSION);
HttpServletRequest request = ((ServletRequestAttributes)requestAttributes).getRequest();
HttpServletResponse response = ((ServletRequestAttributes)requestAttributes).getResponse();好了完成了这个切面的编程后,你就成功把日志功能切入到各个controller中了。看个效果图。

最后,再记录一下各个不同的advice的拦截顺序的问题
情况一,只有一个Aspect类:
无异常:@Around(proceed()之前的部分) → @Before → 方法执行 → @Around(proceed()之后的部分) → @After → @AfterReturning
有异常:@Around(proceed(之前的部分)) → @Before → 扔异常ing → @After → @AfterThrowing (大概是因为方法没有跑完抛了异常,没有正确返回所有@Around的proceed()之后的部分和@AfterReturning两个注解的加强没有能够织入)
情况二,同一个方法有多个@Aspect类拦截:
单个Aspect肯定是和只有一个Aspect的时候的情况是一样的,但不同的Aspect里面的advice的顺序呢??答案是不一定,像是线程一样,没有谁先谁后,除非你给他们分配优先级,同样地,在这里你也可以为@Aspect分配优先级,这样就可以决定谁先谁后了。
优先级有两种方式:
- 实现org.springframework.core.Ordered接口,实现它的getOrder()方法
- 给aspect添加@Order注解,该注解全称为:org.springframework.core.annotation.Order
不管是哪种,都是order的值越小越先执行:
@Order(5)
@Component
@Aspect
public class Aspect1 {
// ...
}
@Order(6)
@Component
@Aspect
public class Aspect2 {
// ...
}这样Aspect1就永远比Aspect2先执行了。
注意点:
- 如果在同一个 aspect 类中,针对同一个 pointcut,定义了两个相同的 advice(比如,定义了两个 @Before),那么这两个 advice 的执行顺序是无法确定的,哪怕你给这两个 advice 添加了 @Order 这个注解,也不行。这点切记。
- 对于@Around这个advice,不管它有没有返回值,但是必须要方法内部,调用一下 pjp.proceed();否则,Controller 中的接口将没有机会被执行,从而也导致了 @Before这个advice不会被触发。
















