面试官(Spring是如何把Bean注册到IOC容器中的())

临文乍了了,彻卷兀若无。这篇文章主要讲述面试官:Spring是如何把Bean注册到IOC容器中的?相关的知识,希望能为你提供帮助。
前言提到Spring就会想到IOC、DI等概念,这是Spring的核心思想,只要使用过Spring框架的人都知道这些概念,但要问到Spring具体是怎么实现IOC的,恐怕只能看Spring的源码才能找到答案,在我看来只要搞清楚两个问题,就能对Spring的整体脉络有个整体的认识

  • Bean是如何注册到IOC注册中的?
  • Bean是如何从IOC容器中get出来的?
面试官(Spring是如何把Bean注册到IOC容器中的())

文章图片

一个简单的例子我们先从一个简单的例子开始,基本上在最开始学习Spring的时候都会从xml配置开始,把你需要交给Spring管理的类配置到xml文件中,你就可以不用管对象的创建了,下面来看一下代码
1、首先定义一个User类
@Data public class User { private String userName; private String password; }

很简单的一个javaBean
2、增加一个xml配置
< ?xml version="1.0" encoding="UTF-8"?> < beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> < bean id="userBean" class="org.kxg.springDemo.User"> < property name="userName" value="https://www.songbingjia.com/android/jack" /> < property name="password" value="https://www.songbingjia.com/android/123456" /> < /bean> < /beans>

3、读取配置,并运行
public class XmlBeanTest { public static void main(String[] args){ ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml"); User user = (User) applicationContext.getBean("userBean"); System.out.println(user.getUserName()); } }

可以看到例子很简单,堪称Spring入门的HelloWorld
从上面几行代码可以看出,首先读取bean.xml中的配置,然后就可以从applicationContext中获取到User对象,那么肯定会有User对象注册到IOC容器中这个步骤
下面我们一起通过源码来看一下Bean是如何注册到Spring IOC容器中的
源码解析从ClassPathXmlApplicationContext开始
public ClassPathXmlApplicationContext(String configLocation) throws BeansException { this(new String[] {configLocation}, true, null); }public ClassPathXmlApplicationContext(String... configLocations) throws BeansException { this(configLocations, true, null); }

从ClassPathXmlApplicationContext的构造方法入手,构造方法传入xml配置文件的路径,这里可以传入单个或多个配置文件
public ClassPathXmlApplicationContext( String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException {super(parent); //将传入的xml配置位置信息设置到configLocations setConfigLocations(configLocations); if (refresh) { //核心方法 refresh(); } }

这里看到,我们传入的配置文件设置到configLocations,然后调用了一个Spring最核心的方法refresh(),这个方法包括了容器启动的所有内容,是我们学习Spring源码的一个入口,可以说你只要把这个方法里面的内容研究清楚了,对于Spring框架的整个脉络会有一个全新的认识,下面我们来看看这个方法里面有些什么
public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context for refreshing. prepareRefresh(); // 注意看这个方法 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context. prepareBeanFactory(beanFactory); try { // Allows post-processing of the bean factory in context subclasses. postProcessBeanFactory(beanFactory); // Invoke factory processors registered as beans in the context. invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation. registerBeanPostProcessors(beanFactory); // Initialize message source for this context. initMessageSource(); // Initialize event multicaster for this context. initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. onRefresh(); // Check for listener beans and register them. registerListeners(); // Instantiate all remaining (non-lazy-init) singletons. finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event. finishRefresh(); }catch (BeansException ex) { if (logger.isWarnEnabled()) { logger.warn("Exception encountered during context initialization - " + "cancelling refresh attempt: " + ex); }// Destroy already created singletons to avoid dangling resources. destroyBeans(); // Reset \'active\' flag. cancelRefresh(ex); // Propagate exception to caller. throw ex; }finally { // Reset common introspection caches in Spring\'s core, since we // might not ever need metadata for singleton beans anymore... resetCommonCaches(); } } }

refresh()方法里面的内容很丰富,从各个方法名称就大致可以看出来其作用,这里我们主要看Bean注册的过程,将目光聚焦到
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

这一行是获取BeanFactory,里面进行了Bean的注册逻辑
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() { refreshBeanFactory(); return getBeanFactory(); }

这里调用的是AbstractRefreshableApplicationContext类的refreshBeanFactory()方法,需要注意一下在看Spring源码的时候,同一个方法可能会有多个子类都实现了,需要注意区分一下,你当前实例化的是哪个子类
protected final void refreshBeanFactory() throws BeansException { if (hasBeanFactory()) { destroyBeans(); closeBeanFactory(); } try { //创建一个BeanFactory DefaultListableBeanFactory beanFactory = createBeanFactory(); beanFactory.setSerializationId(getId()); customizeBeanFactory(beanFactory); //这里进行Bean的加载 loadBeanDefinitions(beanFactory); synchronized (this.beanFactoryMonitor) { this.beanFactory = beanFactory; } } catch (IOException ex) { throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex); } }

因为我们使用的是xml的配置,所以这里调用的是AbstractXmlApplicationContext这个抽象类中的loadBeanDefinitions方法
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException { // 构造一个XmlBeanDefinitionReader,用于读到xml中配置的bean XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); // 配置XmlBeanDefinitionReader beanDefinitionReader.setEnvironment(this.getEnvironment()); beanDefinitionReader.setResourceLoader(this); beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); //初始化XmlBeanDefinitionReader initBeanDefinitionReader(beanDefinitionReader); //加载Bean loadBeanDefinitions(beanDefinitionReader); }

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException { Resource[] configResources = getConfigResources(); if (configResources != null) { reader.loadBeanDefinitions(configResources); } String[] configLocations = getConfigLocations(); if (configLocations != null) { reader.loadBeanDefinitions(configLocations); } }

这里进行了两种不同方式的加载,调用的是不同的方法,我们传入的是configLocations
@Override public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException { Assert.notNull(locations, "Location array must not be null"); int count = 0; for (String location : locations) { count += loadBeanDefinitions(location); } return count; }

public int loadBeanDefinitions(String location, @Nullable Set< Resource> actualResources) throws BeanDefinitionStoreException { ResourceLoader resourceLoader = getResourceLoader(); if (resourceLoader == null) { throw new BeanDefinitionStoreException( "Cannot load bean definitions from location [" + location + "]: no ResourceLoader available"); }if (resourceLoader instanceof ResourcePatternResolver) { // Resource pattern matching available. try { Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location); int count = loadBeanDefinitions(resources); if (actualResources != null) { Collections.addAll(actualResources, resources); } if (logger.isTraceEnabled()) { logger.trace("Loaded " + count + " bean definitions from location pattern [" + location + "]"); } return count; } catch (IOException ex) { throw new BeanDefinitionStoreException( "Could not resolve bean definition resource pattern [" + location + "]", ex); } } else { // Can only load single resources by absolute URL. Resource resource = resourceLoader.getResource(location); int count = loadBeanDefinitions(resource); if (actualResources != null) { actualResources.add(resource); } if (logger.isTraceEnabled()) { logger.trace("Loaded " + count + " bean definitions from location [" + location + "]"); } return count; } }

这个里面主要方法是loadBeanDefinitions(),我们继续往下走
中间省略了一些简单调用
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { Assert.notNull(encodedResource, "EncodedResource must not be null"); if (logger.isTraceEnabled()) { logger.trace("Loading XML bean definitions from " + encodedResource); }Set< EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get(); if (currentResources == null) { currentResources = new HashSet< > (4); this.resourcesCurrentlyBeingLoaded.set(currentResources); } if (!currentResources.add(encodedResource)) { throw new BeanDefinitionStoreException( "Detected cyclic loading of " + encodedResource + " - check your import definitions!"); } try { InputStream inputStream = encodedResource.getResource().getInputStream(); try { InputSource inputSource = new InputSource(inputStream); if (encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); } //进行BeanDefinations加载 return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); } finally { inputStream.close(); } } catch (IOException ex) { throw new BeanDefinitionStoreException( "IOException parsing XML document from " + encodedResource.getResource(), ex); } finally { currentResources.remove(encodedResource); if (currentResources.isEmpty()) { this.resourcesCurrentlyBeingLoaded.remove(); } } }

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {try { //构造xml的Document结构,解析DOM结构 Document doc = doLoadDocument(inputSource, resource); //注册BeanDefinition int count = registerBeanDefinitions(doc, resource); if (logger.isDebugEnabled()) { logger.debug("Loaded " + count + " bean definitions from " + resource); } return count; } catch (BeanDefinitionStoreException ex) { throw ex; } catch (SAXParseException ex) { throw new XmlBeanDefinitionStoreException(resource.getDescription(), "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex); } catch (SAXException ex) { throw new XmlBeanDefinitionStoreException(resource.getDescription(), "XML document from " + resource + " is invalid", ex); } catch (ParserConfigurationException ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "Parser configuration exception parsing XML from " + resource, ex); } catch (IOException ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "IOException parsing XML document from " + resource, ex); } catch (Throwable ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "Unexpected exception parsing XML document from " + resource, ex); } }

从上面的代码可以看出来,Spring是将xml的DOM结构解析后注册到IOC容器中的
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); int countBefore = getRegistry().getBeanDefinitionCount(); documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); return getRegistry().getBeanDefinitionCount() - countBefore; }

这个方法构造了一个BeanDefinitionDocumentReader,进行注册BeanDefinition,并且返回了本次注册Bean的数量
protected void doRegisterBeanDefinitions(Element root) { // Any nested < beans> elements will cause recursion in this method. In // order to propagate and preserve < beans> default-* attributes correctly, // keep track of the current (parent) delegate, which may be null. Create // the new (child) delegate with a reference to the parent for fallback purposes, // then ultimately reset this.delegate back to its original (parent) reference. // this behavior emulates a stack of delegates without actually necessitating one. BeanDefinitionParserDelegate parent = this.delegate; this.delegate = createDelegate(getReaderContext(), root, parent); if (this.delegate.isDefaultNamespace(root)) { String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE); if (StringUtils.hasText(profileSpec)) { String[] specifiedProfiles = StringUtils.tokenizeToStringArray( profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS); // We cannot use Profiles.of(...) since profile expressions are not supported // in XML config. See SPR-12458 for details. if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) { if (logger.isDebugEnabled()) { logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec + "] not matching: " + getReaderContext().getResource()); } return; } } }preProcessXml(root); //进行BeanDefinition转换,将DOM结构的对象转换成BeanDefinition parseBeanDefinitions(root, this.delegate); postProcessXml(root); this.delegate = parent; }

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { if (delegate.isDefaultNamespace(root)) { NodeList nl = root.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (node instanceof Element) { Element ele = (Element) node; if (delegate.isDefaultNamespace(ele)) { //Spring默认元素转换 parseDefaultElement(ele, delegate); } else { //xml中自定义的Element进行解析 delegate.parseCustomElement(ele); } } } } else { delegate.parseCustomElement(root); } }

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) { if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { importBeanDefinitionResource(ele); } else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { processAliasRegistration(ele); } else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { processBeanDefinition(ele, delegate); } else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { // recurse doRegisterBeanDefinitions(ele); } }

可以看到xml中默认的配置元素包括import、alias、bean、beans,这些也是最常用的,我们主要看一个bean的转换
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); if (bdHolder != null) { bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); try { // Register the final decorated instance. BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); } catch (BeanDefinitionStoreException ex) { getReaderContext().error("Failed to register bean definition with name \'" + bdHolder.getBeanName() + "\'", ele, ex); } // Send registration event. getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder)); } }

public static void registerBeanDefinition( BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException {// Register bean definition under primary name. String beanName = definitionHolder.getBeanName(); //注册BeanDefinition registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition()); // Register aliases for bean name, if any. String[] aliases = definitionHolder.getAliases(); if (aliases != null) { for (String alias : aliases) { registry.registerAlias(beanName, alias); } } }

// DefaultListableBeanFactory public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException {Assert.hasText(beanName, "Bean name must not be empty"); Assert.notNull(beanDefinition, "BeanDefinition must not be null"); if (beanDefinition instanceof AbstractBeanDefinition) { try { ((AbstractBeanDefinition) beanDefinition).validate(); } catch (BeanDefinitionValidationException ex) { throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName, "Validation of bean definition failed", ex); } }BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName); if (existingDefinition != null) { if (!isAllowBeanDefinitionOverriding()) { throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition); } else if (existingDefinition.getRole() < beanDefinition.getRole()) { // e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE if (logger.isInfoEnabled()) { logger.info("Overriding user-defined bean definition for bean \'" + beanName + "\' with a framework-generated bean definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]"); } } else if (!beanDefinition.equals(existingDefinition)) { if (logger.isDebugEnabled()) { logger.debug("Overriding bean definition for bean \'" + beanName + "\' with a different definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]"); } } else { if (logger.isTraceEnabled()) { logger.trace("Overriding bean definition for bean \'" + beanName + "\' with an equivalent definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]"); } } //将beanDefinition放入beanDefinitionMap中 this.beanDefinitionMap.put(beanName, beanDefinition); } else { if (hasBeanCreationStarted()) { // Cannot modify startup-time collection elements anymore (for stable iteration) synchronized (this.beanDefinitionMap) { //将beanDefinition放入beanDefinitionMap中 this.beanDefinitionMap.put(beanName, beanDefinition); List< String> updatedDefinitions = new ArrayList< > (this.beanDefinitionNames.size() + 1); updatedDefinitions.addAll(this.beanDefinitionNames); updatedDefinitions.add(beanName); this.beanDefinitionNames = updatedDefinitions; removeManualSingletonName(beanName); } } else { // Still in startup registration phase //将beanDefinition放入beanDefinitionMap中 this.beanDefinitionMap.put(beanName, beanDefinition); this.beanDefinitionNames.add(beanName); removeManualSingletonName(beanName); } this.frozenBeanDefinitionNames = null; }if (existingDefinition != null || containsSingleton(beanName)) { resetBeanDefinition(beanName); } }

源码跟到这里,整个流程基本清楚了,最终beanDefinition存到一个beanDefinitionMap中,key为Bean的名称,value为beanDefinition对象
private final Map< String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap< > (256);

beanDefinitionMap是一个ConcurrentHashMap,所以本质上Bean最终是被注册到一个Map中
上面我们说了很多次beanDefinition,最后注册到容器中的也是这个对象,那它到底是个啥对象?
从注释中可以看出来BeanDefinition是一个用来描述带有属性值、构造方法、还有一些其他进一步信息的Bean实例(半调子英语,也不知道翻译的对不对~)
【面试官(Spring是如何把Bean注册到IOC容器中的())】BeanDefinition是对Bean的抽象,因为配置文件中的Bean是多种多样的,BeanDefinition是对Bean的公共属性进行抽象,在BeanDefinition中很多属性是用来描述xml配置中bean的配置属性的
所以,下面来总结一下整个流程
  • 注册xml配置文件到configLocations
  • 调用refresh()进行整个Context的刷新,实际上就是整个Context的启动
  • Bean的加载会读到配置文件,解析成DOM对象
  • 将DOM对象转换成beanDefinition
  • 将beanDefinition存入beanDefinitionMap,完成整个Bean的注册
没看明白的同学,可以对照这个流程再回头去看看,整个流程下来还是挺清晰的
注解方式下Bean的注册前面我们讲到xml配置文件进行Bean的注册,xml配置是Spring早期常用的配置方式,现在基本上大部分场景上都推荐使用注解的方式,尤其是SpringBoot时代的来临,进一步推动了注解方式的全面使用,下面我们来看看注解方式下的Bean注册,还是从个简单的例子入手
@Component public class AnnotionConfig { @Bean(name = "userBean") public User getUserBean(){ User user = new User(); user.setUserName("Lucy"); return user; } }

public class AnnotionBeanTest { public static void main(String[] args){ AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext("org.kxg.springDemo"); User user = (User) applicationContext.getBean("userBean"); System.out.println(user.getUserName()); } }

这里用到AnnotationConfigApplicationContext,是另外一种容器的实现,传入一个包名,会自动扫描包下面的Spring注解,然后将其注册到容器中
public AnnotationConfigApplicationContext(String... basePackages) { this(); //主要是scan方法完成bean的注册 scan(basePackages); //又到了这个方法,有没有很熟悉~~~ refresh(); }

下面我们重点看一下注解方式的Bean注册
public int scan(String... basePackages) { int beanCountAtScanStart = this.registry.getBeanDefinitionCount(); //扫描包,进行Bean注册 doScan(basePackages); // Register annotation config processors, if necessary. if (this.includeAnnotationConfig) { AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry); }return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart); }

//ClassPathBeanDefinitionScanner protected Set< BeanDefinitionHolder> doScan(String... basePackages) { Assert.notEmpty(basePackages, "At least one base package must be specified"); Set< BeanDefinitionHolder> beanDefinitions = new LinkedHashSet< > (); for (String basePackage : basePackages) { //扫描包下打了注解的类,并将其转换成BeanDefinition Set< BeanDefinition> candidates = findCandidateComponents(basePackage); for (BeanDefinition candidate : candidates) { ScopeMetadata scopeMetadata = https://www.songbingjia.com/android/this.scopeMetadataResolver.resolveScopeMetadata(candidate); candidate.setScope(scopeMetadata.getScopeName()); String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry); if (candidate instanceof AbstractBeanDefinition) { postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName); } if (candidate instanceof AnnotatedBeanDefinition) { AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate); } if (checkCandidate(beanName, candidate)) { BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName); definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); beanDefinitions.add(definitionHolder); //进行BeanDefinition注册 registerBeanDefinition(definitionHolder, this.registry); } } } return beanDefinitions; }

public static void registerBeanDefinition( BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException {// Register bean definition under primary name. String beanName = definitionHolder.getBeanName(); registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition()); // Register aliases for bean name, if any. String[] aliases = definitionHolder.getAliases(); if (aliases != null) { for (String alias : aliases) { registry.registerAlias(beanName, alias); } } }

看到这个方法,有没有点眼熟的感觉
上面xml方式进行Bean注册也调用到这个方法了,所以后面的流程都是一样的,注解方式和xml配置方式从本质上来讲,并没有什么不同,只是Bean的描述不同而已,最终都会被解析成BeanDefinition,注册到容器中,至此整个Bean的注册流程就已经完了。
当然在整个过程中,忽略了很多细节,只看了主线流程。
读源码的时候,很容易陷入细节中,尤其是像Spring这样通用的框架,它为了通用性和扩展性,会把代码写的很“绕”,如果你过于关注细节很容易让自己陷入实现的细节中,一开始看源码的时候,建议不用太关注细节,把主线功能先看完,知道大概的原理后再逐个去击破~

    推荐阅读