## 一、AOP的原理 #### 1、什么是AOP Aspect-Oriented Programming(面向切面编程)是Spring框架的核心特性之一,用于**分离横切关注点**(如日志、事务、安全等与业务逻辑无关的功能)。其核心思想是将这些关注点从业务代码中剥离,集中到**切面(Aspect)** 中管理,从而提升代码的模块化和可维护性。 AOP示意图  **注意**:AOP是采用代理方式实现的 ## 二、用注解方式实现AOP 使用例子:在运行业务方法前、后,输出一段日志。 #### 1、添加`aspectj`依赖 AspectJ编织器将方面应用于Java类。它可以作为一个Java代理,在类加载期间应用加载时编织(LTW),并且包含AspectJ运行时类。 在`pom.xml`配置文件中添加 ```xml org.aspectj aspectjweaver 1.9.24 ``` #### 2、添加注解 在`com.doooog.spring.service`包下,创建`EmpService.java`接口 ```java package com.doooog.spring.service; public interface EmpService { void say(); } ``` 在`com.doooog.spring.service.impl`包下,创建`EmpServiceImpl.java`实现类 ```java package com.doooog.spring.service.impl; import com.doooog.spring.service.EmpService; import org.springframework.stereotype.Service; @Service("empService") public class EmpServiceImpl implements EmpService { @Override public void say() { System.out.println("开始做业务"); } } ``` 新建`com.doooog.spring.advice`包,创建`MyAdvice.java`类,在类头上方添加注解`@Component`、`@Aspect`,`@Aspect`表示这是一个方面代码,也是一个通知对象 ```java package com.doooog.spring.advice; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Component @Aspect public class MyAdvice { public void beforeMethod(JoinPoint joinPoint) { System.out.println("[前置通知日志]" + joinPoint.toString()); } @After("execution(* com.doooog.spring.service.impl.*.*(..))") public void afterMethod(JoinPoint joinPoint){ System.out.println("[后置通知日志]" + joinPoint.toString()); } } ``` **注意**:`@Before`围在业务方法执行前切入,`@After`会在业务方法执行后切入,`execution()`意为`执行Aspect表达式`,是告诉容器在代码的什么位置进行切入,参数解释:任意返回值[`*`] 包[`com.doooog.spring.service.impl`].所有类[`*`].所有方法[`*(`所有参数[`..`]`)`]; 若要切入到含有int参数的方法中应写`*(int, ..)`,若要切入到含有引用数据类型(自己创建的类)参数的方法中应写`*(com.package.Type, ..)`需要写类的全类名,在方法的参数列表中可以加入`JoinPoint`参数,其代表切入到的连接点。 在测试类内调用即可 ```java package com.doooog.spring.test; import com.doooog.spring.controller.UserController; import com.doooog.spring.service.EmpService; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Test { public static void main(String[] args) { // 加载Spring容器(读取Spring配置文件) ApplicationContext context = new ClassPathXmlApplicationContext("com/doooog/spring/SpringConfig.xml"); UserController userController = (UserController) context.getBean("userController"); userController.getUser(); // 此处是切面连接点 EmpService empService = (EmpService) context.getBean("empService"); empService.say(); // 此处是切面连接点 } } ``` #### 3、五种通知类型 ##### 切入点设置 为了避免维护时每个通知的切入点,需要在最上方定义个空方法用来定义切入点位置 ```java @Pointcut("execution(* com.doooog.spring.service.impl.*.*(..))") private void anyMethod() { } ``` ##### 前置通知 在某连接点(JoinPoint 就是要织入的业务方法)之前通知。 ```java @Before("anyMethod()") public void beforeMethod(JoinPoint joinPoint) { System.out.println("[前置通知日志]" + joinPoint.toString()); } ``` ##### 后置通知 当某连接点退出时执行的通知(不论是否发生异常)。 ```java @After("anyMethod()") public void afterMethod(JoinPoint joinPoint) { System.out.println("[后置通知日志]" + joinPoint.toString()); } ``` ##### 返回通知 (最终通知)在这里可以得到业务方法的返回值,但在发生异常时无法得到返回值。 ```java @AfterReturning(pointcut = "anyMethod()", returning = "result") public void afterReturningMethod(JoinPoint joinPoint, Object result) { System.out.println("[返回通知日志], 返回值:" + result + ", " + joinPoint.toString()); } ``` ##### 环绕通知 包围一个连接点(前、后)的通知,也就是在业务方法执行前和执行后执行的通知。 ```java @Around("anyMethod()") public Object aroundMethod(ProceedingJoinPoint pjp) { Object obj = null; System.out.println("[环绕执行前通知日志]" + pjp.toString()); try { obj = pjp.proceed(); System.out.println("[环绕通知返回值]" + obj); System.out.println("[环绕执行后通知日志]" + pjp.toString()); } catch (Throwable e) { System.out.println(e.getMessage()); } return obj; } ``` **注意**:第6行的`pjp.proceed()`必须有,否则不会执行业务方法,若业务也切入了`@Before`、`@After`,那么其切入运行位置取决于`pjp.proceed()`代码的位置。 ##### 异常通知 在业务方法发生异常时执行的通知。 ```java @AfterThrowing(value = "anyMethod()", throwing = "ex") public void afterThrowingMethod(JoinPoint joinPoint, Exception ex) { System.out.println("[异常通知日志], 异常:" + ex.getMessage() + ", " + joinPoint.toString()); } ``` #### 4、动态代理 ##### JDK动态代理 根据目标类`接口`获取代理类实现规则,生成代理对象,这个代理对象也是目标类接口的一个实现类。 ##### CGLib动态代理 根据目标类`本身`获取代理类实现规则,生成代理对象,这个代理对象时目标类的一个子类,如果目标类为`final`类,则无法使用`CGLib`实现动态代理。 ##### SpringAOP代理实现方式 SpringAOP可以自动在两种代理之间进行切换: ① 如果目标类实现了接口,那么通过JDK动态代理实现编织; ② 如果目标类没有实现接口,那么通过CGLib动态代理实现编织,目标类不能为`final`类; ③ 如果目标对象实现了接口,但仍然想要使用CGLib实现编织,可以手动配置。 Loading... ## 一、AOP的原理 #### 1、什么是AOP Aspect-Oriented Programming(面向切面编程)是Spring框架的核心特性之一,用于**分离横切关注点**(如日志、事务、安全等与业务逻辑无关的功能)。其核心思想是将这些关注点从业务代码中剥离,集中到**切面(Aspect)** 中管理,从而提升代码的模块化和可维护性。 AOP示意图  **注意**:AOP是采用代理方式实现的 ## 二、用注解方式实现AOP 使用例子:在运行业务方法前、后,输出一段日志。 #### 1、添加`aspectj`依赖 AspectJ编织器将方面应用于Java类。它可以作为一个Java代理,在类加载期间应用加载时编织(LTW),并且包含AspectJ运行时类。 在`pom.xml`配置文件中添加 ```xml <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.24</version> </dependency> ``` #### 2、添加注解 在`com.doooog.spring.service`包下,创建`EmpService.java`接口 ```java package com.doooog.spring.service; public interface EmpService { void say(); } ``` 在`com.doooog.spring.service.impl`包下,创建`EmpServiceImpl.java`实现类 ```java package com.doooog.spring.service.impl; import com.doooog.spring.service.EmpService; import org.springframework.stereotype.Service; @Service("empService") public class EmpServiceImpl implements EmpService { @Override public void say() { System.out.println("开始做业务"); } } ``` 新建`com.doooog.spring.advice`包,创建`MyAdvice.java`类,在类头上方添加注解`@Component`、`@Aspect`,`@Aspect`表示这是一个方面代码,也是一个通知对象 ```java package com.doooog.spring.advice; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Component @Aspect public class MyAdvice { public void beforeMethod(JoinPoint joinPoint) { System.out.println("[前置通知日志]" + joinPoint.toString()); } @After("execution(* com.doooog.spring.service.impl.*.*(..))") public void afterMethod(JoinPoint joinPoint){ System.out.println("[后置通知日志]" + joinPoint.toString()); } } ``` **注意**:`@Before`围在业务方法执行前切入,`@After`会在业务方法执行后切入,`execution()`意为`执行Aspect表达式`,是告诉容器在代码的什么位置进行切入,参数解释:任意返回值[`*`] 包[`com.doooog.spring.service.impl`].所有类[`*`].所有方法[`*(`所有参数[`..`]`)`]; 若要切入到含有int参数的方法中应写`*(int, ..)`,若要切入到含有引用数据类型(自己创建的类)参数的方法中应写`*(com.package.Type, ..)`需要写类的全类名,在方法的参数列表中可以加入`JoinPoint`参数,其代表切入到的连接点。 在测试类内调用即可 ```java package com.doooog.spring.test; import com.doooog.spring.controller.UserController; import com.doooog.spring.service.EmpService; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Test { public static void main(String[] args) { // 加载Spring容器(读取Spring配置文件) ApplicationContext context = new ClassPathXmlApplicationContext("com/doooog/spring/SpringConfig.xml"); UserController userController = (UserController) context.getBean("userController"); userController.getUser(); // 此处是切面连接点 EmpService empService = (EmpService) context.getBean("empService"); empService.say(); // 此处是切面连接点 } } ``` #### 3、五种通知类型 ##### 切入点设置 为了避免维护时每个通知的切入点,需要在最上方定义个空方法用来定义切入点位置 ```java @Pointcut("execution(* com.doooog.spring.service.impl.*.*(..))") private void anyMethod() { } ``` ##### 前置通知 在某连接点(JoinPoint 就是要织入的业务方法)之前通知。 ```java @Before("anyMethod()") public void beforeMethod(JoinPoint joinPoint) { System.out.println("[前置通知日志]" + joinPoint.toString()); } ``` ##### 后置通知 当某连接点退出时执行的通知(不论是否发生异常)。 ```java @After("anyMethod()") public void afterMethod(JoinPoint joinPoint) { System.out.println("[后置通知日志]" + joinPoint.toString()); } ``` ##### 返回通知 (最终通知)在这里可以得到业务方法的返回值,但在发生异常时无法得到返回值。 ```java @AfterReturning(pointcut = "anyMethod()", returning = "result") public void afterReturningMethod(JoinPoint joinPoint, Object result) { System.out.println("[返回通知日志], 返回值:" + result + ", " + joinPoint.toString()); } ``` ##### 环绕通知 包围一个连接点(前、后)的通知,也就是在业务方法执行前和执行后执行的通知。 ```java @Around("anyMethod()") public Object aroundMethod(ProceedingJoinPoint pjp) { Object obj = null; System.out.println("[环绕执行前通知日志]" + pjp.toString()); try { obj = pjp.proceed(); System.out.println("[环绕通知返回值]" + obj); System.out.println("[环绕执行后通知日志]" + pjp.toString()); } catch (Throwable e) { System.out.println(e.getMessage()); } return obj; } ``` **注意**:第6行的`pjp.proceed()`必须有,否则不会执行业务方法,若业务也切入了`@Before`、`@After`,那么其切入运行位置取决于`pjp.proceed()`代码的位置。 ##### 异常通知 在业务方法发生异常时执行的通知。 ```java @AfterThrowing(value = "anyMethod()", throwing = "ex") public void afterThrowingMethod(JoinPoint joinPoint, Exception ex) { System.out.println("[异常通知日志], 异常:" + ex.getMessage() + ", " + joinPoint.toString()); } ``` #### 4、动态代理 ##### JDK动态代理 根据目标类`接口`获取代理类实现规则,生成代理对象,这个代理对象也是目标类接口的一个实现类。 ##### CGLib动态代理 根据目标类`本身`获取代理类实现规则,生成代理对象,这个代理对象时目标类的一个子类,如果目标类为`final`类,则无法使用`CGLib`实现动态代理。 ##### SpringAOP代理实现方式 SpringAOP可以自动在两种代理之间进行切换: ① 如果目标类实现了接口,那么通过JDK动态代理实现编织; ② 如果目标类没有实现接口,那么通过CGLib动态代理实现编织,目标类不能为`final`类; ③ 如果目标对象实现了接口,但仍然想要使用CGLib实现编织,可以手动配置。 最后修改:2025 年 10 月 17 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 👆请简单粗暴地爱我。