Spring AOP 入门教程
Spring AOP(面向切面编程)是 Spring 框架的一个核心模块,用于实现面向切面编程。
它通过在运行时动态地将额外的功能(如日志、事务管理等)织入到应用程序的代码中,以提供更好的模块化、更清晰的代码和更好的可维护性。
核心概念
-
切面(Aspect): 切面是一个模块化的单元,用于横切关注点(cross-cutting concerns)的实现。例如,日志、事务管理等功能可以作为切面。
-
连接点(Join Point): 连接点是在应用程序执行过程中可以插入切面的点,比如方法的调用或异常的处理。
-
通知(Advice): 通知是在连接点上执行的动作。Spring AOP 提供了多种类型的通知,包括前置通知(Before advice)、后置通知(After advice)、环绕通知(Around advice)、异常通知(After-throwing advice)和返回通知(After-returning advice)。
-
切点(Pointcut): 切点是指在应用程序中定义的连接点的集合。通知会在切点上执行。
-
引入(Introduction): 引入允许向现有的类添加新方法或属性。在 Spring AOP 中,引入允许为现有的类添加新接口。
-
目标对象(Target Object): 目标对象是应用程序中的真实对象,在其上应用通知。
-
代理(Proxy): 代理是创建的对象,它包含了通知应用到的目标对象。
-
织入(Weaving): 织入是将切面与目标对象结合起来创建新的代理对象的过程。它可以在编译时、类加载时或运行时进行。
Spring AOP 通过将这些概念结合起来,允许开发者更方便地管理和实现横切关注点,提高了代码的模块化程度和可维护性。
使用指南
首先,你得在pom.xml
中添加 AOP 依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
然后使用@Aspect
注解创建一个切面类
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingAspect {
// todo
}
切面定义好后,我们需要在代码中定义一个切入点,切入点的定义可以通过@Pointcut
注解定义
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingAspect {
/**
* `@Pointcut`注解定义切入点 该切点定义在LoginVerification子类中的verify方法被调用时
*/
@Pointcut("execution(* com.example.demo.service.LoginVerification+.verify(..))")
private void pointcut() {
}
}
也可以利用自定义注解来标记方法或类,并在切面中使用这些注解作为切入点。
import java.lang.annotation.*;
// 定义一个自定义注解`@Loggable`
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Loggable {
}
@Service
public class MyService {
//在要想应用切面的方法上使用 `@Loggable` 注解
@Loggable
public void performTask() {
// Method logic
}
}
@Aspect
@Component
public class LoggingAspect {
//在切面中使用这个自定义注解作为切入点
@Pointcut("@annotation(com.example.Loggable)")
private void pointcut() {
}
}
根据业务需要定义通知
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingAspect {
/**
* `@Pointcut`注解定义切入点 该切点定义在LoginVerification子类中的verify方法被调用时
*/
@Pointcut("execution(* com.example.demo.service.LoginVerification+.verify(..))")
private void pointcut() {
}
/**
* 前置通知
*
* @param joinPoint
*/
@Before("pointcut()")
public void beforeAdvice(JoinPoint joinPoint) {
System.out.println("Before method execution")
}
/**
* 环绕通知
*/
@Around("pointcut()")
public void around() {
System.out.println("Around method execution")
}
/**
* 后置通知
*/
@After("pointcut()")
public void afterMethodExecution() {
System.out.println("After method execution")
}
/**
* 返回通知
*
* @param result
*/
@AfterReturning(pointcut = "pointcut()", returning = "result")
public void afterMethodExecutionReturn(Object result) {
System.out.println("After method execution: result = " + result);
}
/**
* 异常通知
*
* @param ex
*/
@AfterThrowing(pointcut = "pointcut()", throwing = "ex")
public void afterMethodExecution(Exception ex) {
System.out.println("After method execution: Exception caught: " + ex.getMessage());
}
}
为了使 AOP 起作用,需要在 Spring Boot 应用程序的主类中添加@EnableAspectJAutoProxy
注解。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@SpringBootApplication
@EnableAspectJAutoProxy
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
现在,你已经完成了 AOP 的设置。可以在你的业务代码中调用com.example.demo.service.LoginVerification
子类中的verify
方法,然后观察控制台中的输出。查看AOP的效果
SpringAOP中代理对象创建方式
在 Spring AOP 中,代理对象的创建可以使用两种主要方式:JDK 动态代理和 CGLIB(Code Generation Library)代理。它们之间有一些区别:
-
JDK 动态代理:
- 基于接口: JDK 动态代理要求目标类实现至少一个接口。它利用 Java 标准库中的
java.lang.reflect.Proxy
类来创建代理对象。 - 运行时创建: 在运行时,通过反射机制动态地创建代理对象。代理对象实现了目标类所实现的接口,并重定向方法调用到通知逻辑(Advice)。
- 适用范围: 适用于那些实现了接口的类,对于没有实现接口的类无法进行代理。
- 优势: JDK 动态代理是 Java 标准库提供的功能,遵循标准,更易于使用,同时不会生成过多的代理类。
- 基于接口: JDK 动态代理要求目标类实现至少一个接口。它利用 Java 标准库中的
-
CGLIB 代理:
- 基于继承: CGLIB 通过继承目标类来创建代理对象,不要求目标类实现接口。
- 运行时生成字节码: 在运行时,CGLIB 利用字节码生成库在内存中动态生成目标类的子类作为代理对象。这个子类覆盖了目标类中的非 final 方法,将通知逻辑织入其中。
- 适用范围: 对于没有实现接口的类,也能够进行代理。
- 优势: 能够代理没有实现接口的类,更加灵活,但因为涉及字节码生成,可能对性能有轻微影响。
在 Spring AOP 中,默认情况下:
- 当目标对象实现了至少一个接口时,Spring AOP 会使用 JDK 动态代理。
- 当目标对象没有实现接口时,Spring AOP 会使用 CGLIB 代理。
JDK动态代理实现代码
package com.example.demo;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 接口
interface UserService {
void addUser(String name);
void delUser(String name);
}
// 实现类
class UserServiceImpl implements UserService {
@Override
public void addUser(String name) {
System.out.printf("添加用户:%s", name);
}
@Override
public void delUser(String name) {
System.out.printf("删除用户:%s", name);
}
}
// 切面类
class UserLogger {
public void before() {
System.out.println("---------------操作前");
}
public void after() {
System.out.println("---------------操作完成");
}
public void afterThrowing() {
System.out.println("---------------操作异常");
}
public void afterReturning() {
System.out.println("---------------操作结束");
}
}
// JDK动态代理的实现
class JDKProxy implements InvocationHandler {
private Object targetObject; //目标对象
public Object createProxyInstance(Object targetObject) {
this.targetObject = targetObject;
return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),
targetObject.getClass().getInterfaces(),
this);
}
public Object invoke(Object target, Method method, Object[] arg) throws Throwable {
UserLogger userLogger = new UserLogger();//切面类
try {
//前置通知
userLogger.before();
// 调用目标方法
Object obj = method.invoke(targetObject, arg);// 切入点
userLogger.after();//后置通知
return obj;
} catch (RuntimeException e) {
e.printStackTrace();
userLogger.afterThrowing();//异常通知
} finally {
userLogger.afterReturning();//最终通知
}
return null;
}
}
// 示例
public class Main {
public static void main(String[] args) {
JDKProxy proxy = new JDKProxy();
UserService userService = (UserService) proxy.createProxyInstance(new UserServiceImpl());
userService.addUser("a");
userService.delUser("a");
}
}