1. SpringAOP的基本概念
SpringAOP(Aspect-Oriented Programming)即面向切面编程,是Spring框架体系中非常重要的功能模块之一。AOP与OOP(面向对象编程)相辅相成,提供了一种与OOP不同的抽象软件结构的视图。
在OOP中,程序的基本单元是类,而AOP的基本单元是Aspect(切面),切面能够对那些影响多个类的公共行为进行封装。这种“横切”的技术,将封装对象内部剖解开,将那些影响多个类的公共方法封装到一个可重用的模块中,从而减少系统的重复代码,降低模块间的耦合度,提高系统的可操作性和可维护性。
AOP采取横向抽取机制,取代了传统纵向继承体系中的重复性代码。在业务处理代码中,如日志记录、性能统计、安全控制、事务处理、异常处理等操作,尽管使用OOP可以通过封装或继承的方式达到代码的重用,但仍然有相同的代码分散在各个方法中。而AOP则能够将这些操作封装到独立的模块中,使关注点代码与业务代码分离,从而简化了程序结构,提高了代码的可读性和可维护性。
AOP中的关键概念包括:
- Joinpoint(连接点):类中可以被增强的方法。
- Pointcut(切入点):实际被增强的方法,即在类中可以有很多方法被增强,但实际被增强的方法被称为切入点。
- Advice(通知/增强):增强的逻辑,即具体的切面逻辑代码。
总的来说,SpringAOP通过面向切面编程的方式,提供了一种灵活且强大的机制,用于处理那些跨越多个类和方法的公共行为,从而提高了软件系统的可维护性和可扩展性。
AOP中的关键概念包括:
Joinpoint(连接点):类中可以被增强的方法。
Pointcut(切入点):实际被增强的方法,即在类中可以有很多方法被增强,但实际被增强的方法被称为切入点。
Advice(通知/增强):增强的逻辑,即具体的切面逻辑代码。
总的来说,SpringAOP通过面向切面编程的方式,提供了一种灵活且强大的机制,用于处理那些跨越多个类和方法的公共行为,从而提高了软件系统的可维护性和可扩展性。
2.AOP代码执行顺序
在Spring AOP(Aspect-Oriented Programming)中,当使用环绕通知(Around Advice)时,代码的执行顺序遵循以下逻辑:
-
前置通知(Before Advice): 如果定义了前置通知,它会在目标方法执行之前被调用。前置通知主要用于执行一些准备性的工作,比如开启事务、设置参数等。
-
环绕通知(Around Advice)开始: 环绕通知是AOP中最强大的通知类型,因为它允许你在目标方法执行前后进行自定义的逻辑处理。当目标方法被调用时,环绕通知的逻辑开始执行。
-
环绕通知内部逻辑: 在环绕通知中,你可以决定是否继续执行目标方法,以及何时执行。这通过
ProceedingJoinPoint
的proceed()
方法来实现。如果你希望在目标方法执行前做一些处理,可以在调用proceed()
之前完成;如果需要在目标方法执行后做处理,可以在proceed()
之后完成。 -
目标方法执行: 当环绕通知中的
proceed()
方法被调用时,目标方法开始执行。这是实际业务逻辑的部分。 -
环绕通知后续逻辑: 在目标方法执行完毕后,环绕通知的后续逻辑将执行。这通常包括一些清理工作,比如关闭资源、提交或回滚事务等。
-
后置通知(After Advice): 如果定义了后置通知,它会在目标方法执行之后(无论目标方法是否成功执行)被调用。后置通知通常用于执行一些清理或日志记录工作。
-
异常通知(After Throwing Advice): 如果目标方法抛出了异常,并且定义了异常通知,那么异常通知将在异常被抛出后执行。这允许你执行一些特定的异常处理逻辑。
-
返回通知(After Returning Advice): 如果定义了返回通知,并且目标方法成功返回了结果,那么返回通知将在目标方法返回结果之后执行。这通常用于处理方法的返回值。
总结来说,Spring AOP中的代码执行顺序是:前置通知 -> 环绕通知开始 -> 目标方法执行 -> 环绕通知后续逻辑 -> 后置通知/异常通知/返回通知。
注意:以上顺序基于Spring AOP的默认行为,但可以通过配置或编程方式改变通知的执行顺序。
3. SpringAop常用注解
Spring AOP(Aspect-Oriented Programming)常用的注解包括:
-
@Aspect
: 该注解用于声明一个类为切面类,即这个类将包含一些通知(Advice)和切入点(Pointcut)的定义。 -
@Pointcut
: 该注解用于定义一个切入点,切入点是一个表达式,它指定了在哪些方法执行时要应用通知。切入点表达式通常基于方法签名、异常类型、注解等来定义匹配规则。 -
@Before
: 该注解用于定义前置通知(Before Advice),即目标方法执行之前的通知。前置通知常用于执行一些准备性的工作,如设置参数、开启事务、申请资源等。 -
@After
: 该注解用于定义后置通知(After Advice),即目标方法执行之后的通知。后置通知常用于执行一些清理工作,如关闭资源、记录日志等。 -
@AfterReturning
: 该注解用于定义返回后通知(After Returning Advice),即目标方法正常返回之后的通知。这个通知可以获取到目标方法的返回值,并进行相应的处理。 -
@AfterThrowing
: 该注解用于定义异常后通知(After Throwing Advice),即目标方法抛出异常之后的通知。这个通知可以在异常处理逻辑中发挥作用,如记录异常信息、进行回滚操作等。 -
@Around
: 该注解用于定义环绕通知(Around Advice),即在目标方法执行前后都进行通知。环绕通知可以控制目标方法的执行流程,如是否执行目标方法、何时执行目标方法等。
为了使Spring AOP生效,你还需要在Spring配置中启用AspectJ自动代理,这通常通过在配置类上添加@EnableAspectJAutoProxy
注解来实现。同时,你还需要确保切面类被Spring容器管理,通常通过在切面类上添加@Component
注解来实现。
4. 另外举几个例子说明
@Aspect
@Aspect
是 Spring AOP(Aspect-Oriented Programming)中的一个关键注解,用于定义一个切面(Aspect)。切面是 AOP 的核心概念之一,它代表了横切关注点(cross-cutting concerns)的模块化实现。这些关注点通常包括日志记录、事务管理、权限验证等,它们跨越多个应用层次和多个类,因此不适合直接放在业务逻辑代码中。
@Aspect
注解通常标注在一个类上,该类随后会被 Spring 容器识别为一个切面类,并且其中的通知(Advice)和切入点(Pointcut)会被容器自动处理。切面类可以包含多种类型的通知,如前置通知(@Before
)、后置通知(@After
)、返回后通知(@AfterReturning
)、异常后通知(@AfterThrowing
)以及环绕通知(@Around
)。
下面是一个简单的例子,展示了如何使用 @Aspect
注解定义一个切面类:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;@Aspect
@Component
public class LoggingAspect {// 定义一个切入点,匹配 com.example.service 包下的所有类的所有方法@Pointcut("execution(* com.example.service.*.*(..))")public void serviceMethods() {}// 使用 @Before 注解定义一个前置通知@Before("serviceMethods()")public void logBefore() {System.out.println("Method is being called");// 在这里可以添加日志记录的逻辑}// 其他通知方法...
}
在这个例子中,LoggingAspect
类被标注为 @Aspect
,意味着它是一个切面类。该类定义了一个切入点 serviceMethods()
,它匹配 com.example.service
包下所有类的所有方法。然后,使用 @Before
注解定义了一个前置通知 logBefore()
,该通知会在匹配切入点的方法执行之前执行。
要使这个切面类生效,你需要在 Spring 配置中启用 AspectJ 自动代理,这通常通过在配置类上添加 @EnableAspectJAutoProxy
注解来实现。此外,确保切面类被 Spring 容器管理,通常通过在类上添加 @Component
注解来实现。这样,当目标方法被调用时,相应的通知就会根据切入点的规则被触发执行。
@Pointcut
在Spring AOP(Aspect-Oriented Programming)中,@Pointcut
是一个注解,用于定义一个切入点(Pointcut)。切入点是一个表达式,它指定了在哪些方法执行时要应用通知(Advice)。简而言之,@Pointcut
注解允许你定义一个规则,当这个规则匹配时,相应的通知(如前置通知、后置通知、异常通知等)就会被触发。
@Pointcut
注解通常定义在一个方法上,这个方法没有返回类型,并且通常没有方法体(或者方法体为空)。这个方法的名称代表了切入点的名称,而方法的参数则是一个切入点表达式,用于指定哪些方法匹配该切入点。
切入点表达式通常使用AspectJ的表达式语言编写,可以基于方法签名(如方法名、参数类型、返回类型等)、异常类型、注解等来定义匹配规则。
下面是一个简单的例子,展示了如何使用@Pointcut
注解来定义一个切入点:
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.annotation.Aspect;@Aspect
public class MyAspect {// 定义一个切入点,匹配com.example.service包下所有类的所有方法@Pointcut("execution(* com.example.service.*.*(..))")public void serviceMethods() {// 方法体为空,因为@Pointcut注解定义的是一个切入点规则,而不是具体的业务逻辑}// 其他通知(Advice)可以使用这个切入点// ...
}
在这个例子中,serviceMethods
方法定义了一个切入点,它匹配com.example.service
包下所有类的所有方法。之后,你可以在其他通知方法中使用serviceMethods()
作为切入点表达式,来指定这些通知应该在哪些方法执行时应用。
@Pointcut
注解使得切入点定义更加集中和易于管理,你可以在同一个Aspect类中定义多个切入点,然后在不同的通知中重复使用这些切入点。这有助于提高代码的可读性和可维护性。
@Before
在Spring AOP(Aspect-Oriented Programming)中,@Before
注解用于定义前置通知(Before Advice)。前置通知在目标方法执行之前执行,通常用于执行一些准备性的工作,如设置参数、开启事务、申请资源等。
使用@Before
注解的方法通常会接收一个JoinPoint
参数,它代表了一个连接点(即一个方法的执行)。你可以通过这个参数获取关于目标方法的信息,如方法名、参数等。
下面是一个使用@Before
注解的示例:
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;@Aspect
@Component
public class MyAspect {@Before("execution(* com.example.service.*.*(..))")public void logBeforeMethod(JoinPoint joinPoint) {System.out.println("About to execute method: " + joinPoint.getSignature().getName());// 这里可以添加一些准备性的工作,如开启事务、申请资源等}
}
在这个示例中,@Before
注解定义了一个前置通知logBeforeMethod
,它会在com.example.service
包下所有类的所有方法执行之前执行。通知方法接收一个JoinPoint
参数,允许你获取关于即将执行的方法的信息。
前置通知通常用于执行一些与目标方法执行相关的前置条件检查或资源准备工作。例如,你可能需要在执行数据库操作之前开启事务,或者在调用某个服务之前申请必要的资源。
请注意,@Before
注解的切入点表达式决定了哪些方法执行前会触发这个通知。在上面的示例中,切入点表达式execution(* com.example.service.*.*(..))
意味着所有在com.example.service
包下的类的所有方法执行前都会触发这个通知。
最后,和@After
注解一样,你需要在Spring配置中启用AspectJ自动代理,以便Spring能够识别和处理这些方面(Aspects)。这通常通过在配置类上添加@EnableAspectJAutoProxy
注解来实现。
@After
在Spring AOP(Aspect-Oriented Programming)中,@After
注解用于定义后置通知(After Advice)。后置通知在目标方法执行完毕之后执行,无论目标方法是否成功完成(即无论是否抛出异常)。这意味着,即使目标方法抛出异常,后置通知也会被触发。
使用@After
注解的方法通常会接收一个JoinPoint
参数,它代表了一个连接点(即一个方法的执行)。然而,@After
通知通常不需要这个参数,因为它是在目标方法执行完毕之后运行的,此时你已经无法影响目标方法的执行了。
下面是一个使用@After
注解的示例:
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;@Aspect
@Component
public class LoggingAspect {@After("execution(* com.example.service.*.*(..))")public void logAfterMethod() {System.out.println("Method has been executed.");}
}
在这个示例中,@After
注解定义了一个后置通知logAfterMethod
,它会在com.example.service
包下所有类的所有方法执行完毕后执行。通知方法没有接收任何参数,因为它只是简单地打印一条消息,表明方法已经执行完毕。
请注意,@After
注解的切入点表达式决定了哪些方法执行后会触发这个通知。在上面的示例中,切入点表达式execution(* com.example.service.*.*(..))
意味着所有在com.example.service
包下的类的所有方法执行后都会触发这个通知。
后置通知通常用于执行一些清理工作、日志记录或监视任务,这些任务需要在方法执行后但不需要考虑方法是否成功执行的情况下执行。
最后,请记住要在Spring配置中启用AspectJ自动代理,以便Spring能够识别和处理这些方面(Aspects)。这通常通过在配置类上添加@EnableAspectJAutoProxy
注解来实现。
@AfterReturning
@AfterReturning
是 Spring AOP(Aspect-Oriented Programming)中的一个注解,用于定义一个返回后通知(After Returning Advice)。这种通知会在被切点方法正常执行并返回之后执行,它允许你访问到被切点方法的返回值,并根据这个返回值进行一些后续的处理操作。
这个注解通常用于执行一些在方法执行成功后的逻辑,比如根据方法的返回结果更新某些状态、记录日志、发送通知等。
@AfterReturning
注解有几个重要的属性:
pointcut
或value
:用于指定切入点表达式,确定哪些方法的执行会触发这个通知。returning
:指定一个参数名,这个参数会接收被切点方法的返回值,你可以在通知方法中通过这个参数访问到返回值。
下面是一个使用 @AfterReturning
的例子:
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;@Aspect
@Component
public class LoggingAspect {// 定义一个切入点,匹配 com.example.service 包下的所有类的所有方法@Pointcut("execution(* com.example.service.*.*(..))")public void serviceMethods() {}// 使用 @AfterReturning 注解定义一个返回后通知@AfterReturning(pointcut = "serviceMethods()", returning = "result")public void logAfterReturning(JoinPoint joinPoint, Object result) {System.out.println("Method executed successfully: " + joinPoint.getSignature().getName());System.out.println("Returned value: " + result);// 在这里可以根据返回值进行一些处理,比如记录日志、更新状态等}
}
在这个例子中,logAfterReturning
方法是一个返回后通知,它会在 serviceMethods()
切入点匹配的方法成功执行后执行。通过 returning = "result"
指定了一个参数名 result
,这个参数会接收被切点方法的返回值,然后在通知方法中打印出来。
要使这个切面类生效,你需要在 Spring 配置中启用 AspectJ 自动代理,并确保切面类被 Spring 容器管理。这样,当目标方法成功执行并返回后,相应的返回后通知就会根据切入点的规则被触发执行。
@AfterThrowing
@AfterThrowing
是 Spring AOP(Aspect-Oriented Programming)中的一个注解,用于定义一个异常后通知(After Throwing Advice)。这个通知在被切点方法抛出异常时执行,允许你在方法发生异常时执行一些逻辑,如记录异常信息、进行清理工作、回滚事务等。
@AfterThrowing
注解有几个重要的属性:
pointcut
或value
:用于指定切入点表达式,确定哪些方法的异常会触发这个通知。throwing
:指定一个参数名,该参数将接收被切点方法抛出的异常对象,允许你在通知方法中访问并处理这个异常。
下面是一个使用 @AfterThrowing
的例子:
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;@Aspect
@Component
public class ExceptionHandlingAspect {// 定义一个切入点,匹配 com.example.service 包下的所有类的所有方法@Pointcut("execution(* com.example.service.*.*(..))")public void serviceMethods() {}// 使用 @AfterThrowing 注解定义一个异常后通知@AfterThrowing(pointcut = "serviceMethods()", throwing = "ex")public void handleException(JoinPoint joinPoint, Throwable ex) {System.out.println("Method failed: " + joinPoint.getSignature().getName());System.out.println("Thrown exception: " + ex.getMessage());// 在这里可以记录异常信息、进行清理工作、回滚事务等}
}
在这个例子中,handleException
方法是一个异常后通知,它会在 serviceMethods()
切入点匹配的方法抛出异常时执行。通过 throwing = "ex"
指定了一个参数名 ex
,这个参数会接收被切点方法抛出的异常对象,然后在通知方法中打印出异常信息。
要使这个切面类生效,你需要在 Spring 配置中启用 AspectJ 自动代理,并确保切面类被 Spring 容器管理。这样,当目标方法抛出异常时,相应的异常后通知就会根据切入点的规则被触发执行。
通过结合使用 @Pointcut
来定义复用的切入点表达式,你可以使通知的定义更加简洁和可维护。同时,Spring AOP 还支持其他类型的通知,如 @Before
(前置通知)、@After
(后置通知)和 @Around
(环绕通知),它们提供了在方法执行的不同阶段执行自定义逻辑的能力。
@Around
在Spring AOP中,@Around注解用于定义环绕通知(Around Advice),它是最强大的一种通知类型,因为它允许你在目标方法执行前后进行自定义的逻辑处理,并且可以决定是否继续执行目标方法以及何时执行。
当你使用@Around注解时,你需要定义一个方法,这个方法将接收一个ProceedingJoinPoint参数,这个参数代表了被通知方法的执行点。你可以在这个方法内部实现自己的逻辑,比如前置处理、后置处理、异常处理等。
下面是一个使用@Around注解的示例:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component; @Aspect
@Component
public class MyAspect { @Around("execution(* com.example.service.*.*(..))") public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable { long start = System.currentTimeMillis(); Object proceed = joinPoint.proceed(); // 继续执行目标方法 long executionTime = System.currentTimeMillis() - start; System.out.println(joinPoint.getSignature() + " executed in " + executionTime + "ms"); return proceed; }
}
在这个示例中,@Around注解用于定义一个环绕通知,它会在com.example.service包下所有类的所有方法执行前后起作用。通知方法logExecutionTime接收一个ProceedingJoinPoint参数,允许你在执行目标方法之前和之后插入自定义逻辑。
在logExecutionTime方法中:
记录方法开始执行的时间。
调用proceed()方法执行目标方法。
记录方法执行结束的时间,并计算执行时长。
打印方法的执行时长。
如果proceed()方法没有被调用,那么目标方法将不会被执行。这意味着你可以在环绕通知中根据某些条件决定是否继续执行目标方法。
请注意,为了使用@Around注解,你需要在Spring配置中启用AspectJ自动代理,这通常通过在配置类上添加@EnableAspectJAutoProxy注解来实现。
此外,@Around注解中的切入点表达式(Pointcut Expression)决定了哪些方法会被这个通知所影响。在上面的示例中,切入点表达式execution(* com.example.service..(…))意味着所有在com.example.service包下的类的所有方法都会被这个通知所影响。你可以根据需要调整切入点表达式以匹配特定的方法。
暂时写到这里,以后会接着更新文档