Spring 是后端研发最常用的框架,而 IOC 容器则是 Spring 框架中使用最多的功能,也是 Spring 所有功能的基础。今天简单梳理一下 Spring 容器的一些实现原理,网上有很多资料对 Spring 容器的说明陷入了细节的纠缠中,没能从全局的角度理解容器。我尝试按照自己的理解说明一下。

什么是容器?

首先我们需要明确,Spring 的容器究竟是什么概念。从应用层面看,我们可以把 Spring 提供的 ApplicationContext 接口的具体实现当作容器的实例。因为我们在代码中经常这样使用:

Object bean = applicationContext.getBean("beanName");

通过名称获取容器中对应的 bean。这种用法让你想到了 java 中的什么 API ?是不是跟集合的使用有些类似?

Map<String, Object> objMap;
Object obj = objMap.get("objName");

那么,Spring 底层是不是也是用 Map 来实现容器的功能呢?我们可以看一下对应的代码实现。无论是基于注解的 AnnotationConfigApplicationContext 或者基于 XML 配置文件的 ClassPathXmlApplicationContext,两者的 getBean 方法都继承自 AbstractApplicationContext

@Override  
public Object getBean(String name) throws BeansException {  
   assertBeanFactoryActive();  
   return getBeanFactory().getBean(name);  
}

可以看到,获取 bean 对象的具体操作委托给了 BeanFactory 对象。BeanFactory 又是什么组件?它和 ApplicationContext 的关系是什么?

我们首先了解一下这两个接口整体上的继承关系:

BeanFactory 和 ApplicationContext 的继承关系

具体的细节这里不展开,只简单介绍一下接口的大概作用:

  • BeanFactory 是支持 IOC 容器的顶级接口,它提供了通过名称获取 bean 对象的基本操作。
  • AliasRegistry 及其子接口提供了向容器注册 bean 对象的描述对象(BeanDefinition)的操作。
  • SingletonBeanRegistry 提供了向容器注册 bean 对象的操作。
  • ApplicationContext 是对 BeanFactory 做的扩展。它在 IOC 容器的基础上提供了国际化、配置初始化、容器继承等功能,作为应用上下文使用,是业务代码和框架进行交互的桥梁。

这里我们抛开其他细节,看一下 BeanFactory 的默认实现类 DefaultListableBeanFactory 是如何支持通过名称获取 bean 对象的:

@Override  
public <T> T getBean(Class<T> requiredType, @Nullable Object... args) throws BeansException {  
   Assert.notNull(requiredType, "Required type must not be null");  
   Object resolved = resolveBean(ResolvableType.forRawClass(requiredType), args, false);  
   if (resolved == null) {  
      throw new NoSuchBeanDefinitionException(requiredType);  
   }  
   return (T) resolved;  
}

这个方法进一步调用了 resolveBean 方法:

private <T> T resolveBean(ResolvableType requiredType, @Nullable Object[] args, boolean nonUniqueAsNull) { 
    // 先从当前容器中获取
   NamedBeanHolder<T> namedBean = resolveNamedBean(requiredType, args, nonUniqueAsNull);  
   if (namedBean != null) {  
      return namedBean.getBeanInstance();  
   }  
   // 如果找不到,就从父容器中获取
   // ......
}

resolveNamedBean 方法中,首先解析 bean 对象的名称,然后调用了 AbstractBeanFactory 中的 getBean(String name) 方法:

@Override  
public Object getBean(String name) throws BeansException {  
   return doGetBean(name, null, null, false);  
}

方法 doGetBean 是初始化 bean 对象的核心方法,它会初始化对应的 bean 对象,以及对象的依赖。这里的逻辑比较复杂,我们只看获取对象的部分

protected <T> T doGetBean(  
      String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)  
      throws BeansException {  
  
   String beanName = transformedBeanName(name);  
   Object beanInstance;  
  
   // Eagerly check singleton cache for manually registered singletons.  
   Object sharedInstance = getSingleton(beanName);
   // ......
}

再查看 getSingleton(String name) 方法,这个方法定义在 AbstractBeanFactory 的父类 DefaultSingletonBeanRegistry 中,最终我们会发现单例的 bean 对象放在这里:

/** Cache of singleton objects: bean name to bean instance. */  
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

可以看到,Spring 底层确实是通过 Map 来支持对象的检索的,key 就是 bean 的名称,value 就是对应的 bean 对象。

Spring 容器如何初始化

这里分析一下 ApplicationContext 的初始化。容器初始化的核心方法是 AbstractApplicationContext.refresh()