Spring AOP
AOP 介绍
AOP(Aspect Oriented Programing
:面向切面(方面)编程),是对 OOP 的补充,相对于 OOP 关注的核心任务,AOP 关注的则是一些增益性非核心任务。AOP 技术是一种“横切”技术,它将一些大量重复的、不利于模块重用的横切代码封装成公共可重用模块,从而减少系统重复的代码,降低模块间的耦合度。
Spring AOP 的实现,依赖于动态代理模式,建议学习 AOP 之前,先学习一下代理模式,这样更易于对 AOP 的理解。Spring 使用 JDK 动态代理
和 CGLIB 动态代理
实现 AOP 编程。
JDK 动态代理:
只能针对实现了接口的类进行代理,不能直接对类进行代理;基于反射实现,生成代理对象比较高效;Spring AOP 默认使用 JDK 动态代理。
CGLIB 动态代理:
主要是针对类进行代理(对实现了接口的类也可以代理),对指定的类生成一个子类,生成类的过程效率较低,但执行过程效率很高。类或者类中的方法最好不要使用 final 修饰。使用 CGLIB 需导入 cglib.jar。目前 Spring 已经内嵌了对 CGLIB 的支持,无需再导包。
-
AOP 常见应用场景:日志记录,事务处理,异常处理,性能统计,安全控制等。
-
Spring AOP 实现有三种方式:
注解配置方式
、XML配置方式
、Schema 配置方式
AOP 相关概念
-
连接点:目标类中的方法
-
通知:横切逻辑(比如日志记录、事务处理)。
-
切入点:目标类中切入了通知的连接点
-
切面:通知 + 切入点
-
通知类型:
-
@Before 前置通知:在连接点(目标方法)执行之前执行。
-
@After 后置通知:在连接点执行完毕(正常结束或抛出异常结束)后执行。
-
@AfterReturning 正常返回通知: 在连接点正常执行借宿后执行,如果连接点抛出异常,则不会执行。
-
@AfterThrowing 异常返回通知:在连接点抛出异常后执行。
-
@Around 环绕通知:在连接点执行前后执行,如果连接点抛出异常,则后半部分通知不执行。
-
-
切入点表达式:
-
execution(* 包.*.*(..)) - 向包下所有类的所有方法织入通知。
-
execution(* 包.类.*(..)) - 向包下指定类的所有方法织入通知。
-
execution(* 包.类.方法(..)) - 向包下指定类的指定方法织入通知。
-
jar 包支持
为了使用 Spring AOP 功能,项目中需引入 aspects.jar :
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.3.20</version> </dependency>
准备目标类:
下文中通过对目标类 DemoBean
的方法切入运行日志记录,演示注解方式使用 Spring AOP。
@Component public class DemoBean { public int showInfo(){ System.out.println("show info"); //异常代码 //"".charAt(1); return 1; } public void showMsg(){ System.out.println("show message"); } }
注解配置
@Log4j @Component @Aspect public class LogAspect { @Pointcut("execution(* com.lsy.demo.aop.*.*(..))") public void pointCut(){} @Pointcut("execution(* com.lsy.demo.aop.DemoBean.showInfo(..))") public void pointCut1(){} @Pointcut("execution(* com.lsy.demo.aop.DemoBean.showMsg(..))") public void pointCut2(){} // JoinPoint 即连接点(目标类中的方法),需要就添加此形参,不需要就无需添加 @Before("pointCut()") public void beforeAdvice(JoinPoint joinPoint){ log.info("beforeAdvice: 方法开始执行"); } @After("pointCut()") public void afterAdvice(){ log.info("afterAdvice: 方法执行完毕"); } @AfterThrowing("pointCut()") public void adviceThrow(){ log.info("异常通知:在连接点抛出异常退出时执行的通知"); } @AfterReturning("pointCut()") public void adviceAfterReturn(){ log.info("最终通知:方法执行完毕返回值后执行的通知"); } // ProceedingJoinPoint 即连接点(目标类中的方法) @Around("pointCut()") public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable { log.info("before: 方法开始执行 -------------"); Object obj = joinPoint.proceed(); log.info("after: 方法执行完毕 ---------------"); return obj; } }
启用 AOP
方式1:XML 配置
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- proxy-target-class 默认为false,即使用 JDK 动态代理;true 代表使用 CGLIB动态代理 --> <aop:aspectj-autoproxy proxy-target-class="true"/> </beans>
方式2:注解配置
@Configuration @EnableAspectJAutoProxy public class SpringConfig {}
常用场景示例
@Log4j @Component @Aspect public class DemoAop { @Pointcut("execution(* com.lsy.demo.aop.*.*(..))") public void pointCut(){} /** 1. 日志记录 */ @Around("pc()") public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable { log.info("方法开始执行......"); Object obj = joinPoint.proceed(); log.info("方法结束执行......"); return obj; } /** 2. 性能统计 */ @Around("pc()") public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable { long begin = System.currentTimeMillis(); Object obj = joinPoint.proceed(); //目标类方法执行 long end = System.currentTimeMillis(); System.out.println(end - begin); return obj; } /** 3.异常处理 */ @Around("pc()") public Object aroundAdvice2(ProceedingJoinPoint joinPoint) { Object obj = null; try { obj = joinPoint.proceed(); } catch (Throwable e) { System.out.println(e.getMessage()); e.printStackTrace(); } return obj; } }