思考的过程很重要,解决问题的能力更重要。
IoC:
Spring 容器不仅是缓存,更像是一个管理者,被管理的成员就是 Spring Bean。
IoC 主要是让流程反转,更多的组件创建流程更透明,不需要过多的关注细节。
IOC依赖注入是通过构造方法注入的方式注入对象而非自己创造,交给框架去创造,是DI控制反转的实现
IOC说起来,其实是一种编程思想或原则。相对于传统的编程方式(如从前文到后文这样串行顺序的方式),比如用传统方式,当我依赖一个对象,我要去创建它,对它进行属性配置,然后我才能使用这个对象。 但是对于IOC这种方式来说,它使对象或者组件的创建更为透明,不需要过多的关注细节(如创建对象、给对象设置属性,这些ioc容器都给我们设置好了),以达到解耦的目的。控制反转,简单来理解其实就是把获取依赖动象的方式,交给IOC容器来实现,由主动拉取,变为被动获取
。
IoC 主要实现策略
IOC可能被不同的方式去实现,有两种主要的策略:依赖查询和依赖注入;
IOC是管理bean的容器;Spring 容器不仅是缓存,更像是一个管理者,被管理的成员就是 Spring Bean。
DI 是 IoC 是一种实现手段
,spring中依赖查找和依赖注入都很重要。依赖查找和DI是IOC的两种实现策略,所以DI 不完全等同 IoC
- 服务定位模式(这个不关注)
- 依赖注入:
直接在容器启动时通过构造器,参数,getter setter,接口等形式注入依赖。 优点:性能高,侵入小
组件不需要查找,通常由容器帮我们自动去注入或者手动注入一些事情,这些工作不管是框架上还是我们来做,至少有一点,它依赖的处理,框架或者容器帮我们已经处理掉了,构造器注入、参数注入、Setter注入、接口注入,直接在容器启动时通过构造器,参数,getter setter,接口等形式注入依赖。性能高,侵入小 - 上下文依赖查找:
就是应用程序里面还是要调用容器的bean查找接口查找bean实例 缺点:还是有侵入性,性能低。
容器会提供一种回调的机制,然后到我们的组件,通过上下文查询的方式可以获得这个组件;Java里面Java beans这样的技术,Java beans有一个通用的上下文beanContext,这里面既可以传输我的bean,也可以管理我的bean的层次性;很多spring的一些实现,很多来自于这方面的灵感;就是应用程序里面还是要调用容器的bean查找接口查找bean实例 缺点:还是有侵入性,性能低。 - 模板方法的设计模式:
JdbcTemplate的使用,不需要关心具体callback来源 传统方式,需要自己调用
spring大量用到,比如JDBC里面的JDBC Template这样的实现;这种实现会给我们一种类似于比如说statement这样的callback,这种callback能够帮助我们的实现更为抽象,当我们去实现这样的接口的时候,我们不需要关心callback来源于哪里,那么也实现了一种反转控制的方式;就我们传统的JDBC直接去调用SQL命令的执行是一个相反的操作;JdbcTemplate的使用,不需要关心具体callback来源 传统方式,需要自己调用 - 策略模式:在IOC方面不是特别深刻,
IOC可能被不同的方式去实现,有两种主要的策略:依赖查询和依赖注入;
,依赖查询,容器会提供一种回调的机制到我们的组件,通过上下文的查询能拿到这个组件
为什么要有ioc呢?
实际上,IoC 是为了屏蔽构造细节,new 出来的对象,任何生命周期的细节使用端都是知道的,如果在没有容器的前提下,IoC 是没有存在的必要,不过由于复杂的系统,应用更关注地是对象的运用,而非它的构造和初始化细节。
依赖查找的案例特别多,比如 org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor#finishRegistration 方法,也可以通过 BeanFactory#getBean 等手段来查询其中框架内部使用的情况
IoC 容器的职责
IOC的两种实现主要策略:依赖查找和依赖注入
ioc控制反转服务于以下设计目的: •将任务的执行与实现分离,产生解耦。 •将模块集中在其设计的任务上,关注设计的最终目标,而不是具体的实现。 •释放模块,将模块从关于其他系统如何做的假设中解放出来,而不是依赖于合同。 •为防止更换模块时产生副作用。 控制反转有时被戏称为“好莱坞原则:不要来找我们,我们会找你。这里的我们指的是你所需要的一些东西或者资源。
ioc设计设计目标:
实现bean的管理,让应用程序不用过度关注bean的生命周期及依赖。
ioc包含了:
- bean生命周期管理,依赖的处理:依赖查找及依赖注入
- 配置管理:资源文件,spring容器的管理等
依赖注入和依赖查找的具体区别是什么?
依赖注入 = 理解依赖查找 + 注入(各种注入方式),同时,依赖注入的来源 = 依赖查找来源 + 其他来源。
通用职责 :
依赖处理
-
- 依赖查找
- 依赖注入
生命周期管理
-
- 容器
- 托管的资源(Java Beans 或其他资源),其他资源比如时间监听器可能是外部加入的,不是那个bean,当容器发生事件时,可以通过上下文的方式关联一些其他事件的监听,监听器既不属于bean,也没法通过依赖查找和依赖注入去操作。当然spring bean可以作为事件监听的一个来源,但是不是唯一的来源,所以所托管的资源不完全一定是Java Beans或者所谓的依赖。还有一些外部化配置和外部化文件
配置
-
- 容器 (不需关心什么时候启动和执行任务,只需关心你的任务即可)
- 外部化配置 (属性配置和XML配置)
- 托管的资源(Java Beans 或其他资源)可能对bean产生配置,可能对一些外部化资源产生配置,比如外部的线程池,一些外部的容器,比如tomcat容器,也有pring boot通过嵌入式的方式,由spring上下文影响到容器,相反spring mvc,容器影响springmvc,所以这是两个相辅相成的问题,
传统 IoC 容器的实现
主要实现
Java SE
- Java Beans
- Java ServiceLoader SPI
- JNDI(Java Naming and Directory Interface)
Java EE
- EJB(Enterprise Java Beans)
- Servlet
开源
- Apache Avalon(avalon.apache.org/closed.html…
- PicoContainer(picocontainer.com/)
- Google Guice(github.com/google/guic…
- Spring Framework(spring.io/projects/sp…
Java Beans 作为 IoC 容器
• 特性
- 依赖查找
- 生命周期管理
- 配置元信息(指的管理一些Java Beans相关的一些配置元信息,比如反射以及反射对应的应用)
- 事件(spring的事件机制是基于传统的Java事件机制来做的,他们的实现是类似的)
- 自定义
- 资源管理
- 持久化
• 上述特性来自下面两个规范
- JavaBeans:www.oracle.com/technetwork…
- BeanContext:docs.oracle.com/javase/8/do…
BeanContext规范比较的复杂,BeanContext笼统讲主要是管理bean以及bean自己容器之间的相互依赖关系,这里就包括它的层次性,双亲委派可以用到这个地方来
- Java beans的基本逻辑:
什么才是Javabean,对应Javabean的理解可以认为是一个简单的POJO
Java beans相关的api设计的初衷是什么?要解决哪方面的需求?
Java beans 是一种综合需求的基础,简单地说,它包含 Bean 自省(Bean 内部描述),Bean 时间,Bean 的内容修改(编辑)等等,并且由 BeanContext 统一托管 Bean 实例,简单地说,Spring 有部分思想是借鉴了 Java Beans。Spring 利用了部分的 Java Beans
BeanInfo 也是利用反射实现,不过它是一种 Bean 综合描述信息,有方法、字段、构造器等元信息,方便其他工具或服务器了解。 严格的来说,反射和 ASM 都是字节码提升,不过 ASM 以及 CGLib 用来提升性能
BeanInfo这个类是jdk中rt.jar中提供的一个较为底层的类,BeanInfo 主要是会“内省” 类成员,比如 Property、Method 以及 Constructor 等。
JavaBeans 也是 IoC 容器。Spring ioc容器实现利用了部分的 Java Beans。 avaBeans 基于反射,也使用了 Java Reference
轻量级 IoC 容器:如何界定IOC容器的轻重
轻量级容器的特征:能管控代码运行生命周期,不需要大量配置快速启动,按需依赖,对部署管控
- 可以管理应用程序代码的容器。能够管理我们的应用代码,管理代码的运行启动;能够控制代码的启停,生命周期。
- 快速启动的容器。当然依赖的资源越多,启动速度回变慢
- 不需要任何特殊部署步骤就可以在其中部署对象的容器。容器不需要一些特殊的配置来进行操作,这是Spring Framwork和EJB容器的比较,EIB容器需要大量的XML配置;
- 一个容器,具有如此轻的足迹和最小的API依赖,它可以运行在各种环境。容器能够达到一些比较轻量级的内存占用,以及最小化的API依赖;
- 一个在部署工作和性能方面为添加管理对象设置了很低门槛的容器可以部署和管理细粒度对象以及粗粒度组件。 容器需要一个可以管控的渠道,这个渠道可以帮助我们去部署和管理一些细粒度的对象,甚至一些粗粒度的组件;主要达到一个部署上的效率
轻量级容器的好处:
- 解耦;分离的思想:执行层面和管理层面不应该放在一起;
- 代码复用
- 更大程度面向对象,Spring Framework是很好的面向对象的方式
- 更大化产品化,spring这类产品主要关注的是产品效率,更高的生产率
- 更好的可测试性
Java EE 容器有不少,包括EJB 容器和最为熟悉的 Servlet 容器,除此之外,还有WebSocket、WebServices 等
ioc容器轻:方便使用,配置小,代码耦合度小
依赖查找 VS. 依赖注入
依赖查找和依赖注入没有绝对的好与坏,可以相互使用,根据场景来选择如何使用
依赖查找是主动获取对象,是一种拉的模式,依赖注入式被动提供获取对象,是一种推的模式。他们两个都是解决和处理依赖,通过名称,或者类型对搜索依赖,依赖注入@Autowired是一种低侵入性的注入方式
依赖查找是主动去获取对象,依赖注入是被动提供对象,由框架完成,@Autowired,@Resource通过类型和名称获取对象; 通过beanFactory或者context的getBean()方法是依赖查找?通过@Autowired @Resource是依赖注入。
spring是同时支持依赖注入与依赖查找的,我们可以实现ApplicationcontextAware获取到Applicationcontext,然后通过getBean()来获取到想要的依赖,相当于通过 ApplicationContext 间接地获取 Bean
- 通过beanFactory或者context的getBean()方法是依赖查找
- 通过@Autowired @Resource是依赖注入
spring中主要还是依赖注入,通过setter和构造方法两种方式注入;依赖查找的应用是实现ApplicationContextAware获取ApplicationContext来查找自己想要的bean对象也就是JNDI
依赖查找和依赖注入都是ioc容器的实现方式,依赖查找是一种拉的方式,而依赖注入是一种推的方式,具有更低的代码侵入性,即更少的依赖spring的api。
构造器注入 VS. Setter 注入
Spring团队通常提倡构造函数注入,因为它允许您以不可变对象,并确保所需的依赖项不是空的在构造器将字段设置为 final,可以解决字段在构造阶段赋值时的线程安全。此外,constructor-injected组件总是返回到处于完全初始化状态的客户机(调用)代码。另外如果有大量的构造函数参数是一种不好的代码气味,意味着类可能有太多的责任和应该进行重构,以更好地处理适当的关注点分离。
Setter注入主要应该只用于可选性注入,这些依赖项可以被分配合理的默认值类中的值。否则,必须在代码使用该依赖项的所有地方执行非空检查。一个setter注入的好处是,setter方法使该类的对象易于重新配置或重新配置注入,再一次注入注入对象有一个新的变化。
构造器注入是绝对不允许循环依赖存在的,因为ta要求被注入的bean都是成熟态,而字段注入与setter注入没有这样的要求,同时springboot in action和我还有老师的观点一致,推荐构造注入,因为如果出现循环依赖或者多个依赖代码不够优雅的情况那都是自身的设计有问题应该好好反省反省自己代码结构了
jvm重排序会不会导致构造器的属性set顺序不一致呢?
通常不会,因为在初始化过程时,是单一线程处理,大多数情况是 main 线程。
构造器注入能保证实例具有一致的状态,但不适合多参数构造器的情况 setter注入相对更方便,而且能解决循环依赖的问题 个人推荐使用构造器注入
Java是值传递,只不过是对象时传的值是对象的引用。
各种注入方式循环依赖的问题
Setter 注入的优点:
- JavaBean属性在IDA中得到了很好的支持。
- JavaBean属性是自文档化的。很容易理解方法和属性的含义,不需要文档;
- 如果需要,可以使用标准的JavaBeans属性编辑器机制进行类型转换。
- 任何现有的javabean都可以在面向javabean的IoC容器中使用,而无需修改。
- 如果每个setter都有相应的getter(使属性既可读又可写),则是可以向组件询问其当前配置状态。如果我们想,这是特别有用的保持该状态:例如,在XML表单或数据库中。使用构造函数注入,就没有办法找到当前状态。
- Setter注入对于具有默认值的对象工作得很好,这意味着并非所有属性都需要默认值在运行时提供。
set可以覆盖默认值;
Setter 注入的缺点:
在任何契约中都没有表示调用setter的顺序
。因此,我们有时需要调用调用最后一个setter之后的方法,以初始化组件。Spring提供了initializingbean接口;它还提供了调用任意的init方法。- 并非所有必要的setter在使用之前都被调用过。因此,可以部分地保留对象配置。
构造器注入的优点:
- 确保每个被管理的对象有比较一致的状态,我们把赋值字段设置成final保证对象不可变,从线程安全的角度和未来可变性的角度都是比较好的。
- 在面向对象鼓励对象是不变的,对象一旦创建好了之后就不变,这样可以防止很多问题,如果要变的话把这个对象再重新生成一个新的对象,不用原来的对象,因为Java没有所谓的传值引用,只有传引用的方式,没有值赋值的方式,传对象传的是引入,指向的还是原有的对象,不会生成新的对象
构造器注入可以避免很多问题,比如状态不确定性的被修改;
对程度的维护带来很多的便利
造器注入是绝对不允许循环依赖存在的,因为ta要求被注入的bean都是成熟态,而字段注入与setter注入没有这样的要求,同时springboot in action和我还有老师的观点一致,推荐构造注入,因为如果出现循环依赖或者多个依赖代码不够优雅的情况那都是自身的设计有问题应该好好反省反省自己代码结构了
@Autowired 是注解的注入方式,可以认为是 XML 的注入的补充。
比较大的区别在于: setter注入会存在循环引用 构造器就比较麻烦了,如果有多个依赖,构造器比较繁索
构造器将字段设置为 final,可以解决字段在构造阶段赋值时的线程安全。
面试题
什么是 IoC ?
简单地说,IoC 是反转控制,类似于好莱坞原则,主要有依赖查找和依赖注入实现
按照IOC的定义,很多地方都是IOC,比如说Javabeans是IOC的一个容器实现,servlet的容器也是IOC的实现,因为servlet可以去依赖或者反向的通过JNDI的方式得到外部的一些资源,包括dataSource或者相关EJB组件,与此同时,像 Spring Framework的依赖注入框架也能帮助我们实现IOC,这些是比较常见的IOC实现策略,按照反转控制的定义,包括消息也算,因为消息是被动的,如果说传统的调用模式是一个主动拉的模式,IOC其实是一种推的模式,
推的模式在消息事件以及各种类似于这种反向的观察者模式的扩展都属于IOC,这种就无穷无尽了,这样就看对方怎么去描述,那么如果他仅仅关注依赖注入,那么可以多回答一些依赖注入方面的内容,比如通过构造器注入、set注入,那么它其中有什么好处来做一个分析。作为面试官不会让你把所有的情况都考虑到,说一个三五点就OK了;
依赖查找和依赖注入的区别?
依赖查找是主动或手动的依赖查找方式(通过名称、类型或者路径查找),通常需要依赖容器或标准 API实现(比如servle的api,比如JNDI的API,EJB的API),调用API的方式去获取资源。而依赖注入则是手动或自动依赖绑定的方式,无需依赖特定的容器和API(比如Spring容器已经能够做到不需要依赖外部的这些API就能实现注入,当然需要一些潜在的规则,依赖注入从哪里来,用什么规则去映射)
Spring 作为 IoC 容器有什么优势?
- 典型的 IoC 管理,依赖查找和依赖注入(spring中依赖查找和依赖注入的底层实现基本一样,最重要的是这个数据来源是怎么样的,以及初始化以及相关的生命周期是怎么管理的)
- AOP 抽象(spring在AOP方面的抽象,做得非常好,超越spring其他API的封装)
- 事务抽象(事务有很多级别或者很多传播事务的模式,这个模式在spring抽象中,做得非常好,它其实是对我们JAVA EE传统的一些东西做的一层封装,很多概念不是它提出的)
- 事件机制(spring对事件的扩展,它还是基础Java的标准事件,包括Object、EventObject、EventListener这两个接口进行操作,EventObject是一个抽象类,EventListener是一个接口,是一个标记的接口,凡是事件监听就标记EventListener接口进行扩展,包括spring中的ApplicationListener接口)
- SPI 扩展(传统的包括BeanPostProcessor(我们说的Bean的扩展),BeanFactoryPostProcessor就是IOC容器的一个扩展,就是BeanFactory的一个扩展,还有其他的方式,比如工厂扩展机制,里面有个Spring Factories最多用在spring boot里面,自动注入的时候,就是自动装配的时候经常用到)
- 强大的第三方整合(ORM、JDBC、消息中间件整合,这个整合是通过自己的API实现的,帮助你减少一些中间环节的一个需要,现在spring做得非常多,比如spring提出一个新的数据整合spring data ,封装了一些关系型数据库常用的东西比如CRUD,那么他通过一些契约的方式来动态生成或者反向生成一些中间代码,帮助我们调用一些关于关系型数据库或者非关系型数据库的一些基本操作,帮助我们通过统一的API,不关心我们的数据来源,其实也就是DAO实现的一种方式,DAO是一种特殊的策略模式,DAO是数据存储的一个对象)
- 易测试性
- 更好的面向对象把面向对象做到了极致,用到了很多的设计模式,比如代理模式、适配器模式、观察者模式等等,这些设计模式是很好的面向对象的实现,因此它在接口定义上或者API设计方面,也考虑了流行的设计方法;
这个问题不是非常重要,重要的是要如何去实现或利用IOC框架来实现业务逻辑,简化开发;
- IOC:就是一个控制反转的思想,具体有两种形式去实现:依赖查找和依赖注入
- 依赖查找就是需要一个容器API在业务代码去调用获取对应的依赖,缺点很明显需要依赖容器API,代码侵入会大于后面讲的依赖注入。
- 依赖注入则是自动将依赖注入到bean的依赖属性上,在运行时无法再调用容器API去获取依赖属性了,直接像用普通java属性一样即可。
- 具体实现有spring的ioc(依赖注入),servlet里面用jndi去加载资源(依赖查找),更广义的消息异步机制也是IOC的一种机制(消息发送者不需要同步去调用结果了,由消息接收者通知消息发送者结果) spring IOC的优势:实现了依赖查找及依赖注入两种形式,配置少易使用。其次按需配套了spring事务,aop等其它第三方整合(生态)。
如果一个单例Bean中注入了一个多例Bean,如果要保证这个注入的Bean是多例的,最合适的处理办法是什么呢。
单例的 Bean 注入一个原型的 Bean,那么这个单例 Bean 所注入的对象是独立的,因此就是多例的~
Servlet 是服务端小程序,Java Web 的标准技术
JdbcTemplate 和 RestTemplate,它们都实现了对应的 Operations,而这两个 Operations 都只有一个实现,那就是对应的 Template,既然只有一个实现,那么为啥单独定义一下 Operations 接口?
其实这个接口是为了体现语义,JdbcTemplate 太容易让人联想到模板模式,然而实际是命令模式,Operations 表示动作,更接近于实现语义。 JdbcTemplate 是门面模式 + 命令模式的组合
DAO和ORM有什么区别,再spring框架里为什么要分成两部分呢?
DAO 关注数据存储,ORM 则是对象与关系型数据的映射模型。DAO 不仅是关系型数据之类。
IOC容器的职责是不是仅仅是指Spring中的实现,还是说其他IOC容器实现也都是这个套路
IoC 通常模式是类似的,不过不同的实现有补充,比如 Spring 提供了不少的企业能力,包括事件和生命周期。
Java EE 容器有不少,包括EJB 容器和最为熟悉的 Servlet 容器,除此之外,还有WebSocket、WebServices 等
Spring IoC 容器概述
Spring IoC 依赖查找
我们通过名称和ID查找,名称和ID都是认为是一种鉴定的方式,名称和ID在一个上下文中必须是唯一的,spring里面可以拥有多个上下文,通过名称查找,有两种方式,分别是实时查找和延迟查找;
<properties>
<spring.version>5.2.2.RELEASE</spring.version>
</properties>
<!-- Spring IoC 核心依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
•根据 Bean 名称查找
- • 实时查找
- • 延迟查找
dependency-lookup-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!-- <context:annotation-config/>-->
<!-- <context:component-scan base-package="org.acme" />-->
<!-- Root BeanDefinition 不需要合并,不存在 parent -->
<!-- 普通 beanDefinition GenericBeanDefinition -->
<!-- 经过合并后 GenericBeanDefinition 变成 RootBeanDefinition -->
<bean id="user" class="org.geekbang.thinking.in.spring.ioc.overview.domain.User">
<property name="id" value="1"/>
<property name="name" value="zhansan"/>
<property name="city" value="HANGZHOU"/>
<property name="workCities" value="BEIJING,HANGZHOU"/>
<property name="lifeCities">
<list>
<value>BEIJING</value>
<value>SHANGHAI</value>
</list>
</property>
<property name="configFileLocation" value="classpath:/META-INF/user-config.properties"/>
</bean>
<!-- 普通 beanDefinition GenericBeanDefinition -->
<!-- 合并后 GenericBeanDefinition 变成 RootBeanDefinition,并且覆盖 parent 相关配置-->
<!-- primary = true , 增加了一个 address 属性 -->
<bean id="superUser" class="org.geekbang.thinking.in.spring.ioc.overview.domain.SuperUser" parent="user"
primary="true">
<property name="address" value="杭州"/>
</bean>
<bean id="objectFactory" class="org.springframework.beans.factory.config.ObjectFactoryCreatingFactoryBean">
<property name="targetBeanName" value="user"/>
</bean>
</beans>
/**
* 依赖查找示例
* 1. 通过名称的方式来查找
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
* @since
*/
public class DependencyLookupDemo {
public static void main(String[] args) {
// 配置 XML 配置文件
// 启动 Spring 应用上下文
BeanFactory beanFactory = new ClassPathXmlApplicationContext("classpath:/META-INF/dependency-lookup-context.xml");
// 按照类型查找
lookupByType(beanFactory);
// 按照类型查找结合对象
lookupCollectionByType(beanFactory);
// 通过注解查找对象
lookupByAnnotationType(beanFactory);
// lookupInRealTime(beanFactory);
// lookupInLazy(beanFactory);
}
private static void lookupByAnnotationType(BeanFactory beanFactory) {
if (beanFactory instanceof ListableBeanFactory) {
ListableBeanFactory listableBeanFactory = (ListableBeanFactory) beanFactory;
Map<String, User> users = (Map) listableBeanFactory.getBeansWithAnnotation(Super.class);
System.out.println("查找标注 @Super 所有的 User 集合对象:" + users);
}
}
private static void lookupCollectionByType(BeanFactory beanFactory) {
if (beanFactory instanceof ListableBeanFactory) {
ListableBeanFactory listableBeanFactory = (ListableBeanFactory) beanFactory;
Map<String, User> users = listableBeanFactory.getBeansOfType(User.class);
System.out.println("查找到的所有的 User 集合对象:" + users);
}
}
private static void lookupByType(BeanFactory beanFactory) {
User user = beanFactory.getBean(User.class);
System.out.println("实时查找:" + user);
}
//通过ObjectFactoryCreatingFactoryBean获取ObjectFactory,通过ObjectFactory去查找关联的user对象
private static void lookupInLazy(BeanFactory beanFactory) {
ObjectFactory<User> objectFactory = (ObjectFactory<User>) beanFactory.getBean("objectFactory");
User user = objectFactory.getObject();
System.out.println("延迟查找:" + user);
}
private static void lookupInRealTime(BeanFactory beanFactory) {
User user = (User) beanFactory.getBean("user");
System.out.println("实时查找:" + user);
}
}
依赖查询可以分为名称、类型和注解查找三种方式,包括单个类型和集合类型。
实时查找就是马上查找到bean, 延时查找是 查找到目标 bean 的引用, 然后通过这个引用再来查找目标 bean.
延迟依赖查找主要用于获取 BeanFactory 后,不马上获取相关的 Bean,比如在 BeanFactoryPostProcessor 接口中获取 ConfigurableListableBeanFactory 时,不马上获取,降低 Bean 过早初始化的情况
• 根据 Bean 类型查找
- • 单个 Bean 对象
- • 集合 Bean 对象
• 根据 Bean 名称 + 类型查找
• 根据 Java 注解查找
- • 单个 Bean 对象
- • 集合 Bean 对象
ObjectFactory、BeanFactory、FactoryBean有什么区别?
ObjectFactory
帮助我们延迟加载对象, 对象并不是直接返回了实际的 Bean,而是一个 Bean 的查找代理。当得到 ObjectFactory 对象时,相当于 Bean 没有被创建,只有当 getObject() 方法时,才会触发 Bean 实例化等生命周期。
FactoryBean
是一种特殊的 Bean,需要注册到 IoC 容器,通过容器 getBean 获取 FactoryBean#getObject() 方法的内容,而 BeanFactory#getBean 则是依赖查找,如果 Bean 没有初始化,那么将从底层查找或构建。ObjectFactory 通常是针对单类 Bean 做延迟获取的,BeanFactory 则是全局 Bean 管理的容器。
依赖注入的第一步是通过依赖查找去符合条件的Bean,延迟依赖查找相当于依赖注入的对象是一个代理对像,当调用该对象getObject 方法是才实际依赖查找
当我们使用 Spring 框架时,我们经常需要创建和管理对象。这些对象可能是我们应用程序中的各种组件、服务或实体。在这个过程中,有几个关键的概念需要理解。
首先是 ObjectFactory
,它是一个接口,可以帮助我们延迟加载对象。通常情况下,我们不想在应用程序启动时就创建所有对象,因为这可能会导致资源浪费。所以我们可以使用 ObjectFactory
来获取对象,只有在需要时才会真正创建。这样可以提高性能并避免不必要的资源消耗。
接下来是 BeanFactory
,它是 Spring 框架的核心接口之一。它负责创建、配置和管理我们应用程序中的 bean 对象。bean 是我们应用程序中被 Spring 管理的对象。BeanFactory
会根据我们在配置文件或注解中定义的信息,解析配置并创建相应的 bean 对象。它还负责生命周期管理、依赖注入和属性赋值等功能。
最后是 FactoryBean
,这是一个特殊的接口,允许我们自定义 bean 的创建过程。通常情况下,我们通过配置文件或注解来定义 bean,Spring 会根据定义创建对象。但是,如果我们想要更加灵活地控制 bean 的创建逻辑,我们可以实现 FactoryBean
接口。当我们从容器中获取这个 bean 时,会调用 FactoryBean
实现的方法,生成相应的对象并返回。
简而言之,ObjectFactory
帮助我们延迟加载对象,BeanFactory
是 Spring 中负责创建和管理 bean 的核心接口,而 FactoryBean
允许我们自定义 bean 的创建过程。这些概念一起帮助我们更好地利用 Spring 框架来管理和使用对象。
Spring IoC 依赖注入
- 根据 Bean 名称注入
- 根据 Bean 类型注入
-
- 单个 Bean 对象
-
- 集合 Bean 对象
- 注入容器內建 Bean 对象
- 注入非 Bean 对象
- 注入类型
-
- 实时注入
-
- 延迟注入
依赖查找和依赖注入的BeanFactory不相等,依赖查找和依赖注入数据不同源。
依赖查找和依赖注入区别在于依赖的对象是否为主动获取,是的话,就是依赖查找,否则就是依赖注入,由框架绑定完成。
ObjectFactoryCreatingFactoryBean 是 ObjectFactory 和 FactoryBean 组合形式,通过 FactoryBean 注册 ObjectFactory
Spring IoC 依赖来源
自定义的bean和内建的bean能够通过依赖查找查询到,但是内建依赖是不能通过依赖查找查询到,因为他们的来源不同
内建依赖和自定义Bean的区别 1.内建依赖指的是DefaultListableBeanFactory属性resolvableDependencies这个map里面保存的bean,自定义bean指的是通过DefaultSingletonBeanRegistry#registerSingleton手动注册的bean。它们都在BeanFactory里面; 2.依赖注入的时候比如@AutoWired(AutowiredAnnotationBeanPostProcessor处理)会调用DefaultListableBeanFactory#resolveDependency方法去resolvableDependencies里面找,而依赖查找BeanFactory.getBean(xxx)是不会去resolvableDependencies这个map里面找的。
分为两类,一个是bean,一个是非bean;
- •自定义 Bean(自己用xml配置或注解配置的bean)
- • 容器內建 Bean 对象(非自己定义的Bean,spring容器初始化的Bean,如Environment、BeanDefinitions 和 Singleton Objects。可以通过getBean获取)
- • 容器內建依赖 (非Bean,不可通过获取依赖查找Bean的方法来获取(getBean(XXX)))
内建的 Bean 是普通的 Spring Bean,包括 BeanDefinitions 和 Singleton Objects,而内建依赖则是通过 AutowireCapableBeanFactory 中的 resolveDependency 方法来注册,这并非是一个 Spring Bean,无法通过依赖查找获取~
内建Bean和内建依赖如何区别:
在 Spring 中,"内建 Bean" 和 "内建依赖" 是两个不同的概念。
内建 Bean(Built-in Bean):
内建 Bean 是 Spring 框架自身提供的一些预定义的 bean,它们在容器启动时自动创建。这些内建 Bean 具有特殊的作用和功能,用于支持 Spring 框架的核心功能和特性。例如,ApplicationContext
、BeanFactory
、Environment
等都是 Spring 提供的内建 Bean。
内建 Bean 在 Spring 容器中是自动创建和管理的,无需额外配置,开发者可以直接使用这些 Bean 来实现各种功能。通常情况下,我们无法通过配置文件或注解来定义内建 Bean,而是直接依赖于 Spring 框架的默认行为。
内建依赖(Built-in Dependency):
内建依赖指的是 Spring 框架中某些组件或功能所依赖的其他对象或资源。当我们使用某个组件或功能时,Spring 框架会自动处理相关的依赖关系,确保所需的其他对象或资源被正确注入或加载。
例如,当我们使用数据库访问功能时,需要连接数据库来执行操作。这时,Spring 框架会自动处理数据库连接的创建和注入,我们只需要声明依赖并使用即可。这种自动处理依赖的机制就是内建依赖。
在 Spring 中,通过依赖注入(Dependency Injection)的方式来解决组件之间的依赖关系。内建依赖提供了一种方便和自动化的方式来管理这些依赖关系,而不需要显式地编写额外的代码。
总结:
内建 Bean 是 Spring 框架自身提供的预定义 Bean,用于支持核心功能和特性;而内建依赖是指 Spring 框架中某些组件或功能所依赖的其他对象或资源。内建 Bean 是由 Spring 容器自动创建和管理的,而内建依赖则是通过依赖注入机制自动处理的。
容器内建bean对象和容器内建依赖有什么区别呢?
一个意思,内建的 Bean 也算内部依赖,不过通常是通过正常 Bean 注册流程,即 BeanDefinition 或 singleton
public class UserRepository {
private Collection<User> users; // 自定义 Bean
private BeanFactory beanFactory; // 內建非 Bean 对象(内建依赖)
private ObjectFactory<ApplicationContext> objectFactory;
public Collection<User> getUsers() {
return users;
}
public void setUsers(Collection<User> users) {
this.users = users;
}
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
public BeanFactory getBeanFactory() {
return beanFactory;
}
public ObjectFactory<ApplicationContext> getObjectFactory() {
return objectFactory;
}
public void setObjectFactory(ObjectFactory<ApplicationContext> objectFactory) {
this.objectFactory = objectFactory;
}
}
public static void main(String[] args) {
// 配置 XML 配置文件
// 启动 Spring 应用上下文
// BeanFactory beanFactory = new ClassPathXmlApplicationContext("classpath:/META-INF/dependency-injection-context.xml");
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:/META-INF/dependency-injection-context.xml");
// 依赖来源一:自定义 Bean
UserRepository userRepository = applicationContext.getBean("userRepository", UserRepository.class);
// System.out.println(userRepository.getUsers());
// 依赖来源二:依赖注入(內建依赖)
System.out.println(userRepository.getBeanFactory());
ObjectFactory userFactory = userRepository.getObjectFactory();
System.out.println(userFactory.getObject() == applicationContext);
// 依赖查找(错误)
// System.out.println(beanFactory.getBean(BeanFactory.class));
// 依赖来源三:容器內建 Bean
Environment environment = applicationContext.getBean(Environment.class);
System.out.println("获取 Environment 类型的 Bean:" + environment);
}
private static void whoIsIoCContainer(UserRepository userRepository, ApplicationContext applicationContext) {
// ConfigurableApplicationContext <- ApplicationContext <- BeanFactory
// ConfigurableApplicationContext#getBeanFactory()
// 这个表达式为什么不会成立
System.out.println(userRepository.getBeanFactory() == applicationContext);
// ApplicationContext is BeanFactory
}
Spring IoC 底层容器就是指的 BeanFactory 的实现类,大多数情况是 DefaultListableBeanFactory 这个类,它来管理 Spring Beans,而 ApplicationContext 通常为开发人员接触到的 IoC 容器,它是一个 Facade,Wrap 了 BeanFactory 的实现。
Spring IoC 配置元信息
配置元信息一个是分为你自己的bean,这个bean通常来说是你的业务进行感知的,IOC容器主要是对IOC容器做一个控制,通常来说这是一个非核心的,就是非功能性的一些特性,就是这东西不会影响到你的功能行为,又不会影响到你的业务行为;
配置元信息就是元编程的一部分,通过配置的方式影响行为。
• Bean 定义配置
- • 基于 XML 文件
- • 基于 Properties 文件
- • 基于 Java 注解
- • 基于 Java API(专题讨论)
• IoC 容器配置
- • 基于 XML 文件
- • 基于 Java 注解
- • 基于 Java API (专题讨论)
• 外部化属性配置
- • 基于 Java 注解(@value就是)
Spring IoC 容器
BeanFactory 几乎唯一的实现是 DefaultListableBeanFactory
底层是 BeanFactory,Spring Bean 的维护和生命周期管理均在 BeanFactory 实现类中,绝多大数是指 DefaultListableBeanFactory
springIOC容器很多功能依赖上述的特性;
BeanFactory和ApplicationContext谁才是SpringIoC容器?
BeanFactory是一个底层的IOC容器,ApplicationContext增加了一些自己的特性,ApplicationContext既继承了BeanFactory获取它的全部的能力,ApplicationContext中获取Bean是通过组合了BeanFactory的一个方式,这种方式有点像代理方式,getBean相当于用代理对象去查找,不是自己的查找
BeanFactory和ApplicationContext是同一类事务,只不过在底层实现的时候,ApplicationContext组合了一个BeanFactory实现,所以BeanFactory和ApplicationContext不是同一个对象,尽管复用了同一个接口BeanFactory。
如果用ApplicationContext,千万要调用getBeanFactory方法,去获取真正的底层实现,而非直接去进行替换。
BeanFactory接口提供一些高级配置的机制,能够来管理这些对象(并不是只限于bean) ApplicationContext是BeanFactory的一个子接口,提供了一些特性:
简化整合(和spring AOP特性的整合)、消息资源的处理(用于国际化)、事件发布(spring事件)、应用级别的上下文(比如WebApplicationContext在web使用场景上面)
,BeanFactory是一个提供配置的框架,并且是一个基本功能,BeanFactory是一个很基本的IOC容器,而ApplicationContext是他的一个超集,提供更多企业特性的功能。上下文里面实现是一个组合的方式,在接口上面又是extends的关系。这种方式有点像代理方式,getBean相当于用代理对象去查找
ApplicationContext是BeanFactory的子接口,说明ApplicationContext is BeanFactory。并且ApplicationContext 是BeanFactory的包装类,也就是内部组合了BeanFactory的实现-DefaultListableBeanFactory。为什么包装了DefaultListableBeanFactory,因为它需要简化且丰富功能来为企业开发提供更高的便捷性,也就是说ApplicationContext 是DefaultListableBeanFactory的超集。 至于为什么UserRepository注入的BeanFactory 不等于ClassPathXmlApplicationContext得到的BeanFactory ,是因为AbstractApplicationContext#prepareBeanFactory中 指明了 beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory); 也就是说当byType是BeanFactory.class的时候,获得是的ApplicationContext中的DefaultListableBeanFactory对象。 那真正的IOC的底层实现就是BeanFactory的实现类,因为ApplicationContext是委托DefaultListableBeanFactory来操作getBean等方法的。
UserRepository中的BeanFactory beanFactory属性为什么通过autowired=“byType”,注入的是DefaultListableBeanFactory,而不是ClassPathXmlApplicationContext?既然他们两者属于BeanFactory接口,按type注入,为什么不是ClassPathXmlApplicationContext?是框架哪里设置的吗?
BeanFactory 几乎唯一的实现是 DefaultListableBeanFactory
,ApplicationContext中每次getBean()都是通过调用getBeanFactory方法获取底层实现,就是用DefaultListableBeanFactory来查找bean对象的
答案在 org.springframework.context.support.AbstractApplicationContext#prepareBeanFactory 方法中,其中: beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory); beanFactory.registerResolvableDependency(ResourceLoader.class, this); beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this); beanFactory.registerResolvableDependency(ApplicationContext.class, this); 以上代码明确地指定了 BeanFactory 类型的对象是 ApplicationContext#getBeanFactory() 方法的内容,而非它自生。
BeanFactory和ApplicationContext有何区别?
- ApplicationContext是BeanFactory的子接口
- BeanFactory是一个底层的IOC容器,提供了IOC容器的基本实现,而ApplicationContext则是BeanFactory的超集提供了丰富的企业级特性。
- ApplicationContext是委托DefaultListableBeanFactory来实现Bean的依赖查找和依赖注入。
Spring 应用上下文
BeanFactory 是 Bean 容器,它不提供企业特性,比如 AOP、事务以及 事件等,这些都被 ApplicationContext 支持。
spring中的容器不光是Bean容器,ApplicationContext提供除Bean容器外的其他容器功能。依赖注入是(BeanFactory) spring-beans 提供的 但是需要(@Autowired @Bean) spring-context 提供驱动
BeanFactory 是 Bean 容器,ApplicationContext通过组合BeanFactory提供依赖查找的能力
ApplicationContext是BeanFactory的一个超集,就是所有的特性都继承,同时还提供以下特性;
ApplicationContext 除了 IoC 容器角色,还有提供:
- • 面向切面(AOP)
- • 配置元信息(Configuration Metadata),比如@bean,外部化配置等等都是需要ApplicationContext进行支撑实现
- • 资源管理(Resources)
- • 事件(Events)
- • 国际化(i18n)
- • 注解(Annotations),注解支持都是来自于ApplicationContext支持;
- • Environment 抽象(Environment Abstraction)
使用 Spring IoC 容器
- • BeanFactory 是 Spring 底层 IoC 容器
- • ApplicationContext 是具备应用特性的 BeanFactory 超集
DefaultListableBeanFactory 实现的设计模式有:
- 抽象工厂(BeanFactory 接口实现)
- 组合模式(组合 AutowireCandidateResolver 等实例)
- 单例模式(Bean Scope)
- 原型模式(Bean Scope)
- 模板模式(模板方法定义:AbstractBeanFactory)
- 适配器模式(适配 BeanDefinitionRegistry 接口)
- 策略模式(Bean 实例化)
- 代理模式(ObjectProvider 代理依赖查找)
BeanFactory 是 Spring 框架自己用的,ApplicationContext 是给开发者用的。
ApplicationContext 才区分 XML 和注解,BeanFactory 通常只管理 BeanDefinition 和其 Bean 实例。
Spring IoC 容器生命周期
- 启动
- 运行
- 停止
public class AnnotationApplicationContextAsIoCContainerDemo {
public static void main(String[] args) {
// 创建 BeanFactory 容器
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
// 将当前类 AnnotationApplicationContextAsIoCContainerDemo 作为配置类(Configuration Class)
applicationContext.register(AnnotationApplicationContextAsIoCContainerDemo.class);
// 启动应用上下文
applicationContext.refresh();
// 依赖查找集合对象
lookupCollectionByType(applicationContext);
// 关闭应用上下文
applicationContext.close();
}
/**
* 通过 Java 注解的方式,定义了一个 Bean
*/
@Bean
public User user() {
User user = new User();
user.setId(1L);
user.setName("小马哥");
return user;
}
private static void lookupCollectionByType(BeanFactory beanFactory) {
if (beanFactory instanceof ListableBeanFactory) {
ListableBeanFactory listableBeanFactory = (ListableBeanFactory) beanFactory;
Map<String, User> users = listableBeanFactory.getBeansOfType(User.class);
System.out.println("查找到的所有的 User 集合对象:" + users);
}
}
}
这个refresh用到了模板模式,因为obtailFreshFactory是需要子类自己去实现,但是整个刷新启动过程是固定的
IoC容器启动(主要阶段):
- 1.前期准备工作(记录IoC容器启动时间,校验必要的属性值...)
- 2.创建一个BeanFactory,注册一些内建的Bean对象或者Bean依赖和内建的非Bean的依赖
- 3.对BeanFactory进行扩展,通过BeanFactoryPostProcessors 进行操作
- 4.对Bean的进行扩展,通过BeanPostProcessors 进行操作
- 5.做一些国际化的资源设置
- 6.完成一个IoC容器启动事件的广播
IoC容器的停止(主要阶段):
- 1.销毁容器里面的所有Bean对象
- 2.销毁BeanFactory
refreshBeanFactory 那个锁在最新版的又去掉了 ,这又是为什么呢 refreshBeanFactory()全局好像只有AbstractApplicationContext#refreshBeanFactory 有用到 实际上,增加同步大多数场景也是多余的,毕竟容器创建到启动通常是主线程完成。
如果容器正在使用中,此时调用refresh会影响当前的容器的行为嘛?或者换句话说,类似zk的这种能够实现bean属性实时更新是不是也是触发的refresh方法
这个需要区分情况,如果使用了 Spring Cloud @RefreshScope 的话,Bootstrap 应用上下文会关闭并且重建。其他情况的话,通常不会触发 refresh 方法
面试题精选
什么是 Spring IoC 容器?
DI依赖注入和依赖查找都是IOC容器的实现;
DI容器、DI实现或者一个依赖反转或者依赖注入的方式,都是对的。 Spring框架是实现的依赖反转(IoC)原则。IoC又称为依赖关系注入(DI),还有依赖查找,这个过程会伴随状态的一个依赖,这里主要指通过构造函数参数,工厂参数方法,set的方式注入一些其他的对象,来完成依赖注入。容器会把这些依赖注入信息,放到它创建的bea里面来。
- spring框架式IOC的一个实现;
- DI是它的实现原则;
- 另外一个实现就是关于依赖查找的实现;
BeanFactory 与 FactoryBean?
- BeanFactory是IoC底层容器
- FactoryBean是创建Bean的一种方式,帮助实现复杂的初始化逻辑
Spring Bean有两种实现,普通Bean,和工厂Bean(FactoryBean) 实现工厂Bean的方法就是pojo继承FactoryBean,并实现他的方法,当容器通过getBean()获取bean时,返回的是实现的getObject()方法所返回的对象
SpringIoC容器启动时做了哪些准备? IoC配置元信息读取和解析(XML和bean)、IoC容器生命周期、Spring事件发布、 国际化等