Spring IOC容器的实现原理

2022-02-22  本文已影响0人  坤坤坤坤杨

在设计时,首先需要考虑IOC容器的功能(输入和输出),可以借助下图初步的看一下IOC的整体功能:


image

在此基础上,我们初步的去思考,如果作为一个IOC容器的设计者,主体上应该包含哪几个部分:

1. Spring IOC的体系结构

Spring Bean的创建时典型的工厂模式,一系列的Bean工厂,为开发者管理对象间的依赖关系童工了很多便利和基础服务;在顶层设计结构中主要围绕着BeanFactory和xxxRegistry进行。
BeanFactory:工厂模式定义了IOC容器的基本功能规范
BeanRegistry:向IOC容器手工注册BeanDefinition 对象的方法

1.1 BeanFactory定义了那些基本功能规范?

BeanFactory作为一个最顶层的接口,有三个子类:ListableBeanFactory、HierarchicalBeanFactory 和AutowireCapableBeanFactory。我们看下BeanFactory接口:

public interface BeanFactory {    
      
    //用于取消引用实例并将其与FactoryBean创建的bean区分开来。
    //例如,如果命名的bean是FactoryBean,则获取将返回Factory,而不是Factory返回的实例。
    String FACTORY_BEAN_PREFIX = "&"; 
        
    //根据bean的名字和Class类型等来得到bean实例    
    Object getBean(String name) throws BeansException;    
    Object getBean(String name, Class requiredType) throws BeansException;    
    Object getBean(String name, Object... args) throws BeansException;
    <T> T getBean(Class<T> requiredType) throws BeansException;
    <T> T getBean(Class<T> requiredType, Object... args) throws BeansException;

    //返回指定bean的Provider
    <T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);
    <T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);

    //检查工厂中是否包含给定name的bean,或者外部注册的bean
    boolean containsBean(String name);

    //检查所给定name的bean是否为单例/原型
    boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
    boolean isPrototype(String name) throws NoSuchBeanDefinitionException;

    //判断所给name的类型与type是否匹配
    boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
    boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;

    //获取给定name的bean的类型
    @Nullable
    Class<?> getType(String name) throws NoSuchBeanDefinitionException;

    //返回给定name的bean的别名
    String[] getAliases(String name);
}

1.3 BeanFactory为什么要定义这么多层次的接口?定义了那些接口?

image

主要是为了区分在 Spring 内部在操作过程中对象的传递和转化过程中,对对象的数据访问所做的限制。定义的接口如下:

1.4 如何将Bean注册到BeanFactory中?

Spring 配置文件中每一个<bean>节点元素在 Spring 容器里都通过一个 BeanDefinition 对象表示,它描述了 Bean 的配置信息。而 BeanDefinitionRegistry 接口提供了向容器手工注册 BeanDefinition 对象的方法。

Bean对象存在依赖嵌套等关系,所以设计者设计了BeanDefinition,它用来对Bean对象及关系定义;理解时只需要抓住如下三个要点:

1. BeanDefinition:各种Bean对象及其相互的关系

image
2. BeanDefinitionReader: Bean 的解析过程非常复杂,功能被分的很细,因为这里需要被扩展的地方很多,必须保证有足够的灵活性,以应对可能的变化。Bean 的解析主要就是对 Spring 配置文件的解析。
image
3. BeanDefinitionHolder:BeanDefinitionHolder 这是BeanDefination的包装类,用来存储BeanDefinition,name以及aliases等。
image

1.5 ApplicationContext接口的实现

在考虑ApplicationContext接口的实现时,关键的点在于,不同Bean的配置方式(比如xml,groovy,annotation等)有着不同的资源加载方式,这便衍生除了众多ApplicationContext的实现类。


image
  1. 从类结构设计上看,围绕是否需要Refresh容器衍生出两个抽象类:
  1. 从加载的源来看(比如xml,annotation等), 衍生出众多类型的ApplicationContext, 典型比如:
  1. 设计者在设计时AnnotationConfigApplicationContext为什么是继承GenericApplicationContext?

最后结合设计结构来看一张图:

image

2. IOC初始化流程

首先可以从ClasspathXmlApplicationContext对象入手,创建容器,探究初始化流程。

ApplicationContext context = new ClassPathXmlApplicationContext("aspects.xml", "daos.xml", "services.xml");
public ClassPathXmlApplicationContext(String... configLocations) throws BeansException {
    this(configLocations, true, (ApplicationContext)null);
}

public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException {
    // 设置Bean资源加载器,调用AbstractApplicationContext的构造方法
    super(parent);

    // 设置配置路径
    this.setConfigLocations(configLocations);

    // 初始化容器
    if (refresh) {
        this.refresh();
    }
}

2.1 设置资源解析器和环境

调用父类容器AbstractApplicationContext的构造方法(super(parent)方法)为容器设置好Bean资源加载器

public AbstractApplicationContext(@Nullable ApplicationContext parent) {
    // 默认构造函数初始化容器id, name, 状态 以及 资源解析器
    this();

    // 将父容器的Environment合并到当前容器
    this.setParent(parent);
}

通过AbstractApplicationContext默认构造函数初始化容器id, name, 状态 以及 资源解析器

public AbstractApplicationContext() {
    this.logger = LogFactory.getLog(this.getClass());
    this.id = ObjectUtils.identityToString(this);
    this.displayName = ObjectUtils.identityToString(this);
    this.beanFactoryPostProcessors = new ArrayList();
    this.active = new AtomicBoolean();
    this.closed = new AtomicBoolean();
    this.startupShutdownMonitor = new Object();
    this.applicationStartup = ApplicationStartup.DEFAULT;
    this.applicationListeners = new LinkedHashSet();
    this.resourcePatternResolver = this.getResourcePatternResolver();
}
// Spring资源加载器
protected ResourcePatternResolver getResourcePatternResolver() {
    return new PathMatchingResourcePatternResolver(this);
}

通过AbstractApplicationContext的setParent(parent)方法将父容器的Environment合并到当前容器

public void setParent(@Nullable ApplicationContext parent) {
    this.parent = parent;
    if (parent != null) {
        Environment parentEnvironment = parent.getEnvironment();
        if (parentEnvironment instanceof ConfigurableEnvironment) {
            this.getEnvironment().merge((ConfigurableEnvironment)parentEnvironment);
        }
    }
}

2.2 设置配置路径

在设置容器的资源加载器之后,接下来FileSystemXmlApplicationContet执行setConfigLocations方法通过调用其父类AbstractRefreshableConfigApplicationContext的方法进行对Bean定义资源文件的定位

public void setConfigLocations(@Nullable String... locations) {
    if (locations != null) {
        Assert.noNullElements(locations, "Config locations must not be null");
        this.configLocations = new String[locations.length];

        for(int i = 0; i < locations.length; ++i) {
            // 解析配置路径
            this.configLocations[i] = this.resolvePath(locations[i]).trim();
        }
    } else {
        this.configLocations = null;
    }
}
protected String resolvePath(String path) {
    // 从上一步Environment中解析
    return this.getEnvironment().resolveRequiredPlaceholders(path);
}

2.3 主体流程

Spring IoC容器对Bean定义资源的载入是从refresh()函数开始的,refresh()是一个模板方法,refresh()方法的作用是:在创建IoC容器前,如果已经有容器存在,则需要把已有的容器销毁和关闭,以保证在refresh之后使用的是新建立起来的IoC容器。refresh的作用类似于对IoC容器的重启,在新建立好的容器中对容器进行初始化,对Bean定义资源进行载入。

@Override
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");

        // Prepare this context for refreshing.
        prepareRefresh();

        // Tell the subclass to refresh the internal bean factory.
        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);

            StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
            // Invoke factory processors registered as beans in the context.
            invokeBeanFactoryPostProcessors(beanFactory);

            // Register bean processors that intercept bean creation.
            registerBeanPostProcessors(beanFactory);
            beanPostProcess.end();

            // 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();
            contextRefresh.end();
        }
    }
}

这里的设计上是一个非常典型的资源类加载处理型的思路,头脑中需要形成如下图的顶层思路

2.4 Refresh方法流程

IOC初始化流程.png
上一篇下一篇

猜你喜欢

热点阅读