爱游戏全站app官网入口-爱游戏官网

spring ioc 概述 -爱游戏全站app官网入口

2023-08-21,,

spring 概述

ioc(inversion of control) 控制反转,也叫 di(d_ependency injection_) 依赖注入。是一种设计思想。不过我并不同意所谓反转的说法,因为没有谁规定什么方式就是“标准”的,如果我把ioc作为“标准”,ioc就是“标准”自身,何来反转?不过我行文也是沿用官方的说法,使用ioc描述这个技术

ioc其实是一种组装的思想,最简单理解 ioc 的方法,就是我们的组装电脑

    主板,硬盘,显卡,cpu厂商们先定义好一个个插口。

    然后主板厂商就在他的主板上面预留位置。比如 a 插口是 留给硬盘的,b插口是留给 cpu的。注意,主板生产完成时,硬盘和cpu并没有在主板上,而是空的,不过主板已经写好好了给 cpu 和 硬盘 供电的 功能。

    我们的工作就是根据主板的插口来找件,装配,而不是先买cpu、内存、硬盘再去选主板,这就是ioc ,主板需要插什么接口的,我们就去获得相关的零件,这样整个电脑就可以工作。

首先,我们必须要知道,没有 spring ,我们照样可以做所谓的 ioc,就是 主物预留插口 -> 按插口找件,组装。其实这在我们生活中随处可见。

那么,我们为什么要使用 spring 呢,我们组装电脑,就要获得关于这个电脑的所有部件,比如主板、电源、内存等等,我们自己当然不可能凭空造出来,但是我们可以上商城购买,我们只需要关心接口对不对,而不用关心他是怎么生产的。那么,spring 的角色就是一个商城。

我们可以把 spring ioc 看成一个全产业链的商城,他会把产业链中的第一层原料开始,每一层都登记进入商城。在这个商城里面,我们可以买到任何层级的零件。之后,我们就可以自由的控制一切:如果我们是厂商,我们可以买到我们想要的零件。如果我们是消费者,想组装电脑,就可以买到主板,硬盘和cpu。如果我们有了机房,就可以直接买到若干个电脑。甚至,我们有一个国家,就可以在上面买到关于这个国家的一切。我们再不用关心东西是怎么来的,我们只管买即可。东西生产由其他人负责。而spring ioc做的就是整个产业链从头到尾的组装,上架工作。

spring ioc的好处也是众说纷纭,我也没有找到很有说服力的解释,姑且写一些在下面:

    轻松实现面向接口编程,中心管理,按需取用,各个环节完全解耦,比如,网站环境和测试环境差异巨大,也可以轻松切换。

    可以监听和控制所管理对象的生命周期,并且执行相关的操作

    因为可以控制对象的生命周期,所以可以轻松通过 aop 进行对象增强

    轻松整合整个 java 生态链,对于开发者来说,整合是轻松友好的。

spring ioc 用法解析

随着 springboot 的潮流, 我们坚持只使用注解配置 spring ioc

springioc使用例子

maven 配置依赖

1
2
3
4
5

org.springframework
spring-context
4.3.11.release

spring-context 会自动将 spring-core、spring-beans、spring-aop、spring-expression 这几个基础 jar 包带进来。

定义一个接口 (类比的话,就是主物上面的一个接口)

1
2
3
public interface messageservice {
string getmessage();
}

定义接口实现类(类比的话,就是零件自身),并且使用注解注册到 spring 商城

1
2
3
4
5
6
7
@component("messageservice")
public class messageserviceimpl implements messageservice { public string getmessage() {
return "hello world";
}
}

使用: (类比的话,就是从商城买东西) (这里没有使用依赖注入(自动装配),下面会介绍)

1
2
3
4
5
6
7
8
9
10
// main.java
@componentscan // 非常重要,会扫描包下所有注解
public class main {
public static void main(string[] args) {
applicationcontext context = new annotationconfigapplicationcontext(main.class);
messageservice messageservice = (messageservice) context.getbean("messageservice");
system.out.println(messageservice.getmessage());
// 输出 hello, world
}
}

springioc 登记方式

spring中管理的对象都叫 bean,就像商城里面的一种商品。spring 管理的对象默认是单例的,也就是一种商品只有一件,不过可以重复购买(注入)。下面,我们说一下如何上架这些商品。

普通类的对象登记

@component 通用的登记方式,以下的注解都包含这个注解的功能,并且可能有额外的含义

@service 通常用于服务层

@controller 通常用于控制层

@repository 通常用于数据库仓库层

后面加(“”) 定义 bean 的名字,也可以不定义自动由 spring 生成。

例子:

1
2
3
4
5
6
7
@component("messageservice")
public class messageserviceimpl implements messageservice { public string getmessage() {
return "hello world";
}
}

工厂创建的对象登记

我们有时候想通过一个工厂的方式,根据传入不同对象,生成不同的对象,并登记到 spring,我们要这么做

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@configuration
public class dataconfig{
@bean("asource") // 配置文件来源,通常用 properties 文件定义
public datasource source(){
datasource source = new redisproperties();
source.sethost("localhost");
source.setpassword("123");
return source;
}
@bean(name = "redistemplate")
 public redistemplate redistemplate(@qualifier("asource") datasource source) {
return super.stringredistemplate(source);
 }
}

我们现在就注册到了一个 redistemplate 的 bean,是通过我们的配置文件生产的。
其中有这些注解

@configuration 说明这是个配置类

@bean 说明这个函数是用来创建 bean 的

@qualifier 说明我们引入哪一个 bean 作为 传入参数。

我们可以写多组这样的函数,就会创造出不同的 bean

另外还有可能用到的是 @lazy ,可以让 bean 在用的时候再加载。

springioc 注入方式

当然,除了上面的 getbean 外,spring还给我们封装了许多方法方便我们买东西:
其中,@autowired 注解 最常用,意思是按类型装配,如果这个类型的零件只有一个,那么就默认选这一个。如果是有多个,那么还需要我们指定具体是哪一个。

setter

1
2
3
4
5
6
7
8
@component
public class customer {
private person person;
@autowired
public void setperson (person person) {
this.person=person;
}
}

filed

1
2
3
4
5
6
@component
public class customer {
@autowired
private person person;
private int type;
}

构造函数

1
2
3
4
5
6
7
8
@component
public class customer {
private person person;
@autowired // 甚至可以直接不写
public customer (person person) {
this.person=person;
}
}

如果一个类型对应多个 bean,使用 @qualifier 指定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@component
public class beanb1 implements beaninterface {
//
}
@component
public class beanb2 implements beaninterface {
//
}
@component
public class beana {
@autowired
@qualifier("beanb2")
private beaninterface dependency;
...
}

spring ioc 生命周期与扩展点

spring ioc生命周期:

    初始化 beanfactory

      创建 beanfactory

      读取 beandefinition

    通过beandefinition,初始化 bean

    供外部调用

    beanfactory销毁

我们可以在 spring ioc 的生命周期中置入各种我们自定义的逻辑。

    单独类实现接口,一般是应用于全局的

    bean 继承 aware 类,一般是用于该 bean 获取环境变量

    bean 实现 接口,一般是用于该 bean

    bean 上 注解,一般是用于该 bean

初始化 beanfactory 之后

实现 beanfactorypostprocessor 在beanfactory初始化之后(已经读取了配置但还没初始化bean),做一些操作,可以读取并修改beandefinition

1
2
3
4
5
6
7
@component
public class lifecyclecontrol implements beanfactorypostprocessor {
@override
public void postprocessbeanfactory(configurablelistablebeanfactory beanfactory) throws beansexception {
//.. dosomething
}
}

初始化 bean 时

顺序如下:

开始几步分别是:实例化、构造函数、设置属性函数,并且,在这里,其实spring早就已经把所有属性都注入好了,下面的过程都是 spring 预留给用户扩展的。

beannameaware抽象类 可以 获取到 beanname,其他几个 aware 类似

1
2
3
4
5
6
7
@component
public class testb extends beannameaware {
@override
public void setbeanname(string name) {
// 会告诉 testb 他的 beanname 是什么
}
}

beanpostprocessor接口 对每一个 bean 初始化前后进行置入,有多少个 bean 就会执行多少次

    postprocessbeforeinitialization(object bean, string beanname)

    postprocessafterinitialization(object bean, string beanname)

@postconstruct 注解(在bean上),用户自定义的该bean的初始化操作

initializingbean接口的afterpropertiesset()方法会被调用

init-method 用注解的话,这个一般不用了,一定要用的话,可以用 @bean(initmethod = “initmethodname”),在配置中心配置,不能在 bean 上配置。

spring ioc 构成

bean

bean 是 spring 里面最基本的单位,如果把 spring 管理的对象看成一群人,那么 bean 就是每一个人。
在用户看来,bean 就是一个实际的对象,比如

1
messageservice messageservice = context.getbean(messageservice.class);

在spring内部,bean的本质是一个 beanname -> beandefinition 的键值对 ,即用于搜索的名字,和他的实际定义内容。
比如:

1

就定义了一个 bean,name 是 messageservice ,beandefinition 的内容之一是 ma.luan.example.messageserviceimpl

applicationcontext:

spring ioc 的门面,供用户调用,起到统筹全局的作用。背后它从源(resource)读取配置 ,为每个 bean 生成配置对象(beandefinition) 到工厂(beanfactory)的注册中心(registry) ,控制工厂管理 bean (autowire),代理工厂的 getbean 操作。我们可以使用多种输入方式 context。

beanfactory

spring 的核心,他管理着一个注册中心 registry,并且负责管理 bean(加载类,实例化,注入属性等),并且提供 getbean 等操作

beanfactory: 可以获取一个 bean 的能力的接口,有getbean方法

listablebeanfactory,有一次获取多个 bean 能力的接口,有getbeans方法

hierarchicalbeanfactory,有继承能力的工厂接口,有一个 getparentbeanfactory 方法,可以获取父工厂,先不用管

autowirecapablebeanfactory 可以自动装配的工厂接口,继承它让我们的工厂可以对 bean 自动创建,属性进行自动插入的能力。autowirebean、createbean、initializebean、applybeanpostprocessorsbeforeinitialization 等等,是工厂最核心的能力

defaultlistablebeanfactory,继承了所有接口,是我们最常使用的标准工厂。他继承并实现了所有的能力

    getbean

    getbeans

    getparentbeanfactory

    autowirecapable

    config (修改设置)

applicationcontext虽然也继承了 beanfactory,但是实际上是复用了他的 getbean() 等接口,实际逻辑代码并没有什么继承关系

beanregistry

注册中心,维护着多个 map,用于登记 bean 的信息,包括 beandefinition 的 map,还有 存放单例的 singletonobjects 的 map 等等。

beandefinition

beandefinition存放我们在配置文件中对某个 bean 的所有注册信息,和存放该对象的实际单例对象。
比如,
我们在 xml 文件中配置

1

那么, beanregistry 中 会有
messageservice -> beandefinition
beandefinition里面有关于这个 bean 的所有配置

resourceloader

负责从多个配置源(resource)读取配置文件,源包括 xml、注解等等,xml有可能来自本地或者网络。

definitionreader

从 resourceloader 加载到配置资源后,把配置转成(read) definition 的形式

启动过程分析

简单起见,我们可能还是要用回 xml 配置启动的方法(classpathxmlapplicationcontext)来分析 (真香),不过其实内部大同小异。

1
2
3
4
5
6
public static void main(string[] args) {
applicationcontext context = new classpathxmlapplicationcontext("classpath:application.xml");
messageservice messageservice = context.getbean(messageservice.class);
system.out.println(messageservice.getmessage());
// 输出 hello world
}

classpathxmlapplicationcontext 构造方法

1
2
3
4
5
6
7
8
9
public classpathxmlapplicationcontext(string[] configlocations, boolean refresh, applicationcontext parent)
throws beansexception { super(parent);
setconfiglocations(configlocations); // 保存xml路径
if (refresh) {
refresh(); // 第一次执行会到这里,初始化
}
}

核心启动器:refresh 方法

refresh方法是启动的核心方法,执行了启动的所有操作,后面还会提到余下的部分

1
2
3
4
5
6
7
8
9
10
11
12
public void refresh() throws beansexception, illegalstateexception {
synchronized (this.startupshutdownmonitor) {
// 准备工作,记录下容器的启动时间、标记“已启动”状态、处理配置文件中的占位符
preparerefresh(); // 创建工厂,读取 xml,把 beanname -> beandefinition 在 beanfactory 的 registry 里 注册
configurablelistablebeanfactory beanfactory = obtainfreshbeanfactory();  // .... }
}

创建工厂并在 context 中保存引用

obtainfreshbeanfactory 方法

这个方法干了下面几件事

    创建了 beanfactory (defaultlistablebeanfactory),并且把 beanfactory 赋值给 context 保存

    读取 context 的配置,设置该工厂 是否允许 bean 覆盖、是否允许循环引用 等

    context 读取加载 beandefinition,把 beandefinition 注册到 beanfactory 的 map 中

      配置 definitionreader,读取 resource

      从xml读取配置树,转换成 definition,触发监听事件

创建好之后的 beanfactory 的一部分的截图

创建工厂后的维护操作

主要是在 beanfactory 里面注册实现了各种接口的bean,factory会为每一个特殊的接口类型维护一个列表,以后到达特定的位置,就会遍历这个列表。
比如 实现了 beanpostprocessors 的接口的 bean 有 a,b,c,那么到时候初始化 bean 之前,就会遍历调用a,b,c的 postprocessbeforeinitialization方法,初始化 bean 之后,就会调用 a,b,c 的postprocessafterinitialization 方法,具体什么时候调用什么方法,请查看后面写的 spring bean 生命周期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@override
public void refresh() throws beansexception, illegalstateexception {
synchronized (this.startupshutdownmonitor) { preparerefresh(); configurablelistablebeanfactory beanfactory = obtainfreshbeanfactory(); // 主要是设置 beanfactory 的 classloader
preparebeanfactory(beanfactory); try {
// 注册实现了 beanfactory 的 bean
postprocessbeanfactory(beanfactory);
// 调用上面注册的 bean 的相关方法 (用于 beanfactory 读取 definition 之后,初始化之前)
invokebeanfactorypostprocessors(beanfactory);
// 这个比较会用到,检测 注册好的 bean 里面,实现了 beanpostprocessors 接口的 bean
// 等下初始化 bean 的前后,会调用这些所有 bean 的 相关方法
registerbeanpostprocessors(beanfactory);
// 国际化的,用不到
initmessagesource();
// 注册applicationevent接口的bean,初始化事件广播器
initapplicationeventmulticaster();
// 给子类的钩子,会再注册一些内置 bean
onrefresh();
// 注册实现了 applicationlistener 接口的 bean
registerlisteners(); // 初始化所有的 singleton beans (lazy-init 的除外):下面讲
finishbeanfactoryinitialization(beanfactory); // ...
}
//...
}

初始化 bean

spring 默认 初始化的 bean对象 都是单例的,采用的是单例注册表的方法。
我们重点关注单例如何实现,怎么解决循环引用

首先,初始化入口在 finishbeanfactoryinitialization(beanfactory),就是在 refresh() 方法的尾部。这个方法会进行马上初始化的 bean 进行马上初始化。

因为要兼容 延迟初始化(getbean时候加载) 和 马上初始化,所以最合适的方式就是把加载的逻辑写在 getbean 里边,需要马上加载的时候,提前调用 getbean 即可。

finishbeanfactoryinitialization

核心方法是 preinstantiatesinletons():
对符合条件的所有 beandefinition 里面 的 bean 执行了初始化操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public void preinstantiatesingletons() throws beansexception {
// this.beandefinitionnames 保存了所有的 beannames
list beannames = new arraylist(this.beandefinitionnames); // 触发所有的非懒加载的 singleton beans 的初始化操作
for (string beanname : beannames) { // 非抽象、非懒加载的 singletons。如果配置了 'abstract = true',那是不需要初始化的
if (!bd.isabstract() && bd.issingleton() && !bd.islazyinit()) { if (isfactorybean(beanname)) {
// factorybean 的话,在 beanname 前面加上 ‘&’ 符号。再调用 getbean
final factorybean factory = (factorybean) getbean(factory_bean_prefix beanname);
}
else {
// 对于普通的 bean,只要调用 getbean(beanname) 这个方法就可以进行初始化了
getbean(beanname);
}
}
} }

getbean

我去掉了部分无关紧要的代码,如果有兴趣可以去看原文件 abstractbeanfactory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@override
public object getbean(string name) throws beansexception {
return dogetbean(name, null, null, false);
} @suppresswarnings("unchecked")
protected t dogetbean(
final string name, final class requiredtype, final object[] args, boolean typecheckonly)
throws beansexception {
// 获取 beanname
final string beanname = transformedbeanname(name); // 这个是返回值
object bean; // 检查单例是否已经初始化,单例全部注册在 regisrty 的 singletonobjects,多例不会在这里注册
object sharedinstance = getsingleton(beanname);
// 如果注册表中没有这个单例,会返回 null,后面还会讲到这个方法 // 开始创建 bean
if (sharedinstance != null) {
bean = getobjectforbeaninstance(sharedinstance, name, beanname, null);
// 默认非 beanfactor 时,等同于 bean = sharedinstance
} else {
// 如果上面为 null,则说明单例未初始化,或者是多例 final rootbeandefinition mbd = getmergedlocalbeandefinition(beanname);
// 这里,实际执行了:this.beandefinitionmap.get(beanname)
// 就是把 beandefinition 拿出来了 // 这里有一百多行代码,除了插入各种钩子和特殊情况,其实我们只执行了一行代码
bean = createbean(beanname, mbd, args)

return (t) bean;
}

所以,getbean 就是 配套钩子 执行 createbean 方法
创建好的对象会放在 单例注册表 singletonobjects 中,下次再取的时候就从表里取,而不重新创建,从而实现单例模式。

createbean方法

如果单例还未创建,会在此创建 bean
createbean 方法 主要做了些前置的工作,包括给 aop 预留的拦截器 (aop时,返回 proxy 对象而不是真正的对象)
然后委托给 docreatebean 方法,主要做了下面这些事:

    实例化对象

    装配属性

    执行 initializingbean 接口,beanpostprocessor 接口钩子, init 方法钩子 (生命周期钩子)

我们主要关心一下怎么解决循环引用的问题:

1
2
3
4
5
6
7
8
9








这样就构成了一个循环依赖,而我们默认是单例的,那如何一次性创建三个对象呢?
首先,在实例化对象的时候,先在 singletonfactories 注册一个工厂

1
2
3
4
5
6
7
addsingletonfactory(beanname, new objectfactory

circlea 属性注入时,到了 circleb ,会 getbean(circleb) ,然后又会 getbean(circlec)
getbean(circlec) 时,又看到了a ,会调用回 getbean(circlea),在getbean的时候,会调用getsingleton,他会先从工厂取,这个时候a已经在工厂列表了,然后用getobject,就可以拿到a的引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
protected object getsingleton(string beanname, boolean allowearlyreference) {
object singletonobject = this.singletonobjects.get(beanname);
// getbean 默认走这里,从单例注册表拿已经创建好的单例,但是现在a还没创建好
if (singletonobject == null && issingletoncurrentlyincreation(beanname)) {
// 单例注册表里面没有,对象正在初始化,符合循环引用的条件
synchronized (this.singletonobjects) {
singletonobject = this.earlysingletonobjects.get(beanname);
if (singletonobject == null && allowearlyreference) {
objectfactory singletonfactory = this.singletonfactories.get(beanname);
if (singletonfactory != null) {
singletonobject = singletonfactory.getobject();
// getobject,就是把a的引用拉过来了,a其实还没建好,不过他的引用已经有了
this.earlysingletonobjects.put(beanname, singletonobject);
this.singletonfactories.remove(beanname);
}
}
}
}
// 返回a的引用给c
return (singletonobject != null_object ? singletonobject : null);
}

这样就可以解决循环引用的问题

拾遗

spring中的监听器用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 自定义事件
public class myevent extends applicationevent {
public myevent(object source) {
super(source);
}
}
// 自定义 bean 实现 applicationcontextaware 接口
@component
public class hellobean implements applicationcontextaware {
private applicationcontext applicationcontext;
private string name;
public void setapplicationcontext(applicationcontext context) {
this.applicationcontext = context;
}
// 当调用 setname 时, 触发事件
public void setname(string name) {
this.name = name;
applicationcontext.publishevent(new myevent(this)); // 这行代码执行完会立即被监听到
}
public string getname() {
return name;
}
}
// 自定义监听器, 监听上面的事件
@component
public class myapplicationlistener implements applicationlistener {
@override
public void onapplicationevent(applicationevent event) {
if (event instanceof myevent) {
system.out.println(((hellobean)event.getsource()).getname());
}
}
}

spring ioc中的主要设计模式

    单例模式

    观察者模式 (listener)

参考资料

spring ioc 容器源码分析
tiny-spring: a tiny ioc container refer to spring.
spring4参考手册中文版
spring framework reference documentation
spring的扩展点
spring bean life cycle tutorial

spring ioc 概述的相关教程结束。

网站地图