AOP代理
1、开启AOP代理
1.1、Spring MVC中的AOP配置
依赖
<!--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
或相应的配置文件中加入:
<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
依赖:
<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的代理实例:
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 {@Autowiredprivate ApplicationContext applicationContext;@Override@Transactionalpublic void methodA() {// 这个方法是事务控制的methodB();}@Transactionalpublic void methodB() {// 这个方法也需要事务控制}private void methodB() {// 通过ApplicationContext手动获取代理MyService proxy = applicationContext.getBean(MyService.class);proxy.methodB(); // 通过代理的方式调用,保证事务有效。}
}
注意:如果方法 methodB()
在外部调用时已经标记为 @Transactional
,在自我注入时可直接使用。
2. 使用 @Lazy
注解结合自我注入
当然,这种方式需要在 @Autowired
的时候被标记为 @Lazy
。这是因为Spring需要通过代理来解析循环依赖的影响。
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@Lazyprivate MyService myService; // 使用 @Lazy 注解进行自我注入@Override@Transactionalpublic void methodA() {// 这个方法是事务控制的myService.methodB(); // 通过代理调用,能够保持事务}@Transactionalpublic void methodB() {// 这个方法的事务控制同样有效}
}
除了上述提到的通过 ApplicationContext
手动获取代理对象和使用 @Lazy
注解进行自我注入之外,实际上还有一些其他方法可以用来确保在同一 Service 内部调用其他方法时,事务能够正常工作。
3. 拆分 Service
如果业务逻辑允许,把相关的方法拆分到不同的 Service 中,可以有效避免自调用的问题。例如:
@Service
public class MyServiceA {@Autowiredprivate MyServiceB myServiceB;@Transactionalpublic void methodA() {myServiceB.methodB(); // 调用另一个 Service 的方法}
}@Service
public class MyServiceB {@Transactionalpublic void methodB() {// 事务逻辑}
}
这种方式是最为推荐的,因为它符合单一职责原则,也让代码结构更加清晰。
4. 使用 AopProxyUtils
类
Spring 提供了一些工具类,可以用来获取当前对象的代理,通过这些工具类也可以借助 Spring AOP 机制来实现自我调用。AopProxyUtils
是其中之一,但通常在实际使用中不常见。
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 {@Autowiredprivate MyServiceImpl self; // self 是 Spring 的代理对象@Transactionalpublic void methodA() {self.methodB(); // 通过代理自己调用}@Transactionalpublic void methodB() {// 事务逻辑}
}
5. 使用 AspectJ
如果项目中已经使用了 AspectJ,则可以考虑将业务逻辑提取到独立的 Aspect 中,然后通过 AOP 处理事务。这也是一种较为复杂但灵活的做法。
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 中调用方法,但可以实现某些业务的解耦。
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;}@Transactionalpublic void methodA() {// 事务逻辑eventPublisher.publishEvent(new MyEvent(this)); // 触发事件}
}// 事件监听器
@Service
public class EventListenerService {@EventListener@Transactionalpublic 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的能力。