一文搞懂Spring IOC(万字长文深度解析源码)
Spring IOC超详细源码解析
- 一. 介绍
- 二. 使用IOC
- 三. 工作原理和工作流程
- IOC的工作原理
- 主要做了些什么?
- 是怎么做到的?
- 1、加载解析Bean
- 2、注册Bean
- 3、实例化Bean(从图纸变成实物)
- 4、依赖注入
- 5、初始化Bean
- 6、使用Bean
- 7、销毁Bean
- 动手debug尝试
- 四. 单例池管理(缓存已创建的Bean)
- 五. 扩展机制(BeanPostProcessor)
一. 介绍
什么是IOC
IOC是Spring的核心之一。全称是 Inversion of Control,中文翻译就是“控制反转”。是面向对象编程的一种设计原则,用来减少代码之间的耦合。
IOC为何出现?又为何叫”控制反转“?
在没有spring IOC之前,每次Java对象的创建、配置和管理都得程序员自己手动一个个去解决,在工作中往往Java对象往往成百上千,一个一个创建处理,显然不现实。为了方便对象管理,减少程序员工作量,就有了Spring IOC。
将原本应用代码对对象创建和管理的控制权都交给Spring容器,减少代码的耦合,便于维护和扩展。
IOC通过依赖注入(DI)来管理组成应用程序的组件,这些组件也就是Java对象,被称为Spring Beans。
所以IOC也可以理解为一个对象的自动装配的大容器。
二. 使用IOC
基于xml或者基于注解。其中基于注解可以是配置类+注解,也可以是扫包+注解。以下为简单示例。
User.class
@Data //lombok注解 public class User { private String username; private String sex; }
application.xml(基于xml方式时需要创建)
BeanConfiguration.java(基于注解+配置类时需要创建)
@Configuration //告诉spring这是一个配置类 public class BeanConfiguration { @Bean(value="user") //丢到spring容器中,给spring管理。这样Spring就知道这是一个bean,会创建这个对象 //如果不加value值,默认bean的名字就是方法名,加value属性可以自定义bean的名字 public User user(){ User user = new User(); user.setUsername("张三"); user.setSex("男"); return user; } }
基于注解+扫包方式时,只需要修改User.java文件
@Data @Component public class User { @Value("张三") //注入属性值 private String username; @Value("男") private String sex; }
test.java
public class test { public static void main(String[] args) { //1、基于xml文件 //加载和解析xml配置 ClassPathXmlApplicationContext context1 = new ClassPathXmlApplicationContext("application.xml"); //获取bean System.out.println(context1.getBean("user")); //2、基于注解+配置类 //加载和解析注解配置类 AnnotationConfigApplicationContext context2 = new AnnotationConfigApplicationContext(BeanConfiguration.class); System.out.println(context2.getBean("user")); //当配置类很多的时候,可以放在一个包下,通过指定包来加载,避免一个一个去手动加载 AnnotationConfigApplicationContext context3 = new AnnotationConfigApplicationContext("com.esther.config"); System.out.println(context3.getBean("user")); //3、基于注解+扫包 //最简单的方式,不再需要依赖于xml文件或者配置类,而是直接将bean的创建类交给目标类,让目标类来创建。 //只需要在实体类上加上注解@Component,告诉Spring这个类要被注入到ioc,让ioc去创建这个类对象。如果没有component注解,下面输出就报报错找不到bean。 AnnotationConfigApplicationContext context4 = new AnnotationConfigApplicationContext("com.esther.pojo"); System.out.println(context4.getBean(User.class)); } }
扩展:依赖注入
Company.class
@Data public class Company { @Value("001") private int code; @Autowired private User employee; //依赖另一个对象 }
ioc创建完user和company对象后,会自动把user对象装配到company对象中,这就是依赖注入
@Autowired是通过类型进行注入,如果需要通过名称注入使用@Qualifier(“xxx”),此时需要在类的component注解上加上名称,如@Component(“xxx”)
三. 工作原理和工作流程
在了解完Spring IOC是什么,为什么要出现以及基本的使用后,那就该疑问了,IOC到底是怎么实现对象的自动装配的呢?
Spring提供了两个主要的IOC容器:BeanFactory和ApplicationContext。
BeanFactory翻译就是对象工厂,是Spring IOC容其的最底层实现,负责管理Bean的生命周期。(Bean即Java对象)
ApplicationContext是BeanFactory的子接口,不仅包含了BeanFactory的功能,还提供了更多高级的功能。比如:支持国际化、事件机制、资源加载以及AOP
IOC的工作原理
依赖注入(Dependency Injection)是IOC的核心实现方式
IOC容器通过反射机制来创建和管理Bean实例。容器会维护一个Bean的缓存池,当需要使用Bean时,可以直接从缓存池中获取,而不需要每次都重新创建。
IOC容器通过配置文件或者注解来解析Bean的依赖关系。在配置文件中,可以通过标签来定义Bean和其依赖;在注解中,可以通过@Autowired、@Resource等注解来指定依赖关系。
主要做了些什么?
- 加载配置
可以加载基于XML的配置文件、基于Java注解的配置类等,获取Bean的定义信息。
- 实例化Bean
根据配置信息,通过反射机制创建Bean的实例。
- 依赖注入
在创建Bean实例时,为Bean实例注入依赖的其他Bean或者基本类型的属性值。
- 生命周期管理
控制Bean的创建和销毁过程,包括初始化和销毁方法的调用。
是怎么做到的?
1、加载解析Bean
想要管理Bean,首先需要把Bean加载进来。
- IOC容器启动时首先会加载配置元数据(如XML文件、Java注解或者Java配置类定义数据)
- 加载过程主要由 ApplicationContext 接口的实现类完成。如ClassPathXmlApplicationContext (基于xml文件)或 AnnotationConfigApplicationContext(基于注解)。
ApplicationContext的所有实现类:
关键源码(refresh方法)
(Spring 5.x版本) public class ClassPathXmlApplicationContext extends AbstractApplicationContext { public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh) { super(configLocations, refresh); if (refresh) { refresh(); } } @Override protected void refresh() { synchronized (this.startupShutdownMonitor) { // 准备刷新上下文 prepareRefresh(); // 加载配置文件 loadBeanDefinitions(); // 完成刷新 finishRefresh(); } } private void loadBeanDefinitions() { if (getConfigLocations() != null) { for (String location : getConfigLocations()) { loadBeanDefinition(location); } } } private void loadBeanDefinition(String location) { // 加载 XML 文件 XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this); //此方法具体见下一代码块 reader.loadBeanDefinitions(location); } }
public class XmlBeanDefinitionReader { public void loadBeanDefinitions(String location) { // 加载 XML 文件 Document doc = loadDocument(location); // 解析 XML 文件 parseBeanDefinitions(doc); } private void parseBeanDefinitions(Document doc) { // 获取根元素 Element root = doc.getDocumentElement(); // 解析每个 bean 元素 NodeList nodeList = root.getElementsByTagName("bean"); for (int i = 0; i
源码分析
以ClassPathXmlApplicationContext为例
- 当我们在代码中写下ApplicationContext context = new ClassPathXmlApplicationContext(“beans.xml”)
容器就已经开始启动了。它在构造函数中调用了refresh() 方法,这个方法会加载并解析配置文件。
- 在 loadBeanDefinition() 方法中 ,使用XmlBeanDefinitionReader来解析XML配置文件。
- XmlBeanDefinitionReader会通过ClassPathResource加载 xml,使用DOM解析器将XML转成Document对象。然后遍历bean标签,创建BeanDefinition对象。
BeanDefinition 是什么?
它就像Bean的 “身份证”,保存了类名、作用域(单例/原型)等元数据。
BeanDefinition包含了类的名称、依赖关系、初始化方法、销毁方法等元数据信息。
拓展
Spring 6.x版本用 ReentrantLock 替换了 synchronized,如下:
private final ReentrantLock refreshLock = new ReentrantLock(); public void refresh() { refreshLock.lock(); try { prepareRefresh(); // 后续步骤... } finally { refreshLock.unlock(); } }
为什么要升级?
主要是为了提升并发性和灵活性。 ReentrantLock 提供了更灵活的锁管理(如超时、中断),利用tryLock减少死锁风险,尤其在复杂并发场景下性能更好。
2、注册Bean
加载解析完配置时,IOC把配置信息转换成容器可以理解的数据结构(BeanDefinition对象)。然后就开始注册Bean了
场景类比
想象BeanDefinition是家具的设计图纸,而BeanFactory是工厂的仓库。这一步就是把所有图纸存进仓库。
关键源码:
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) { this.beanDefinitionMap.put(beanName, beanDefinition); // ...其他处理(如清除缓存) }
源码分析
- 这里发挥作用的主要是DefaultListableBeanFactory类的registerBeanDefinition(String beanName, BeanDefinition beanDefinition) 方法。
- DefaultListableBeanFactory是BeanFactory接口的实现类
拓展
Spring 6.x中对存储BeanDefinition的数据结构做了性能优化(例如使用更高效的ConcurrentHashMap变体),在存在大量Bean时减少内存的占用。
3、实例化Bean(从图纸变成实物)
- 当应用程序需要使用某个bean时,Spring IOC容器会注册的bean的信息创建Bean的实例。
此场景问题
如何将BeanDefinition 变成真正的Java对象?比如 new User()
关键源码:
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory { public Object getBean(String name) throws BeansException { // 获取 BeanDefinition BeanDefinition beanDefinition = getBeanDefinition(name); // 创建 Bean 实例 return createBean(beanDefinition); } private Object createBean(BeanDefinition beanDefinition) { // 获取 Bean 的类名 String className = beanDefinition.getBeanClassName(); // 加载类 Class beanClass = loadClass(className); // 实例化 Bean Object bean = instantiateBean(beanClass); // 注入依赖 injectDependencies(bean, beanDefinition); // 初始化 Bean initializeBean(bean, beanDefinition); return bean; } private Object instantiateBean(Class beanClass) { // 使用反射机制创建实例 try { return beanClass.newInstance(); } catch (InstantiationException | IllegalAccessException e) { throw new BeanCreationException("Failed to instantiate bean", e); } } }
源码分析
- 在createBean() 方法中,首先获取从前面注册的beanDefinitionMap中获取注册的BeanDefinition对象,拿到bean的定义信息。
- 然后通过反射机制创建Bean的实例
- 接着注入依赖并初始化bean
4、依赖注入
- Spring容器会根据bean的定义信息,将依赖的bean注入到目标bean中。(如前面实例的职工对象里面有一个属性是用户对象)
- 依赖注入可以通过构造函数、Setter方法或者字段注解(如@Autowired、@Resource)来实现。
关键源码:
private void injectDependencies(Object bean, BeanDefinition beanDefinition) { // 获取 Bean 的类 Class beanClass = bean.getClass(); // 获取所有字段 Field[] fields = beanClass.getDeclaredFields(); for (Field field : fields) { // 检查是否有 @Autowired 注解 if (field.isAnnotationPresent(Autowired.class)) { // 获取依赖的 Bean 名称 String dependencyName = field.getName(); // 获取依赖的 Bean 实例 Object dependency = getBean(dependencyName); // 注入依赖 try { field.setAccessible(true); field.set(bean, dependency); } catch (IllegalAccessException e) { throw new BeanCreationException("Failed to inject dependency", e); } } } }
源码分析
- DefaultListableBeanFactory类提供了 injectDependencies() 方法,用于注入依赖。
- 在injectDependencies() 方法中,遍历Bean的所有字段,检查是否有@Autowired 注解,如果有,则获取依赖的Bean实例,并通过反射机制注入到目标Bean中。
5、初始化Bean
Spring的IOC容器会调用Bean的初始化方法,完成Bean的初始化工作。初始化方法可以通过实现InitializingBean 接口、使用@PostConstruct 注解或者在XML配置中指定 init-method 来定义。
关键源码:
private void initializeBean(Object bean, BeanDefinition beanDefinition) { // 检查是否实现了 InitializingBean 接口 if (bean instanceof InitializingBean) { try { ((InitializingBean) bean).afterPropertiesSet(); } catch (Exception e) { throw new BeanCreationException("Failed to initialize bean", e); } } // 检查是否有 @PostConstruct 注解 Method[] methods = bean.getClass().getDeclaredMethods(); for (Method method : methods) { if (method.isAnnotationPresent(PostConstruct.class)) { try { method.setAccessible(true); method.invoke(bean); } catch (Exception e) { throw new BeanCreationException("Failed to initialize bean", e); } } } // 调用 XML 配置中的 init-method String initMethod = beanDefinition.getInitMethod(); if (initMethod != null) { try { Method method = bean.getClass().getMethod(initMethod); method.invoke(bean); } catch (Exception e) { throw new BeanCreationException("Failed to initialize bean", e); } } }
源码分析
- 在initializingBean() 方法中,首先检查Bean是否实现了InitializingBean 接口,如果是,则调用afterpropertiesSet() 方法。
- 接着,检查是否有@PostConstruct 注解的方法,并调用这些方法
- 最后调用XML配置中指定的 init-method
6、使用Bean
应用程序可以通过Spring容器获取Bean实例,并调用其方法。
关键源码:
public interface ApplicationContext extends EnvironmentAware, ResourceLoaderAware, ApplicationEventPublisher, MessageSource { Object getBean(String name) throws BeansException; T getBean(String name, Class requiredType) throws BeansException; Object getBean(String name, Object... args) throws BeansException; }
源码分析
- ApplicationContext 接口提供了 getBean() 方法,用于获取Bean实例。
- 在ClassPathXmlApplicationContext()类中,getBean() 方法会调用 DefaultListableBeanFactory 的 getBean() 方法,获取Bean实例。
7、销毁Bean
当Spring容器关闭时,会调用Bean的销毁方法。销毁方法可以通过实现 DisposableBean 接口、使用@PreDestory 注解或者在XML配置中指定destroy-method 来定义。
关键源码:
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory { public void destroyBean(String name) { // 获取 BeanDefinition BeanDefinition beanDefinition = getBeanDefinition(name); // 获取 Bean 实例 Object bean = getBean(name); // 检查是否实现了 DisposableBean 接口 if (bean instanceof DisposableBean) { try { ((DisposableBean) bean).destroy(); } catch (Exception e) { throw new BeanCreationException("Failed to destroy bean", e); } } // 检查是否有 @PreDestroy 注解 Method[] methods = bean.getClass().getDeclaredMethods(); for (Method method : methods) { if (method.isAnnotationPresent(PreDestroy.class)) { try { method.setAccessible(true); method.invoke(bean); } catch (Exception e) { throw new BeanCreationException("Failed to destroy bean", e); } } } // 调用 XML 配置中的 destroy-method String destroyMethod = beanDefinition.getDestroyMethod(); if (destroyMethod != null) { try { Method method = bean.getClass().getMethod(destroyMethod); method.invoke(bean); } catch (Exception e) { throw new BeanCreationException("Failed to destroy bean", e); } } } }
源码分析
- 在 destoryBean() 方法中,首先检查Bean是否实现了DisposableBean接口,如果是,则调用 destory() 方法。
- 接着,检查是否有@PreDestroy注解的方法,并调用这些方法。
- 最后调用XML配置中指定的 destory-method
动手debug尝试
在 IDEA 中打开 Spring 源码,找到 AbstractApplicationContext.refresh() 方法,依次跳转查看每个步骤的执行流程,结合断点调试观察 Bean 的创建过程。通过实践,这些抽象概念会变得直观!
Spring 6.x版本,IOC注册bean调用链
AbstractApplicationContext类
refresh() =》 obtainFreshBeanFactory() =》refreshBeanFactory()
AbstractRefreshableApplicationContext类
refreshBeanFactory() =》loadBeanDefinitions()
AbstractXmlApplicationContext类
loadBeanDefinitions(DefaultListableBeanFactory beanFactory) =》loadBeanDefinitions(XmlBeanDefinitionReader reader)
AbstractBeanDefinitionReader类
loadBeanDefinitions(Resource… resources) =》loadBeanDefinitions((Resource)resource)
XmlBeanDefinitionReaderl类
loadBeanDefinitions(Resource resource) =》 doLoadBeanDefinitions() =》 registerBeanDefinitions()
DefaultBeanDefinitionDocumentReader类
registerBeanDefinitions() =》 doRegisterBeanDefinitions()
Spring 6.x版本,IOC依赖注入调用链
AbstractApplicationContext.refresh()
→ finishBeanFactoryInitialization(beanFactory) // 初始化所有单例 Bean
→ beanFactory.preInstantiateSingletons()
→ DefaultListableBeanFactory.getBean(beanName)
→ AbstractBeanFactory.doGetBean(…)
→ AbstractAutowireCapableBeanFactory.createBean(…)
→ doCreateBean(…) // 核心步骤 ❗
→ createBeanInstance() // 1. 通过构造器实例化
→ populateBean() // 2. 【依赖注入发生在此处】
→ initializeBean() // 3. 初始化(例如 @PostConstruct)
四. 单例池管理(缓存已创建的Bean)
思考
单例Bean如何保证全局唯一? 单例池。
关键源码:(DefaultSingletonBeanRigistry类)
private final Map singletonObjects = new ConcurrentHashMap(256); public Object getSingleton(String beanName) { Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { // 双重检查锁创建单例... } return singletonObject; }
Spring 6.x优化了单例创建的并发控制逻辑,减少锁竞争,提升容器启动速度。
五. 扩展机制(BeanPostProcessor)
思考
如何在Bean初始化前后插入自定义逻辑?比如打印日志。
关键源码:(BeanPostPorcessor类)
@Component public class MyBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) { System.out.println("初始化之前:" + beanName); return bean; } }
源码分析
这段源码触发点是在initionlizeBean()方法中,循环调用所有BeanPostProcessor的方法。
- 当应用程序需要使用某个bean时,Spring IOC容器会注册的bean的信息创建Bean的实例。
- 当我们在代码中写下ApplicationContext context = new ClassPathXmlApplicationContext(“beans.xml”)
- 加载配置