目录
AOP
Spring AOP(Aspect-Oriented Programming,面向切面编程)是 Spring 框架的一个重要特性,它允许开发者在不修改原有代码的情况下,对程序进行增强。下面从概念、核心术语、实现方式、应用场景等方面进行详细介绍。
概念
在传统的面向对象编程(OOP)中,代码的组织是基于类和对象的,主要关注业务逻辑的实现。然而,有些功能(如日志记录、事务管理、权限验证等)会散布在多个类和方法中,导致代码的重复和耦合度增加。AOP 则是一种新的编程范式,它将这些横切关注点(如日志、事务等)从业务逻辑中分离出来,形成独立的模块(切面),从而提高代码的可维护性和可复用性。
核心术语
- 切面(Aspect):切面是一个模块化的关注点,它包含了一组通知和切入点。例如,日志记录可以作为一个切面,其中包含了在方法执行前后记录日志的逻辑。
- 通知(Advice):通知定义了在目标方法执行前后或抛出异常时要执行的代码。常见的通知类型包括:
- 前置通知(Before Advice):在目标方法执行之前执行。
- 后置通知(After Advice):在目标方法执行之后执行,无论目标方法是否抛出异常。
- 返回通知(After Returning Advice):在目标方法正常返回后执行。
- 异常通知(After Throwing Advice):在目标方法抛出异常后执行。
- 环绕通知(Around Advice):环绕目标方法的执行,可以在目标方法执行前后进行额外的处理。
- 切入点(Pointcut):切入点定义了哪些方法会被 AOP 增强。可以使用表达式来指定切入点,例如使用 AspectJ 表达式。例如,
execution(* com.example.service.*.*(..))
表示匹配com.example.service
包下的所有类的所有方法。- 连接点(Join Point):连接点是程序执行过程中的一个点,例如方法调用、异常抛出等。在 Spring AOP 中,连接点通常指的是方法调用。
- 目标对象(Target Object):目标对象是被 AOP 增强的对象。
- 代理对象(Proxy Object):代理对象是由 AOP 框架创建的对象,它包含了目标对象的功能,并在目标方法执行前后添加了额外的逻辑。
- 织入(Weaving):织入是将切面应用到目标对象并创建代理对象的过程。织入可以在编译时、类加载时或运行时进行。
实现方式
Spring AOP 支持两种实现方式:基于代理的 AOP 和基于 AspectJ 的 AOP。
基于代理的 AOP
Spring AOP 默认使用基于代理的 AOP 实现,它又分为 JDK 动态代理和 CGLIB 代理。
- JDK 动态代理:基于接口的代理,目标对象必须实现至少一个接口。Spring AOP 会创建一个实现了目标对象接口的代理对象,通过反射机制调用目标方法。
- CGLIB 代理:基于子类的代理,目标对象不需要实现接口。Spring AOP 会创建一个目标对象的子类作为代理对象,通过继承和方法拦截来实现 AOP 增强。
基于 AspectJ 的 AOP
Spring AOP 也支持使用 AspectJ 作为底层实现。AspectJ 是一个功能强大的 AOP 框架,提供了丰富的切面编程语法和工具。Spring AOP 可以与 AspectJ 集成,使用 AspectJ 的注解和语法来定义切面和通知。
示例代码
以下是一个使用 Spring AOP 和 AspectJ 注解实现日志记录的示例:
1. 添加依赖
在 pom.xml
中添加 Spring AOP 和 AspectJ 的依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2. 定义业务接口和实现类
// 业务接口
public interface UserService {
void addUser(String username);
}
// 业务实现类
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
@Override
public void addUser(String username) {
System.out.println("Adding user: " + username);
}
}
3. 定义切面类
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingAspect {
// 前置通知
@Before("execution(* com.example.service.UserService.addUser(..))")
public void beforeAddUser(JoinPoint joinPoint) {
System.out.println("Before adding user: " + joinPoint.getArgs()[0]);
}
// 后置通知
@After("execution(* com.example.service.UserService.addUser(..))")
public void afterAddUser(JoinPoint joinPoint) {
System.out.println("After adding user: " + joinPoint.getArgs()[0]);
}
}
4. 配置 Spring Boot 应用
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
5. 测试代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@Component
public class TestRunner implements CommandLineRunner {
@Autowired
private UserService userService;
@Override
public void run(String... args) throws Exception {
userService.addUser("John");
}
}
应用场景
- 日志记录:在方法执行前后记录日志,方便调试和监控。
- 事务管理:在方法执行前后开启和提交事务,确保数据的一致性。
- 权限验证:在方法执行前进行权限验证,确保用户有权限访问该方法。
- 性能监控:在方法执行前后记录方法的执行时间,用于性能分析。
综上所述,Spring AOP 是一个强大的工具,可以帮助开发者更好地管理横切关注点,提高代码的可维护性和可复用性。
AOP(Aspect-Oriented Programming)
面向切面编程,用于那些与业务无关,但对多个对象产生影响的公共行为和逻辑,抽取公共模块复用,降低耦合度,提高程序可重用性,提高开发效率。
主要功能:记录操作日志、安全控制、事务处理、异常处理等。
AOP实现:对方法前后进行拦截,在方法执行之前开启事务,在执行完目标方法之后根据执行情况提交或者回滚事务
在方法之前和之后实现处理。在运行期会为目标对象生成一个动态代理对象,并在代理对象中实现对目标对象的增强。[用户行为记录、参数的加解密、权限认证、日志记录]
Dynamic /daɪˈnæmɪk/ 动态的
反射机制通过反射我们可以直接操作类或者对象,比如获取某个对象的类定义,获取类声明的属性和方法,调用方法或者构造对象,甚至可以运行时修改类定义。
静态代理:代理类是我们自己定义好的,在程序运行之前就已经编译完成,但是动态代理的代理类是在程序运行时创建的。
SpringAOP思想的实现一般都是基于代理模式 ,在Java中采用JDK动态代理模式,但是JDK动态代理模式只能代理接口而不能代理类。因此SpringAOP会在CGLIB、JDK动态代理之间进行切换。
代理类型
代理类型包括:静态代理、动态代理。
动态代理
AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。
JDK动态代理
要求被代理类必须实现一个接口,核心是InvocationHandler接口和Proxy类。[ˈprɒksi]
核心其实就是代理对象的生成,Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h),通过该方法生成字节码,动态的创建一个代理类。