Spring AOP的应用
# 一、介绍
# 1. 什么是Spring AOP
Spring AOP(Aspect-Oriented Programming)是Spring框架的一个重要模块,它提供了一种在不修改源代码的情况下,通过横切关注点的方式来增强应用程序的能力。
AOP通过将应用程序的功能分为核心业务逻辑和横切关注点两个部分,将横切关注点动态地织入到核心业务逻辑中,从而实现了对核心业务逻辑的增强。
# 2. Spring AOP的作用和优势
Spring AOP的优势主要体现在以下几个方面:
- 非侵入性:使用Spring AOP不需要修改原始类的代码,只需通过配置和注解的方式来实现增强,避免了代码的污染。
- 高度可配置:Spring AOP提供了灵活的配置方式,可以根据实际需求来选择切入点、通知类型等,满足不同场景的需求。
- 易于集成:Spring AOP与Spring框架紧密集成,可以很方便地与其他Spring组件(如Spring MVC、Spring Boot等)进行集成。
- 支持多种代理方式:Spring AOP既支持基于JDK动态代理的方式,也支持基于CGLIB动态代理的方式,可以根据实际情况选择合适的代理方式。
Spring AOP的作用主要有以下几个方面:
- 日志记录:通过AOP可以方便地在方法执行前后打印日志,记录方法的调用和返回结果,方便排查问题和监控系统运行情况。
- 性能监控:AOP可以在方法执行前后计时,统计方法的执行时间,帮助开发人员找到系统性能瓶颈。
- 事务管理:通过AOP可以将事务的开启、提交、回滚等操作与业务逻辑解耦,提高代码的可维护性和可测试性。
- 异常处理:AOP可以捕获方法抛出的异常,并进行统一的处理,避免异常信息散落在各个方法中,提高代码的可读性和可维护性。
# 3. Spring AOP的核心概念和术语
在Spring AOP中,有一些核心概念和术语需要了解:
- 切面(Aspect):切面是横切关注点的模块化,它包含了多个通知和切入点。
- 通知(Advice):通知定义了在什么时候、在哪个切入点上应用切面的逻辑。在Spring AOP中,有以下几种通知类型:前置通知(Before)、后置通知(After)、返回通知(AfterReturning)、异常通知(AfterThrowing)和环绕通知(Around)。
- 切入点(Pointcut):切入点是指那些我们希望在应用程序中插入切面的地方。可以通过表达式或注解的方式来定义切入点。
- 连接点(Joinpoint):连接点是在应用程序执行期间能够插入切面的点,比如方法的调用、方法的执行、异常的抛出等。
- 织入(Weaving):织入是指将切面应用到目标对象,并创建一个代理对象的过程。织入可以在编译时、类加载时、运行时等不同的阶段进行。
# 二、实现原理
# 1. 静态代理和动态代理
在介绍Spring AOP的实现原理之前,我们先来了解一下静态代理和动态代理的概念。
静态代理是指在编译时就已经确定了代理类和被代理类的关系,代理类和被代理类是一一对应的。在静态代理中,通过手动编写代理类来增强被代理类的功能。静态代理的缺点是当被代理类的方法发生变化时,代理类也需要相应地进行修改。
动态代理是在运行时动态生成代理类的方式,无需手动编写代理类。动态代理可以分为两种方式:JDK动态代理和CGLIB动态代理。
# 2. JDK动态代理
JDK动态代理是基于接口的代理方式,它通过反射机制来动态生成代理类。在JDK动态代理中,代理类必须实现一个或多个接口,代理类的方法与被代理类的方法一一对应。JDK动态代理通过实现InvocationHandler接口来定义代理类的行为。
JDK动态代理的实现原理是通过Proxy类和InvocationHandler接口来实现的。当使用JDK动态代理时,首先需要定义一个InvocationHandler的实现类,实现invoke方法,在invoke方法中定义代理类的行为。然后使用Proxy类的newProxyInstance方法动态生成代理对象,该代理对象就可以调用被代理类的方法。
下面是一个简单的示例:
首先,定义一个接口UserService
:
public interface UserService {
void saveUser();
}
然后,创建一个实现UserService
接口的目标对象UserServiceImpl
:
public class UserServiceImpl implements UserService {
@Override
public void saveUser() {
System.out.println("保存用户信息");
}
}
接下来,创建一个实现InvocationHandler
接口的代理类UserProxy
:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class UserProxy implements InvocationHandler {
private Object target;
public UserProxy(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理前的处理");
Object result = method.invoke(target, args);
System.out.println("代理后的处理");
return result;
}
}
最后,使用Proxy
类创建代理对象并调用方法:
import java.lang.reflect.Proxy;
public class Main {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
UserProxy userProxy = new UserProxy(userService);
UserService proxy = (UserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(),
userService.getClass().getInterfaces(), userProxy);
proxy.saveUser();
}
}
运行上述代码,输出结果如下:
代理前的处理
保存用户信息
代理后的处理
可以看到,代理对象在调用目标对象的方法前后进行了额外的处理。
需要注意的是,JDK动态代理只能代理接口,不能代理具体的类。如果需要代理具体的类,可以考虑使用其他的代理库,比如CGLIB。
# 3. CGLIB动态代理
CGLIB动态代理是基于类的代理方式,它通过继承被代理类的方式来生成代理类。在CGLIB动态代理中,代理类和被代理类是继承关系,代理类可以直接调用被代理类的方法。
CGLIB动态代理的实现原理是通过Enhancer类和MethodInterceptor接口来实现的。Enhancer类是CGLIB库的核心类,它用于生成代理类。MethodInterceptor接口定义了代理类的拦截逻辑,通过实现该接口来定义代理类的行为。
下面是一个简单的示例:
首先,引入CGLIB库的依赖。在Maven项目中,可以在pom.xml
文件中添加以下依赖:
<dependencies>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
</dependencies>
然后,定义一个目标类UserService
:
public class UserService {
public void saveUser() {
System.out.println("保存用户信息");
}
}
接下来,创建一个实现MethodInterceptor
接口的代理类UserProxy
:
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class UserProxy implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("代理前的处理");
Object result = proxy.invokeSuper(obj, args);
System.out.println("代理后的处理");
return result;
}
}
最后,使用CGLIB库创建代理对象并调用方法:
import net.sf.cglib.proxy.Enhancer;
public class Main {
public static void main(String[] args) {
UserService userService = new UserService();
UserProxy userProxy = new UserProxy();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserService.class);
enhancer.setCallback(userProxy);
UserService proxy = (UserService) enhancer.create();
proxy.saveUser();
}
}
运行上述代码,输出结果如下:
代理前的处理
保存用户信息
代理后的处理
可以看到,代理对象在调用目标对象的方法前后进行了额外的处理。
需要注意的是,CGLIB动态代理可以代理具体的类,而不仅限于接口。但是,CGLIB动态代理生成的代理类是目标类的子类,因此无法代理被final
修饰的类和方法。此外,CGLIB动态代理的性能相对于JDK动态代理来说更好,但是它的使用也更加复杂一些。
# 4. Spring AOP基于动态代理的实现原理
Spring AOP基于动态代理来实现切面的织入。在Spring AOP中,当使用代理对象调用目标方法时,实际上是调用了代理对象的拦截器链,拦截器链中包含了多个通知(Advice)。每个通知在目标方法执行的不同阶段执行相应的逻辑,比如前置通知在目标方法执行前执行,后置通知在目标方法执行后执行。
Spring AOP中的代理对象是通过ProxyFactoryBean来创建的。ProxyFactoryBean是一个FactoryBean,它实现了FactoryBean接口,用于生成代理对象。在创建代理对象时,ProxyFactoryBean会根据配置的切入点和通知来生成代理对象,并将通知织入到代理对象中的拦截器链中。
Spring AOP默认使用JDK动态代理来创建代理对象,如果是对类而非接口进行增强,那么则使用CGLIB动态代理来处理。
# Spring AOP和AspectJ的关系
Spring AOP 是基于 AspectJ 的简化版本,它提供了一种更简单的方式来实现面向切面编程(AOP)。AspectJ 是一个功能强大的面向切面编程语言,它提供了广泛的功能和灵活性。Spring AOP 则是在 AspectJ 的基础上进行了封装和简化,以便更容易集成到 Spring 框架中。
Spring AOP 使用了 AspectJ 的切点表达式语言,并且支持一部分 AspectJ 的注解和通知类型。但是,Spring AOP 并没有实现 AspectJ 的完整语言功能,它只提供了一部分的功能,并且更加专注于与 Spring 框架的集成。
Spring AOP 的主要目标是提供一种轻量级的 AOP 解决方案,方便开发者在 Spring 应用中使用 AOP。相比之下,AspectJ 更加强大和灵活,它提供了更多的功能和更复杂的语法,可以在任何 Java 程序中使用。
总体来说,Spring AOP 是对 AspectJ 的简化版,它提供了一种轻量级的 AOP 解决方案,专注于与 Spring 框架的集成。而 AspectJ 则是一个功能强大的面向切面编程语言,提供了更广泛的功能和更复杂的语法,可以在任何 Java 程序中使用。
# 三、使用方法
# 1. 配置Spring AOP
在使用Spring AOP之前,需要进行一些配置来启用AOP功能。
通过注解配置来启用Spring AOP需要在Spring配置类上添加@EnableAspectJAutoProxy注解:
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
// ...
}
这样就可以自动为带有@Aspect
注解的类创建代理对象,并将代理对象织入到目标对象中。
@EnableAspectJAutoProxy
提供了一些属性,可以用来配置AspectJ自动代理的行为:
proxyTargetClass:指定是否强制使用CGLIB代理,默认为false。如果设置为true,将使用CGLIB代理而不是JDK动态代理来创建代理对象。CGLIB代理可以代理类而不仅仅是接口。
exposeProxy:指定是否将代理对象暴露给AOP代理链中的方法。默认为false。如果设置为true,可以通过AopContext.currentProxy()获取当前代理对象。
# 2. 定义切点和切面
在使用Spring AOP时,需要定义切点和切面。切点定义了在应用程序中哪些连接点应用切面逻辑,切面定义了切面逻辑的具体实现。
# 定义切点
切点可以通过表达式或注解的方式来定义。
下面是 Spring 框架支持的 9 个 AspectJ 切点表达式函数,它们可以根据需求选择并匹配切点:
execution()
:用于匹配方法的执行实例。 示例:- execution(* com.example.service.*(..)) 匹配 com.example.service 包中所有类的所有方法的执行。
- execution(public * *(..)) 匹配所有 public 方法的执行。
within()
:指定目标类,匹配类中的所有方法。 示例:- within(com.example.service.*) 匹配 com.example.service 包中所有类的所有方法。
- within(com.example.service.AccountService) 匹配 com.example.service.AccountService 类的所有方法。
this()
:指定 AOP 代理的类型,以便将 Advices 应用于实现指定接口的任何 Spring bean。 示例:- this(com.example.service.AccountService) 匹配实现 AccountService 接口的任何 Spring bean。
target()
:指定目标对象的类型,以便将 Advices 应用于这些对象的方法。 示例:- target(com.example.service.AccountServiceImpl) 匹配所有在 AccountServiceImpl 中声明的方法。
- target(java.lang.Cloneable) 匹配实现 Cloneable 接口的所有类的所有方法。
args()
:用于匹配参数并允许 Advices 中传递参数。 示例:- args(java.io.Serializable) 匹配接受 Serializable 类型参数的所有方法。
- args(..,java.lang.String) 匹配其最后一个参数为 String 的所有方法。
@target()
:作用目标类标记了指定注解的所有方法。 示例:- @target(org.springframework.stereotype.Repository) 匹配标记了 @Repository 注解的类的所有方法。
@args()
:当执行的方法的参数,包含特定注解时。 示例:- @args(org.springframework.stereotype.Repository) 匹配参数包含 @Repository 注解的所有方法。
@within()
:匹配标记有特定注解的类及其所有子类,只要方法在这个类的层级内(无论是在此类中直接定义还是继承或实现的),都会被匹配。换句话说,它匹配特定注解的类以及其内部所有级别的方法。而@target只会匹配带有特定注解的目标类的方法,如果父类带有此注解但子类没有,则子类的方法无法匹配到。这种表达式只对目标类有效,对其子类无效。 示例:- @within(org.springframework.stereotype.Controller) 匹配标记了 @Controller 注解的类及其所有方法。
@annotation()
:用于匹配当前执行方法持有指定注解的方法。 示例:- @annotation(org.springframework.web.bind.annotation.GetMapping) 匹配标记了 @GetMapping 注解的所有方法。
- @annotation(org.springframework.transaction.annotation.Transactional) 匹配标记了 @Transactional 注解的所有方法。
切点表达式函数使用到的通配符:
*
:星号通配符,用于匹配任何数量的字符,包括零个字符。使用星号通配符可以匹配任何方法名称、参数数量和类型。例如在切入点表达式execution(* com.example.service.*(..))
中,第一个星号代表方法的任何返回值,第二个星号代表com.example.service包中的任何方法。..
:两点通配符,用于匹配任何数量的字符,但这些字符必须表示起码一个完整的类型或完整的方法参数列表。例如在切入点表达式within(com.example.service..*)
中,两点代表com.example.service包及其子包。在execution(* com.example.service.*(..))
中,(..)表示方法的任何数量(包括零个)的参数。+
:加号通配符,用于指定类及其子类(仅适用于接口和类,不适用于方法)。例如,在切入点表达式within(com.example.service.MyService+)
中,加号代表MyService接口及其所有的实现类。
切点表达式函数按其是否支持通配符及支持的程度,可以分为3类:
- 支持所有通配符:execution() within()
- 仅支持+通配符:args() this() target()
- 不支持通配符:@args() @within() @target() @annotation
支持的逻辑运算符:
&&
与。也可以使用and||
或。可以用or替换!
非。可以用not替换
# 定义切面
切面是一个带有@Aspect注解的类,它包含了多个通知和切点。通知定义了在切点处执行的逻辑。切面的定义方式如下:
@Aspect
@Component
public class MyAspect {
// 定义切点
@Pointcut("execution(* com.example.service.*.get*(..))")
public void myPointcut() {}
// 前置通知
@Before("myPointcut()")
public void beforeAdvice() {
// 在目标方法执行前执行的逻辑
}
// 后置通知
@AfterReturning("@annotation(com.example.annotation.Demo) && args(java.lang.String)")
public void afterReturningAdvice() {
// 在目标方法执行后执行的逻辑
}
}
# 3. 编写通知
Spring AOP提供了多种通知类型,可以根据实际需求选择合适的通知类型。
# 前置通知(Before)
前置通知是在目标方法执行之前执行的通知。它可以用于在方法执行之前进行一些预处理操作或者在方法执行前进行一些安全性检查。
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class BeforeAdvice {
@Before("execution(* com.example.MyClass.myMethod(..))")
public void beforeAdvice() {
System.out.println("执行前置通知");
// 进行一些预处理操作或安全性检查
}
}
# 后置通知(After)
后置通知是在目标方法执行之后执行的通知。它可以用于在方法执行之后进行一些清理操作或者记录方法的返回值。
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class AfterAdvice {
@After("execution(* com.example.MyClass.myMethod(..))")
public void afterAdvice() {
System.out.println("执行后置通知");
// 进行一些清理操作或记录返回值
}
}
# 返回通知(AfterReturning)
返回通知是在目标方法成功执行并返回结果后执行的通知。它可以用于记录方法的返回结果或对返回结果进行一些处理。
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class AfterReturningAdvice {
@AfterReturning(pointcut = "execution(* com.example.MyClass.myMethod(..))", returning = "result")
public void afterReturningAdvice(Object result) {
System.out.println("执行返回通知");
// 对返回结果进行处理或记录
}
}
# 异常通知(AfterThrowing)
异常通知是在目标方法抛出异常后执行的通知。它可以用于记录异常信息或进行异常处理。
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class AfterThrowingAdvice {
@AfterThrowing(pointcut = "execution(* com.example.MyClass.myMethod(..))", throwing = "exception")
public void afterThrowingAdvice(Exception exception) {
System.out.println("执行异常通知");
// 对抛出的异常进行处理或记录异常信息
}
}
# 环绕通知(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 AroundAdvice {
@Around("execution(* com.example.MyClass.myMethod(..))")
public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("执行环绕通知 - 前");
// 在目标方法执行之前的操作
Object result = proceedingJoinPoint.proceed(); // 调用目标方法
// 在目标方法执行之后的操作
System.out.println("执行环绕通知 - 后");
return result;
}
}
# 引介通知(DeclareParents)
引介通知是将新的接口和实现引入到现有类中的一种方式。它可以为已有类动态地添加新的接口和方法。
public interface AdditionalFunctionality {
void additionalMethod();
}
public class TargetClass {
public void existingMethod() {
System.out.println("This is an existing method.");
}
}
@Aspect
public class AdditionalFunctionalityAspect {
@DeclareParents(value = "com.example.TargetClass", defaultImpl = AdditionalFunctionalityImpl.class)
private AdditionalFunctionality additionalFunctionality;
// 这里的 AdditionalFunctionalityImpl 是 AdditionalFunctionality 接口的默认实现
// 你可以根据需要自行编写实现类
@Before("execution(* com.example.TargetClass.existingMethod()) && this(additionalFunctionality)")
public void beforeExistingMethod(AdditionalFunctionality additionalFunctionality) {
System.out.println("Before existing method.");
additionalFunctionality.additionalMethod();
}
}
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
TargetClass target = context.getBean(TargetClass.class);
// 使用 AdditionalFunctionality 接口调用新增的方法
AdditionalFunctionality additionalFunctionality = (AdditionalFunctionality) target;
additionalFunctionality.additionalMethod();
context.close();
}
}
# 4. 通知执行的顺序
- 如果增强在同一个切面类中声明,则依靠类中定义的顺序织入
- 如果增强位于不同切面类中,且这些切面类都实现了Ordered接口,则有接口方法顺序号决定(号小的先织入)
- 如果增强位于不同切面类中,且这些切面类都没有实现Ordered接口,则织入的顺序是不确定的
# 5. 访问连接点信息
AspectJ使用JoinPoint接口表示目标类连接点对象,如果是环绕增强,使用ProceedingJoinPoint表示连接点对象。
JoinPoint接口提供了一系列方法,可以获取连接点的信息,如方法名、参数等。
- Object[] getArgs():获取连接点方法运行时的入参列表
- Signature getSignature():获取连接点的方法签名对象
- Object getTarget():获取连接点所在的目标对象
- Object getThis():获取代理对象本身
- static JoinPoint.StaticPart staticPart():获取连接点静态部分
ProceedingJoinPoint接口继承了JoinPoint接口,它新增了两个用于执行连接点方法的方法:
- Object proceed() throws Throwable:通过反射执行目标对象的连接点处的方法
- Object proceed(Object[] args) throws Throwable:通过反射执行目标对象连接点处的方法,传入的参数是args数组
# 6. 绑定连接点方法入参
当使用 args()
、this()
、target()
、@args()
、@within()
、@target()
和 @annotation()
这些切点函数时,可以通过指定参数名,将目标对象连接点上的方法入参绑定到增强方法中。下面是一个示例,演示如何使用这些切点函数并将参数绑定到增强方法中。
假设我们有一个服务类 UserService
,其中有一个方法 addUser(String name, int age)
用于添加用户。我们希望在这个方法执行之前打印出用户的姓名和年龄。
@Service
public class UserService {
public void addUser(String name, int age) {
// 添加用户的逻辑
System.out.println("添加用户:" + name + ", 年龄:" + age);
}
}
接下来,我们使用 @Before
注解结合切点函数来实现增强方法,将方法的入参绑定到增强方法中。
@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.example.UserService.addUser(String, int)) && args(name, age)")
public void beforeAddUser(String name, int age) {
System.out.println("用户的姓名是:" + name);
System.out.println("用户的年龄是:" + age);
}
}
在上面的示例中,我们使用 execution()
切点表达式来匹配 UserService
类的 addUser(String, int)
方法。通过 args(name, age)
,我们将方法的入参 name
和 age
绑定到增强方法 beforeAddUser()
的参数中。
# 四、Spring类中调用自身AOP方法的解决方案
当调用同类中被AOP管理起来的方法时,会出现AOP管理失效的情况。
例如这个类:
@Repository
public class RemarkRecordDao extends BaseDao<RemarkRecord> {
@Override
protected String getTableName() {
return "remark_record";
}
/** 获得最新沟通记录 */
@JpaQuery
public String findRemarkBySourceIdAndSourceTypeSortByIdDesc(Long sourceId, int sourceType) {
...
}
/** 获得线索的最新备注 */
public String findNewestRemarkByClueId(Long id) {
return findRemarkBySourceIdAndSourceTypeSortByIdDesc(id, 1);
}
}
findNewestRemarkByClueId()
方法需要调用类中的findRemarkBySourceIdAndSourceTypeSortByIdDesc()
方法,问题是findRemarkBySourceIdAndSourceTypeSortByIdDesc()
进行了AOP拦截,findNewestRemarkByClueId()
在类中直接调用findRemarkBySourceIdAndSourceTypeSortByIdDesc()
方法,将不会经过AOP拦截
工具类: 写了一个工具类来解决自己调用自己的问题:
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class Aops {
/**
* 为了解决循环aop调用,要使用这个方法 用法:
*
* <pre>
* Aops.getSelf(this)
* </pre>
*
* @param t t一般入参为this,而this只能是类对象,不可以是代理类,这一点要注意
*/
public static <T> T getSelf(T t) {
try {
T currentProxy = (T) AopContext.currentProxy();
// 有时出现currentProxy和t类型不一致,这里做一下判断
if (currentProxy.getClass().isInterface() || currentProxy.getClass().getSuperclass().equals(t.getClass())) {
return currentProxy;
}
} catch (IllegalStateException e) {
// 一般会报错:Cannot find current proxy: Set 'exposeProxy' property on
// Advised to 'true' to make it available.
// 此时表明这个类中没有aop方法,直接返回t即可
log.error("Aop获取自身代理对象失败,对象类型:" + t.getClass());
}
return t;
}
}
上面的代码这样改:
@Repository
public class ErpRemarkRecordDao extends BaseDao<ErpRemarkRecord> {
@Override
protected String getTableName() {
return "erp_remark_record";
}
/** 获得最新沟通记录 */
@JpaQuery
public String findRemarkBySourceIdAndSourceTypeSortByIdDesc(Long sourceId, int sourceType) {
return null;
}
/** 获得线索的最新备注 */
public String findNewestRemarkByClueId(Long id) {
return Aops.getSelf(this).findRemarkBySourceIdAndSourceTypeSortByIdDesc(id, 1);
}
}
Aops.getSelf(this)
获得代理对象,而非当前对象。
需要注意的是,Spring容器中AOP的配置必须为cglib代理(不能用Java Proxy),且exposeProxy=true。
如果用的是Spring Boot,需要在启动类加上注解:
@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)
# 五、总结
本文介绍了Spring AOP的基本概念、实现原理和使用方法。Spring AOP是Spring框架的一个重要模块,通过切面和通知的方式来增强应用程序的功能。通过静态代理和动态代理的方式,Spring AOP可以在不修改源代码的情况下,将横切关注点织入到核心业务逻辑中。
在使用Spring AOP时,需要进行配置来启用AOP功能,并定义切点和切面。切点定义了在应用程序中哪些连接点应用切面逻辑,切面定义了切面逻辑的具体实现。通知则定义了在切点处执行的逻辑,包括前置通知、后置通知、返回通知、异常通知和环绕通知等。
Spring AOP常见的应用场景包括日志记录、性能监控、事务管理和异常处理等。通过在切面中添加相应的通知,可以实现对这些功能的增强。
总而言之,Spring AOP是一个强大的增强代码能力的工具,能够提升应用程序的可维护性、可测试性和可扩展性。合理使用Spring AOP可以使代码更加清晰、简洁,并提高系统的整体质量。
祝你变得更强!