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的能力。

相关推荐
龚思凯1 分钟前
Node.js 模块导入语法变革全解析
后端·node.js
天行健的回响4 分钟前
枚举在实际开发中的使用小Tips
后端
on the way 1237 分钟前
行为型设计模式之Mediator(中介者)
java·设计模式·中介者模式
保持学习ing9 分钟前
Spring注解开发
java·深度学习·spring·框架
wuhunyu9 分钟前
基于 langchain4j 的简易 RAG
后端
techzhi10 分钟前
SeaweedFS S3 Spring Boot Starter
java·spring boot·后端
异常君34 分钟前
Spring 中的 FactoryBean 与 BeanFactory:核心概念深度解析
java·spring·面试
weixin_461259411 小时前
[C]C语言日志系统宏技巧解析
java·服务器·c语言
cacyiol_Z1 小时前
在SpringBoot中使用AWS SDK实现邮箱验证码服务
java·spring boot·spring
竹言笙熙1 小时前
Polarctf2025夏季赛 web java ez_check
java·学习·web安全