美文网首页
Spring(三)IoC之核心组件装配解析

Spring(三)IoC之核心组件装配解析

作者: Colors_boy | 来源:发表于2021-01-21 12:16 被阅读0次

建议先看最后的总结再细读全文

由上一篇文章得知,从 bean 的装配到获取,分别用到了三大组件:

  • 资源抽象 Resource
  • 工厂 DefaultListableBeanFactory
  • 配置信息读取器 BeanDefinitionReader
public class SpringDemo {
    public static void main(String[] args) {
        // 1.指定加载的资源文件
        Resource resource = new ClassPathResource("spring.xml");
        // 2.创建管理bean的工厂
        DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory();
        // 3.资源读取器,把读取的到信息装配到 defaultListableBeanFactory 里面,工厂再对 bean 进行管理
        BeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(defaultListableBeanFactory);
        // 4.读取装配文件 xml 里面的信息
        beanDefinitionReader.loadBeanDefinitions(resource);
        // 5.获取 bean
        Student student = defaultListableBeanFactory.getBean("student", Student.class);
        System.out.println(student.getName());
        System.out.println(student.getAge());
    }
}

1. ClassPathResource

接下来谈谈spring是如何通过 ClassPathResource 把资源加载进来。

首先来看看源码:

    private final String path;

    @Nullable
    private ClassLoader classLoader;

    @Nullable
    private Class<?> clazz;

    public ClassPathResource(String path) {
        // 继续调用来下面的方法
        this(path, (ClassLoader) null);
    }
    
    public ClassPathResource(String path, @Nullable ClassLoader classLoader) {
        // 断言
        Assert.notNull(path, "Path must not be null");
        // 判断这个路径是否为一个合法的路径,并解析成spring能识别的标准路径
        String pathToUse = StringUtils.cleanPath(path);
        if (pathToUse.startsWith("/")) {
            pathToUse = pathToUse.substring(1);
        }
        // 已经是一个带解析并且能识别的path
        this.path = pathToUse;
        this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
    }
    
    public ClassPathResource(String path, @Nullable Class<?> clazz) {
        Assert.notNull(path, "Path must not be null");
        this.path = StringUtils.cleanPath(path);
        this.clazz = clazz;
    }
    

从源码可以看出,ClassPathResource 可以通过给定 class 或者给定的 classloader来进行资源的加载。ClassPathResource 的 构造方法主要是判断加载资源的路径是否有误,然后由默认的线程上下文类加载器(在运行期间,可以动态地去改变类加载器加载地方式)加载资源。如果传过来的classloader是空的,则:

    public static ClassLoader getDefaultClassLoader() {
        ClassLoader cl = null;
        try {
            cl = Thread.currentThread().getContextClassLoader();
        }
        catch (Throwable ex) {
            // Cannot access thread context ClassLoader - falling back...
        }
        if (cl == null) {
            // No thread context class loader -> use class loader of this class.
            cl = ClassUtils.class.getClassLoader();
            if (cl == null) {
                // getClassLoader() returning null indicates the bootstrap ClassLoader
                try {
                    cl = ClassLoader.getSystemClassLoader();
                }
                catch (Throwable ex) {
                    // Cannot access system ClassLoader - oh well, maybe the caller can live with null...
                }
            }
        }
        return cl;
    }

从作者的源码和注释中可以看出,首先判断当前线程的上下文类加载器是否存在,若不存在,则使用当前 ClassUtils 的 classloader,cl还是为空则说明当前的 cl 是用到了 bootstrap classloader,如果一个类是通过bootstrap classloader 载入的,那我们通过这个类去获得classloader的话,有些jdk的实现是会返回一个null的。所以,如果以上都获取不到 classloader,最终会由 SystemClassLoader 系统类加载器 来进行加载。

2. DefaultListableBeanFactory

DefaultListableBeanFactory 是 BeanFactory 的一个默认实现类,它继承了AbstractAutowireCapableBeanFactory,实现了ConfigurableListableBeanFactory, BeanDefinitionRegistry。

defaultlistablebeanfactory.png

DefaultListableBeanFactory构造函数源码分析

public DefaultListableBeanFactory() {
        super();
    }

这里的无参构造函数调用了父类的方法,继续分析:

    public AbstractAutowireCapableBeanFactory() {
        super();
        ignoreDependencyInterface(BeanNameAware.class);
        ignoreDependencyInterface(BeanFactoryAware.class);
        ignoreDependencyInterface(BeanClassLoaderAware.class);
    }

可以看到创建了一个 AbstractAutowireCapableBeanFactory ,它 的无参构造方法除了继续调用了父类的方法之外,忽略掉了3个class文件,为了在依赖注入的时候不应该用这三种类型来进行依赖的注入。继续往上跟:

    public AbstractBeanFactory() {
    }

只是创建了一个 AbstractBeanFactory 实例。

3. BeanDefinitionReader 实现类 XmlBeanDefinitionReader

实现类XmlBeanDefinitionReader构造方法源码分析:

    public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
        super(registry);
    }

把工厂一起传给了父类,继续往上跟:

    protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {
        Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
        this.registry = registry;

        // Determine ResourceLoader to use.
        if (this.registry instanceof ResourceLoader) {
            this.resourceLoader = (ResourceLoader) this.registry;
        }
        else {
            this.resourceLoader = new PathMatchingResourcePatternResolver();
        }

        // Inherit Environment if possible
        if (this.registry instanceof EnvironmentCapable) {
            this.environment = ((EnvironmentCapable) this.registry).getEnvironment();
        }
        else {
            this.environment = new StandardEnvironment();
        }
    }

这里可以看到通过 ResourceLoader 去加载资源文件并继承它的环境,判断是否实现了ResourceLoader,若没有,则:

    public PathMatchingResourcePatternResolver() {
        this.resourceLoader = new DefaultResourceLoader();
    }
    @Override
    @Nullable
    public ClassLoader getClassLoader() {
        return (this.classLoader != null ? this.classLoader : ClassUtils.getDefaultClassLoader());
    }

又回到了上面所说获取类加载器的方法,是一种典型的回退思想。这算是为工厂装配bean准备环境的一步。

  • EnvironmentCapable:如果可以的话 registry 把环境也继承了,如果没有则创建一个StandardEnvironment。

4. XmlBeanDefinitionReader.loadBeanDefinitions()

对 xml 文件信息整体的解析,将解析出来的xml信息装配成一个bean,并且把bean存放到工厂当中。看看 loadBeanDefinitions 做了哪些操作:

    public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
        return loadBeanDefinitions(new EncodedResource(resource));
    }

首先把 resource 传给 EncodedResource,让 EncodedResource 封装成一个真正带编码或者字符集的资源。且encoding 和 charset 是互斥关系:

    public EncodedResource(Resource resource) {
        this(resource, null, null);
    }

    public EncodedResource(Resource resource, @Nullable String encoding) {
        this(resource, encoding, null);
    }

    public EncodedResource(Resource resource, @Nullable Charset charset) {
        this(resource, null, charset);
    }

    private EncodedResource(Resource resource, @Nullable String encoding, @Nullable Charset charset) {
        super();
        Assert.notNull(resource, "Resource must not be null");
        this.resource = resource;
        this.encoding = encoding;
        this.charset = charset;
    }

然后再调用重载的 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();

        // 防止循环依赖,因为set集合不可重复的原因,若集合中存在 encodedResource A,再次add A 的时候则会抛出异常
        if (!currentResources.add(encodedResource)) {
            throw new BeanDefinitionStoreException(
                    "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
        }

        // 获取 encodedResource 里面 resource 的输入流
        try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
            // InputSource 用字节流创建一个新的输入源,是 org.xml.sax 提供的一个对象,并不是 spring 内部的
            InputSource inputSource = new InputSource(inputStream);
            // 这里我们传入的是 null
            if (encodedResource.getEncoding() != null) {
                inputSource.setEncoding(encodedResource.getEncoding());
            }
            // doLoadBeanDefinitions 方法在下文做出分析,实际是从指定的XML文件加载bean定义的方法
            return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
        }
        catch (IOException ex) {
            throw new BeanDefinitionStoreException(
                    "IOException parsing XML document from " + encodedResource.getResource(), ex);
        }
        finally {
            currentResources.remove(encodedResource);
            if (currentResources.isEmpty()) {
                // resourcesCurrentlyBeingLoaded 是 threadlocal 变量,remove 防止内存泄漏
                this.resourcesCurrentlyBeingLoaded.remove();
            }
        }
    }
  • doLoadBeanDefinitions
    // 实际从指定的XML文件加载 bean 定义的方法
    protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
            throws BeanDefinitionStoreException {

        try {
            // 通过doLoadDocument加载inputSource资源,返回一个 doc 对象
            Document doc = doLoadDocument(inputSource, resource);
            // 注册给定DOM文档中包含的bean定义,完成对 xml 的解析
            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);
        }
    }

可以看出 doLoadBeanDefinitions 是对 spring 装配 bean 的主要实现。下一章看看Spring是如何装配bean的。

5. 总结

通过上面的分析得知,spring 想要把 bean 注册到工厂,然后再从工厂中获取(IoC容器思想)具体需要以下操作(文中采取的是xml方式注入,和注解方式注入实质上原理一样):

  • 通过ClassPathResource 把 xml 配置文件加载进来,加载方式有两种:
    1. 通过默认的线程上下文类加载器(SystemClassLoader)
    2. 通过给定的 class 对象
  • 定义一个Bean工厂 DefaultListableBeanFactory用来装配和管理Bean的所有信息,指定注入的相关方式,但这时和资源文件还没有任何交互
  • 定义一个资源读取器BeanDefinitionReader,调用其实现类XmlBeanDefinitionReader把读取到的beans信息装配到DefaultListableBeanFactory,这时已经准备好了装配bean的加载器和环境并和bean工厂关联起来。
  • 通过调用XmlBeanDefinitionReader.loadBeanDefinitions()方法把加载进来xml资源解析并装配成bean,一同注册到DefaultListableBeanFactory。此过程中,底层还有很多操作,可以概括为先把 xml 转成 inputSource 流资源,再转化成相应的 doc 对象,最终在DefaultBeanDefinitionDocumentReader类中通过doRegisterBeanDefinitions 方法,递归遍历doc对象的所有节点和元素从而完成bean定义的注册。

至此操作已全部完毕,下一章将会继续分析spring装配bean的详细过程。

相关文章

网友评论

      本文标题:Spring(三)IoC之核心组件装配解析

      本文链接:https://www.haomeiwen.com/subject/dwirzktx.html