SpringIOC的理解及源码解读(一)

前言

本人在稀土掘金上的文章,搬到CSDN,非抄袭。

以下文字均为手敲,引用字段我会进行说明,拒绝抄袭、复制从我做起。

在Spring学习过程中,IOC是重中之重,而其中又以Bean的加载为最,故学好Bean的加载十分重要。其重要性主要体现在以下三点:
(1)有助于程序员理解策略模式、设计原则、算法应用;
(2)能更深层次的理解Spring家族成员的“家规”;
(3)为后续的SpringBoot、SpringCloud等框架的学习开辟道路;

前人对Bean的加载已经有所总结,其中蚂蚁P8大佬爱撒谎的男孩写了spring的Bean加载过程 - 云+社区 - 腾讯云 (tencent.com),石玉军在微信公众号Java学习录中发表了SpringIOC源码解析(上) (qq.com)SpringIOC源码解析(下) (qq.com),Java技术驿站发布了[死磕 Spring] — IOC 之深入理解 Spring IoC - Java 技术驿站 (cmsblogs.com)。这些文章内容详细, 能从refresh()出发鞭辟入里的解答Bean的加载全流程,在一定程度上推动了知识的传递。但这些文章仍不可避免的有言语啰嗦、部分问题表达不清、源码解释不够深入的问题。

故本文针对网络上关于Bean的加载资料繁多杂乱、表达不清、不够深入的特点,整合了基于AplicationContext的Bean加载流程,重新梳理了Java8下的Bean加载源码体系。本文是第一次在掘金上写文章,以期能助力知识的传递及掘金论坛的发展。
注:写的菜别骂我,请帮助我成为大佬,之后我再改。

作者介绍
姓名:比卷帘门还能卷
职业:学生(23年应届)
爱好:摸鱼、装大佬讨论技术
座右铭:保持真实,保持清醒

正文

1. IOC是什么?

大佬:IOC都不知道你说精通Spring?IOC就是创建一个IOC容器,由这个容器统一管理对象的生命周期和对象之间的关系。也可以叫控制反转或者依赖注入(DI)。
:???啥玩意?
大佬:滚

于是经过我自己的琢磨发现,IOC就是饿了么(免费版)嘛~~
别急,看我举例:
今天午饭我想吃土豆炖龙肉,我要去选土豆,选鲜嫩小龙,自己做。
耗时3天。


,我默认是吃土的,今天自己做一个土豆龙肉,后来发现冰箱里还有点辣椒肉,又炒了个辣椒回锅肉。这是我今天的操作。

我类:

package Test;

//我
public class me {
    //我的午饭
    private myLunch mylunch;
    //默认我的午饭(构造方法注入)
    me(){
        mylunch=new myLunch("土","土");
        mylunch.cook();
    }
    //指定午饭款式(Setter方法注入)
    public void setMylunch(myLunch mylunch) {
        this.mylunch = mylunch;
    }
    public static void main(String[] args) {
        //冰箱里的剩菜(提前有的模板)
        myLunch fryMeetWithPepper=new myLunch("青椒","回锅肉");
        //我先创建一个我自己,默认吃土
        me i=new me();
        //自己做个土豆龙肉
        myLunch potatostewedDragon=new myLunch("土豆","龙肉");
        potatostewedDragon.cook();
        //昨晚剩下的青椒艹回锅肉
        i.setMylunch(fryMeetWithPepper);
        fryMeetWithPepper.cook();
    }
}

午饭类:

package Test;
//午饭类
public class myLunch {
    //食材1
    private String material1;
    //食材2
    private String material2;
    //默认的午饭
    myLunch(String material1,String material2){
        this.material1=material1;
        this.material2=material2;
        System.out.println("食材:"+material1+"和"+material2);
    }
    //放入食材1
    public void setMaterial1(String material1) {
        this.material1 = material1;
    }
    //放入食材2
    public void setMaterial2(String material2) {
        this.material2 = material2;
    }
    //自己做
    public void cook() {
        System.out.println("我给自己做了午饭:"+this.material1+"炒"+this.material2);
    }
}

结果:

image.png


做完了吃饱了,干净又卫生,只是本卷王要累死了。不知道各位读者老爷有没有发现什么?没关系,我下载了一个饿了么(免费版),各位老爷再来看看。


<dependencies>       
    <dependency>           
        <groupId>org.springframework</groupId>            
        <artifactId>spring-context</artifactId>
        <version>5.0.0.RELEASE</version>   
    </dependency>
</dependencies>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="mylunch" class="Test.myLunch">
        <!--不用你构造方法了,我知道你爱吃土,给你放锅里了-->
        <!--默认食材1-->
        <constructor-arg index="0" value=""/>
        <!--默认食材2-->
        <constructor-arg index="1" value=""/>
    </bean>
</beans>
package Test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class me2 {
    public static void main(String[] args) {
        //尊敬的少爷,您的饿了么(免费版)下载好了
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:application-ioc.xml");
        myLunch mylunch=context.getBean(myLunch.class);
        //什么也不选,下单只能吃土
        mylunch.cook();
        //来个龙肉,再来个土豆,再下单
        mylunch.setMaterial1("龙肉");
        mylunch.setMaterial2("土豆");
        mylunch.cook();
        //来个海参,再来个鲍鱼,再下单
        mylunch.setMaterial1("海参");
        mylunch.setMaterial2("鲍鱼");
        mylunch.cook();
        //来个千年人参,再来个万年灵芝,再下单
        mylunch.setMaterial1("千年人参");
        mylunch.setMaterial2("万年灵芝");
        mylunch.cook();
    }
}

结果:
image.png

通过自己做饭和使用饿了么对比可以看出,自己做饭需要买菜、洗菜、做菜、刷碗…(需要负责午餐对象创建、赋值、销毁等,耦合性较高),饿了么只需要下载APP(容器)之后选择你想要的菜就可以了,这个菜(对象)会由饿了么分配给你。

为什么叫控制反转?
原来你自己做菜,现在由饿了么做菜,菜的控制权由你转移到了饿了么(IOC容器)。也就是对象不再由开发者管理,变成由虚拟容器来管理。这个控制发生了反转,即由人转移到了程序。
为什么叫依赖注入?
我依赖菜,有才可以吃午饭,然而我需要的午饭是由饿了么(IOC容器)注入到我这个类里面的。所以也叫依赖注入,注入方式为反射,反射允许程序在运行时动态的生成对象、执行对象的方法、改变的对象的属性。

2.IOC如何加载Bean?

饿了么里面为什么有这么多做好的菜?为了搞清楚这一点我们有必要从刚才的APP说起。

//尊敬的少爷,您的饿了么(免费版)下载好了
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:application-ioc.xml");
myLunch mylunch=context.getBean(myLunch.class);

进入classPathxmlApplcationContext中我们会发现:

image.png
其中只有一个成员变量就是Resource类型的数组??也许你会有疑问这个Resource是干什么的?(虽然我们这个例子没有用到,但是我还是建议你看一下)
我们检索configResource会发现,他在下面的代码里面十分关键。

image.png
这里的代码是另一个构造方法,与我们的代码不同的是,这里传入的是一个数组,也就是多个字符串,而我们的例子只传了一个字符串,这里还传入了Class对象。在此构造方法中先执行了super,通过追踪可以发现,这个方法一直到

image.png
这里则是对AbstractApplicationContext进行赋初始值。先不继续深入,回头看。经过连续两个判空之后,创建了一个Resource数组configResource并通过Class参数和Path数组参数创建CassPathResource对象给数组赋值。后执行refresh,refresh十分重要在此先不说。
CassPathResource对象是什么?通过追溯可以找到

image.png
至此,我们引出一个重要的接口,Resource。
[死磕 Spring] — IOC 之 Spring 统一资源加载策略 - Java 技术驿站 (cmsblogs.com)

在学 Java SE 的时候我们学习了一个标准类 java.net.URL,该类在 Java SE 中的定位为统一资源定位器(Uniform Resource Locator),但是我们知道它的实现基本只限于网络形式发布的资源的查找和定位。然而,实际上资源的定义比较广泛,除了网络形式的资源,还有以二进制形式存在的、以文件形式存在的、以字节流形式存在的等等。而且它可以存在于任何场所,比如网络、文件系统、应用程序中。所以 java.net.URL 的局限性迫使 Spring 必须实现自己的资源加载策略,该资源加载策略需要满足如下要求:

  1. 职能划分清楚。资源的定义和资源的加载应该要有一个清晰的界限;
  2. 统一的抽象。统一的资源定义和资源加载策略。资源加载后要返回统一的抽象给客户端,客户端要对资源进行怎样的处理,应该由抽象资源接口来界定。

这位大佬是这样形容的Resource,Spring 将资源的定义和资源的加载区分开了,Resource 定义了统一的资源,那资源的加载则由 ResourceLoader 来统一定义。
OK ,那么Resource做了什么呢?看看它提供的几大方法。

image.png
这些方法大部分由抽象实现类AbstractResource进行了实现,所以需要重写的话,重写AbstractResource类就好。
告诉我你们看到了什么?接口,里面有方法的实现了,看到了吗?自从Java8开始,接口里面的方法就可以实现了,目的是为了解决一些接口中不常用的方法导致每个实现类都必须要去实现这类方法,但是也不可避免的带来了冲突问题,感兴趣的同学可以自行搜索。(题外话)
image.png
观察AbstracResource的子类

image.png
不难发现这么常用的五类资源,其中classPathResource正是我们开头所看到的

image.png为什么默认是classPathResource?原因在下图。

image.png
所以真相大白了,ClassPathXmlApplicationContext是要搭配classPathResource使用的。
那么研究一下ClassPathXmlApplicationContext,这各类一直追到最底层会发现其实源自于一个叫ResourceLoader的类。

image.png
classpath:前缀正是在这里设置的规则。其中getResource是根据所提供资源的路径 location 返回 Resource 实例。而getClassLoader则是直接返回返回 ClassLoader 实例。实现类为

image.png
说明了三种情况:
“/”-classPathContextResource
“classpath”-classPathResource
“URL”-调用 getResourceByPath(),主要为FileSystemResourceLoader使用。

image.png
FileSystemResourceLoader为FileSystemXmlApplicationContext提供服务。
而FileSystemXmlApplicationContext是ClassPathXmlApplicationContext兄弟。

image.png
其中ResourcePatternResolver 是 ResourceLoader 的扩展,它支持根据指定的资源路径匹配模式每次返回多个 Resource 实例。
到这里终于说完了说完了Spring的类加载策略,接下来可以说说类的加载方式了。
回到刚刚的构造方法

image.png

image.png
值得一提的是这个configLocations中只有一个值,“classpath:application-ioc.xml”。
第一行super在上面已经讲过了,主要任务是执行父类AbstractApplicationContext的构造方法。
第二行setConfigLocations可以详细说说,传入[“classpath:application-ioc.xml”]。

image.png
可以看到先对locations进行了判空,当然不为空了,接下来,通过resolvePath方法将值赋值给configLocations。接下来看看这个resolvePath是什么来路。

image.png
上图可以看出,先执行了getEnvironment()

image.png

image.png

image.png
接着看createEnvironment()方法,发现它返回了一个StandardEnvironment类,而这个类中的customizePropertySources方法就会往资源列表中添加Java进程中的变量和系统的环境变量。

image.png

返回resolvePath继续看resolveRequiredPlaceholders(path)方法。

image.png

image.png
由于strictHelper默认是空的,所以会执行createPlacholdHelper。

image.png
继续创建并返回一个propertyPlaceholderHelper,并传入下图参数作为构造参数。

image.png

image.png
可以看的出,正是在这里传入了占位符。
这个类中定义了三种括号。

image.png

image.png
在此方法内判断了括号是否符合规则,并返回对象。
回头看这个方法

image.png
将刚刚生成的占位符对象以及"classpath:application-ioc.xml"传入。

image.png
继续往下看

image.png
判空后执行parseStringValue,看来这里就是最终的处理逻辑了。

image.png

image.png
将递归结果(括号中间的内容)输入到resolvwPlaceholder。

image.png

image.png

image.png
这么大的方法!这么多细节要处理!他怎么敢递归的啊!真的膜拜大佬!!

image.png
不得不说,parseStringValue这个方法写的太秀了,看傻了。

image.png

image.png
回到这里,爷爷类AbstractRefreshableConfigApplicationContext的configLocation已经复制完毕,返回孙子类ClassPathXmlApplicationContext。

image.png

累了,改天好好写一下refresh方法,今天就到这里了。
本人研二学生一枚,理解有限,欢迎指正。谢谢大家。

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值