我经过实现一个简易的 Spring IoC 容器,算是入门了 Spring 框架。本文是对实现过程的一个总结提炼,须要配合源码阅读,源码地址。程序员
结合本文和源码,你应该能够学到:Spring 的原理和 Spring Boot 的原理。github
Spring 框架是 Java 开发的,Java 是面向对象的语言,因此 Spring 框架自己有大量的抽象、继承、多态。对于初学者来讲,光是理清他们的逻辑就很麻烦,我摒弃了那些包装,只实现了最本质的功能。代码不是很严谨,但只为了理解 Spring 思想却够了。spring
下面正文开始。框架
在没有 Spring 框架的远古时代,咱们业务逻辑长这样:ide
public class PetStoreService { AccountDao accountDao = new AccountDao(); } public class AccountDao { } PetStoreService petStoreService = new PetStoreService();
处处都是 new 关键字,须要开发人员显式的使用 new 关键字来建立业务类对象(实例)。这样有不少弊端,如,建立的对象太多,耦合性太强,等等。wordpress
有个叫 Rod Johnson 老哥对此很不爽,就开发了一个叫 Spring 的框架,就是为了干掉 new 关键字(哈哈,我杜撰的,只是为了说明 Spring 的做用)。函数
有了 Spring 框架,由框架来新建对象,管理对象,并处理对象之间的依赖,咱们程序员不再用 new 业务类对象了。咱们来看看 Spring 框架是如何实现的吧。spring-boot
注:如下 Spring 框架简写为 Spring工具
本节源码对应:v0
首先,Spring 须要实例化类,将其转换为对象。在 Spring 中,咱们管(业务)类叫 Bean,因此实例化类也能够称为实例化 Bean。
早期 Spring 须要借助 xml 配置文件来实现实例化 Bean,能够分为三步(配合源码 v1 阅读):
关于类加载和反射,前者能够看看《深刻理解 Java 虚拟机》第 7 章,后者能够看看《廖雪峰 Java 教程》反射 部分。本文只学习 Spring,这两个知识点不作深刻讨论。
名词解释:
本节源码对应:v1
实现实例化 Bean 后,此时成员变量(引用)还为 null:
此时须要经过一种方式实现,让引用指向实例,咱们管这一步叫填充属性。
当一个 Bean 的成员变量类型是另外一个 Bean 时,咱们能够说一个 Bean 依赖于另外一个 Bean。因此填充属性,也能够称为依赖注入(Dependency Injection,简称 DI)。
抛开 Spring 不谈,在正常状况下,咱们有两种方式实现依赖注入,一、使用 Setter() 方法,二、使用构造方法。使用 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 阅读):
<property>
,表明对应 Setter() 方法。<property>
,存放到 BeanDefinition 的 propertyNames 中。基于构造函数实现依赖注入的方式跟 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 配置文件。好比:
<bean>
<property>
@Component 用于生成 BeanDefinition,原理(配合源码 v4 阅读):
@Autowired 用于依赖注入,原理(配合源码 v4 阅读):
至此,咱们仍是在须要经过配置文件来实现组件扫描。有没有彻底不使用配置文件的方式?有!
咱们可使用 @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 查阅。
名词解释:
本节源码对应:v4
说到了 @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 包的组件注入容器?
<context:component-scan base-package="引入 jar 包的 base-package" /> ...
嗯,本节没有源码
以上实现了一个简易的 Spring IoC 容器,顺便说了一下 Spring Boot 原理。Spring 还有不少重要功能,如:管理 Bean 生命周期、AOP 的实现,等等。后续有机会再作一次分享。
来个注解小结:
有的童鞋可能还会有这样的疑问:
jdk jar 包、工具 jar 包的类是否须要注入容器?
全文完。
本文由博客一文多发平台 OpenWrite 发布!