美文网首页
一次关于SpringIOC的技术分享

一次关于SpringIOC的技术分享

作者: WAHAHA402 | 来源:发表于2019-08-11 19:01 被阅读0次
    Spring框架 模块概览

    核心容器包含了以下几个模块:

    • spring-core
    • spring-beans
    • spring-context、spring-context-support
    • spring-expression

    Spring中最核心的两个类:

    1. DefaultListableBeanFactory

    DefaultListableBeanFactory而是整个bean加载的核心部分,是Spring注册及加载bean的默认实现。


    继承关系图
    继承关系图

    xxxRegistry:是对xxx的增删改查
    xxxBeanFactory:bean的工厂
    doXXX():xxx方法的真正实现
    AbstractXXX类:是对XXX类的默认实现

    2. XmlBeanDefinitionReader

    XmlBeanFactory继承自DefaultListableBeanFactory,增加了XmlBeanDefinitionReader类型的reader属性,XmlBeanFactory中主要使用该reader属性对资源文件进行读取和注册。

    BeanFactory context = new XmlBeanFactory(new ClassPathResource("beanFactory.xml"));
    
    配置文件读取相关类图
    1. 通过继承自 AbstractBeanDefinitionReader 中的方法,来使用 ResourceLoader 将资源文件路径转换为对应的 Resource 文件
    2. 通过 DocumentLoader 对 Resource 文件进行转换,将 Resource 文件转换为 Document 文件
    3. 通过实现接口 BeanDefinitionDocumentReader 的 DefaultBeanDefinitionDocumentReader 类对 Document 进行解析,并使用 BeanDefinitionParserDelegate 对 Element 进行解析。

    配置文件封装

    在 Java 中,将不同来源的资源抽象成 URL,通过注册不同的 handler(URLStreamHandler)来处理不同来源的资源的读取逻辑, Spring 对其内部使用到的资源实现了自己的抽象结构:Resource接口来封装底层资源。

    public interface InputStreamSource {
        InputStream getInputStream() throws IOException;
    }
    
    public interface Resource extends InputStreamSource {
    
        boolean exists();
        boolean isReadable();
        boolean isOpen();
        URI getURI() throws IOException;
        File getFile() throws IOException;
        long contentLength() throws IOException;
        long lastModified() throws IOException;
        Resource createRelative(String relativePath) throws IOException;
        String getFilename();
        String getDescription();
    }
    

    对不同来源的资源文件都有相应的Resource 实现: FileSystemResource、ClasspathResource、UrlResource、InputStreamResource、ByteArrayResource等。有了Resource接口,便可以对所有资源文件进行统一的处理。
    日常开发中,资源文件的加载也是经常用到的,可以直接使用Spring提供的类,比如希望加载文件时可以使用以下代码。

    Resource resource = new ClassPathResource("beanFactoryTest.xml");
    InputStream inputStream = resource.getInputSream();
    

    默认标签的解析

    BeanDefinition

    BeanDefinition 是一个接口,在 Spring 中存在三种实现,RootBeanDefinition、ChildBeanDefinition、GenericBeanDefinition。三种实现均继承了 AbstactBeanDefinition,其中 BeanDefinition 是配置文件中的 <bean> 元素标签在容器中的内部表示形式。<bean>元素标签拥有 class、scope、lazy-init 等配置属性。BeanDefinition则提供了相应的 beanClass、scope、lazyInit属性,BeanDefinition 和 <bean> 中的属性是一一对应的。

    public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccessor
            implements BeanDefinition, Cloneable {
    
        private volatile Object beanClass;
        // bean 的作用范围,对应 bean 属性 scope
        private String scope = SCOPE_DEFAULT;
        // 是否是抽象,对应 abstract
        private boolean abstractFlag = false;
        // 是否延迟加载,对应 lazy-init
        private boolean lazyInit = false;
        // 自动注入模式,对应 autowire
        private int autowireMode = AUTOWIRE_NO;
        private int dependencyCheck = DEPENDENCY_CHECK_NONE;
    
        // 用来表示一个 bean 的实例化依靠另一个 bean 先实例化,对应 depend-on
        private String[] dependsOn;
    
        // autowire-candidate 属性设置为 false,这样容器在查找自动装配对象时,
        // 将不考虑该 bean,即它不会被考虑作为其他 bean 自动装配的候选者,但是该 bean 本身还是可以
        // 使用自动装配来注入其他 bean 的。
        // 对应 bean 属性 autowire-candidate
        private boolean autowireCandidate = true;
    
        // 自动装配时当出现多个 bean 候选者时,将作为首选者,对应 primary
        private boolean primary = false;
    
        // 用于记录 Qualifier,对应子元素 qualifier
        private final Map<String, AutowireCandidateQualifier> qualifiers =
                new LinkedHashMap<String, AutowireCandidateQualifier>(0);
    
        // 允许访问非公开的构造器和方法,程序设置
        private boolean nonPublicAccessAllowed = true;
    
        ...
    
        /**
        对应 bean 属性 factory-bean
        <bean id="instanceFactoryBean" class="xxx"/>
        <bean id="currentTime" factory-bean="instanceFactoryBean" factory-method="createTime"/>
        **/
        private String factoryBeanName;
        // 对应 factory-method
        private String factoryMethodName;
        // 记录构造函数注入属性,对于 constructor-arg
        private ConstructorArgumentValues constructorArgumentValues;
    
        // 普通属性集合
        private MutablePropertyValues propertyValues;
        // 方法重写的持有者,记录 lookup-method、replaced-method 元素
        private MethodOverrides methodOverrides = new MethodOverrides();
        // 初始化方法,对应 init-method
        private String initMethodName;
        // 销毁方法,对应 destory-method
        private String destroyMethodName;
        // ...各种 set/get
    }
    
    • RootBeanDefinition 最常用的实现类,它对应一般性的 <bean> 元素标签
    • ChildBeanDefinition 配置文件中的子 <bean>
    • GenericBeanDefinition 2.5 新加入的 bean 文件配置属性定义类,是一站式服务类。

    Spring 通过 BeanDefinition 将配置文件中的 <bean> 配置信息转换为容器的内部表示,并将这些 BeanDefinition 注册到 BeanDefinitionRegistry 中。Spring 容器的 BeanDefinitionRegistry 就像是 Spring 配置信息的内存数据库,主要是以 map 的形式保存的,后续操作直接从 BeanDefinitionRegistry 中读取配置信息。

    bean的加载

    User user = (User)context.getBean("testbean");
    大致过程:

    1. 转换对应 beanName。这里的 beanName 可能是别名,可能是 FactoryBean,所以需要一系列的解析:
    • 去除 FactoryBean 的修饰符,也就是如果 name="&aa",那么会首先去除 & 使得 name=aa
    • 取指定 alias 所表示的最终 beanName,例如别名 A 指向名称为 B 的 bean 则返回 B;若别名 A 指向别名 B,别名 B 又指向名称为 C 的 bean 则返回 C
    1. 尝试从缓存中加载单例

    单例在 Spring 的同一个容器内只会被创建一次,后续在获取 bean,就直接从单例缓存中获取了。当然这里也只是尝试加载,首先尝试从缓存中加载,如果加载不成功则再次尝试从 singletonFactories 中加载。

    由于存在依赖注入的问题,所以在 Spring 中创建 bean 的原则是不等 bean 创建完成就会将创建的 ObjectFactory 提早曝光加入到缓存中,一旦下一个 bean 创建需要依赖上一个 bean 则直接使用 ObjectFactory。
    Spring 获取 bean 的规则中有这样一条:尽可能保证所有 bean 初始化后都会调用注册的 BeanPostProcesser 的 postProcessAfterInitialization 方法进行处理,在世纪开发过程中可以根据此特性设计自己的逻辑业务。

    1. bean 的实例化
    2. 原型模式的依赖检查
      只有单例会尝试解决循环依赖。
    3. 在非 singleton 下检测 parentBeanFactory,看是否需要进入 parentBeanFactory 中加载(当前 BeanFactory 中无该 bean 且 parentBeanFactory 存在且存在该 bean)
    4. 将存储 XML 配置文件的 GernericBeanDefinition 转换为 RootBeanDefinition。方便 Bean 的后续处理。
    5. 寻找依赖
    6. 针对不同的 scope 进行 bean 的创建
    7. 类型转换(requiredType = true)

    特殊的bean: FactoryBean(举例)

    一般来说,Spring 是通过反射机制利用 bean 的 class 属性指定实现类来实例化 bean 的。FactoryBean 是为了对付配置 bean 的复杂性的。

    public interface FactoryBean<T> {
        T getObject() throws Exception;
        Class<?> getObjectType();
        // 如果返回 true 则 getObject() 时候会将实例放入 Spring 容器中单实例缓存池中
        boolean isSingleton(); 
    }
    

    实现了 XxxFactoryBean之后,解析<bean id="xxx" class="xx.xx.XxxFactoryBean" />时候会调用该其实现的 getObject() 方法

    循环依赖

    1. 什么是循环依赖?
      循环依赖就是循环引用,就是两个或多个 bean 相互之间持有对方。循环依赖不是循环调用,循环调用是指方法之间的环调用的,循环调用除非有终止条件,否则无法解决。
    • 构造器循环依赖
    • setter循环依赖
    • prototype 范围的循环依赖
    1. Spring是如何解决循环依赖的?
    • 构造器循环依赖
      无法解决,抛出 BeanCurrentlyInCreationException 异常.
      Spring容器将每一个正在创建的bean标识符放在一个“当前创建bean池”中,bean标识符在创建过程中将一直保持在这个池中,因此如果在创建 bean 过程中发现自己已经在“当前创建bean池”(isSingletonCurrentlyInCreation(beanName))里时,将抛出BeanCurrentlyInCreationException异常表示循环依赖;而对于创建完毕的bean将从“当前创建bean池”中清除掉。
    • setter循环依赖
    addSingletonFactory(beanName, new ObjectFactory() {
      public Object getObject() throws BeansException {
       return getEarlyBeanReference(beanName, mbd, bean);
      }
    });
    

    具体步骤如下:

    1. Spring 容器创建单例 testA bean,首先根据无参构造器创建 bean,并暴露一个 ObjectFactory 用于返回一个提前暴露一个创建中的 bean,并将testA标识符放到 『当前创建 bean 池』,然后进行 setter 注入 testB。
    2. Spring 容器创建单例testBbean,首先根据无参构造器创建 bean,并暴露一个ObjectFactory用于返回一个提前暴露一个创建中的 bean,并将“testB”标识符放到『当前创建bean池』,然后进行 setter 注入 circle。
    3. Spring 容器创建单例testC bean,首先根据无参构造器创建 bean,并暴露一个ObjectFactory用于返回一个提前暴露一个创建中的 bean,并将testC标识符放到『当前创建bean池』,然后进行setter注入testA。进行注入testA时由于提前暴露了ObjectFactory工厂,从而使用它返回提前暴露一个创建中的 bean。
    4. 最后在依赖注入testB和testA,完成 setter 注入。
    • prototype 范围的循环依赖
      对于 prototype 作用域 bean,Spring 容器无法完成依赖注入,因为 Spring 容器不进行缓存 prototype 作用域的 bean,因此无法提前暴露一个创建中的 bean。

    相关文章

      网友评论

          本文标题:一次关于SpringIOC的技术分享

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