Spring--AOP

一、Spring AOP简介

Spring AOP(Aspect-Oriented Programming,面向切面编程)是Spring框架的一个核心特性,它允许开发者将横切关注(如日志、事务管理、安全性等)从业务逻辑中分离出来,从而提高代码的可重用性、可维护性和可扩展性。

二、Spring AOP相关概念

  1. 切面(Aspect):切面是横切关注点的模块化,它包含了切点和通知。在Spring中,可以通过使用@Aspect注解将一个类定义为切面。
  2. 切点(Pointcut):切点用于定义通知应该在什么时候触发。它通过匹配连接点来确定通知具体的执行位置。在Spring AOP中,可以使用AspectJ切点表达式(如execution())来匹配单个连接点或多个连接点。
  3. 通知(Advice):通知是指在切面中定义的具体的横切逻辑,它会在满足特定条件时被执行。根据通知执行的时间点不同,通知又可分为五种类型:
    • 前置通知(Before Advice):在目标方法执行之前执行。
    • 后置通知(After Returning Advice):在目标方法正常执行后执行。
    • 异常通知(After Throwing Advice):在目标方法抛出异常后执行。
    • 最终通知(After (Finally) Advice):在目标方法执行完成后执行,无论是否正常结束。
    • 环绕通知(Around Advice):包围目标方法的执行,可以在方法执行前后执行自定义逻辑。
  4. 连接点(Join Point):连接点表示程序执行过程中可能触发通知执行的点,可能是方法的调用或异常的抛出。切点其实就可以看作是具体执行通知的那个连接点。在Spring AOP中,连接点特指方法的执行。
  5. 目标对象(Target Object):目标对象是被一个或多个通知包围的业务逻辑对象。
  6. 织入(Weaving):织入是将切面应用到目标对象以创建新的代理对象的过程。Spring AOP支持编译时织入、类加载时织入和运行时织入。
  7. 代理(Proxy):代理是一个代表另一个目标对象的对象,它实现了目标对象的接口,并在调用目标对象的方法之前或之后插入额外的行为。在Spring AOP中,切面通过创建代理对象来实现。
  8. 引入(Introduction):引入允许我们向现有类添加新的方法或属性。在Spring AOP中,这是通过声明一个切面并使用@DeclareParents注解来实现的。

三、Spring AOP作用及优点

主要作用:

  1. 解耦:将业务逻辑与横切逻辑分离,降低模块间的耦合度。
  2. 增强可维护性:通过集中管理横切关注点,使得业务逻辑更容易理解和维护。
  3. 提高可重用性:横切逻辑可以作为独立的组件被不同的系统重用。
  4. 简化开发:减少了重复代码的编写,提高了开发效率。
  5. 增强功能:可以方便地向系统中添加新的横切功能,如日志、安全性检查等。

优点:

  1. 模块化:AOP允许开发者将横切关注点模块化,使得代码更加清晰,易于管理。
  2. 降低复杂性:通过将横切逻辑从业务逻辑中分离出来,降低了代码的复杂性。
  3. 易于测试:由于横切逻辑与业务逻辑分离,因此更容易对业务逻辑进行单元测试。
  4. 灵活性:AOP提供了一种灵活的设计机制,可以在不影响原有业务逻辑的前提下,动态地添加或修改功能。
  5. 可扩展性:通过添加新的切面,可以轻松地扩展系统的功能,而无需修改现有的代码。

四、Spring AOP实现原理

Spring AOP 实现原理基于Java动态代理机制。Spring AOP 提供了两种代理方式:JDK动态代理和CGLIB代理。

JDK动态代理:

  • JDK动态代理只能代理实现了接口的类。
  • 当创建一个代理对象时,JDK动态代理会在运行时动态地创建一个实现了所需接口的代理类。
  • 代理类会覆盖接口中的所有方法,并在方法内部调用目标对象的相应方法,同时可以在调用前后加入额外的逻辑(通知)。
  • JDK动态代理是通过反射(Reflection)API来实现的,通过Proxy类和InvocationHandler接口。
  • 当客户端调用代理对象的方法时,InvocationHandler的invoke方法会被触发,然后可以执行前置通知(Before advice)、执行目标方法、执行后置通知(After returning advice)等操作。

CGLIB动态代理:

  • CGLIB(Code Generation Library)是一个高性能的代码生成库,它可以在运行时扩展Java类和实现Java接口。
  • 对于没有实现接口的类,Spring AOP默认使用CGLIB来创建代理对象。相比于JDK动态代理使用更加灵活。
  • CGLIB通过继承目标类并重写其非final方法的方式来创建子类代理。
  • 代理类中重写的方法会包含调用目标对象方法前后的通知逻辑。

五、Spring AOP 与 AspectJ AOP的区别

Spring AOP和AspectJ AOP都是实现面向切面编程(AOP)的技术,它们在很多方面都相似,但也存在一些关键区别。Spring AOP是一个轻量级的AOP实现,适合大多数Spring应用的基本需求;而AspectJ是一个功能更全面的AOP框架,适用于更复杂和性能敏感的场景。以下是两者的一些不同点:

  1. 目标:
    • Spring AOP主要用于简化企业级应用开发,它侧重于提供一种轻量级的AOP解决方案。
    • AspectJ是一个更加强大的AOP框架,提供了全面的AOP功能,它不仅可以用于企业级应用开发,还可以用于各种复杂的AOP场景。
  2. 实现原理:
    • Spring AOP基于代理模式,它使用JDK动态代理和CGLIB库来创建代理对象,只支持方法级别的切面。
    • AspectJ是一个独立的AOP框架,它提供了比Spring AOP更丰富的织入时机和方式,包括编译时和类加载时织入,支持字段、方法、构造器等的织入。
  3. 织入时机:
    • Spring AOP支持编译时、类加载时和运行时织入。
    • AspectJ支持更多种类的织入时机,包括编译时和加载时织入,并且提供了比Spring AOP更复杂的织入选项。
  4. 性能:
    • 由于Spring AOP是基于代理的,可能会引入额外的性能开销,尤其是当目标对象不适合使用JDK动态代理时(比如接口为空)。
    • AspectJ提供了更底层的支持,通常在性能上优于Spring AOP,特别是在复杂的AOP场景下。
  5. 功能范围:
    • Spring AOP被设计为与Spring框架紧密集成,主要用于实现简单的AOP需求,如日志、安全性等。
    • AspectJ提供了更全面的AOP功能,包括ITD(Inter-type declarations,跨类型声明),允许开发者在类定义之外添加新的方法、属性等。
  6. 集成:
    • Spring AOP是Spring框架的一部分,与Spring的其他模块(如IoC容器)紧密集成。
    • AspectJ虽然可以独立使用,但Spring提供了对AspectJ的支持,允许开发者在Spring应用中使用AspectJ的高级特性。
  7. 使用场景:
    • 对于大多数Spring应用,Spring AOP已经足够满足需求,特别是当与Spring的其他特性(如依赖注入)集成时。
    • AspectJ更适合那些需要高级AOP功能或者对性能有严格要求的场景。

六、Spring AOP 切点

Spring AOP使用AspectJ的切点表达式语言,以下是一些常用的切点表达式:

  • execution(* com.example.service.*.*(..)):匹配com.example.service包下所有类的所有方法。
  • within(com.example.service.*):匹配com.example.service包下所有类。
  • this(com.example.service.MyService):匹配实现了MyService接口的所有bean。
  • target(com.example.service.MyService):匹配目标对象实现了MyService接口的所有bean。
  • @annotation(org.springframework.transaction.annotation.Transactional):匹配带有Transactional注解的方法。
  • args(java.io.Serializable):匹配接受Serializable类型参数的方法。

切点表达式可以组合使用,以实现更复杂的匹配规则:

  • &&:逻辑与,两者都匹配时才匹配。
  • ||:逻辑或,其中之一匹配时就匹配。
  • !:逻辑非,取反操作。

示例:
如果你想要匹配com.example.service包下所有类的所有public方法,并且这些方法上有@Transactional注解,你可以使用以下切点表达式:

@Pointcut("execution(public * com.example.service.*.*(..)) && @annotation(org.springframework.transaction.annotation.Transactional)")
public void transactionalService() {
    // 方法体可以为空,这里只是作为切点的一个标识
}

然后你可以在通知中引用这个切点:

@Before("transactionalService()")
public void beforeTransactionalMethod(JoinPoint joinPoint) {
    // 在事务性方法执行之前做一些事情
}

七、Spring AOP 通知

每个通知都可以通过@Before、@AfterReturning、@AfterThrowing、@After和@Around注解来声明,并且可以通过pointcut属性或者作为方法参数来指定要应用的切点。以下是一些使用通知的示例:

// 前置通知
@Before("execution(* com.example.service.*.*(..))")
public void beforeAdvice() {
    System.out.println("执行前置通知");
}

// 后置通知
@AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result")
public void afterReturningAdvice(Object result) {
    System.out.println("执行后置通知,返回值: " + result);
}

// 异常通知
@AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "ex")
public void afterThrowingAdvice(Exception ex) {
    System.out.println("执行异常通知,异常信息: " + ex.getMessage());
}

// 最终通知
@After("execution(* com.example.service.*.*(..))")
public void afterAdvice() {
    System.out.println("执行最终通知");
}

// 环绕通知
@Around("execution(* com.example.service.*.*(..))")
public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
    System.out.println("执行环绕通知之前");
    Object result = pjp.proceed(); // 调用目标方法
    System.out.println("执行环绕通知之后");
    return result;
}

八、Spring AOP 失效场景

Spring AOP(面向切面编程)可能在以下场景中失效:

  1. 直接实例化对象:如果通过new关键字直接实例化一个类,而不是通过Spring容器获取,那么这个实例就不会被Spring的代理机制所管理,因此AOP的功能也就无法应用。
  2. 非Spring管理的Bean:如果Bean没有被Spring容器管理,即没有使用Spring的注解或XML配置,那么AOP无法对其应用。
  3. final方法或类:Spring AOP无法代理final方法,因为final方法不能被子类覆盖,也就无法对这些方法应用通知。同样,如果一个类被声明为final,那么也不能使用基于类的代理。
  4. 基于接口的JDK动态代理限制:如果目标对象没有实现接口,Spring将无法使用JDK动态代理,需要使用CGLIB代理,但这也有局限性,因为CGLIB代理主要是靠继承的方式动态生成目标类的子类来实现,所以无法代理final类。
  5. 私有方法:默认情况下,Spring AOP不会拦截私有方法。尽管可以通过特定配置使CGLIB代理拦截私有方法,但这并不是一个好的实践。
  6. 构造函数:构造函数不能被代理,因为它们是用来初始化对象状态的,而且在对象创建时就会被调用。
  7. 静态方法:静态方法属于类本身,而不是类的实例,因此Spring AOP无法对静态方法应用通知。
  8. 同类型的方法调用:如果一个Spring管理的Bean内部调用同一个Bean的另一个方法,这种自调用不会经过代理,因此不会应用AOP通知。
  9. 并发模式:在使用某些并发模式时(如CallerRunsPolicy、SameThreadExecution),可能会绕过代理,导致AOP不被触发。

九、Spring AOP使用实例

  1. 在Spring Boot中使用AOP,首先需要在项目中添加AOP相关的依赖。可以在pom.xml文件中添加以下依赖:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
  1. 接下来再定义一个切面类,使用@Aspect注解标记。然后,定义切点表达式来指定哪些方法需要被拦截,以及通知(Advice)。
@Aspect
@Component
public class LoggingAspect {

    @Before("execution(* com.example.service.*.*(..))")
    public void logBefore(JoinPoint joinPoint) {
        System.out.println("方法执行之前:" + joinPoint.getSignature().getName());
    }

    @After("execution(* com.example.service.*.*(..))")
    public void logAfter(JoinPoint joinPoint) {
        System.out.println("方法执行之后:" + joinPoint.getSignature().getName());
    }
}

在上面的例子中,@Before注解定义了一个前置通知,它会在com.example.service包下所有方法执行之前被调用。@After注解定义了一个后置通知,它会在这些方法执行之后被调用。JoinPoint参数提供了关于正在执行的方法的信息。
切点表达式execution(* com.example.service.*.*(..))的含义是:

  • execution:指定这是一个执行通知。
  • * com.example.service.*:匹配com.example.service包下的所有类。
  • *(..):匹配任意方法,无论参数数量和类型。
  1. 最后在Spring Boot应用的主类上添加@EnableAspectJAutoProxy注解,以启用自动代理,使得AOP切面能够生效:
@SpringBootApplication
@EnableAspectJAutoProxy
public class SpringBootAopApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootAopApplication.class, args);
    }
}

当运行Spring Boot应用,并且调用com.example.service包下的任何方法时,都会触发LoggingAspect中定义的前置和后置通知,实现日志记录的功能。

  • 22
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值