1.你对Spring IoC 的理解?
IOC容器实际就是指的spring容器。从容器概念、控制反转、依赖注入三个方面回答这个问题:
1.容器概念
实际上就是个map(key,value),里面存的是各种对象(在xml里配置的bean节点、@repository、@service、@controller、@component),在项目启动的时候会读取配置文件里面的bean节点,扫描到上述注解的类根据全限定类名使用反射创建对象放到map里。
这个时候map里就有各种对象了,接下来我们在代码里需要用到里面的对象时,再通过DI(依赖)注入(autowired、resource等注解)。
2.控制反转
没有引入IOC容器之前,对象A依赖于对象B,那么对象A在初始化或者运行到某一点的时候,自己必须主动去创建对象B或者使用已经创建的对象B。无论是创建还是使用对象B,控制权都在自己手上。
引入IOC容器之后,对象A与对象B之间失去了直接联系,当对象A运行到需要对象B的时候,IOC容器会主动创建一个对象B注入到对象A需要的地方。
通过前后的对比,不难看出来:对象A获得依赖对象B的过程,由主动行为变为了被动行为,控制权颠倒过来了,全部对象的控制权全部上缴给“第三方”IOC容器,所以,IOC容器成了整个系统的关键核心,它起到了一种类似“粘合剂”的作用,把系统中的所有对象粘合在一起发挥作用。
3.依赖注入
控制被反转之后,获得依赖对象的过程由自身管理变为了由IOC容器主动注入。依赖注入是实现IOC的方法,就是由IOC容器在运行期间,动态地将某种依赖关系注入到对象之中。
2. IoC 和 DI 的区别?
DI 依赖注入不完全等同于 IoC,Spring 框架是一个 IoC 容器的实现,DI 依赖注入是它的实现的一个方式或策略,IOC提供依赖查找和依赖注入两种依赖处理,管理着 Bean 的生命周期。
依赖查找和依赖注入都是 IoC 的实现策略。依赖查找就是在应用程序里面主动调用 IoC 容器提供的接口去获取对应的 Bean 对象,而依赖注入是在 IoC 容器启动或者初始化的时候,通过构造器、字段、setter 方法或者接口等方式注入依赖。依赖查找相比于依赖注入对于开发者而言更加繁琐,具有一定的代码入侵性,需要借助 IoC 容器提供的接口,所以我们总是强调后者。依赖注入在 IoC 容器中的实现也是调用相关的接口(autowired、resource等获取 Bean 对象,只不过这些工作都是在 IoC 容器启动时由容器帮助实现了,在应用程序中我们通常很少主动去调用接口获取 Bean 对象。
3.DI依赖注入的方式区别(构造器注入和 Setter 注入)
构造器注入:通过构造器的参数注入相关依赖对象
Setter 注入:通过 Setter 方法注入依赖对象,也可以理解为字段注入
对于两种注入方式的看法:
- 构造器注入可以避免一些尴尬的问题,比如说状态不确定性地被修改,在初始化该对象时才会注入依赖对象,一定程度上保证了 Bean 初始化后就是不变的对象,这样对于我们的程序和维护性都会带来更多的便利;
- 构造器注入不允许出现循环依赖,因为它要求被注入的对象都是成熟态,保证能够实例化,而 Setter 注入或字段注入没有这样的要求;
- 构造器注入可以保证依赖的对象能够有序的被注入,而 Setter 注入或字段注入底层是通过反射机制进行注入,无法完全保证注入的顺序;
- 如果构造器注入出现比较多的依赖导致代码不够优雅,我们应该考虑自身代码的设计是否存在问题,是否需要重构代码结构。
什么是Java Bean?
public class People {
privete String name;
private int age;
public void setName(String newName) {
name = newName;
}
public String getName() {
return name;
}
public void setAge(int neweAge) {
age = newAge;
}
public int getAge() {
return age;
}
}
这段代码,相信每个初学者刚开始写程序的时候都写过,当时最常听到就是不要将每个属性都暴露给调用者,应该根据封装的思想将其封装成私有属性,通过 getter 和 setter 来操作。
刚入门写这些代码时,总是迷茫,自己写的这个东西有什么用,每次写的 Demo 仿佛只是个玩具。再去读各种框架还是不知从何下手。其实,我们在不经意间就已经写了一个 Java Bean 了。
Java Bean就是一种类,并非所有的类都是 Java Bean,其是一种特殊的类,具有以下特征:
- 提供一个默认的无参构造函数。
- 需要被序列化要实现 Serializable 接口。
- 可能有一系列可读写属性,并且一般是 private 的。
- 可能有一系列的 getter 或 setter 方法。
根据封装的思想,我们使用 get 和 set 方法封装 private 的属性,并且根据属性是否可读写来选择封装方法。Java Bean最大的特征是私有的属性,其作用也就是把一组数据组合成一个特殊的类便于传输。
Spring Factory深入理解
说到Spring
框架,人们往往大谈特谈一些似乎高逼格的东西,比如依赖注入,控制反转,面向切面等等。但是却忘记了最基本的一点:Spring的本质是一个bean工厂(beanFactory)或者说bean容器
,它按照我们的要求,生产我们需要的各种各样的bean,提供给我们使用。只是在生产bean的过程中,需要解决bean之间的依赖问题,才引入了依赖注入(DI)这种技术。也就是说依赖注入是beanFactory生产bean时为了解决bean之间的依赖的一种技术而已
。
那么我们为什么需要Spring框架来给我们提供这个beanFactory的功能呢?原因是一般我们认为是,可以将原来硬编码的依赖,通过Spring这个beanFactory这个工厂来注入依赖,也就是说原来只有依赖方和被依赖方,现在我们引入了第三方——spring这个beanFactory,由它来解决bean之间的依赖问题,达到了松耦合
的效果;这个只是原因之一,还有一个更加重要的原因:在没有spring这个beanFactory之前,我们都是直接通过new来实例化各种对象,现在各种对象bean的生产都是通过beanFactory来实例化的,这样的话,spring这个beanFactory就可以在实例化bean的过程中,进行一些额外的处理——也就是说beanFactory会在bean的生命周期的各个阶段中对bean进行各种管理,并且spring将这些阶段通过各种接口暴露给我们,让我们可以对bean进行各种处理,我们只要让bean实现对应的接口,那么spring就会在bean的生命周期调用我们实现的接口来处理该bean
。下面我们看是如何实现这一点的。
spirng Bean作用域:
默认情况下,所有的 Spring Bean 都是单例的,也就是说在整个 Spring 应用中, Bean 的实例只有一个。如果我们需要创建多个实例的对象,那么应该将 Bean 的 scope 属性定义为 prototype,如果 Spring 需要每次都返回一个相同的 Bean 实例,则应将 Bean 的 scope 属性定义为 singleton。(默认)
Spring 5 共提供了 6 种 scope 作用域:
Spring 根据 Bean 的作用域来选择 Bean 的管理方式:
对于 singleton 作用域的 Bean 来说,Spring IoC 容器能够精确地控制 Bean 何时被创建、何时初始化完成以及何时被销毁;
对于 prototype 作用域的 Bean 来说,Spring IoC 容器只负责创建,然后就将 Bean 的实例交给客户端代码管理,Spring IoC 容器将不再跟踪其生命周期。
我们可以在 Spring Bean 的 Java 类中,通过实现 InitializingBean 和 DisposableBean 接口,指定 Bean 的生命周期回调方法。
我们还可以在 Spring 的 XML 配置中,通过 元素中的 init-method 和 destory-method 属性,指定 Bean 的生命周期回调方法。
重点:Spring Bean 的生命周期?
生命周期:Spring中bean的生命周期(易懂版)
在传统的 Java 应用中,Bean 的生命周期很简单,使用 Java 关键字 new 进行 Bean 的实例化后,这个 Bean 就可以使用了。一旦这个 Bean 长期不被使用,Java 自动进行垃圾回收。
Spring 中 Bean 的生命周期较复杂,大致可以分为以下 5 个阶段:
- Bean 的实例化
- Bean 属性赋值
- Bean 的初始化
- Bean 的使用
- Bean 的销毁
具体如下:
1-3属于 BeanDefinition 配置元信息阶段,BeanDefinition 是 Spring Bean 的“前身”,其内部包含了初始化一个 Bean 的所有元信息.以注解类变成Spring Bean为例,Spring会扫描指定包下面的Java类(springboot通过Application 的启动程序类中 @ComponentScan 的注解)然后将其变成beanDefinition对象,然后Spring会根据beanDefinition来创建bean,特别要记住一点,Spring是根据beanDefinition来创建Spring bean的。为什么不直接使用对象的class对象来创建bean呢?因为在class对象仅仅能描述一个对象的创建,它不足以用来描述一个Spring bean,而对于是否为懒加载、是否是首要的、初始化方法是哪个、销毁方法是哪个,这个Spring中特有的属性在class对象中并没有,所有Spring就定义了beanDefinition来完成bean的创建。
Spring Bean 元信息配置阶段,
- xml中通过bean节点来配置;
- 使用@Service、@Controller、@Conponent等注解。
- Spring Bean 元信息解析阶段, 读取bean的xml配置文件,然后解析xml文件中的各种bean的定义,将xml文件中的每一个<bean />元素分别转换成一个BeanDefinition对象,其中保存了从配置文件中读取到的该bean的各种信息,用于实例化一个 Spring Bean
- Spring Bean 元信息注册阶段,将 BeanDefinition 配置元信息 保存至 BeanDefinitionRegistry 的 ConcurrentHashMap 集合中
4-5 属于实例化阶段,实例化bean对象(通过构造方法或者工厂方法),想要生成一个 Java Bean 对象,那么肯定需要根据 Bean 的元信息先实例化一个对象;
- Spring BeanDefinition 合并阶段,定义的 Bean 可能存在层次性关系,则需要将它们进行合并,存在相同配置则覆盖父属性,最终生成一个 RootBeanDefinition 对象
- Spring Bean 的实例化阶段,类加载器加载出一个 Class 对象,通过这个 Class 对象的构造器创建一个实例对象,构造器注入在此处会完成。在实例化阶段 Spring 提供了实例化前后两个扩展点。
接下来的 6
属于属性赋值阶段,实例化后的对象还是一个空对象,我们需要根据 Bean 的元信息对该对象的所有属性进行赋值;
Spring Bean 属性赋值阶段,在 Spring 实例化后,需要对其相关属性进行赋值,注入依赖的对象。Spring 主要使用两种方式来对属性进行注入
- setter注入(重点)
- 构造注入 (了解知道)
setter 注入在 Spring 实例化 Bean 的过程中,IoC 容器首先会调用默认的构造方法(无参构造方法)实例化 Bean(Java 对象),然后通过 Java 的反射机制调用这个 Bean 的 setXxx() 方法,将属性值注入到 Bean 中。
步骤:
1.在 Bean 中提供一个默认的无参构造函数,因为如果没有无参构造,spring就无法通过无参构造的方式创建对象,更别提通过反射获取对象里的setter方法,进行赋值了(没有其他带参构造函数的情况下,可省略,默认是有无参构造的),并为所有需要注入的属性提供一个 setXxx() 方法
2.在 Spring 的 XML 配置文件中,使用 <beans> 及其子元素 <bean> 对 Bean 进行定义
3.在 <bean> 元素内使用 <property> 元素对各个属性进行赋值。
后面的 7
、8
、9
属于初始化阶段,在 Java Bean 对象生成后,可能需要对这个对象进行相关初始化工作才予以使用
- Aware 接口回调阶段,如果
Spring Bean 是 Spring 提供的 Aware 接口类型
(例如 BeanNameAware、ApplicationContextAware),这里会进行接口的回调,注入相关对象(例如 beanName、ApplicationContext)
引用:spring中的Aware接口原来是这么回事:Aware的功能:bean实现个某某Aware接口,然后这个bean就可以通过实现接口的方法,来接收接口方法传递过来的资源。
- Spring Bean 初始化阶段,这里会调用 Spring Bean 配置的初始化方法,执行顺序:@PostConstruct 标注方法、实现 InitializingBean 接口的 afterPropertiesSet() 方法、自定义初始化方法。
在初始化阶段 Spring 提供了初始化前后两个扩展点
(BeanPostProcessor 的 postProcessBeforeInitialization、postProcessAfterInitialization 方法) - Spring Bean 初始化完成阶段,在所有的 Bean(不是抽象、单例模式、不是懒加载方式)初始化后,Spring 会再次遍历所有初始化好的单例 Bean 对象.
最后的 10
、11
属于销毁阶段,当 Spring 应用上下文关闭或者主动销毁某个 Bean 时,可能需要对这个对象进行相关销毁工作,最后等待 JVM 进行回收。
- Spring Bean 销毁阶段,当 Spring 应用上下文关闭或者你主动销毁某个 Bean 时则进入 Spring Bean 的销毁阶段,执行顺序:@PreDestroy 注解的销毁动作、实现了 DisposableBean 接口的 Bean 的回调、destroy-method 自定义的销毁方法。这里也有一个销毁前阶段,也属于 Spring 提供的一个扩展点,@PreDestroy 就是基于这个实现的
- Spring 垃圾收集(GC)
Spring中的@Autowired自动装配
1. 什么是自动装配?
自动装配就是 Spring 会在容器中自动的查找,并自动的给 bean 装配及其关联的属性;依赖注入的本质就是装配,装配是依赖注入的具体行为。
在传统的使用 xml 文件装配 bean 是一件很繁琐的事情,而且还需要找到对应类型的 bean 才能装配,一旦 bean 很多,就不好维护了。为了解决这种问题,spring 使用注解来进行自动装配。自动装配就是开发人员不必知道具体要装配哪个 bean 的引用,这个识别的工作会由 spring 来完成。与自动装配配合的还有“自动检测”,这个动作会自动识别哪些类需要被配置成 bean,进而来进行装配。涉及到自动装配 bean 的依赖关系时,Spring 提供了 4 种自动装配策略:
public interface AutowireCapableBeanFactory extends BeanFactory {
// 无需自动装配
int AUTOWIRE_NO = 0;
// 按名称自动装配 bean 属性
int AUTOWIRE_BY_NAME = 1;
// 按类型自动装配 bean 属性
int AUTOWIRE_BY_TYPE = 2;
// 按构造器自动装配
int AUTOWIRE_CONSTRUCTOR = 3;
// 过时方法,Spring3.0 之后不再支持
@Deprecated
int AUTOWIRE_AUTODETECT = 4;
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。