您现在的位置是:首页 >其他 >Spring AOP(面向切面编程)详解网站首页其他
Spring AOP(面向切面编程)详解
文章目录
前言
Spring 提供了功能强大IOC、AOP等功能,前面博主对于 Spring IoC 的内容进行了整理,给出了个人的理解,并模拟实现了 Spring IoC(完成至发现Bean注解的循环依赖),具体内容请见 HB个人博客:Spring之IOC控制反转、DI依赖注入介绍和使用(详解)、模拟实现 IoC
在软件开发过程中,我们通常需要在多个模块或功能中重复执行相同的一段代码。这些重复的代码片段通常是关注点,例如日志记录、安全检查、事务处理等。这种现象会导致代码的重复,影响项目的可维护性和可读性。为了解决这个问题,AOP(面向切面编程)应运而生。博主将详细介绍个人对 Spring AOP 相关概念、使用方法和实现原理的理解。
AOP 概述
在软件业,AOP为(Aspect Oriented Programming)的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
——摘自百度百科AOP 面向切面编程:https://baike.baidu.com/item/AOP/1332219?fromModule=lemma_inlink
AOP(Aspect Oriented Programming,面向切面编程)是一种编程思想,它的主要目的是将通用功能从业务逻辑中抽离出来,以达到代码复用和模块化的目的。
AOP的基本概念
- 切面(Aspect):切面是一个模块化的关注点,通常包含一个或多个通知(Advice)和切入点(Pointcut)的组合。切面的作用是将横切关注点模块化,以便于重用和维护。
- 通知(Advice):通知是切面中执行的具体操作。通知有五种类型:前置通知(Before)、后置通知(After)、返回通知(AfterReturning)、异常通知(AfterThrowing)和环绕通知(Around)。
- 切入点(Pointcut):切入点是一个表达式,用于定义在哪些方法上应用通知。切入点可以使用通配符匹配方法名、参数类型和返回值类型。
- 连接点(JoinPoint):连接点是程序执行过程中的某个特定的点,例如方法执行、异常抛出等。切面可以在连接点附近织入通知。
- 织入(Weaving):织入是将切面代码插入到目标类中的过程。织入可以在编译期(Compile-time)、类加载期(Load-time)或运行期(Runtime)完成。
AOP 应用场景
-
日志记录:可以将日志记录代码从业务逻辑代码中分离出来,使得业务逻辑更加清晰简洁。
-
安全控制:实现安全控制策略。
-
性能监控:实现应用程序性能监控。
-
异常处理:实现异常处理策略。
-
事务管理:实现事务管理策略。
-
缓存管理:实现缓存管理策略。
AOP 常用的接口
-
JoinPoint:表示程序执行的连接点,可以获取被拦截方法的参数和返回值等信息。
-
ProceedingJoinPoint:是 JoinPoint 的一个子接口,它提供了
proceed()
方法,用于调用被拦截的方法。 -
Aspect:是切面的抽象,它由切点和通知组成。
-
Pointcut:切点是用于定义拦截规则的表达式,它可以匹配到程序执行的连接点。
-
Advice:通知是在切点拦截到的连接点上要执行的逻辑,包括Before、After、Around等类型。
Spring 中支持五种类型Advice;
通知类型 连接点 实现接口 前置通知 方法前 org.springframework.aop.MethodBeforeAdvice 后置通知 方法后 org.springframework.aop.MethodBeforeAdvice 环绕通知 方法前后 org.aopalliance.intercept.MethodInterceptor 异常抛出通知 方法抛出异常 org.springframework.aop.ThrowsAdvice 引介通知 类中增加新的方法 org.springframework.aop.IntroductionInterceptor 即 AOP 在不改变原有代码的情况下,去增加新的功能。
-
AopProxy:是 AOP 代理的接口,定义了获取代理对象的方法。
-
ProxyFactory:是 Spring AOP 用于创建和管理代理对象的工厂。
-
TargetSource:是目标对象的接口,定义了获取目标对象的方法。
-
Interceptor:是拦截器的接口,用于拦截方法调用并执行通知逻辑。
Spring AOP 实现方式
Spring AOP主要有两种实现方式:基于XML的配置和基于注解(@AspectJ)的配置。
基于XML的配置
首先在父 module 中导入依赖:
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.20</version>
</dependency>
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
在子 module:aop 的 pom.xml 导入依赖:
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
<scope>runtime</scope>
</dependency>
基于 XML 的配置需要在 Spring 的配置文件中定义切面、通知和切入点。下面是一个基于 XML 的 Spring AOP 配置示例:
<!-- 配置目标类 -->
<bean id="userService" class="com.example.UserService"/>
<!-- 配置切面类 -->
<bean id="logAspect" class="com.example.LogAspect"/>
<!-- 配置AOP -->
<aop:config>
<!-- 定义切面 -->
<aop:aspect id="aspect" ref="logAspect">
<!-- 定义切入点 -->
<aop:pointcut id="pointcut" expression="execution(* com.example.UserService.*(..))"/>
<!-- 定义前置通知 -->
<aop:before method="before" pointcut-ref="pointcut"/>
<!-- 定义后置通知 -->
<aop:after method="after" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
基于注解的配置
基于注解的配置需要在切面类上使用@Aspect注解,并在Spring配置文件中开启自动代理。下面是一个基于注解的Spring AOP配置示例:
@Aspect
public class LogAspect {
@Before("execution(* com.example.UserService.*(..))")
public void before() {
System.out.println("前置通知:方法执行前");
}
@After("execution(* com.example.UserService.*(..))")
public void after() {
System.out.println("后置通知:方法执行后");
}
}
在Spring配置文件中开启自动代理:
<aop:aspectj-autoproxy/>
<bean id="userService" class="com.example.UserService"/>
<bean id="logAspect" class="com.example.LogAspect"/>
Spring AOP 的使用
下面我们以一个简单的用户管理应用为例,演示如何使用Spring AOP实现日志记录功能。
- 导入依赖,见上文XML配置中。
一、Spring 的 API 接口实现AOP
核心:主要是使用 Spring 的 API 接口实现
-
创建接口
IUserService
public interface IUserService { public void add(); public void delete(); public void update(); public void select(); }
-
创建目标类
UserService
public class UserService implements IUserService{ @Override public void add() { System.out.println("增加"); } @Override public void delete() { System.out.println("删除"); } @Override public void update() { System.out.println("修改"); } @Override public void select() { System.out.println("查询"); } }
-
创建日志类
log
public class Log implements MethodBeforeAdvice, AfterReturningAdvice { /** * method:要执行的目标对象的方法 * args:参数 * target:目标对象 */ @Override public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println("执行" + target.getClass().getName() + "的" + method.getName()); } @Override public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println("执行了" + method.getName() + "方法,返回值为" + returnValue); } }
-
创建
applicationContext.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"> <aop:aspectj-autoproxy/> <!--使用原生的 Spring API 接口--> <!--注册 bean--> <bean id="userService" class="com.hb.service.UserService"/> <bean id="log" class="com.hb.log.Log"/> <aop:config> <!--切入点:pointcut;表达式:execution(要执行的位置:正则)--> <aop:pointcut id="pointcut" expression="execution(* com.hb.service.UserService.*(..))"/> <!--增加环绕执行--> <aop:advisor advice-ref="log" pointcut-ref="pointcut"/> </aop:config> </beans>
-
测试以及测试结果
public class Test { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); IUserService userService = (IUserService) context.getBean("userService"); userService.add(); userService.delete(); userService.update(); userService.select(); } }
结果:
二、自定义类实现AOP
核心:主要是切面定义
-
创建一个自定义类:
public class Custom { public void before() { System.out.println("===执行方法前==="); } public void after() { System.out.println("===执行方法后==="); } }
-
创建
applicationContext.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"> <aop:aspectj-autoproxy/> <!--方式二:自定义类--> <bean id="custom" class="com.hb.custom.Custom"/> <aop:config> <!--自定义切面--> <aop:aspect ref="custom"> <!--切入点--> <aop:pointcut id="point" expression="execution(* com.hb.service.UserService.*(..))"/> <!--通知--> <aop:before method="before" pointcut-ref="point"/> <aop:after method="after" pointcut-ref="point"/> </aop:aspect> </aop:config> </beans>
-
测试以及测试结果:
public class Test { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); IUserService userService = (IUserService) context.getBean("userService"); userService.add(); userService.delete(); } }
结果:
三、注解实现AOP
核心:使用注解实现
-
创建目标类:
//使用注解方式实现AOP @Aspect //标注这个类是一个切面 public class PointCut { @Before("execution(* com.hb.service.UserService.*(..))") public void before() { System.out.println("===执行方法前==="); } @After("execution(* com.hb.service.UserService.*(..))") public void after() { System.out.println("===执行方法后==="); } //在环绕通知中,可以给一个参数,代表要获取处理切入的点 @Around("execution(* com.hb.service.UserService.*(..))") public void around(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("===环绕前==="); Object proceed = joinPoint.proceed(); //执行方法 System.out.println("===环绕后==="); } }
-
创建
applicationContext.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"> <!--开启注解支持--> <aop:aspectj-autoproxy/> <bean id="userService" class="com.hb.service.UserService"/> <bean id="log" class="com.hb.log.Log"/> <!--方式三:注解--> <bean id="pointCut" class="com.hb.byAnnotation.PointCut"/> </beans>
-
测试以及测试结果:
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
IUserService userService = (IUserService) context.getBean("userService");
userService.delete();
}
}
运行测试类,结果如下:
模拟实现 Spring AOP
具体实现过程请见 HB个人博客:模拟实现 Spring AOP