作者:DeppWang、原文地址

我通过实现一个浅易的 Spring IoC 容器,算是入门了 Spring 框架。本文是对实现历程的一个总结提炼,需要配合源码阅读,源码地址。

连系本文和源码,你应该可以学到:Spring 的原理和 Spring Boot 的原理。

Spring 框架是 Java 开发的,Java 是面向工具的语言,以是 Spring 框架自己有大量的抽象、继续、多态。对于初学者来说,光是理清他们的逻辑就很贫苦,我摒弃了那些包装,只实现了最本质的功效。代码不是很严谨,但只为了明白 Spring 头脑却够了。

下面正文最先。

零、前言

在没有 Spring 框架的远古时代,我们营业逻辑长这样:

public class PetStoreService {
    AccountDao accountDao = new AccountDao();
}

public class AccountDao {
}

PetStoreService petStoreService = new PetStoreService();

四处都是 new 关键字,需要开发人员显式的使用 new 关键字来建立营业类工具(实例)。这样有许多坏处,如,建立的工具太多,耦合性太强,等等。

有个叫 Rod Johnson 老哥对此很不爽,就开发了一个叫 Spring 的框架,就是为了干掉 new 关键字(哈哈,我杜撰的,只是为了说明 Spring 的作用)。

有了 Spring 框架,由框架来新建工具,治理工具,并处置工具之间的依赖,我们程序员再也不用 new 营业类工具了。我们来看看 Spring 框架是若何实现的吧。

注:以下 Spring 框架简写为 Spring

本节源码对应:v0

一、实现「实例化 Bean 」

首先,Spring 需要实例化类,将其转换为工具。在 Spring 中,我们管(营业)类叫 Bean,以是实例化类也可以称为实例化 Bean。

早期 Spring 需要借助 xml 设置文件来实现实例化 Bean,可以分为三步(配合源码 v1 阅读):

  1. 从 xml 设置文件获取 Bean 信息,如全限命名等,将其作为 BeanDefinition(Bean 界说类)的属性
  2. 使用一个 Map 存放所有 BeanDefinition,此时 Spring 本质上是一个 Map,存放 BeanDefinition
  3. 当获取 Bean 实例时,通过类加载器,凭据全限命名,获得其类工具,通过类工具行使反射建立 Bean 实例

关于类加载和反射,前者可以看看《深入明白 Java 虚拟机》第 7 章,后者可以看看《廖雪峰 Java 教程》反射 部门。本文只学习 Spring,这两个知识点不做深入讨论。

名词解释:

  • 全限命名:指编译后的 class 文件在 jar 包中的路径

本节源码对应:v1

二、实现「填充属性(依赖注入)」

实现实例化 Bean 后,此时成员变量(引用)还为 null:

此时需要通过一种方式实现,让引用指向实例,我们管这一步叫填充属性。

当一个 Bean 的成员变量类型是另一个 Bean 时,我们可以说一个 Bean 依赖于另一个 Bean。以是填充属性,也可以称为依赖注入(Dependency Injection,简称 DI)。

抛开 Spring 不谈,在正常情况下,我们有两种方式实现依赖注入,1、使用 Setter() 方式,2、使用组织方式。使用 Setter() 方式如下:

public class PetStoreService {
    private AccountDao accountDao;
    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }
}

public class AccountDao {
}

PetStoreService petStore = new PetStoreService();
petStore.setAccountDao(new AccountDao()); // 将依赖 new AccountDao() 注入 petStore

实在早期 Spring 也是通过这两种方式来实现依赖注入的。下面是 Spring 通过 xml 文件 + Setter() 来实现依赖注入的步骤(配合源码 v2 阅读):

  1. 给 PetStoreService 添加 Setter() 方式,并稍微修改一下 xml 设置文件,添加 <property>,代表对应 Setter() 方式。
  2. 从 xml 设置文件获取 Bean 的属性 <property>,存放到 BeanDefinition 的 propertyNames 中。
  3. 通过 propertyName 获取属性实例,行使反射,通过 Setter() 方式实现填充属性(依赖注入)

基于组织函数实现依赖注入的方式跟 Setter() 方式差不多,感兴趣可以 Google 搜索查看。

由于 Spring 实现了依赖注入,以是我们程序员没有了建立工具的控制权,以是也被称为控制反转(Inversion of Control,简称 IoC)。由于 Spring 使用 Map 治理 BeanDefinition,我们也可以将 Spring 称为 IoC 容器。

本节源码对应:v2

三、使用「单例模式、工厂方式模式」

前面两步实现了获取 Bean 实例时建立 Bean 实例,但 Bean 实例经常使用,不能每次都新建立。实在在 Spring 中,一个营业类只对应一个 Bean 实例,这需要使用单例模式。

单例模式:一个类有且只有一个实例

Spring 使用类工具建立 Bean 实例,是若何实现单例模式的?

Spring 实在使用一个 Map 存放所有 Bean 实例。建立时,先看 Map 中是否有 Bean 实例,没有就建立;获取时,直接从 Map 中获取。这种方式能保证一个类只有一个 Bean 实例。

private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(64);

早期 Spring 使用 Bean 的计谋是用到时再实例化所用 Bean,杰出代表是 XmlBeanFactory,后期为了实现更多的功效,新增了 ApplicationContext,两者都继续于 BeanFactory 接口。这使用了工厂方式模式。

工厂方式模式:界说一个用于建立工具的接口,让子类决议实例化哪一个类。Factory Method 使一个类的实例化延迟到其子类。

我们将 BeanIocContainer 修改为 BeanFactory 接口,只提供 getBean() 方式。建立(IoC)容器由其子类自己实现。

ApplicationContext 和 BeanFactory 的区别:ApplicationContext 初始化时就实例化所有 Bean,BeanFactory 用到时再实例化所用 Bean。

本节源码对应:v3

三、实现「注解」

前面使用 xml 设置文件的方式,实现了实例化 Bean 和依赖注入。这种方式对照贫苦,还容易失足。Spring 从 2.5ref 最先可使用注解替换 xml 设置文件。好比:

  1. 使用 @Component 注解取代 <bean>
  2. 使用 @Autowired 注解取代 <property>

@Component 用于天生 BeanDefinition,原理(配合源码 v4 阅读):

  • 凭据 component-scan 指定路径,找到路径下所有包罗 @Component 注解的 Class 文件,作为 BeanDefinition
  • 若何判断 Class 是否有 @Component:行使字节码手艺,获取 Class 文件中的元数据(注解等),判断元数据中是否有 @Componet

@Autowired 用于依赖注入,原理(配合源码 v4 阅读):

  • 通过反射,查看 Field 中是否有 @Autowired 类型的注解,有,则使用反射实现依赖注入

至此,我们照样在需要通过设置文件来实现组件扫描。有没有完全不使用设置文件的方式?有!

我们可以使用 @Configuration 替换设置文件,并使用 @ComponentScan 来替换设置文件的 <context:component-scan>

@Configuration // 将类标记为 @Configuration,代表这个类是相当于一个设置文件
@ComponentScan // ComponentScan 扫描 PetStoreConfig.class 所在路径及其所在路径所有子路径的文件
public class PetStoreConfig {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(PetStoreConfig.class);
        PetStoreService userService = context.getBean(PetStoreService.class);
        userService.getAccountDao();
    }
}

使用注解实在跟使用 xml 设置文件一样,目的是将设置类作为入口,实现扫描组件,将其加载进 IoC 容器中的功效。

AnnotationConfigApplicationContext 是专为针对设置类的启动类。实在现机制,可以 Google 查阅。

名词解释:

  • Component:组件
  • Autowired:自动装配

本节源码对应:v4

四、Spring Boot 原理

说到了 @Configuration 和 @ComponentScan,就不得不提 Spring Boot。由于 Spring Boot 就使用了 @Configuration 和 @ComponentScan,你可以点开 @SpringBootApplication 看到。

我们发现,Spring Boot 启动时,并没有使用 AnnotationConfigApplicationContext 来指定启动某某 Config 类。这是由于它使用了 @EnableAutoConfiguration 注解。

Spring Boot 行使了 @EnableAutoConfiguration 来自动加载标识为 @Configuration 的设置类到容器中。Spring Boot 还可以将需要自动加载的设置类放在 spring.factories 中,Spring Boot 将自动加载 spring.factories 中的设置类。spring.factories 需放置于META-INF 下。

如 Spring Boot 项目启动时,autocofigure 包中将自动加载到容器的(部门)设置类如下:

以上也是 Spring Boot 的原理。

在 Spring Boot 中,我们引入的 jar 包都有一个字段,starter,我们叫 starter 包。

标识为 starter(启动器)是由于引入这些包时,我们不用设置分外操作,它能被自动装配,starter 包一样平常都包罗自己的 spring.factories。如 spring-cloud-starter-eureka-server:

如 druid-spring-boot-starter:

有时候我们还需要自界说 starter 包,好比在 Spring Cloud 中,当某个应用要挪用另一个应用的代码时,要么挪用方使用 Feign(HTTP),要么将被挪用方自界说为 starter 包,让挪用方依赖引用,再 @Autowired 使用。此时需要在被挪用方设置设置类和 spring.factories:

@Configuration
@ComponentScan
public class ProviderAppConfiguration {
}

// spring.factories
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.amy.cloud.amycloudact.ProviderAppConfiguration

固然,你也可以把这两个文件放在挪用方(此时要指定扫描路径),但一样平常放在被挪用方。ps:若是你两个应用的 base-package 路径一样,那么可以不用这一步。

说了 Spring Boot,那么在 Spring MVC,若何将引入 jar 包的组件注入容器?

  • 跟扫描本项目包一样,在 xml ,增添引入 jar 包的扫描路径:
<context:component-scan base-package="引入 jar 包的 base-package" />
...

嗯,本节没有源码

五、结语

以上实现了一个浅易的 Spring IoC 容器,顺便说了一下 Spring Boot 原理。Spring 另有许多主要功效,如:治理 Bean 生命周期、AOP 的实现,等等。后续有机遇再做一次分享。

来个注解小结:

  • Spring 只实例化标识为 @Component 的组件(即营业类工具)
  • @Component 作为组件标识
  • @Autowired 用于判断是否需要依赖注入
  • @ComponentScan 指定组件扫描路径,不指定即为当前路径
  • @Configuration 代表设置类,作为入口
  • @EnableAutoConfiguration 实现加载设置类

有的童鞋可能还会有这样的疑问:

jdk jar 包、工具 jar 包的类是否需要注入容器?

  • 回覆是不需要,由于容器只治理营业类,注入容器的类都有 @Component 注解。

全文完。

本文由博客一文多发平台 OpenWrite 公布!

,

进入sunbet官网手机版登陆

欢迎进入sunbet官网手机版登陆!Sunbet 申博提供申博开户(sunbet开户)、SunbetAPP下载、Sunbet客户端下载、Sunbet代理合作等业务。

Allbet Gaming声明:该文看法仅代表作者自己,与Allbet Gaming无关。转载请注明:江西宜春教育网:造轮子:实现一个浅易的 Spring IoC 容器
发布评论

分享到:

齐齐哈尔招聘大全:中华电信与台科大互助生长5G AR/VR{应用技}术
2 条回复
  1. 伸博最新网址
    伸博最新网址
    (2020-04-22 01:51:04) 1#

    SunbetSunbet www.sumeruminecraft.com Sunbet是Sunbet的大型娱乐网站,Sunbet简单快捷,业内口碑极好,是你的最佳选择。sunbet,就是要您玩得开心,赢得更开心!这反转很溜

    1. 深度时空
      深度时空
      (2020-05-14 05:53:34)     

      www.66rfd.com(www.lphggs.com)是Sunbet 申博的官方网站。www.66rfd.com提供申博开户(sunbet开户)、SunbetAPP下载、Sunbet代理合作等业务。很喜欢这个站,啥都有

发表评论

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。