Spring bean和常见问题

Spring bean的实例化

Spring bean和常见问题_第1张图片

  1. 从配置信息的bean里面读取bean(XML格式相当于一个个document节点,循环读取)
  2. 将读取的bean的信息存放在BeanDefinitionReader中(这是个接口)
  3. 然后在经过了BeanFactoryPostProcessor,在进入工厂之前做一些自定义操作
  4. 然后进入了BeanFactory工厂,反射实例newInstance
  5. 然后有BeanPostProcessor进行自定义操作,在实例化之前
  6. 实例化完毕,后续就是生命周期了

Spring bean的生命周期

Spring bean和常见问题_第2张图片

  1. Spring启动,查找并加载需要被Spring管理的bean,进行Bean的实例化

  2. Bean实例化后对将Bean的引入和值注入到Bean的属性

  3. 如果Bean实现了BeanNameAware接口的话,Spring将Bean的Id传递给setBeanName()方法

  4. 如果Bean实现了BeanFactoryAware接口的话,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入

  5. 如果Bean实现了ApplicationContextAware接口的话,Spring将调用Bean的setApplicationContext()方法,将bean所在应用上下文引用传入进来。还会去判断是否Web环境,进行一个注入

  6. 如果Bean实现了BeanPostProcessor接口,Spring就将调用他们的postProcessBeforeInitialization()方法。

  7. 如果Bean 实现了InitializingBean接口,Spring将调用他们的afterPropertiesSet()方法。类似的,如果bean使用init-method声明了初始化方法,该方法也会被调用

  8. 如果Bean 实现了BeanPostProcessor接口,Spring就将调用他们的postProcessAfterInitialization()方法。

  9. 此时,Bean已经准备就绪,可以被应用程序使用了。他们将一直驻留在应用上下文中,直到应用上下文被销毁。

  10. 果bean实现了DisposableBean接口,Spring将调用它的destory()接口方法,同样,如果bean使用了destory-method 声明销毁方法,该方法也会被调用。


Spring的属性注入有哪几种

  1. 构造方法注入,它保证了Bean实例在实例化后就可以使用

  2. set方法注入,在Spring配置文件种,通过 《property》设置注入的属性

  3. p名称空间注入,这里我们添加了一个p名称空间 :xmlns:p=“http://www.springframework.org/schema/p”

    
    
  4. SpEL注入

    <bean id="category" class="com.zhou.ioc.demo4.Category">
            <property name="name" value="#{'女装'}"/>
        bean>
    
  5. 复杂类型注入
    数组类型的属性注入
    List集合类型的属性注入
    Set集合类型的属性注入
    Map集合类型的属性注入
    Properties类型的属性注入

  6. 注解注入,@Autowired(按类型再按名字)@Resource (按名字再按类型),@Bean等


IOC

**Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想。**在Java开发中,**Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。**如何理解好Ioc呢?理解好Ioc的关键是要明确“谁控制谁,控制什么,为何是反转(有反转就应该有正转了),哪些方面反转了”,那我们来深入分析一下:

●**谁控制谁,控制什么:**传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对 象的创建;谁控制谁?当然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。

●**为何是反转,哪些方面反转了:**有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。

所谓IoC,对于spring框架来说,就是由spring来负责控制对象的生命周期和对象间的关系,你想要什么女朋友,温柔的,可爱的,只要容器有,它马上会给你,但是相反你也被容器管理,别的人需要你这样的男朋友,也会把你给它,动态的向某个对象提供它所需要的其他对象

Spring所倡导的开发方式就是如此,所有的类都会在spring容器中登记,告诉spring你是个什么东西,你需要什么东西,然后spring会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的东西。所有的类的创建、销毁都由 spring来控制,也就是说控制对象生存周期的不再是引用它的对象,而是spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被spring控制,所以这叫控制反转。


依赖注入

组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。**依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。**通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。

谁依赖于谁:当然是应用程序依赖于IoC容器

●**为什么需要依赖:**应用程序需要IoC容器来提供对象需要的外部资源;

谁注入谁:很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象

●注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)

简单来说,就是你把钱存在银行,但你以后取钱都要去银行,这就是依赖银行取钱,它帮你安全便捷管理钱,你存取的操作就是依赖注入,银行注入了钱


懒加载和直接加载的区别

  1. 懒加载可以根据需要再进行加载,对空间资源合理利用,防止大数据长久占用内存,但是如果有错误的话,只有等到加载的时候才会发现,启动容器还发现不了
  2. 一开始就能发现文件里的错误,方便进行排错和DeBug,但是一开始就占用了内存,创建出来了

场景:

  1. 单例饿汉式和懒汉式
  2. BeanFactory(懒汉)和Applicationtext(饿汉) 两者是父子容器关系,跟bean息息相关

单例Bean和多例Bean的区别

  1. 单例Bean可以进行一个懒加载,等到getBean()的时候再去实例化,而多例的不行,创建容器的时候就实例化了,执行了doCreateBean(),拿到的是一个新的实例
  2. 单例Bean的销毁和回收可以通过Spring容器完成,而多例Bean则需要用户自己完成回收,关闭资源,说白了多例Bean就相当于生出来的孩子自生自灭

为什么用单例或者多例?何时用?

之所以用单例,是因为没必要每个请求都新建一个对象,这样子既浪费CPU又浪费内存

之所以用多例,是为了防止并发问题;即一个请求改变了对象的状态,此时对象又处理另一个请求,而之前请求对对象状态的改变导致了对象对另一个请求做了错误的处理;

当对象含有可改变的状态时(更精确的说就是在实际应用中该状态会改变),则多例,否则单例。

spring是如何解决循环依赖的

场景一:字段上的循环依赖 成员变量引入即A依赖B,B依赖A

先了解三级缓存分别指:

singletonFactories : 单例对象工厂的cache     (逼上梁山,拿到就销毁这个cache,转存入二级缓存)

earlySingletonObjects :提前暴光的单例对象的Cache  (二级缓存,对象还在初始化呢)
这里就是解决循环依赖的关键,发生在createBeanInstance之后,也就是说单例对象此时已经被创建出来(调用了构造器)。这个对象已经被生产出来了,虽然还不完美(还没有进行初始化的第二步和第三步),但是已经能被人认出来了(根据对象引用能定位到堆中的对象),所以Spring此时将这个对象提前曝光出来让大家认识,让大家使用。

singletonObjects:单例对象的cache   (优先找一级)
  1. 在finishBeanFactoryInitialization中,开始初始化A,毋庸置疑通过反射
  2. 之后【非完美对象】开始设置属性字段,此时发现需要一个B的对象。同时已标记A处于正在初始化阶段
  3. 显然接下来,开始去初始化B的对象,同样的手法,到设置属性阶段,发现需要A对象
  4. 于是乎,spring又开始去初始化对象A的依赖,此时先从缓存singletonObjects去取,没有再去看是否正处于初始阶段,是则再从缓存earlySingletonObjects中取,再没有,则看是否存在allowEarlyReference,是则从singletonFactories中取
  5. 将早期对象A设置到B中,再把B设置到A中

场景二: 构造器的循环依赖

构造器的循环依赖问题无法解决,只能拋出BeanCurrentlyInCreationException异常,

因为加入singletonFactories三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决。

Spring容器会将每一个正在创建的Bean 标识符放在一个“当前创建Bean池”中,Bean标识符在创建过程中将一直保持在这个池中,因此如果在创建Bean过程中发现自己已经在“当前创建Bean池”里时将抛出BeanCurrentlyInCreationException异常表示循环依赖;而对于创建完毕的Bean将从“当前创建Bean池”中清除掉。

Spring容器先创建单例A,A依赖B,然后将A放在“当前创建Bean池”中,此时创建B,B依赖C ,然后将B放在“当前创建Bean池”中,此时创建C,C又依赖A, 但是,此时A已经在池中,所以会报错,,因为在池中的Bean都是未初始化完的,所以会依赖错误 ,(初始化完的Bean会从池中移除)

场景三: Setter属性注入循环依赖

Spring先是用构造实例化Bean对象 ,创建成功后,Spring会通过以下代码提前将对象暴露出来,此时的对象A还没有完成属性注入,属于早期对象,此时Spring会将这个实例化结束的对象放到一个Map中,并且Spring提供了获取这个未设置属性的实例化对象引用的方法。 结合我们的实例来看,当Spring实例化了A、B、C后,紧接着会去设置对象的属性,此时A依赖B,就会去Map中取出存在里面的单例B对象,以此类推,不会出来循环的问题喽

不要使用基于构造函数的依赖注入,可以通过以下方式解决:

  1. 在字段上使用@Autowired注解,让Spring决定在合适的时机注入

  2. 用基于setter方法的依赖注入。

怎么知道Bean处于循环依赖中?

检测循环依赖相对比较容易,Bean在创建的时候可以给该Bean打标,如果递归调用回来发现正在创建中的话,即说明了循环依赖了。

注入,可以通过以下方式解决:

  1. 在字段上使用@Autowired注解,让Spring决定在合适的时机注入

  2. 用基于setter方法的依赖注入。

怎么知道Bean处于循环依赖中?

检测循环依赖相对比较容易,Bean在创建的时候可以给该Bean打标,如果递归调用回来发现正在创建中的话,即说明了循环依赖了。

你可能感兴趣的:(Spring,面试,spring,java)