Spring AOP
- OOP:Object-Oriented Programming,面向对象编程;AOP:Aspect-Oriented Programming,面向切面编程
- Advisor:spring 自己的 AOP 组件;AspectJ :三方实现的 AOP 组件
- 底层对 bean 创建代理对象,达到增强目的。如果目标 bean 实现了接口,就用 JDK 动态代理;如果没有,就用 CGLIB
- 声明式事务
@Transactional
注解底层就使用 AOP 实现事务提交回滚的
AspectJ VS Advisor
说人话 AspectJ 更强大、性能更好、可以不依赖 spring;总结就是:简单场景使用 Advisor,复杂场景使用 AspectJ。下面是两者详细对比
AspectJ | Advisor | |
---|---|---|
定义 | AspectJ 是一个完整的 AOP 框架,支持面向切面编程的编译时和运行时特性。 | Advisor 是 Spring AOP 的一个概念,表示一个切面中的通知和切入点的组合。 |
编程模型 | 提供了丰富的语言支持,包括注解、XML 配置和代码注入。 | 主要通过注解(如 @Before , @After )或 XML 配置来定义。 |
切面实现 | 可以在编译时、类加载时或运行时进行织入,支持复杂的切点表达式。 | 主要通过 Spring 容器在运行时使用动态代理实现,切点表达式相对简单。 |
织入方式 | 支持编译时织入、类加载时织入和运行时织入。 | 仅支持运行时织入,通常使用 Java 动态代理或 CGLIB 代理。 |
切点表达式 | 使用 AspectJ 表达式,支持更复杂的匹配规则。 | 使用 Spring AOP 的切点表达式,功能相对有限。 |
应用范围 | 可用于任何 Java 应用,包括独立 Java 程序和 Spring 应用。 | 主要用于 Spring 应用中,与 Spring 生态系统紧密集成。 |
支持的通知类型 | 支持多种通知类型,如 @Before , @After , @Around , @AfterReturning , @AfterThrowing 。 |
支持 Before , After , Around 和其他通知类型,通常由 Advisor 定义。 |
性能 | AspectJ 的性能较高,因为它可以在编译时进行优化。 | Spring AOP 的性能相对较低,因为它依赖于运行时代理。 |
易用性 | 对于初学者来说,学习曲线可能较陡峭。 | 更加简单直观,易于上手。 |
AOP 概念
概念 | 定义 | 说人话 |
---|---|---|
切面 (Aspect) | 定义了一个横切关注点的模块,包含通知和切入点。 | 就是我们定义的切面类 |
连接点 (Join Point) | 程序执行过程中一个可以插入通知的点,例如方法调用、方法执行、对象创建等。 | 目标对象所有方法 |
切入点 (Pointcut) | 定义在哪些连接点上应用通知的规则或表达式,决定通知的应用范围。 | 目标对象被增强的方法 |
通知 (Advice) | 在连接点上执行的行为,分为前置通知、后置通知、返回通知、异常通知和环绕通知。 | |
织入 (Weaving) | 将切面与目标对象结合的过程,发生在编译时、类加载时或运行时。 | 将切点和通知组合的过程 |
目标对象 (Target Object) | 被切面影响的对象,通常是包含业务逻辑的类。 |
通知
通知类型 | 描述 |
---|---|
前置通知 (Before) | 在连接点执行之前运行的通知。 |
后置通知 (After) | 在连接点执行之后运行的通知,通常在连接点成功或失败后都会执行。 |
返回通知 (After Returning) | 在连接点成功完成后执行的通知,可以访问连接点的返回值(目标方法执行不能发生异常)。 |
异常通知 (After Throwing) | 在连接点抛出异常时执行的通知,可以访问异常信息。 |
环绕通知 (Around) | 在连接点执行之前和之后都可以执行的通知,允许控制连接点的执行,能够选择不执行连接点的方法(目标方法执行不能发生异常)。 |
切点表达式
- 就是定义切点,也就是指明哪些方法要被增强
- Advisor 的切点表达式是 AspectJ 的子集
AspectJ 示例
-
常用切点表达式(可以 and or 取反 指定方法返回值 指定参数个数 指定参数类型等等)
-
某个特殊的方法:com.example.service.UserService 类中所有以 find 开头的公共方法
execution(public * com.example.service.UserService.find*(..))
-
类中的所有方法:com.example.service 包下所有类的所有方法
execution(* com.example.service.*.*(..))
-
特定参数类型的方法:com.example.service.UserService 类中接受一个 User 对象作为参数的方法
execution(String com.example.service.UserService.getName(..))
-
com.example.service 包及其子包下的所有类中的所有方法
execution(* com.example.service..*.*(..))
-
com.example.service 包及其子包下的所有类中的所有方法
within 不能指定某个方法,本身是类级别的;execution 可以指定某个方法
如果exection 切某个包及其子包下所有方法就和 within 功能一样了
within(com.example.service..*)
-
带有特定注解的方法:被 @Loggable 注解标记的方法
@annotation(com.example.annotation.Loggable)
-
结合多个条件:匹配 com.example.service 包中所有被 @Loggable 注解标记的方法
execution(* com.example.service.*.*(..)) && @annotation(com.example.annotation.Loggable)
-
有异常抛出的方法:匹配 com.example.service 包下所有方法,并且这些方法抛出 Exception
execution(* com.example.service.*.*(..)) throws Exception
-
-
两种连接点
- JoinPoint 获取连接点信息,比如方法、参数、目标对象等,可用于
@Before
、@After
、@AfterReturning
- ProceedingJoinPoint 是 JointPoint 的子接口,额外提供 proceed() 来执行目标方法,仅用于
@Around
- JoinPoint 获取连接点信息,比如方法、参数、目标对象等,可用于
// 自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LogExecution {
}
// 切面类
@Aspect
@Component
public class LoggingAspect {
// 定义切点
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceMethods() {}
// 前通知
@Before("serviceMethods()")
public void logBefore(JoinPoint joinPoint) {
System.out.println("Executing method...");
System.out.println("Before method: " + joinPoint.getSignature());
}
// 后通知
@After("serviceMethods()")
public void logAfter(JoinPoint joinPoint) {
System.out.println("Method execution completed.");
}
// 返回通知
@AfterReturning(pointcut = "serviceMethods()", JoinPoint joinPoint, returning = "result")
public void logAfterReturning(Object result) {
System.out.println("Method returned: " + result);
}
// 异常通知
@AfterThrowing(pointcut = "serviceMethods()", throwing = "ex")
public void logAfterThrowing(Throwable ex) {
System.out.println("Method threw an exception: " + ex);
}
// 环绕通知
@Around("serviceMethods()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("Before method: " + joinPoint.getSignature());
Object result = joinPoint.proceed(); // 执行目标方法
System.out.println("After method: " + joinPoint.getSignature());
return result;
}
// 使用自定义注解的通知
@Before("@annotation(LogExecution)")
public void logBeforeAnnotatedMethod() {
System.out.println("Executing annotated method...");
}
}
Advisor 示例
Advisor 配合 AspectJ 更优雅,也能不结合 AspectJ 使用
纯粹使用 Advisor 没 AspectJ 方便,通知、切点等都要使用 java 代码实现
结合 AspectJ 的话,既然已经用了 AspectJ 为什么不直接使用要再引入 Advisor 呢
不结合 AspectJ:
-
定义通知
public class LoggingAdvice implements MethodInterceptor { @Override public Object invoke(MethodInvocation invocation) throws Throwable { System.out.println("Method " + invocation.getMethod().getName() + " is being called"); return invocation.proceed(); // 继续执行目标方法 } }
-
定义切点
public class LoggingPointcut implements Pointcut { @Override public ClassFilter getClassFilter() { return ClassFilter.TRUE; // 适用于所有类 } @Override public MethodMatcher getMethodMatcher() { return new NameMatchMethodMatcher() { @Override public boolean matches(String methodName, Class<?> targetClass) { return methodName.startsWith("get"); // 适用于所有以 "get" 开头的方法 } }; } }
-
定义切面类
public class LoggingAdvisor extends DefaultPointcutAdvisor { public LoggingAdvisor() { super(new LoggingPointcut(), new LoggingAdvice()); } }
-
注册切面
@Configuration public class AopConfig { @Bean public LoggingAdvisor loggingAdvisor() { return new LoggingAdvisor(); } }
JDK动态代理增强 bean
AOP 的原理就是给目标对象创建代理对象,达到增强目标对象方法的目的
如果目标对象实现了接口就是用 JDK 动态代理,如果没实现接口就是用三方的 CGLIB 代理
如果不使用 AOP 想要增强一个 bean 可以这样做:
@Component
public class Test implements BeanPostProcessor, ApplicationContextAware {
private ApplicationContext applicationContext;
// ApplicationContextAware 感知回调回传 IOC 容器
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
// bean 工厂后置处理器,拦截所有的 bean
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// 只增强 User bean
if (bean instanceof User) {
User user = (User) applicationContext.getBean("user");
return Proxy.newProxyInstance(
user.getClass().getClassLoader(),
user.getClass().getInterfaces(),
(Object proxy, Method method, Object[] args) -> {
// 如果不是 sayHello 方法不增强(只增强 sayHello 方法)
if (!method.getName().equals("sayHello")){
return method.invoke(user, args);
}
System.out.println("执行前.....");
Object invoke = method.invoke(user, args); // 执行 bean 本身的方法,这里就是 sayHello
System.out.println("执行后.....");
return invoke;
}
);
}
// 如果 bean 不是 User 直接返回原来的
return bean;
}
}
标签:service,通知,example,public,AOP,com,AspectJ
From: https://www.cnblogs.com/cyrushuang/p/18474036