一、学习目标
面向切面的编程(AOP)通过提供另一种思考程序结构的方式来补充面向对象的编程(OOP)。OOP中模块化的关键单位是类,而AOP中模块化的单位是切面。切面使跨越多种类型和对象的关注点(如事务管理)模块化。
spring的关键组件之一是AOP框架。虽然Spring IoC容器并不依赖于AOP(意味着如果你不想使用AOP,就不需要使用),但AOP补充了Spring IoC,提供了一个非常有能力的中间件解决方案。
AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,它旨在提高模块化程度。在AOP中,程序被划分为不同的关注点(或称为切面),这些关注点可以独立地开发、修改和重用。AOP主要用于处理横切关注点(cross-cutting concerns),这些关注点通常跨越多个类或方法,如日志记录、事务管理、安全控制、性能监控等。
二、AOP
AOP的核心概念
-
切面(Aspect):切面是跨多个类和方法的横切关注点的模块化,如事务管理。切面可以定义切入点、通知(增强)等。
-
连接点(Joinpoint):连接点是程序执行中的一个点,如方法的执行或异常的处理。在Spring AOP中,连接点通常是方法的执行点。
-
切入点(Pointcut):切入点是一组连接点的集合,这些连接点通常与特定的类或方法相关。切入点定义了哪些连接点将被增强(即应用通知)。
-
通知(Advice):通知是切面在特定连接点执行的动作。通知有多种类型,包括前置通知(在方法执行前执行)、后置通知(在方法执行后执行)、环绕通知(在方法执行前后执行,并可以决定方法是否继续执行)、异常通知(在方法抛出异常时执行)和最终通知(无论方法是否成功执行,都会执行)。
-
目标对象(Target):目标对象是被一个或多个切面增强的对象。
-
代理(Proxy):代理是由AOP框架创建的对象,用于实现切面功能的织入。代理对象在目标对象的基础上增加了额外的功能。
-
织入(Weaving):织入是将切面应用到目标对象并创建代理对象的过程。织入可以在编译时、类加载时或运行时进行。
三、三种实现方式
1.搭建测试环境
项目结构:
导入依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.2</version>
</dependency>
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>compile</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
接口类
public interface blogService {
public void addBlog();
public void deletBlog();
public void updateBlog();
public void query();
}
接口实现类
public class blogServiceImpl implements blogService{
public void addBlog() {
System.out.println("增加了一篇博客");
}
public void deletBlog() {
}
public void updateBlog() {
}
public void query() {
}
}
xml文件中注册bean
<?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">
<!--注册bean-->
<bean id="blog" class="com.lzh.service.blogServiceImpl"></bean>
</beans>
测试
public class myTest {
@Test
public void test01(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
blogService blog = (blogService) context.getBean("blog");
blog.addBlog();
}
}
2.springAPI实现
通过Spring的AOP功能,实现了在不修改blogServiceImpl
类代码的情况下,为其所有方法执行前后添加额外的逻辑。这是通过定义切入点来指定哪些方法会被拦截,并通过定义顾问来指定在这些方法执行前后执行哪些逻辑(即Before
和After
类中的方法)来实现的。
/**
* 实现了Spring AOP的AfterReturningAdvice接口的类,用于在目标方法正常执行完成后执行一些逻辑。
* 这个类将作为AOP通知(Advice)使用,在Spring配置中需要被注册为一个Bean,并与切点(Pointcut)关联。
*/
public class After implements AfterReturningAdvice {
/**
* 当目标方法正常执行完成后,此方法将被调用。
*
* @param returnValue 目标方法的返回值。如果目标方法没有返回值(即void类型),则此参数为null。
* @param method 被调用的目标方法对象,包含了方法的所有信息(如名称、参数类型等)。
* @param args 传递给目标方法的参数数组。如果目标方法没有参数,则此数组为空。
* @param target 目标对象实例,即被代理的对象。
* @throws Throwable 如果在通知执行过程中发生异常,则此异常将被抛出。
* 注意:如果此通知中抛出了异常,它可能会阻止后续通知(如果有的话)的执行,
* 并且可能会影响到目标方法的返回值(如果目标方法返回void,则不会有影响)。
*/
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
// 输出目标方法执行后的相关信息
System.out.println("执行了" + target.getClass().getName() // 目标对象的类名
+ "的" + method.getName() + "方法," // 目标方法的名称
+ "返回值:" + returnValue); // 目标方法的返回值
}
}
/**
* Before类实现了Spring AOP的MethodBeforeAdvice接口,
* 用于在目标方法执行之前执行一些前置逻辑。
*
* 这个类可以被Spring容器管理,并通过AOP配置应用到指定的目标方法上,
* 以实现无侵入式的日志记录、安全检查、性能监控等功能。
*/
public class Before implements MethodBeforeAdvice {
/**
* before方法会在目标方法执行之前被调用。
*
* @param method 被调用的目标方法的Method对象,包含方法名、参数类型等信息。
* @param args 目标方法执行时传入的参数数组,如果方法没有参数,则为空数组。
* @param target 目标对象,即被代理的对象,实际执行方法的那个对象。
* @throws Throwable 如果前置逻辑执行中抛出异常,则此异常会被传递给调用者,
* 可能导致目标方法不会被执行。
*
* 此方法的主要用途是执行一些前置逻辑,如日志记录、权限检查等,
* 而不改变目标方法的参数或返回值。
*/
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
// 输出目标对象的类名、被调用的方法名以及一个简单的执行通知
System.out.println(target.getClass().getName() + "的" + method.getName() + "方法被执行了");
}
}
<!--注册bean-->
<bean id="blog" class="com.lzh.service.blogServiceImpl"></bean>
<bean id="before" class="com.lzh.service.Before"></bean>
<bean id="after" class="com.lzh.service.After"></bean>
<!--aop的配置-->
<!--切入点 expression:表达式匹配要执行的方法-->
<!--执行环绕; advice-ref执行方法 . pointcut-ref切入点-->
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* com.lzh.service.blogServiceImpl.*(..))"/>
<aop:advisor advice-ref="before" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="after" pointcut-ref="pointcut"/>
</aop:config>
3.自定义类实现
public class MyPointcut {
public void before(){
System.out.println("--before--");
}
public void after(){
System.out.println("--after--");
}
}
修改xml文件:
<!--注册bean-->
<bean id="blog" class="com.lzh.service.blogServiceImpl"></bean>
<bean id="before" class="com.lzh.service.Before"></bean>
<bean id="after" class="com.lzh.service.After"></bean>
<bean id="second" class="com.lzh.config.MyPointcut"></bean>
<!--aop的配置-->
<!--aop:config>
<aop:pointcut id="pointcut" expression="execution(* com.lzh.service.blogServiceImpl.*(..))"/>
<aop:advisor advice-ref="before" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="after" pointcut-ref="pointcut"/>
</aop:config-->
<!--aop的配置-->
<aop:config>
<aop:aspect ref="second">
<aop:pointcut id="myPonitcut" expression="execution(* com.lzh.service.blogServiceImpl.*(..))"/>
<aop:before pointcut-ref="myPonitcut" method="before"/>
<aop:after pointcut-ref="myPonitcut" method="after"/>
</aop:aspect>
</aop:config>
测试结果:
4.注解实现
// 使用@Aspect注解定义一个切面类,用于在指定的方法执行前后或周围进行增强处理
@Aspect
public class AnnotationPointcut {
// 使用@Before注解定义一个前置增强,该增强会在指定方法执行前执行
// "execution(* com.lzh.service.blogServiceImpl.*(..))"是切点表达式,表示匹配com.lzh.service.blogServiceImpl包下所有类的所有方法
@Before("execution(* com.lzh.service.blogServiceImpl.*(..))")
public void before(){
System.out.println("---before---"); // 在匹配的方法执行前打印信息
}
// 使用@After注解定义一个后置增强,该增强会在指定方法执行后执行(无论方法是否抛出异常)
@After("execution(* com.lzh.service.blogServiceImpl.*(..))")
public void after(){
System.out.println("---after---"); // 在匹配的方法执行后打印信息
}
// 使用@Around注解定义一个环绕增强,该增强可以在方法执行前后进行自定义处理
// 环绕增强需要接收一个ProceedingJoinPoint类型的参数,用于控制目标方法的执行
@Around("execution(* com.lzh.service.blogServiceImpl.*(..))")
public Object around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("环绕前"); // 在目标方法执行前打印信息
System.out.println("签名:"+jp.getSignature()); // 打印目标方法的签名信息
// 通过jp.proceed()执行目标方法,并获取其返回值
// 注意:如果目标方法抛出异常,这里也会抛出相同的异常
Object proceed = jp.proceed();
System.out.println("环绕后"); // 在目标方法执行后打印信息
System.out.println(proceed); // 打印目标方法的返回值
// 返回目标方法的执行结果
return proceed;
}
}
<!--注册bean-->
<bean id="blog" class="com.lzh.service.blogServiceImpl"></bean>
<bean id="before" class="com.lzh.service.Before"></bean>
<bean id="after" class="com.lzh.service.After"></bean>
<bean id="second" class="com.lzh.config.MyPointcut"></bean>
<!--aop的配置-->
<!--aop:config>
<aop:pointcut id="pointcut" expression="execution(* com.lzh.service.blogServiceImpl.*(..))"/>
<aop:advisor advice-ref="before" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="after" pointcut-ref="pointcut"/>
</aop:config-->
<!--aop的配置-->
<!--aop:config>
<aop:aspect ref="second">
<aop:pointcut id="myPonitcut" expression="execution(* com.lzh.service.blogServiceImpl.*(..))"/>
<aop:before pointcut-ref="myPonitcut" method="before"/>
<aop:after pointcut-ref="myPonitcut" method="after"/>
</aop:aspect>
</aop:config-->
<bean id="annotationPointcut" class="com.lzh.config.AnnotationPointcut"/>
<aop:aspectj-autoproxy/>