1. 从“硬编码”到“容器托管”IOC思想的本质与价值如果你是从早期的Java EE开发一路走过来的或者经历过纯粹用new关键字来组装对象的时代那么你对Spring的IOC控制反转容器带来的变革一定感触颇深。我记得在十多年前一个UserService要调用UserDao我们得在UserService的构造函数或者初始化方法里手动new UserDaoImpl()而UserDaoImpl又得自己去new DataSource()。这种代码随处可见牵一发而动全身测试时想替换个Mock对象都异常麻烦。Spring IOC的出现本质上解决的正是这个“对象创建与依赖组装权”的问题。它把原本由应用程序代码主动控制的创建过程反转给了容器Container来管理。这不仅仅是省了几行new代码那么简单它带来的是整个软件架构的松耦合和可测试性的飞跃。简单来说IOC是一种设计原则而依赖注入DI Dependency Injection是它的具体实现模式。Spring通过其IOC容器成为了这个模式的卓越实践者。容器就像是一个超级工厂它根据你提供的“图纸”配置负责生产实例化所有对象并按照图纸上的说明依赖关系把各个零件依赖对象装配到一起最后把成品交给你。你的业务代码从此不再关心UserDao具体是怎么来的它只需要声明“我需要一个UserDao”容器自然会在合适的时机给它。这种转变让我们的代码可以更专注于业务逻辑本身而不是复杂的对象生命周期管理和依赖查找。那么Spring是如何在源码层面实现这套机制的呢核心就在于两个接口体系BeanFactory和ApplicationContext。很多人刚开始会混淆其实BeanFactory是IOC容器的最低级接口提供了最基本的Bean管理功能比如根据名字获取Bean。而ApplicationContext是它的一个功能更丰富的子接口我们平时用的ClassPathXmlApplicationContext、AnnotationConfigApplicationContext都属于它。ApplicationContext在BeanFactory的基础上增加了国际化、事件传播、资源加载、以及更便捷的AOP集成等功能。可以说BeanFactory是心脏ApplicationContext是包含了心脏的完整躯体。接下来我们就深入到源码中看看这个“躯体”是如何启动并运转起来的。2. 容器启动的引擎BeanDefinition的加载与注册当我们写下new ClassPathXmlApplicationContext(“applicationContext.xml”)这行代码时Spring这座庞大机器的引擎就被启动了。这个过程远不止解析一个XML文件那么简单其核心是BeanDefinition的加载与注册。你可以把BeanDefinition理解为一个“Bean的配方”或者“施工蓝图”它定义了Bean的所有元数据类名、作用域、是否懒加载、依赖关系、属性值、初始化/销毁方法等等。容器在启动阶段并不直接创建Bean对象而是先读取配置把这些“蓝图”全部收集、注册到一个“蓝图仓库”里。2.1 配置解析从XML到BeanDefinition以经典的XML配置为例Spring通过BeanDefinitionReader接口体系来解析。ClassPathXmlApplicationContext内部持有一个XmlBeanDefinitionReader。启动时它会调用reader.loadBeanDefinitions(configLocation)方法。// 简化流程示意 public ClassPathXmlApplicationContext(String configLocation) { // ... 父类构造器初始化资源模式解析器等 this.setConfigLocations(configLocation); this.refresh(); // 这是核心入口 }refresh()方法是ApplicationContext初始化的核心模板方法定义了容器启动、刷新、关闭的完整生命周期。在refresh()中会调用obtainFreshBeanFactory()来获取或刷新BeanFactory进而触发loadBeanDefinitions(beanFactory)。在XmlBeanDefinitionReader.loadBeanDefinitions中会将XML文件资源转换为org.springframework.core.io.Resource对象。使用DocumentLoader将XML解析为W3C的Document对象。调用BeanDefinitionDocumentReader来解析这个Document。默认实现是DefaultBeanDefinitionDocumentReader。解析bean标签的关键在BeanDefinitionParserDelegate这个委托类中。对于每一个bean标签创建一个GenericBeanDefinition对象BeanDefinition接口的一个通用实现。解析id和name属性作为Bean的名称。解析class属性将字符串形式的类名转换为Class对象。解析scope属性默认为singleton。解析property子元素。对于value直接设置值对于ref会生成一个RuntimeBeanNameReference对象表示对另一个Bean的引用此时并不直接注入只是记录下依赖的Bean名称。将解析完所有属性的GenericBeanDefinition对象注册到BeanFactory持有的一个BeanDefinition注册中心——通常是DefaultListableBeanFactory内部的ConcurrentHashMapString, BeanDefinition中。Key是Bean的名字Value就是其“蓝图”。注意这里有一个非常重要的细节。XML解析时对于refuserDao这样的属性它只是把字符串“userDao”记录到BeanDefinition的PropertyValues中。此时并不会检查userDao这个Bean是否已经存在。这意味着在XML配置中Bean定义的顺序在大多数情况下是无关紧要的除了depends-on等特殊情况。容器允许“向前引用”即先定义依赖方后定义被依赖方。这给了配置很大的灵活性。2.2 注解配置的崛起ComponentScan与Bean随着注解的普及基于Java的配置方式Configuration已成为主流。其核心是AnnotationConfigApplicationContext和相关的后置处理器。当使用ComponentScan(“com.example”)时容器会扫描指定包下所有带有Component及其衍生注解Service,Repository,Controller的类。这个过程是由ClassPathBeanDefinitionScanner完成的。扫描器会遍历类路径找到候选类然后使用AnnotationBeanDefinitionDefinition来解析类上的注解生成一个ScannedGenericBeanDefinition并注册。对于Configuration类中的Bean方法处理则更加巧妙。它是由ConfigurationClassPostProcessor这个BeanFactoryPostProcessor来处理的。在容器刷新过程的invokeBeanFactoryPostProcessors(beanFactory)阶段这个后置处理器会被调用。它会解析所有Configuration类将每个Bean方法也转换为一个BeanDefinition。特别的是对于Bean方法Spring会生成一个特殊的BeanDefinition通常是ConfigurationClassBeanDefinition其factoryBeanName指向Configuration类本身的Bean名factoryMethodName指向具体的Bean方法名。这意味着这个Bean的实例化不是通过简单的new而是通过调用配置类实例的相应方法来完成的这为实现单例、代理等高级特性提供了基础。无论来源是XML还是注解最终所有Bean的“蓝图”都会被统一注册到BeanFactory的beanDefinitionMap中。至此容器完成了“备战”阶段所有Bean的配方都已就位接下来就是真正的“制造”和“装配”阶段。3. Bean的生命周期实例化、填充与初始化Bean从一张蓝图BeanDefinition变成一个立即可用的对象要经历一个复杂的生命周期。Spring通过一系列精妙的接口和回调机制将这个生命周期管理得井井有条。理解这个过程对于解决Bean创建过程中的各种诡异问题至关重要。3.1 实例化创建Bean的原始对象当容器需要获取一个Bean比如因为它是另一个Bean的依赖或者被直接getBean()且该Bean尚未实例化时创建过程就开始了。核心入口在AbstractBeanFactory.doGetBean()方法。首先容器会检查所请求的Bean是否是单例Singleton以及单例缓存中是否已存在。如果是原型Prototype作用域则每次都会进入创建流程。实例化的第一步是创建Bean的原始实例。这发生在AbstractAutowireCapableBeanFactory.createBeanInstance()方法中。Spring提供了几种策略使用工厂方法如果BeanDefinition中指定了factory-method来自XML的factory-method属性或Bean方法则通过反射调用该静态或实例方法来创建对象。使用构造器自动装配如果Bean的构造器上有Autowired注解Spring会尝试从容器中查找匹配的Bean作为参数通过反射调用该构造器。使用默认构造器最常见的情况直接调用类的无参构造器通过BeanUtils.instantiateClass实现。这里有一个重要的源码细节对于需要被AOP代理的Bean比如有Transactional注解Spring在这个阶段并不会直接创建目标类的实例。为什么因为代理需要包裹一个目标对象。如果先创建了目标对象再创建代理那么代理内部持有的目标对象和容器中其他Bean直接引用的可能就不是同一个如果是单例就破坏了单例。Spring的解决方案是使用“SmartInstantiationAwareBeanPostProcessor”。AbstractAutoProxyCreatorAOP代理创建的核心类就是一个这样的后置处理器。它在postProcessBeforeInstantiation()方法中有机会返回一个代理对象来提前终止标准的实例化流程。如果它返回了非null对象那么后续的标准实例化、属性填充都不会进行直接进入初始化后阶段。这是理解Spring AOP与IOC协同工作的关键点之一。3.2 属性填充依赖注入的核心实例化得到一个“空壳”对象后下一步就是填充其属性即依赖注入。这个过程在AbstractAutowireCapableBeanFactory.populateBean()方法中完成。依赖注入主要有两种方式ByType (自动装配 byType)Spring根据属性的类型在容器中寻找唯一匹配的Bean进行注入。如果有多个同类型Bean则需要配合Qualifier指定名称否则会抛出NoUniqueBeanDefinitionException。ByName (自动装配 byName)Spring根据属性的名称在容器中寻找同名Bean进行注入。对于Autowired注解的属性是由AutowiredAnnotationBeanPostProcessor这个后置处理器来处理的。它在postProcessProperties()方法中会解析字段或方法上的Autowired然后通过beanFactory.resolveDependency()来解析依赖。这个方法会处理Qualifier、Value、Lazy等注解最终获取到依赖的Bean对象如果依赖的Bean还未创建则会触发其创建流程这可能是一个递归过程然后通过反射Field.set()或Method.invoke()将依赖设置到目标Bean中。对于XML中配置的property nameuserDao refuserDao其解析发生在更早的BeanDefinition解析阶段但注入动作也发生在这里。容器会获取PropertyValues中保存的值或引用进行注入。实操心得在属性填充阶段如果发生循环依赖A依赖BB又依赖ASpring是如何解决的这是一个经典面试题。对于单例Bean且采用构造器注入Spring无法解决会直接抛出BeanCurrentlyInCreationException。但对于单例Bean且采用Setter方法或字段注入Spring通过三级缓存巧妙地解决了这个问题。简单来说三级缓存指的是三个MapsingletonObjects一级缓存存放完全初始化好的单例Bean。earlySingletonObjects二级缓存存放早期暴露的Bean已实例化但未填充属性。singletonFactories三级缓存存放单例工厂对象用于创建早期引用。当创建A时实例化后Spring会将一个能产生A早期引用可能是原始对象也可能是代理对象的工厂放入三级缓存。接着为A填充属性时发现需要B于是去创建B。创建B填充属性时又需要A此时从三级缓存中的工厂可以拿到A的早期引用尽管A的属性还没填完将其放入二级缓存同时从三级缓存删除。B拿到A的早期引用完成初始化放入一级缓存。最后A拿到初始化完成的B完成自己的属性填充和初始化也放入一级缓存。通过提前暴露一个“不完整”的Bean引用打破了循环。理解三级缓存是深入Spring源码的必修课。3.3 初始化回调与代理增强属性填充完成后Bean进入了初始化阶段对应AbstractAutowireCapableBeanFactory.initializeBean()方法。这个阶段是Bean生命周期中回调最集中的地方。Aware接口回调如果Bean实现了各种Aware接口如BeanNameAware,BeanFactoryAware,ApplicationContextAwareSpring会在此处调用相应方法将容器信息“感知”给Bean。BeanPostProcessor前置处理调用所有BeanPostProcessor的postProcessBeforeInitialization()方法。这是一个非常重要的扩展点很多Spring内置功能如PostConstruct处理、ApplicationContext的自动注入都是通过特定的BeanPostProcessor实现的。初始化方法执行执行Bean自定义的初始化方法。如果Bean实现了InitializingBean接口调用其afterPropertiesSet()方法。如果Bean定义了init-methodXML或PostConstruct注解的方法也会在此处被调用。PostConstruct是由CommonAnnotationBeanPostProcessor处理的它也是一个BeanPostProcessor在postProcessBeforeInitialization阶段执行。BeanPostProcessor后置处理调用所有BeanPostProcessor的postProcessAfterInitialization()方法。这是AOP代理创建的关键时机AbstractAutoProxyCreator的postProcessAfterInitialization()方法会检查当前Bean是否需要被代理例如是否有Transactional注解或匹配某个AOP切点表达式。如果需要它会创建一个代理对象JDK动态代理或CGLIB代理并返回。注意此时返回的可能已经不是原始的Bean对象而是它的代理对象了。容器后续持有的和注入给其他Bean的都是这个代理对象。经过以上步骤一个完整的、经过所有生命周期回调、可能已被AOP代理包裹的Bean就创建完成了并被放入单例池一级缓存中等待被其他Bean依赖或应用程序获取。4. AOP的实现基石代理模式与织入时机AOP面向切面编程是Spring框架的另一大基石它允许我们将横切关注点如日志、事务、安全模块化。Spring AOP默认使用基于代理的实现方式其核心可以概括为在运行时为目标对象创建一个代理对象所有对目标对象的方法调用都先经过这个代理对象由代理对象决定是否以及如何执行增强Advice逻辑。4.1 代理的两种选择JDK与CGLIBSpring AOP主要使用两种技术创建代理JDK动态代理基于接口。要求目标类至少实现一个接口。代理对象会实现和目标类相同的接口因此只能拦截接口中定义的方法。内部使用java.lang.reflect.Proxy和InvocationHandler实现。CGLIB代理基于子类。通过动态生成目标类的一个子类来创建代理。因此可以代理没有实现接口的类。它通过方法拦截器MethodInterceptor来织入增强逻辑。Spring默认的策略是如果目标对象实现了接口则使用JDK动态代理如果没有实现任何接口则使用CGLIB。你也可以通过配置如EnableAspectJAutoProxy(proxyTargetClass true)强制使用CGLIB代理这样无论是否有接口都创建基于子类的代理。注意事项由于CGLIB是通过继承实现的所以对于final类或final方法CGLIB无法生成代理。同时代理类会重写目标类的方法因此目标类内部的方法调用this.someMethod()不会经过代理这可能导致自调用时AOP失效。这是一个常见的坑。4.2 织入的时机与Bean生命周期的融合AOP的织入Weaving时机是理解Spring AOP源码的关键。它并非在编译期修改字节码而是在运行时与IOC容器的Bean创建过程紧密集成。具体来说就是在上一节提到的BeanPostProcessor.postProcessAfterInitialization()阶段。AbstractAutoProxyCreator是负责自动创建代理的核心类。在postProcessAfterInitialization()中它会获取增强器Advisor遍历容器中所有的Advisor切面的一种抽象包含一个Advice和一个Pointcut。这些Advisor可能来自使用Aspect注解的类也可能来自XML配置的aop:advisor。匹配切点Pointcut对于当前正在初始化的Bean使用每个Advisor中的Pointcut切点表达式进行匹配判断当前Bean的类和方法是否需要被增强。创建代理如果找到了匹配的Advisor则说明该Bean需要被代理。Spring会使用ProxyFactory来创建代理对象。ProxyFactory会根据配置目标类、接口、Advisor列表等决定使用JDK还是CGLIB并生成最终的代理对象。返回代理将这个代理对象返回给容器。从此容器中和应用程序中获取到的就是这个代理对象而非原始的目标对象。4.3 增强Advice的类型与执行链Spring AOP支持多种类型的增强对应不同的执行时机Before前置增强在目标方法执行前运行。AfterReturning后置增强在目标方法成功执行后运行。AfterThrowing异常增强在目标方法抛出异常后运行。After最终增强在目标方法执行后运行无论成功还是异常类似于finally块。Around环绕增强功能最强大可以控制是否执行目标方法以及在执行前后自定义行为。它接收一个ProceedingJoinPoint参数需要显式调用proceed()来执行目标方法。在代理对象的方法被调用时所有这些匹配的增强会形成一个“拦截器链”MethodInterceptor链。对于Around增强它本身就是一个MethodInterceptor。对于其他几种注解增强Spring内部也会将它们适配成对应的MethodInterceptor实现如AspectJAfterAdvice,AfterReturningAdviceInterceptor等。执行时代理对象会创建一个ReflectiveMethodInvocation对象它持有目标对象、方法、参数以及拦截器链。然后调用其proceed()方法该方法会按顺序遍历拦截器链逐个执行拦截器的invoke()方法。Around拦截器在其中调用proceedingJoinPoint.proceed()实际上就是递归地调用ReflectiveMethodInvocation.proceed()从而驱动链向下执行直到最后一个拦截器调用目标方法。调用完成后再沿着调用链逐层返回。这就实现了增强逻辑在目标方法周围的精确织入。5. 源码级案例一个事务方法背后的完整旅程为了将IOC和AOP的源码流程串联起来我们来看一个典型的场景一个带有Transactional注解的Service方法被调用时Spring在背后做了什么。假设我们有如下代码Service public class UserService { Autowired private UserRepository userRepository; Transactional public void createUser(User user) { userRepository.save(user); // ... 其他业务 } }容器启动与Bean定义注册Spring启动扫描到UserService类识别Service和Transactional注解。为UserService创建BeanDefinition并注册。同时因为EnableTransactionManagement或XML的tx:annotation-driven/容器会注册一个关键的BeanPostProcessor——InfrastructureAdvisorAutoProxyCreator它是AbstractAutoProxyCreator的子类以及处理事务的Advisor如BeanFactoryTransactionAttributeSourceAdvisor。UserService Bean的创建与代理当其他Bean依赖UserService或首次调用getBean(“userService”)时容器开始创建UserService实例。实例化UserService对象。填充属性将UserRepository注入。进入初始化阶段。在postProcessAfterInitialization中InfrastructureAdvisorAutoProxyCreator开始工作。它发现UserService类中的createUser方法上有Transactional注解匹配到了事务相关的Advisor。因此它为这个原始的UserService对象创建一个代理假设是CGLIB代理。注意此时UserService的原始Bean已经完成了属性注入userRepository已注入。容器最终将这个代理对象作为userServiceBean放入单例池。方法调用与事务拦截当你在Controller中调用userService.createUser(user)时你实际上调用的是代理对象的方法。CGLIB代理的方法拦截器会启动。它发现该方法匹配事务切点。事务拦截器TransactionInterceptor开始工作它从DataSourceTransactionManager获取一个数据库连接并在此连接上关闭自动提交开启一个新事务。将连接绑定到当前线程通过TransactionSynchronizationManager。然后调用invokeWithinTransaction方法在其内部会继续调用ReflectiveMethodInvocation.proceed()这最终会调用到原始UserService对象的createUser方法。原始方法内部的userRepository.save(user)在执行时会从事务同步管理器中获取到当前线程绑定的同一个数据库连接从而所有数据库操作都在同一个事务中。如果原始方法执行成功事务拦截器在方法返回后提交事务。如果原始方法抛出异常事务拦截器会回滚事务。这个流程清晰地展示了IOC如何管理Bean的生命周期和依赖以及AOP如何通过与IOC生命周期的集成BeanPostProcessor在运行时无缝地织入横切逻辑事务管理。原始Bean负责纯粹的商业逻辑而代理对象负责附加的系统级服务两者通过依赖注入完美协作。6. 深度排查当IOC与AOP不按预期工作时即使理解了原理在实际开发中我们仍会遇到各种诡异的问题。下面是一些常见问题的排查思路和源码层面的解释。6.1 Bean注入失败NoSuchBeanDefinitionException这是最常见的问题。除了检查包扫描路径、注解是否正确这些基本项从源码角度可以深入以下几点依赖查找时机注入发生在populateBean()阶段。此时容器会调用beanFactory.resolveDependency()来解析依赖。对于Autowired它默认按类型查找。如果找到多个同类型Bean需要Qualifier。如果按类型找不到会尝试按属性名查找。如果都失败则抛出异常。关键点确保你期望被注入的Bean其BeanDefinition已被成功注册检查扫描路径、Component注解。它本身没有因为循环依赖、初始化失败等原因导致创建失败。作用域匹配。例如一个request作用域的Bean无法被一个singleton的Bean直接注入因为singleton在容器启动时创建而request在每次请求时创建。这种情况可以考虑使用Scope(proxyMode ScopedProxyMode.TARGET_CLASS)或ObjectProvider。配置类与Bean方法的陷阱在Configuration类中Bean方法之间的调用看起来是普通Java方法调用但实际上会被CGLIB代理拦截以确保返回的是单例Bean。但如果你在同一个类中从一个Bean方法内部调用另一个Bean方法并且这个类没有被Spring代理比如标记为Configuration(proxyBeanMethods false)或者是一个普通的Component那么这种调用就是普通调用不会从容器中获取Bean可能导致意外行为。6.2 AOP失效的几种情况与原因自调用问题在同一个Bean内部方法A调用方法B即使方法B有Transactional或自定义切面增强也不会生效。因为自调用是通过this引用进行的而this指向的是目标对象本身不是Spring创建的代理对象。解决方案将方法B抽取到另一个Bean中。通过AopContext获取当前代理对象需要配置EnableAspectJAutoProxy(exposeProxy true)然后调用((UserService)AopContext.currentProxy()).methodB()。使用Autowired将自己注入但需注意循环依赖。final类或final方法如果目标类是final的或者目标方法是final的CGLIB无法生成子类代理会导致基于CGLIB的AOP失效。JDK动态代理对final方法无影响因为它基于接口。非public方法Spring AOP默认使用JDK动态代理或CGLIB代理它们通常只能代理public方法。对于protected、private或包级私有的方法AOP增强不会生效。可以通过配置使用AspectJ的编译时或加载时织入LTW来解决但这超出了Spring AOP的默认范畴。切点表达式Pointcut写错这是最直接的原因。使用execution(* com.example.service.*.*(..))和execution(* com.example.service..*.*(..))多一个点是有区别的后者会匹配子包。务必仔细检查表达式是否能正确匹配到目标方法。Bean被多次代理如果一个Bean既匹配了事务切面又匹配了自定义的日志切面它会被代理两次吗不会。AbstractAutoProxyCreator在postProcessAfterInitialization中会检查当前Bean是否已经是Advice、Pointcut、Advisor或AopInfrastructureBean类型或者是否已经被代理过通过判断是否是SpringProxy类型。如果已经是代理则通常不会再次代理。所有匹配的Advisor会被收集起来一次性织入到同一个代理中。6.3 循环依赖的排查与解决虽然Spring解决了Setter注入的单例Bean循环依赖但遇到问题时仍需排查构造器注入的循环依赖无法解决需重构代码使用Lazy注解延迟加载其中一个依赖或者改为Setter注入。Service public class ServiceA { private final ServiceB serviceB; // Lazy 注解可以加在这里 public ServiceA(Lazy ServiceB serviceB) { this.serviceB serviceB; } }原型PrototypeBean的循环依赖Spring不会缓存原型Bean因此无法通过提前暴露引用的方式解决。遇到时会抛出BeanCurrentlyInCreationException。必须通过设计模式如使用事件、观察者模式或引入第三方对象来打破循环。多例非单例与作用域代理当Singleton Bean注入一个Request/ Session Scoped Bean时因为Singleton初始化时Request可能不存在会导致注入失败。此时可以使用作用域代理Scope(value WebApplicationContext.SCOPE_REQUEST, proxyMode ScopedProxyMode.TARGET_CLASS)。这样注入的实际上是一个代理每次调用时代理会去当前请求中获取真实的Bean。调试循环依赖时可以开启Spring的调试日志logging.level.org.springframework.beansDEBUG观察Bean的创建顺序和缓存状态能清晰地看到三级缓存的操作过程。7. 性能调优与最佳实践启示阅读源码不仅是为了解决问题更是为了写出更好的代码。从Spring IOC和AOP的实现中我们可以汲取很多设计思想和最佳实践。合理使用Bean的作用域Singleton默认无状态的工具类、Service、Repository最适合。享受容器缓存带来的性能优势。Prototype有状态的Bean每次需要新实例时使用。但需谨慎因为创建成本较高且Spring不管理其完整生命周期。Request/Session/Application仅在Web环境中使用用于存储与特定请求、会话或应用全局相关的状态。懒加载Lazy的权衡使用Lazy注解或XML的lazy-init”true”可以延迟Singleton Bean的初始化加速应用启动。但代价是第一次请求该Bean时会稍有延迟。对于非核心路径的、初始化耗时的Bean使用懒加载是很好的优化手段。选择正确的AOP实现方式Spring AOP代理简单、易用与Spring容器无缝集成。适用于方法级别的拦截是大多数事务、日志、监控场景的首选。AspectJ编译时/加载时织入CTW/LTW功能更强大可以拦截字段访问、构造器调用、静态方法等性能也通常优于动态代理。但需要额外的编译器ajc或Java Agent配置更复杂。当你的切面需求超出了方法调用范围时才需要考虑AspectJ。保持切面轻量级切面中的逻辑应尽可能高效。避免在切面中执行耗时操作如远程调用、复杂IO尤其是在Around的proceed()之前。因为这会直接影响所有匹配方法的性能。理解代理的代价代理会带来轻微的性能开销方法调用多了一层间接层和调试复杂性栈跟踪中会看到代理类。在不需要AOP的地方避免不必要的代理。例如对于内部工具类如果不需被AOP增强可以将其声明为普通的Java类而非Spring Bean或者通过Configuration(proxyBeanMethods false)来减少CGLIB代理的创建。善用BeanPostProcessor进行扩展BeanPostProcessor是Spring提供的强大扩展点。理解IOC生命周期的你可以编写自己的BeanPostProcessor来在Bean创建前后执行自定义逻辑例如自动注入某些特定注解标记的字段、对Bean进行动态增强等。这是很多高级Spring库如Spring Boot的自动配置实现的基础。通过深入Spring IOC和AOP的源码我们看到的不仅仅是一个功能强大的框架更是一套精妙的设计模式工厂、模板方法、代理、观察者等和软件设计原则控制反转、依赖注入、关注点分离的教科书级实现。理解这些不仅能让你更得心应手地使用Spring更能提升你的系统设计能力和代码质量。下次当你的Transactional没有回滚或者Autowired注入为null时希望你能想起这篇文章从源码的生命周期里找到问题的根源。
Spring IOC与AOP源码解析:从Bean生命周期到代理机制
1. 从“硬编码”到“容器托管”IOC思想的本质与价值如果你是从早期的Java EE开发一路走过来的或者经历过纯粹用new关键字来组装对象的时代那么你对Spring的IOC控制反转容器带来的变革一定感触颇深。我记得在十多年前一个UserService要调用UserDao我们得在UserService的构造函数或者初始化方法里手动new UserDaoImpl()而UserDaoImpl又得自己去new DataSource()。这种代码随处可见牵一发而动全身测试时想替换个Mock对象都异常麻烦。Spring IOC的出现本质上解决的正是这个“对象创建与依赖组装权”的问题。它把原本由应用程序代码主动控制的创建过程反转给了容器Container来管理。这不仅仅是省了几行new代码那么简单它带来的是整个软件架构的松耦合和可测试性的飞跃。简单来说IOC是一种设计原则而依赖注入DI Dependency Injection是它的具体实现模式。Spring通过其IOC容器成为了这个模式的卓越实践者。容器就像是一个超级工厂它根据你提供的“图纸”配置负责生产实例化所有对象并按照图纸上的说明依赖关系把各个零件依赖对象装配到一起最后把成品交给你。你的业务代码从此不再关心UserDao具体是怎么来的它只需要声明“我需要一个UserDao”容器自然会在合适的时机给它。这种转变让我们的代码可以更专注于业务逻辑本身而不是复杂的对象生命周期管理和依赖查找。那么Spring是如何在源码层面实现这套机制的呢核心就在于两个接口体系BeanFactory和ApplicationContext。很多人刚开始会混淆其实BeanFactory是IOC容器的最低级接口提供了最基本的Bean管理功能比如根据名字获取Bean。而ApplicationContext是它的一个功能更丰富的子接口我们平时用的ClassPathXmlApplicationContext、AnnotationConfigApplicationContext都属于它。ApplicationContext在BeanFactory的基础上增加了国际化、事件传播、资源加载、以及更便捷的AOP集成等功能。可以说BeanFactory是心脏ApplicationContext是包含了心脏的完整躯体。接下来我们就深入到源码中看看这个“躯体”是如何启动并运转起来的。2. 容器启动的引擎BeanDefinition的加载与注册当我们写下new ClassPathXmlApplicationContext(“applicationContext.xml”)这行代码时Spring这座庞大机器的引擎就被启动了。这个过程远不止解析一个XML文件那么简单其核心是BeanDefinition的加载与注册。你可以把BeanDefinition理解为一个“Bean的配方”或者“施工蓝图”它定义了Bean的所有元数据类名、作用域、是否懒加载、依赖关系、属性值、初始化/销毁方法等等。容器在启动阶段并不直接创建Bean对象而是先读取配置把这些“蓝图”全部收集、注册到一个“蓝图仓库”里。2.1 配置解析从XML到BeanDefinition以经典的XML配置为例Spring通过BeanDefinitionReader接口体系来解析。ClassPathXmlApplicationContext内部持有一个XmlBeanDefinitionReader。启动时它会调用reader.loadBeanDefinitions(configLocation)方法。// 简化流程示意 public ClassPathXmlApplicationContext(String configLocation) { // ... 父类构造器初始化资源模式解析器等 this.setConfigLocations(configLocation); this.refresh(); // 这是核心入口 }refresh()方法是ApplicationContext初始化的核心模板方法定义了容器启动、刷新、关闭的完整生命周期。在refresh()中会调用obtainFreshBeanFactory()来获取或刷新BeanFactory进而触发loadBeanDefinitions(beanFactory)。在XmlBeanDefinitionReader.loadBeanDefinitions中会将XML文件资源转换为org.springframework.core.io.Resource对象。使用DocumentLoader将XML解析为W3C的Document对象。调用BeanDefinitionDocumentReader来解析这个Document。默认实现是DefaultBeanDefinitionDocumentReader。解析bean标签的关键在BeanDefinitionParserDelegate这个委托类中。对于每一个bean标签创建一个GenericBeanDefinition对象BeanDefinition接口的一个通用实现。解析id和name属性作为Bean的名称。解析class属性将字符串形式的类名转换为Class对象。解析scope属性默认为singleton。解析property子元素。对于value直接设置值对于ref会生成一个RuntimeBeanNameReference对象表示对另一个Bean的引用此时并不直接注入只是记录下依赖的Bean名称。将解析完所有属性的GenericBeanDefinition对象注册到BeanFactory持有的一个BeanDefinition注册中心——通常是DefaultListableBeanFactory内部的ConcurrentHashMapString, BeanDefinition中。Key是Bean的名字Value就是其“蓝图”。注意这里有一个非常重要的细节。XML解析时对于refuserDao这样的属性它只是把字符串“userDao”记录到BeanDefinition的PropertyValues中。此时并不会检查userDao这个Bean是否已经存在。这意味着在XML配置中Bean定义的顺序在大多数情况下是无关紧要的除了depends-on等特殊情况。容器允许“向前引用”即先定义依赖方后定义被依赖方。这给了配置很大的灵活性。2.2 注解配置的崛起ComponentScan与Bean随着注解的普及基于Java的配置方式Configuration已成为主流。其核心是AnnotationConfigApplicationContext和相关的后置处理器。当使用ComponentScan(“com.example”)时容器会扫描指定包下所有带有Component及其衍生注解Service,Repository,Controller的类。这个过程是由ClassPathBeanDefinitionScanner完成的。扫描器会遍历类路径找到候选类然后使用AnnotationBeanDefinitionDefinition来解析类上的注解生成一个ScannedGenericBeanDefinition并注册。对于Configuration类中的Bean方法处理则更加巧妙。它是由ConfigurationClassPostProcessor这个BeanFactoryPostProcessor来处理的。在容器刷新过程的invokeBeanFactoryPostProcessors(beanFactory)阶段这个后置处理器会被调用。它会解析所有Configuration类将每个Bean方法也转换为一个BeanDefinition。特别的是对于Bean方法Spring会生成一个特殊的BeanDefinition通常是ConfigurationClassBeanDefinition其factoryBeanName指向Configuration类本身的Bean名factoryMethodName指向具体的Bean方法名。这意味着这个Bean的实例化不是通过简单的new而是通过调用配置类实例的相应方法来完成的这为实现单例、代理等高级特性提供了基础。无论来源是XML还是注解最终所有Bean的“蓝图”都会被统一注册到BeanFactory的beanDefinitionMap中。至此容器完成了“备战”阶段所有Bean的配方都已就位接下来就是真正的“制造”和“装配”阶段。3. Bean的生命周期实例化、填充与初始化Bean从一张蓝图BeanDefinition变成一个立即可用的对象要经历一个复杂的生命周期。Spring通过一系列精妙的接口和回调机制将这个生命周期管理得井井有条。理解这个过程对于解决Bean创建过程中的各种诡异问题至关重要。3.1 实例化创建Bean的原始对象当容器需要获取一个Bean比如因为它是另一个Bean的依赖或者被直接getBean()且该Bean尚未实例化时创建过程就开始了。核心入口在AbstractBeanFactory.doGetBean()方法。首先容器会检查所请求的Bean是否是单例Singleton以及单例缓存中是否已存在。如果是原型Prototype作用域则每次都会进入创建流程。实例化的第一步是创建Bean的原始实例。这发生在AbstractAutowireCapableBeanFactory.createBeanInstance()方法中。Spring提供了几种策略使用工厂方法如果BeanDefinition中指定了factory-method来自XML的factory-method属性或Bean方法则通过反射调用该静态或实例方法来创建对象。使用构造器自动装配如果Bean的构造器上有Autowired注解Spring会尝试从容器中查找匹配的Bean作为参数通过反射调用该构造器。使用默认构造器最常见的情况直接调用类的无参构造器通过BeanUtils.instantiateClass实现。这里有一个重要的源码细节对于需要被AOP代理的Bean比如有Transactional注解Spring在这个阶段并不会直接创建目标类的实例。为什么因为代理需要包裹一个目标对象。如果先创建了目标对象再创建代理那么代理内部持有的目标对象和容器中其他Bean直接引用的可能就不是同一个如果是单例就破坏了单例。Spring的解决方案是使用“SmartInstantiationAwareBeanPostProcessor”。AbstractAutoProxyCreatorAOP代理创建的核心类就是一个这样的后置处理器。它在postProcessBeforeInstantiation()方法中有机会返回一个代理对象来提前终止标准的实例化流程。如果它返回了非null对象那么后续的标准实例化、属性填充都不会进行直接进入初始化后阶段。这是理解Spring AOP与IOC协同工作的关键点之一。3.2 属性填充依赖注入的核心实例化得到一个“空壳”对象后下一步就是填充其属性即依赖注入。这个过程在AbstractAutowireCapableBeanFactory.populateBean()方法中完成。依赖注入主要有两种方式ByType (自动装配 byType)Spring根据属性的类型在容器中寻找唯一匹配的Bean进行注入。如果有多个同类型Bean则需要配合Qualifier指定名称否则会抛出NoUniqueBeanDefinitionException。ByName (自动装配 byName)Spring根据属性的名称在容器中寻找同名Bean进行注入。对于Autowired注解的属性是由AutowiredAnnotationBeanPostProcessor这个后置处理器来处理的。它在postProcessProperties()方法中会解析字段或方法上的Autowired然后通过beanFactory.resolveDependency()来解析依赖。这个方法会处理Qualifier、Value、Lazy等注解最终获取到依赖的Bean对象如果依赖的Bean还未创建则会触发其创建流程这可能是一个递归过程然后通过反射Field.set()或Method.invoke()将依赖设置到目标Bean中。对于XML中配置的property nameuserDao refuserDao其解析发生在更早的BeanDefinition解析阶段但注入动作也发生在这里。容器会获取PropertyValues中保存的值或引用进行注入。实操心得在属性填充阶段如果发生循环依赖A依赖BB又依赖ASpring是如何解决的这是一个经典面试题。对于单例Bean且采用构造器注入Spring无法解决会直接抛出BeanCurrentlyInCreationException。但对于单例Bean且采用Setter方法或字段注入Spring通过三级缓存巧妙地解决了这个问题。简单来说三级缓存指的是三个MapsingletonObjects一级缓存存放完全初始化好的单例Bean。earlySingletonObjects二级缓存存放早期暴露的Bean已实例化但未填充属性。singletonFactories三级缓存存放单例工厂对象用于创建早期引用。当创建A时实例化后Spring会将一个能产生A早期引用可能是原始对象也可能是代理对象的工厂放入三级缓存。接着为A填充属性时发现需要B于是去创建B。创建B填充属性时又需要A此时从三级缓存中的工厂可以拿到A的早期引用尽管A的属性还没填完将其放入二级缓存同时从三级缓存删除。B拿到A的早期引用完成初始化放入一级缓存。最后A拿到初始化完成的B完成自己的属性填充和初始化也放入一级缓存。通过提前暴露一个“不完整”的Bean引用打破了循环。理解三级缓存是深入Spring源码的必修课。3.3 初始化回调与代理增强属性填充完成后Bean进入了初始化阶段对应AbstractAutowireCapableBeanFactory.initializeBean()方法。这个阶段是Bean生命周期中回调最集中的地方。Aware接口回调如果Bean实现了各种Aware接口如BeanNameAware,BeanFactoryAware,ApplicationContextAwareSpring会在此处调用相应方法将容器信息“感知”给Bean。BeanPostProcessor前置处理调用所有BeanPostProcessor的postProcessBeforeInitialization()方法。这是一个非常重要的扩展点很多Spring内置功能如PostConstruct处理、ApplicationContext的自动注入都是通过特定的BeanPostProcessor实现的。初始化方法执行执行Bean自定义的初始化方法。如果Bean实现了InitializingBean接口调用其afterPropertiesSet()方法。如果Bean定义了init-methodXML或PostConstruct注解的方法也会在此处被调用。PostConstruct是由CommonAnnotationBeanPostProcessor处理的它也是一个BeanPostProcessor在postProcessBeforeInitialization阶段执行。BeanPostProcessor后置处理调用所有BeanPostProcessor的postProcessAfterInitialization()方法。这是AOP代理创建的关键时机AbstractAutoProxyCreator的postProcessAfterInitialization()方法会检查当前Bean是否需要被代理例如是否有Transactional注解或匹配某个AOP切点表达式。如果需要它会创建一个代理对象JDK动态代理或CGLIB代理并返回。注意此时返回的可能已经不是原始的Bean对象而是它的代理对象了。容器后续持有的和注入给其他Bean的都是这个代理对象。经过以上步骤一个完整的、经过所有生命周期回调、可能已被AOP代理包裹的Bean就创建完成了并被放入单例池一级缓存中等待被其他Bean依赖或应用程序获取。4. AOP的实现基石代理模式与织入时机AOP面向切面编程是Spring框架的另一大基石它允许我们将横切关注点如日志、事务、安全模块化。Spring AOP默认使用基于代理的实现方式其核心可以概括为在运行时为目标对象创建一个代理对象所有对目标对象的方法调用都先经过这个代理对象由代理对象决定是否以及如何执行增强Advice逻辑。4.1 代理的两种选择JDK与CGLIBSpring AOP主要使用两种技术创建代理JDK动态代理基于接口。要求目标类至少实现一个接口。代理对象会实现和目标类相同的接口因此只能拦截接口中定义的方法。内部使用java.lang.reflect.Proxy和InvocationHandler实现。CGLIB代理基于子类。通过动态生成目标类的一个子类来创建代理。因此可以代理没有实现接口的类。它通过方法拦截器MethodInterceptor来织入增强逻辑。Spring默认的策略是如果目标对象实现了接口则使用JDK动态代理如果没有实现任何接口则使用CGLIB。你也可以通过配置如EnableAspectJAutoProxy(proxyTargetClass true)强制使用CGLIB代理这样无论是否有接口都创建基于子类的代理。注意事项由于CGLIB是通过继承实现的所以对于final类或final方法CGLIB无法生成代理。同时代理类会重写目标类的方法因此目标类内部的方法调用this.someMethod()不会经过代理这可能导致自调用时AOP失效。这是一个常见的坑。4.2 织入的时机与Bean生命周期的融合AOP的织入Weaving时机是理解Spring AOP源码的关键。它并非在编译期修改字节码而是在运行时与IOC容器的Bean创建过程紧密集成。具体来说就是在上一节提到的BeanPostProcessor.postProcessAfterInitialization()阶段。AbstractAutoProxyCreator是负责自动创建代理的核心类。在postProcessAfterInitialization()中它会获取增强器Advisor遍历容器中所有的Advisor切面的一种抽象包含一个Advice和一个Pointcut。这些Advisor可能来自使用Aspect注解的类也可能来自XML配置的aop:advisor。匹配切点Pointcut对于当前正在初始化的Bean使用每个Advisor中的Pointcut切点表达式进行匹配判断当前Bean的类和方法是否需要被增强。创建代理如果找到了匹配的Advisor则说明该Bean需要被代理。Spring会使用ProxyFactory来创建代理对象。ProxyFactory会根据配置目标类、接口、Advisor列表等决定使用JDK还是CGLIB并生成最终的代理对象。返回代理将这个代理对象返回给容器。从此容器中和应用程序中获取到的就是这个代理对象而非原始的目标对象。4.3 增强Advice的类型与执行链Spring AOP支持多种类型的增强对应不同的执行时机Before前置增强在目标方法执行前运行。AfterReturning后置增强在目标方法成功执行后运行。AfterThrowing异常增强在目标方法抛出异常后运行。After最终增强在目标方法执行后运行无论成功还是异常类似于finally块。Around环绕增强功能最强大可以控制是否执行目标方法以及在执行前后自定义行为。它接收一个ProceedingJoinPoint参数需要显式调用proceed()来执行目标方法。在代理对象的方法被调用时所有这些匹配的增强会形成一个“拦截器链”MethodInterceptor链。对于Around增强它本身就是一个MethodInterceptor。对于其他几种注解增强Spring内部也会将它们适配成对应的MethodInterceptor实现如AspectJAfterAdvice,AfterReturningAdviceInterceptor等。执行时代理对象会创建一个ReflectiveMethodInvocation对象它持有目标对象、方法、参数以及拦截器链。然后调用其proceed()方法该方法会按顺序遍历拦截器链逐个执行拦截器的invoke()方法。Around拦截器在其中调用proceedingJoinPoint.proceed()实际上就是递归地调用ReflectiveMethodInvocation.proceed()从而驱动链向下执行直到最后一个拦截器调用目标方法。调用完成后再沿着调用链逐层返回。这就实现了增强逻辑在目标方法周围的精确织入。5. 源码级案例一个事务方法背后的完整旅程为了将IOC和AOP的源码流程串联起来我们来看一个典型的场景一个带有Transactional注解的Service方法被调用时Spring在背后做了什么。假设我们有如下代码Service public class UserService { Autowired private UserRepository userRepository; Transactional public void createUser(User user) { userRepository.save(user); // ... 其他业务 } }容器启动与Bean定义注册Spring启动扫描到UserService类识别Service和Transactional注解。为UserService创建BeanDefinition并注册。同时因为EnableTransactionManagement或XML的tx:annotation-driven/容器会注册一个关键的BeanPostProcessor——InfrastructureAdvisorAutoProxyCreator它是AbstractAutoProxyCreator的子类以及处理事务的Advisor如BeanFactoryTransactionAttributeSourceAdvisor。UserService Bean的创建与代理当其他Bean依赖UserService或首次调用getBean(“userService”)时容器开始创建UserService实例。实例化UserService对象。填充属性将UserRepository注入。进入初始化阶段。在postProcessAfterInitialization中InfrastructureAdvisorAutoProxyCreator开始工作。它发现UserService类中的createUser方法上有Transactional注解匹配到了事务相关的Advisor。因此它为这个原始的UserService对象创建一个代理假设是CGLIB代理。注意此时UserService的原始Bean已经完成了属性注入userRepository已注入。容器最终将这个代理对象作为userServiceBean放入单例池。方法调用与事务拦截当你在Controller中调用userService.createUser(user)时你实际上调用的是代理对象的方法。CGLIB代理的方法拦截器会启动。它发现该方法匹配事务切点。事务拦截器TransactionInterceptor开始工作它从DataSourceTransactionManager获取一个数据库连接并在此连接上关闭自动提交开启一个新事务。将连接绑定到当前线程通过TransactionSynchronizationManager。然后调用invokeWithinTransaction方法在其内部会继续调用ReflectiveMethodInvocation.proceed()这最终会调用到原始UserService对象的createUser方法。原始方法内部的userRepository.save(user)在执行时会从事务同步管理器中获取到当前线程绑定的同一个数据库连接从而所有数据库操作都在同一个事务中。如果原始方法执行成功事务拦截器在方法返回后提交事务。如果原始方法抛出异常事务拦截器会回滚事务。这个流程清晰地展示了IOC如何管理Bean的生命周期和依赖以及AOP如何通过与IOC生命周期的集成BeanPostProcessor在运行时无缝地织入横切逻辑事务管理。原始Bean负责纯粹的商业逻辑而代理对象负责附加的系统级服务两者通过依赖注入完美协作。6. 深度排查当IOC与AOP不按预期工作时即使理解了原理在实际开发中我们仍会遇到各种诡异的问题。下面是一些常见问题的排查思路和源码层面的解释。6.1 Bean注入失败NoSuchBeanDefinitionException这是最常见的问题。除了检查包扫描路径、注解是否正确这些基本项从源码角度可以深入以下几点依赖查找时机注入发生在populateBean()阶段。此时容器会调用beanFactory.resolveDependency()来解析依赖。对于Autowired它默认按类型查找。如果找到多个同类型Bean需要Qualifier。如果按类型找不到会尝试按属性名查找。如果都失败则抛出异常。关键点确保你期望被注入的Bean其BeanDefinition已被成功注册检查扫描路径、Component注解。它本身没有因为循环依赖、初始化失败等原因导致创建失败。作用域匹配。例如一个request作用域的Bean无法被一个singleton的Bean直接注入因为singleton在容器启动时创建而request在每次请求时创建。这种情况可以考虑使用Scope(proxyMode ScopedProxyMode.TARGET_CLASS)或ObjectProvider。配置类与Bean方法的陷阱在Configuration类中Bean方法之间的调用看起来是普通Java方法调用但实际上会被CGLIB代理拦截以确保返回的是单例Bean。但如果你在同一个类中从一个Bean方法内部调用另一个Bean方法并且这个类没有被Spring代理比如标记为Configuration(proxyBeanMethods false)或者是一个普通的Component那么这种调用就是普通调用不会从容器中获取Bean可能导致意外行为。6.2 AOP失效的几种情况与原因自调用问题在同一个Bean内部方法A调用方法B即使方法B有Transactional或自定义切面增强也不会生效。因为自调用是通过this引用进行的而this指向的是目标对象本身不是Spring创建的代理对象。解决方案将方法B抽取到另一个Bean中。通过AopContext获取当前代理对象需要配置EnableAspectJAutoProxy(exposeProxy true)然后调用((UserService)AopContext.currentProxy()).methodB()。使用Autowired将自己注入但需注意循环依赖。final类或final方法如果目标类是final的或者目标方法是final的CGLIB无法生成子类代理会导致基于CGLIB的AOP失效。JDK动态代理对final方法无影响因为它基于接口。非public方法Spring AOP默认使用JDK动态代理或CGLIB代理它们通常只能代理public方法。对于protected、private或包级私有的方法AOP增强不会生效。可以通过配置使用AspectJ的编译时或加载时织入LTW来解决但这超出了Spring AOP的默认范畴。切点表达式Pointcut写错这是最直接的原因。使用execution(* com.example.service.*.*(..))和execution(* com.example.service..*.*(..))多一个点是有区别的后者会匹配子包。务必仔细检查表达式是否能正确匹配到目标方法。Bean被多次代理如果一个Bean既匹配了事务切面又匹配了自定义的日志切面它会被代理两次吗不会。AbstractAutoProxyCreator在postProcessAfterInitialization中会检查当前Bean是否已经是Advice、Pointcut、Advisor或AopInfrastructureBean类型或者是否已经被代理过通过判断是否是SpringProxy类型。如果已经是代理则通常不会再次代理。所有匹配的Advisor会被收集起来一次性织入到同一个代理中。6.3 循环依赖的排查与解决虽然Spring解决了Setter注入的单例Bean循环依赖但遇到问题时仍需排查构造器注入的循环依赖无法解决需重构代码使用Lazy注解延迟加载其中一个依赖或者改为Setter注入。Service public class ServiceA { private final ServiceB serviceB; // Lazy 注解可以加在这里 public ServiceA(Lazy ServiceB serviceB) { this.serviceB serviceB; } }原型PrototypeBean的循环依赖Spring不会缓存原型Bean因此无法通过提前暴露引用的方式解决。遇到时会抛出BeanCurrentlyInCreationException。必须通过设计模式如使用事件、观察者模式或引入第三方对象来打破循环。多例非单例与作用域代理当Singleton Bean注入一个Request/ Session Scoped Bean时因为Singleton初始化时Request可能不存在会导致注入失败。此时可以使用作用域代理Scope(value WebApplicationContext.SCOPE_REQUEST, proxyMode ScopedProxyMode.TARGET_CLASS)。这样注入的实际上是一个代理每次调用时代理会去当前请求中获取真实的Bean。调试循环依赖时可以开启Spring的调试日志logging.level.org.springframework.beansDEBUG观察Bean的创建顺序和缓存状态能清晰地看到三级缓存的操作过程。7. 性能调优与最佳实践启示阅读源码不仅是为了解决问题更是为了写出更好的代码。从Spring IOC和AOP的实现中我们可以汲取很多设计思想和最佳实践。合理使用Bean的作用域Singleton默认无状态的工具类、Service、Repository最适合。享受容器缓存带来的性能优势。Prototype有状态的Bean每次需要新实例时使用。但需谨慎因为创建成本较高且Spring不管理其完整生命周期。Request/Session/Application仅在Web环境中使用用于存储与特定请求、会话或应用全局相关的状态。懒加载Lazy的权衡使用Lazy注解或XML的lazy-init”true”可以延迟Singleton Bean的初始化加速应用启动。但代价是第一次请求该Bean时会稍有延迟。对于非核心路径的、初始化耗时的Bean使用懒加载是很好的优化手段。选择正确的AOP实现方式Spring AOP代理简单、易用与Spring容器无缝集成。适用于方法级别的拦截是大多数事务、日志、监控场景的首选。AspectJ编译时/加载时织入CTW/LTW功能更强大可以拦截字段访问、构造器调用、静态方法等性能也通常优于动态代理。但需要额外的编译器ajc或Java Agent配置更复杂。当你的切面需求超出了方法调用范围时才需要考虑AspectJ。保持切面轻量级切面中的逻辑应尽可能高效。避免在切面中执行耗时操作如远程调用、复杂IO尤其是在Around的proceed()之前。因为这会直接影响所有匹配方法的性能。理解代理的代价代理会带来轻微的性能开销方法调用多了一层间接层和调试复杂性栈跟踪中会看到代理类。在不需要AOP的地方避免不必要的代理。例如对于内部工具类如果不需被AOP增强可以将其声明为普通的Java类而非Spring Bean或者通过Configuration(proxyBeanMethods false)来减少CGLIB代理的创建。善用BeanPostProcessor进行扩展BeanPostProcessor是Spring提供的强大扩展点。理解IOC生命周期的你可以编写自己的BeanPostProcessor来在Bean创建前后执行自定义逻辑例如自动注入某些特定注解标记的字段、对Bean进行动态增强等。这是很多高级Spring库如Spring Boot的自动配置实现的基础。通过深入Spring IOC和AOP的源码我们看到的不仅仅是一个功能强大的框架更是一套精妙的设计模式工厂、模板方法、代理、观察者等和软件设计原则控制反转、依赖注入、关注点分离的教科书级实现。理解这些不仅能让你更得心应手地使用Spring更能提升你的系统设计能力和代码质量。下次当你的Transactional没有回滚或者Autowired注入为null时希望你能想起这篇文章从源码的生命周期里找到问题的根源。