简介
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 语言,提供了一个专门的编译器,在编译时提供横向代码的植入
spring4.0执行顺序
正常执行顺序
around开始 – before – 方法调用 – around结束 – after 执行 – afterReturning 执行
异常执行顺序
around开始 – before – 方法调用 – around结束 – after 执行 – afterThrowing 执行
spring5.28执行顺序
正常执行顺序
around开始 – before – 方法调用 – afterReturning 执行 – after 执行 – around结束
异常执行顺序
around开始 – before – 方法调用 – afterThrowing 执行 – after 执行 – around结束
普通使用(直接匹配包)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
| import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest; import java.util.Arrays;
@Aspect @Component public class MyAop { private final Logger log = LoggerFactory.getLogger(this.getClass());
@Pointcut("execution(* com.dw.study.controller..*.*(..))") public void aspect() {}
@Before(value = "aspect()") public void before(JoinPoint joinPoint) { log.info("AOP before 执行... :参数类型:{}", joinPoint.getArgs()); }
@After(value = "aspect()") public void after(JoinPoint joinPoint) { log.info("AOP after 执行... :{}", joinPoint.toLongString()); }
@AfterReturning(value = "execution(* com.dw.study.controller..*.*(..))", returning = "result") public void afterReturning(JoinPoint joinPoint, Object result) { log.info("AOP afterReturning 执行... :返回结果:{}", result); }
@Around(value = "aspect()") public Object around(ProceedingJoinPoint proceedingJoinPoint) { try { log.info("AOP around开始... 执行方法... :{}", proceedingJoinPoint.getSignature().getName()); Object proceed = proceedingJoinPoint.proceed(); log.info("AOP around结束... 执行方法... :{}", proceedingJoinPoint.getSignature().getName()); return proceed; } catch (Throwable throwable) { log.info("AOP around 执行错误... error :{}", throwable.getMessage()); throwable.printStackTrace(); return "执行around出错。。。"; } }
@AfterThrowing(value = "aspect()", throwing = "exception") public void afterThrowing(JoinPoint joinPoint, Throwable exception) { log.error("AOP afterThrowing 执行... , msg : {}", exception.getMessage()); if (exception instanceof NullPointerException) log.info("空指针异常"); } }
|
自定义注解匹配(无参)
1.自定义注解
1 2 3 4
| @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface MethodAspect { }
|
2.定义切面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| @Aspect @Component public class MyAop { private final Logger log = LoggerFactory.getLogger(this.getClass());
@Pointcut("@annotation(com.dw.study.customAnnotation.MethodAspect)") public void testMyAnnotationAspect() {}
@Before(value = "testMyAnnotationAspect()") public void before(JoinPoint joinPoint) { log.info("AOP before 执行... :参数类型:{}", joinPoint.getArgs()); } }
|
3.扫描
1 2 3 4 5
| @RequestMapping("hello") @MethodAspect public String hello(){ return "SpringBoot-HelloWorld"; }
|
自定义注解匹配(带参)
1.自定义注解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface MethodAspect {
String value();
String description() default "default description"; }
|
2.定义切面
如果需要获取@annotation中的值,需要和方法参数名相同
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| @Aspect @Component public class MyAop { private final Logger log = LoggerFactory.getLogger(this.getClass());
@Pointcut("@annotation(com.dw.study.customAnnotation.MethodAspect)") public void testMyAnnotationAspect() {}
@Before(value = "testMyAnnotationAspect() && @annotation(methodAspect)") public void before(JoinPoint joinPoint, MethodAspect methodAspect) { log.info("AOP before 执行... :参数类型:{}", joinPoint.getArgs()); log.info("before-value== "+ methodAspect.value() + "before-description== " + methodAspect.description()); ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); System.out.println("URL : " + request.getRequestURL().toString()); System.out.println("HTTP_METHOD : " + request.getMethod()); System.out.println("IP : " + request.getRemoteAddr()); System.out.println("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName()); System.out.println("ARGS : " + Arrays.toString(joinPoint.getArgs())); }
@Around(value = "testMyAnnotationAspect() && @annotation(methodAspect)") public Object around(ProceedingJoinPoint proceedingJoinPoint, MethodAspect methodAspect) { try { log.info("AOP around开始... 执行方法... :{}", proceedingJoinPoint.getSignature().getName()); log.info("around-value== "+ methodAspect.value() + "around-description== " + methodAspect.description()); Object proceed = proceedingJoinPoint.proceed(); log.info("AOP around结束... 执行方法... :{}", proceedingJoinPoint.getSignature().getName()); return proceed; } catch (Throwable throwable) { log.info("AOP around 执行错误... error :{}", throwable.getMessage()); throwable.printStackTrace(); return "执行around出错。。。"; } } }
|
附文
为了更好地理解 AOP,我们需要了解一些它的相关术语。这些专业术语并不是 Spring 特有的,有些也同样适用于其它 AOP 框架,如 AspectJ。
它们的含义如下表所示:
名称 |
说明 |
Joinpoint(连接点) |
指那些被拦截到的点,在 Spring 中,指可以被动态代理拦截目标类的方法。 |
Pointcut(切入点) |
指要对哪些 Joinpoint 进行拦截,即被拦截的连接点。 |
Advice(通知) |
指拦截到 Joinpoint 之后要做的事情,即对切入点增强的内容。 |
Target(目标) |
指代理的目标对象。 |
Weaving(植入) |
指把增强代码应用到目标上,生成代理对象的过程。 |
Proxy(代理) |
指生成的代理对象。 |
Aspect(切面) |
切入点和通知的结合。 |
Advice 直译为通知,也有的资料翻译为“增强处理”,共有 5 种类型,如下表所示:
通知 |
说明 |
before(前置通知) |
通知方法在目标方法调用之前执行 |
after(后置通知) |
通知方法在目标方法返回或异常后调用 |
after-returning(返回后通知) |
通知方法会在目标方法返回后调用 |
after-throwing(抛出异常通知) |
通知方法会在目标方法抛出异常后调用 |
around(环绕通知) |
通知方法会将目标方法封装起来 |
execution表达式:
用于匹配方法执行的连接点,属于方法级别, 语法: execution(修饰符 返回值类型 方法名(参数)异常)
语法参数 |
描述 |
修饰符 |
可选,如public,protected,写在返回值前,任意修饰符填* 号就可以 |
返回值类型 |
必选 ,可以使用* 来代表任意返回值 |
方法名 |
必选 ,可以用* 来代表任意方法 |
参数 |
必选, () 代表是没有参数,(..) 代表是匹配任意数量,任意类型的参数,当然也可以指定类型的参数进行匹配,如要接受一个String类型的参数,则(java.lang.String) , 任意数量的String类型参数:(java.lang.String..) 等等。。。 |
异常 |
可选,语法:throws 异常 ,异常是完整带包名,可以是多个,用逗号分隔 |
execution()表达式案例:
- 拦截
com.dw.study
包下的所有子包里的任意类的任意方法
execution(* com.dw.study..*.*(..))
- 拦截
com.dw.study
.Test2Controller下的任意方法
execution(* **
com.dw.study**.Test2Controller.*(..))
- 拦截任何修饰符为public的方法
execution(public * * (..))
- 拦截
com.dw.study
下的所有子包里的以ok开头的方法
execution(* **
com.dw.study**..*.ok* (..))
JoinPoint
JoinPoint对象封装了SpringAop中切面方法的信息,在切面方法中添加JoinPoint参数,就可以获取到封装了该方法信息的JoinPoint对象。 除 @Around 外,每个通知的方法里都可以加或者不加参数JoinPoint。JoinPoint包含了类名、被切面的方法名、参数等属性。@Around 参数必须为 ProceedingJoinPoint
方法名 |
功能 |
Signature getSignature(); |
获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的Class等信息 |
Object[] getArgs(); |
获取传入目标方法的参数对象 |
Object getTarget(); |
获取被代理的对象 |
Object getThis(); |
获取代理对象 |
ProceedingJoinPoint
ProceedingJoinPoint对象是JoinPoint 的子接口, 该对象只用在@Around的切面方法中:
方法名 |
功能 |
Object proceed() throws Throwable |
执行目标方法 |
Object proceed(Object[] var1) throws Throwable |
传入的新的参数去执行目标方法 |
小结
AOP 是 Spring 的核心之一,在 Spring 中经常会使用 AOP 来简化编程。
在 Spring 框架中使用 AOP 主要有以下优势:
- 提供声明式企业服务,特别是作为 EJB 声明式服务的替代品。最重要的是,这种服务是声明式事务管理。
- 允许用户实现自定义切面。在某些不适合用 OOP 编程的场景中,采用 AOP 来补充。
- 可以对业务逻辑的各个部分进行隔离,从而使业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时也提高了开发效率。
参考