Spring Aop

AOP代理

1、开启AOP代理

1.1、Spring MVC中的AOP配置

依赖

xml 复制代码
<!--aop依赖1:aspectjrt -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.9.5</version>
</dependency>

<!--aop依赖2: aspectjweaver -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.5</version>
</dependency>

在Spring MVC中,使用XML配置时,如果希望使用基于注解的AspectJ AOP,需要在applicationContext.xml或相应的配置文件中加入:

xml 复制代码
<aop:aspectj-autoproxy />

这行配置的作用是启用自动的AspectJ代理处理,允许Spring扫描带有@Aspect@Before@After等注解的类,并根据定义的切入点执行相应的横切逻辑。

1.2、Spring Boot中的AOP配置

在Spring Boot中,由于其自动配置的特性,许多功能都可以通过简单的添加依赖来实现,而不需要手动进行复杂的XML配置。Spring Boot中使用AOP时,只需要引入相关的依赖,Spring Boot会自动配置AOP所需的组件。

例如,如果你在Spring Boot应用中添加了spring-boot-starter-aop依赖:

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

上面的start包含了aop和aspectjweaver

这样,Spring Boot会自动启用AspectJ代理,允许你直接使用@Aspect和相关的AOP注解,而无需手动配置<aop:aspectj-autoproxy>

1.3、总结

  • Spring MVC :需要在XML配置中手动添加<aop:aspectj-autoproxy>来启用AOP。
  • Spring Boot :通过引入spring-boot-starter-aop依赖,启用AOP功能,简化了配置过程。

这种设计让Spring Boot提供了一种更为简便和现代的开发体验,减轻了开发者的负担。

2、spring代理自身

在Spring框架中,如果你希望在一个服务类内部调用另一个事务性的方法,并确保事务能够正常工作,不会因为直接调用导致失效(即不使用代理),你可以利用Spring的自我注入形式。

问题背景

在Spring中,事务是通过AOP代理实现的。当你在一个service内部直接调用另一个同一个类的方法时,实际上是通过方法调用的形式,而不是通过代理形式,这样就不会触发事务管理,因此事务可能会失效。

解决方案

1. 使用 ApplicationContext 获取代理对象

可以从Spring的ApplicationContext中手动获取该Service的代理实例:

java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class MyServiceImpl implements MyService {

    @Autowired
    private ApplicationContext applicationContext;

    @Override
    @Transactional
    public void methodA() {
        // 这个方法是事务控制的
        methodB();
    }

    @Transactional
    public void methodB() {
        // 这个方法也需要事务控制
    }
    
    private void methodB() {
        // 通过ApplicationContext手动获取代理
        MyService proxy = applicationContext.getBean(MyService.class);
        proxy.methodB(); // 通过代理的方式调用,保证事务有效。
    }
}

注意 :如果方法 methodB() 在外部调用时已经标记为 @Transactional,在自我注入时可直接使用。

2. 使用 @Lazy 注解结合自我注入

当然,这种方式需要在 @Autowired 的时候被标记为 @Lazy。这是因为Spring需要通过代理来解析循环依赖的影响。

java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class MyServiceImpl implements MyService {

    @Autowired
    @Lazy
    private MyService myService; // 使用 @Lazy 注解进行自我注入

    @Override
    @Transactional
    public void methodA() {
        // 这个方法是事务控制的
        myService.methodB(); // 通过代理调用,能够保持事务
    }

    @Transactional
    public void methodB() {
        // 这个方法的事务控制同样有效
    }
}

除了上述提到的通过 ApplicationContext 手动获取代理对象和使用 @Lazy 注解进行自我注入之外,实际上还有一些其他方法可以用来确保在同一 Service 内部调用其他方法时,事务能够正常工作。

3. 拆分 Service

如果业务逻辑允许,把相关的方法拆分到不同的 Service 中,可以有效避免自调用的问题。例如:

java 复制代码
@Service
public class MyServiceA {

    @Autowired
    private MyServiceB myServiceB;

    @Transactional
    public void methodA() {
        myServiceB.methodB(); // 调用另一个 Service 的方法
    }
}

@Service
public class MyServiceB {

    @Transactional
    public void methodB() {
        // 事务逻辑
    }
}

这种方式是最为推荐的,因为它符合单一职责原则,也让代码结构更加清晰。

4. 使用 AopProxyUtils

Spring 提供了一些工具类,可以用来获取当前对象的代理,通过这些工具类也可以借助 Spring AOP 机制来实现自我调用。AopProxyUtils 是其中之一,但通常在实际使用中不常见。

java 复制代码
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class MyServiceImpl {

    @Autowired
    private MyServiceImpl self; // self 是 Spring 的代理对象

    @Transactional
    public void methodA() {
        self.methodB(); // 通过代理自己调用
    }

    @Transactional
    public void methodB() {
        // 事务逻辑
    }
}
5. 使用 AspectJ

如果项目中已经使用了 AspectJ,则可以考虑将业务逻辑提取到独立的 Aspect 中,然后通过 AOP 处理事务。这也是一种较为复杂但灵活的做法。

java 复制代码
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.After;
import org.springframework.stereotype.Service;

@Aspect
@Service
public class BusinessAspect {

    @After("execution(* MyServiceImpl.methodA(..))")
    public void afterMethodA() {
        // 处理逻辑
    }
}

注意:这需要额外的配置和理解 AspectJ 的工作原理。

6. 事件驱动方式

如果业务逻辑相对复杂,可以考虑使用 Spring 事件机制来触发事务。虽然这种方法不直接在同一个 Service 中调用方法,但可以实现某些业务的解耦。

java 复制代码
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class MyService {

    private final ApplicationEventPublisher eventPublisher;

    public MyService(ApplicationEventPublisher eventPublisher) {
        this.eventPublisher = eventPublisher;
    }

    @Transactional
    public void methodA() {
        // 事务逻辑
        eventPublisher.publishEvent(new MyEvent(this)); // 触发事件
    }
}

// 事件监听器
@Service
public class EventListenerService {

    @EventListener
    @Transactional
    public void handleMyEvent(MyEvent event) {
        // 事务逻辑
    }
}

总结

每种方案都有其适用的场景和优劣所在。大部分情况下,将方法拆分到不同的 Service 中是最佳实践,它能够保持代码的清晰性和可维护性。在复杂的应用中,可以根据具体的需求考虑其他方法,但在选择方案时要注意事务管理的影响。

3、AspectJ、JDK代理和CGLIB代理

都是Java中实现AOP(面向切面编程)和动态代理的技术,它们有不同的特点和使用场景。下面是它们之间的关系和主要区别:

1. AspectJ

AspectJ是一个功能强大的AOP框架,它可以在编译时、类加载时、运行时进行切面编程。通过AspectJ,开发者可以定义切面(Aspect)、连接点(Join Point)、通知(Advice)等概念来实现横切关注点的模块化。

AspectJ允许定义更复杂的切入点表达式,可以应用于不同的连接点,例如方法调用、构造函数、字段访问等。

2. JDK动态代理

JDK动态代理是Java内置的动态代理机制,要求被代理的类实现一个或多个接口。通过创建Proxy类的实例和实现InvocationHandler接口,可以在运行时创建代理对象。

JDK动态代理只能代理实现了接口的类,无法直接代理类本身。

3. CGLIB代理

**CGLIB(Code Generation Library)**是一个强大的、高性能的字节码生成库,可以在运行时动态创建一个类的子类。CGLIB可以用于类代理,甚至可以代理没有实现任何接口的类。

CGLIB通过继承方式进行代理,因此不能代理final类和final方法。

4、总结

  • 使用场景:

    • AspectJ:在需要复杂AOP,而不仅仅是简单的代理时,可以使用AspectJ。需要在项目中引入AspectJ的支持。
    • JDK代理:适用于接口代理,但仅限于实现了接口的类。
    • CGLIB代理:适合需要对类进行代理的场景,尤其是没有接口可供代理的情况。
  • 优缺点:

    • AspectJ:功能丰富,但配置复杂,学习曲线较陡。
    • JDK动态代理:相对简单,但只能代理接口。
    • CGLIB:强大且灵活,但生成的子类可能会对性能产生一定影响,且不支持final类和方法的代理。
  • 关系

    这三者都可以用于AOP,但实现方式不同,选择合适的代理方式取决于具体的需求和场景。在实际应用中,Spring框架通常使用JDK动态代理和CGLIB代理,对于简单的接口代理使用JDK,对于没有接口的类或需要更复杂的功能时使用CGLIB。AspectJ则可以与Spring整合使用提升AOP的能力。

相关推荐
suweijie7682 小时前
SpringCloudAlibaba | Sentinel从基础到进阶
java·大数据·sentinel
公贵买其鹿3 小时前
List深拷贝后,数据还是被串改
java
向前看-6 小时前
验证码机制
前端·后端
xlsw_6 小时前
java全栈day20--Web后端实战(Mybatis基础2)
java·开发语言·mybatis
神仙别闹7 小时前
基于java的改良版超级玛丽小游戏
java
黄油饼卷咖喱鸡就味增汤拌孜然羊肉炒饭8 小时前
SpringBoot如何实现缓存预热?
java·spring boot·spring·缓存·程序员
暮湫8 小时前
泛型(2)
java
超爱吃士力架8 小时前
邀请逻辑
java·linux·后端
南宫生8 小时前
力扣-图论-17【算法学习day.67】
java·学习·算法·leetcode·图论
转码的小石8 小时前
12/21java基础
java