1. 程式人生 > >Spring原理解析(2)- IOC容器

Spring原理解析(2)- IOC容器

Spring IoC容器介紹


    IOC叫控制反轉,DI叫依賴注入,是對IOC更簡單的詮釋。
    控制反轉是把傳統上由程式程式碼直接操控的物件的呼叫權交給容器。就是對元件物件控制權的轉移,從程式程式碼本身轉移到了spring容器,由容器來建立物件並管理物件之間的依賴關係。
    DI是對IOC更準確的描述,即元件之間的依賴關係由容器在執行期決定,形象的來說,即由容器動態的將某種依賴關係注入到元件之中。常用注入方式:set方法注入、構造方法注入、P名稱空間注入、spel注入。

   官網原文如下:This chapter covers the Spring Framework implementation of the Inversion of Control (IoC) [1] principle. IoC is also known as dependency injection (DI). It is a process whereby objects define their dependencies, that is, the other objects they work with, only through constructor arguments, arguments to a factory method, or properties that are set on the object instance after it is constructed or returned from a factory method. The container then injects those dependencies when it creates the bean. This process is fundamentally the inverse, hence the name Inversion of Control (IoC), of the bean itself controlling the instantiation or location of its dependencies by using direct construction of classes, or a mechanism such as the Service Locator pattern.

 

Spring IoC整體設計


該圖摘自spring技術內幕。

圖中的兩條線路是主要的兩條介面設計線路圖。

整體介面系統都是以beanfactory和applicationContext為核心。兩者區別:

①ApplicationContext 介面繼承BeanFactory介面,Spring核心工廠是BeanFactory ,BeanFactory採取延遲載入,第一次getBean時才會初始化Bean, ApplicationContext是會在載入配置檔案時初始化Bean。

②ApplicationContext是對BeanFactory擴充套件,它可以進行國際化處理、事件傳遞和bean自動裝配以及各種不同應用層的Context實現。開發中基本都在使用ApplicationContext, web專案使用WebApplicationContext ,很少用到BeanFactory。

 

spring ioc 設計詳解


     這裡採用相對原始的方式使用spring,方便深入瞭解springioc容器的初始化。其他的各種不同的實現,針對不懂得資源載入方式,擴充套件了一些功能,整體思路是相同的。

ClassPathResource resource = new ClassPathResource("beans.xml"); 

DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); 

XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); 

reader.loadBeanDefinitions(resource)

主要分為了三個過程:

     1.resource定位

     2.beanDefinition的載入。

     3.向ioc容器註冊beanDefinition的過程。這個過程通過呼叫BeanDefinitionRegistry介面的實現來完成。

     在這裡DefaultListableBeanFactory是一個純粹的ioc容器,需要給他配置特定的讀取器才能完成功能。而我們常用的application已經提供了一系列不同的resource,不需要手動指定。例如:

      FileSystemXmlApplicationContext 從檔案系統中載入,

     ClassPathXmlApplicationContext 從class path載入,

     XmlWebApplicationContext 可以在web容器中載入。

    下面以FileSystemXmlApplicationContext為例詳細的介紹。

     先給出整體的uml圖。

      

   整個載入的入口不再過多的描述。

public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, 
                        ApplicationContext parent) throws BeansException { 
    super(parent); 
    setConfigLocations(configLocations); 
    if (refresh) { 
        refresh(); 
    } 
}

     直接找到FileSystemXmlApplicationContext的建構函式即可。refresh()方法就是這個ioc容器載入的入口,該方法的具體實現在AbstractApplicationContext。

public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext, DisposableBean

下面我不去按照原始碼一步步的跟進。而是去按照ioc容器的整體的一個載入流程,做介紹。有一個整體思路,大家在一步步的去跟進程式碼,也比較清晰。

   

@Override
public void refresh() throws BeansException, IllegalStateException {
	synchronized (this.startupShutdownMonitor) {
	    // 重新整理上下文
            prepareRefresh();
            //這裡是在子類中啟動refreshBeanFactort()的地方
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
            // Prepare the bean factory for use in this context.
            prepareBeanFactory(beanFactory);
            try {
                // 設定beanFactory的後置處理
                postProcessBeanFactory(beanFactory);
                //呼叫beanFactory的後處理器,這些後處理器是在bean定義中向容器註冊的
                invokeBeanFactoryPostProcessors(beanFactory);
                //註冊bean的後處理器,在bean建立過程中呼叫
                registerBeanPostProcessors(beanFactory);
                //對上下文中的訊息源進行初始化
                initMessageSource();
                //初始化上下文中的事件機制
                initApplicationEventMulticaster();
                //初始化其他的特殊bean
                onRefresh();
                //檢查監聽bean並且將這些bean向容器註冊
                registerListeners();
                //例項化所有的(non-lazy-init)單利
                finishBeanFactoryInitialization(beanFactory);
                //釋出容器事件,結束refresh過程
                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();
	  }
     }
}

 

 BeanDefinition的載入和解析

    從abstractRefreshableApplicationContext的refreshBeanFactory方法開始入手,瞭解整個bean定義資訊的載入過程。

 

protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
        // 建立 XmlBeanDefinitionReader 
        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
        beanDefinitionReader.setEnvironment(this.getEnvironment());
     //為xmlbeanDefinitionReader 設定resourceloader
        beanDefinitionReader.setResourceLoader(this);
        beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

        //啟動bean定義資訊載入過程
        initBeanDefinitionReader(beanDefinitionReader);
        loadBeanDefinitions(beanDefinitionReader);
    }
 

   下面是loadBeanDefinitions呼叫的地方,首先得到beanDefinition資訊的resource定位,然後呼叫xmlBeanDefinitionReader來讀取.

     整體的呼叫鏈

     

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
        //以resource的方式獲取配置檔案的資源位置
        Resource[] configResources = getConfigResources();
        if (configResources != null) {
            reader.loadBeanDefinitions(configResources);
        }
        //以String的形式獲取
        String[] configLocations = getConfigLocations();
        if (configLocations != null) {
            reader.loadBeanDefinitions(configLocations);
        }
 }

 

 

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
        Assert.notNull(encodedResource, "EncodedResource must not be null");
        if (logger.isInfoEnabled()) {
            logger.info("Loading XML bean definitions from " + encodedResource.getResource());
        }

        Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
        if (currentResources == null) {
            currentResources = new HashSet<EncodedResource>(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());
                }
                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();
            }
        }
    }

 具體的讀取過程在doLoadBeanDefinitions。

     

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
            throws BeanDefinitionStoreException {
        try {
         //完成具體的文件讀取
            Document doc = doLoadDocument(inputSource, resource);
         //按照spring的bean的語義要求進行解析並轉化為容器的內部資料結構。

            return registerBeanDefinitions(doc, resource);
        }
        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);
        }
    }

 

   beanDefinition 的載入分為兩部分。首先呼叫xml的解析器得到document物件。然後documentReader中按照spring的bean的規則進行解析。

    BeanDefinition的註冊

    在DefaultListableBeanFactory中,通過一個map來持有載入的beanDefinition的。

/** Map of bean definition objects, keyed by bean name */
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>(256);

再DefaultListableBeanFactory中實現了beanDefinitionRegistry的介面,這個介面完成了beanDefinition向容器的註冊。就是將解析得到的beandefinition設定到hashmap中。
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 oldBeanDefinition;

        oldBeanDefinition = this.beanDefinitionMap.get(beanName);
        if (oldBeanDefinition != null) {
            if (!isAllowBeanDefinitionOverriding()) {
                throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
                        "Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
                        "': There is already [" + oldBeanDefinition + "] bound.");
            }
            else if (oldBeanDefinition.getRole() < beanDefinition.getRole()) {
                // e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
                if (this.logger.isWarnEnabled()) {
                    this.logger.warn("Overriding user-defined bean definition for bean '" + beanName +
                            "' with a framework-generated bean definition: replacing [" +
                            oldBeanDefinition + "] with [" + beanDefinition + "]");
                }
            }
            else if (!beanDefinition.equals(oldBeanDefinition)) {
                if (this.logger.isInfoEnabled()) {
                    this.logger.info("Overriding bean definition for bean '" + beanName +
                            "' with a different definition: replacing [" + oldBeanDefinition +
                            "] with [" + beanDefinition + "]");
                }
            }
            else {
                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("Overriding bean definition for bean '" + beanName +
                            "' with an equivalent definition: replacing [" + oldBeanDefinition +
                            "] with [" + beanDefinition + "]");
                }
            }
            this.beanDefinitionMap.put(beanName, beanDefinition);
        }
        else {
            if (hasBeanCreationStarted()) {
                // Cannot modify startup-time collection elements anymore (for stable iteration)
                synchronized (this.beanDefinitionMap) {
                    this.beanDefinitionMap.put(beanName, beanDefinition);
                    List<String> updatedDefinitions = new ArrayList<String>(this.beanDefinitionNames.size() + 1);
                    updatedDefinitions.addAll(this.beanDefinitionNames);
                    updatedDefinitions.add(beanName);
                    this.beanDefinitionNames = updatedDefinitions;
                    if (this.manualSingletonNames.contains(beanName)) {
                        Set<String> updatedSingletons = new LinkedHashSet<String>(this.manualSingletonNames);
                        updatedSingletons.remove(beanName);
                        this.manualSingletonNames = updatedSingletons;
                    }
                }
            }
            else {
                // Still in startup registration phase
                this.beanDefinitionMap.put(beanName, beanDefinition);
                this.beanDefinitionNames.add(beanName);
                this.manualSingletonNames.remove(beanName);
            }
            this.frozenBeanDefinitionNames = null;
        }

        if (oldBeanDefinition != null || containsSingleton(beanName)) {
            resetBeanDefinition(beanName);
        }
    }

ioc 容器的依賴注入


上文介紹了ioc容器的初始化過程,主要完成了ioc容器建立beanDefinition資料對映。並沒有看到ioc容器對bean依賴關係進行注入。

  介面getbean就是出發依賴注入發生的地方。下面從defaultlistableBeanFactory的基礎類abstractBeanFactory入手。

  getbean最終都會呼叫到doGetBean

protected <T> T doGetBean(
            final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
            throws BeansException {

        final String beanName = transformedBeanName(name);
        Object bean;

        // Eagerly check singleton cache for manually registered singletons.
        Object sharedInstance = getSingleton(beanName);
        if (sharedInstance != null && args == null) {
            if (logger.isDebugEnabled()) {
                if (isSingletonCurrentlyInCreation(beanName)) {
                    logger.debug("Returning eagerly cached instance of singleton bean '" + beanName +
                            "' that is not fully initialized yet - a consequence of a circular reference");
                }
                else {
                    logger.debug("Returning cached instance of singleton bean '" + beanName + "'");
                }
            }
            bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
        }

        else {
            // Fail if we're already creating this bean instance:
            // We're assumably within a circular reference.
            if (isPrototypeCurrentlyInCreation(beanName)) {
                throw new BeanCurrentlyInCreationException(beanName);
            }

            // Check if bean definition exists in this factory.
            BeanFactory parentBeanFactory = getParentBeanFactory();
            if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
                // Not found -> check parent.
                String nameToLookup = originalBeanName(name);
                if (args != null) {
                    // Delegation to parent with explicit args.
                    return (T) parentBeanFactory.getBean(nameToLookup, args);
                }
                else {
                    // No args -> delegate to standard getBean method.
                    return parentBeanFactory.getBean(nameToLookup, requiredType);
                }
            }

            if (!typeCheckOnly) {
                markBeanAsCreated(beanName);
            }

            try {
                final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
                checkMergedBeanDefinition(mbd, beanName, args);

                // Guarantee initialization of beans that the current bean depends on.
                String[] dependsOn = mbd.getDependsOn();
                if (dependsOn != null) {
                    for (String dep : dependsOn) {
                        if (isDependent(beanName, dep)) {
                            throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                                    "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
                        }
                        registerDependentBean(dep, beanName);
                        getBean(dep);
                    }
                }

                // Create bean instance.
                if (mbd.isSingleton()) {
                    sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
                        @Override
                        public Object getObject() throws BeansException {
                            try {
                                return createBean(beanName, mbd, args);
                            }
                            catch (BeansException ex) {
                                // Explicitly remove instance from singleton cache: It might have been put there
                                // eagerly by the creation process, to allow for circular reference resolution.
                                // Also remove any beans that received a temporary reference to the bean.
                                destroySingleton(beanName);
                                throw ex;
                            }
                        }
                    });
                    bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
                }

                else if (mbd.isPrototype()) {
                    // It's a prototype -> create a new instance.
                    Object prototypeInstance = null;
                    try {
                        beforePrototypeCreation(beanName);
                        prototypeInstance = createBean(beanName, mbd, args);
                    }
                    finally {
                        afterPrototypeCreation(beanName);
                    }
                    bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
                }

                else {
                    String scopeName = mbd.getScope();
                    final Scope scope = this.scopes.get(scopeName);
                    if (scope == null) {
                        throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
                    }
                    try {
                        Object scopedInstance = scope.get(beanName, new ObjectFactory<Object>() {
                            @Override
                            public Object getObject() throws BeansException {
                                beforePrototypeCreation(beanName);
                                try {
                                    return createBean(beanName, mbd, args);
                                }
                                finally {
                                    afterPrototypeCreation(beanName);
                                }
                            }
                        });
                        bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
                    }
                    catch (IllegalStateException ex) {
                        throw new BeanCreationException(beanName,
                                "Scope '" + scopeName + "' is not active for the current thread; consider " +
                                "defining a scoped proxy for this bean if you intend to refer to it from a singleton",
                                ex);
                    }
                }
            }
            catch (BeansException ex) {
                cleanupAfterBeanCreationFailure(beanName);
                throw ex;
            }
        }

        // Check if required type matches the type of the actual bean instance.
        if (requiredType != null && bean != null && !requiredType.isAssignableFrom(bean.getClass())) {
            try {
                return getTypeConverter().convertIfNecessary(bean, requiredType);
            }
            catch (TypeMismatchException ex) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Failed to convert bean '" + name + "' to required type '" +
                            ClassUtils.getQualifiedName(requiredType) + "'", ex);
                }
                throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
            }
        }
        return (T) bean;
    }