在Spring框架中,循环依赖是指两个或多个Bean相互依赖形成的闭环关系。比如BeanA依赖BeanB,同时BeanB又依赖BeanA。这种场景在构造器注入模式下会直接导致容器启动失败,因为Spring无法同时满足两个Bean的实例化条件。
Spring解决这个问题的核心思路是"提前暴露引用"。通过将尚未完全初始化的Bean提前暴露给其他依赖方,打破实例化与属性注入之间的强顺序约束。这种机制具体通过三级缓存实现:
java复制// Spring容器中实际的三级缓存结构
public class DefaultSingletonBeanRegistry {
// 一级缓存:完整Bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>();
// 二级缓存:早期引用
private final Map<String, Object> earlySingletonObjects = new HashMap<>();
// 三级缓存:对象工厂
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>();
}
一级缓存(singletonObjects):
二级缓存(earlySingletonObjects):
三级缓存(singletonFactories):
以BeanA依赖BeanB,BeanB依赖BeanA为例:
关键点:三级缓存中的ObjectFactory只有在发生循环依赖时才会被调用,避免了不必要的代理对象创建。
三级缓存设计体现了典型的空间换时间思想:
按需创建代理:
缓存分层管理:
避免重复创建:
AOP(面向切面编程)是Spring框架的三大核心特性之一(另外两个是IoC和事务管理)。它通过代理机制实现了横切关注点的模块化,典型应用包括:
连接点(JoinPoint):
切点(Pointcut):
java复制// 匹配Service包下所有方法
@Pointcut("execution(* com.example.service.*.*(..))")
// 匹配带有特定注解的方法
@Pointcut("@annotation(com.example.anno.LogExecution)")
通知(Advice):
| 通知类型 | 执行时机 | 典型应用场景 |
|---|---|---|
| @Before | 方法执行前 | 参数校验、权限检查 |
| @AfterReturning | 方法正常返回后 | 结果日志记录 |
| @AfterThrowing | 方法抛出异常后 | 异常处理 |
| @After | 方法结束后(无论成功/异常) | 资源清理 |
| @Around | 包围整个方法执行 | 事务管理、性能监控 |
切面(Aspect):
java复制@Aspect
@Component
public class LoggingAspect {
@Before("serviceMethods()")
public void logMethodCall(JoinPoint jp) {
logger.info("调用方法: " + jp.getSignature());
}
}
织入(Weaving):
Spring AOP使用两种代理技术:
JDK动态代理:
java复制public class $Proxy1 implements TargetInterface {
private InvocationHandler h;
public Object invoke(Object proxy, Method method, Object[] args) {
// 调用处理器逻辑
}
}
CGLIB代理:
java复制public class TargetClass$$EnhancerByCGLIB extends TargetClass {
private MethodInterceptor interceptor;
public void method() {
interceptor.intercept(this, method, args, proxy);
}
}
Spring的代理选择遵循以下规则:
强制CGLIB的场景:
优先JDK代理的场景:
性能对比表:
| 维度 | JDK动态代理 | CGLIB代理 |
|---|---|---|
| 创建速度 | 快 | 较慢(需生成字节码) |
| 执行速度 | 快(直接方法调用) | 稍慢(方法拦截) |
| 内存占用 | 低 | 较高(生成子类) |
| 功能限制 | 仅限接口方法 | 可代理所有方法 |
实际建议:在Spring Boot应用中,通常保持默认配置(优先JDK代理)即可。对于需要代理具体类方法的场景,可通过@EnableAspectJAutoProxy(proxyTargetClass=true)启用CGLIB。
当一个方法匹配多个通知时,执行顺序如下:
示例代码:
java复制@Aspect
@Component
public class OrderAspect {
@Around("execution(* com.example..*(..))")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("Around before");
Object result = pjp.proceed();
System.out.println("Around after");
return result;
}
@Before("execution(* com.example..*(..))")
public void before() {
System.out.println("Before");
}
@After("execution(* com.example..*(..))")
public void after() {
System.out.println("After");
}
}
执行输出:
code复制Around before
Before
[目标方法执行]
Around after
After
当多个切面匹配同一个方法时,默认顺序不确定。可以通过以下方式控制:
示例:
java复制@Aspect
@Order(1)
@Component
public class LoggingAspect {
// 先执行的切面
}
@Aspect
@Order(2)
@Component
public class TransactionAspect {
// 后执行的切面
}
执行顺序规则:
避免过于宽泛的表达式:
java复制// 不推荐 - 匹配范围太大
@Pointcut("execution(* com.example..*(..))")
// 推荐 - 精确限定包路径
@Pointcut("execution(* com.example.service.*.*(..))")
使用within限定类范围:
java复制// 只匹配特定注解标记的类
@Pointcut("within(@org.springframework.stereotype.Service *)")
组合使用切点表达式:
java复制@Pointcut("execution(* com.example.service.*.*(..)) && " +
"!execution(* com.example.service.internal.*.*(..))")
合理选择代理方式:
延迟代理创建:
java复制@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
@Service
public class HeavyService {
// 大型Bean可延迟代理创建
}
避免不必要的代理:
java复制@Configuration
@EnableAspectJAutoProxy(exposeProxy = false) // 非必要不暴露AopContext
public class AppConfig {}
检查切面是否被Spring管理:
验证切点表达式:
检查代理类型:
查看执行顺序:
代理创建耗时:
通知执行耗时:
内存泄漏:
Spring AOP代理创建的核心流程:
Bean后处理器阶段:
代理工厂配置:
代理生成:
拦截器链执行:
核心类:
生成代码示例:
java复制public final class $Proxy1 extends Proxy implements TargetInterface {
private static Method m1;
private static Method m2;
static {
m1 = Class.forName("TargetInterface").getMethod("method1");
m2 = Class.forName("TargetInterface").getMethod("method2");
}
public $Proxy1(InvocationHandler h) {
super(h);
}
public void method1() {
h.invoke(this, m1, null);
}
}
特点:
核心类:
生成代码示例:
java复制public class TargetClass$$EnhancerByCGLIB extends TargetClass {
private MethodInterceptor interceptor;
public void method() {
MethodProxy mp = ...;
interceptor.intercept(this,
TargetClass$$EnhancerByCGLIB.method,
args,
mp);
}
// 生成的FastClass加速方法调用
public Object invoke(int index, Object[] args) {
switch(index) {
case 0: return method(args);
}
}
}
特点:
作为新一代字节码操作库,ByteBuddy相比CGLIB的优势:
Spring从5.2开始实验性支持ByteBuddy,可通过以下配置启用:
properties复制spring.aop.proxy-target-class=true
spring.aop.bytebuddy.enabled=true
AOP代理与三级缓存的交互流程:
Bean实例化后,将创建代理的ObjectFactory放入三级缓存:
java复制addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
发生循环依赖时,调用ObjectFactory.getObject():
java复制// AbstractAutowireCapableBeanFactory
protected Object getEarlyBeanReference(String name, Object bean) {
// 应用后处理器(包括AOP代理创建)
for (BeanPostProcessor bp : getBeanPostProcessors()) {
bean = bp.getEarlyBeanReference(bean, name);
}
return bean;
}
AbstractAutoProxyCreator的处理逻辑:
java复制public Object getEarlyBeanReference(Object bean, String name) {
// 如果Bean需要代理,则创建早期代理对象
if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), name)) {
return bean;
}
return wrapIfNecessary(bean, name);
}
这种设计确保了:
切面职责单一化:
切点表达式维护:
通知代码精简:
代理方式显式声明:
切面执行优化:
java复制@Around("execution(* com.example..*(..)) && " +
"args(arg) && @annotation(metric)")
缓存代理对象:
java复制@Around("execution(* com.example.service.*.*(..))")
public Object cacheResult(ProceedingJoinPoint pjp) {
String key = generateCacheKey(pjp);
return cache.get(key, () -> pjp.proceed());
}
选择性代理:
java复制@Bean
@ConditionalOnProperty("metrics.enabled")
public MetricsAspect metricsAspect() {
return new MetricsAspect();
}
自调用问题:
java复制((TargetClass) AopContext.currentProxy()).method();
初始化顺序问题:
java复制@DependsOn("myBean")
@Aspect
@Component
public class MyAspect {}
异常处理遗漏:
java复制@Around("...")
public Object handleException(ProceedingJoinPoint pjp) {
try {
return pjp.proceed();
} catch (Exception e) {
// 处理异常
throw new BusinessException(e);
}
}
循环依赖复杂场景:
java复制@Autowired
public void setDependency(@Lazy Dependency dependency) {
this.dependency = dependency;
}
在实际项目中使用Spring AOP时,我最大的体会是:AOP是一把双刃剑,用得好可以大幅提升代码的模块化和可维护性,但过度使用会导致系统行为难以追踪。建议遵循"显式优于隐式"的原则,对于重要的业务切面,应该通过日志或监控手段让切面行为可见化。同时,要特别注意AOP与Spring其他特性(如事务、缓存、异步)的交互影响,这些组合使用时常会产生微妙的边界情况。