IoC

Ioc 是一款 spring ioc 核心功能简化实现版本,便于学习和理解原理。

创作目的

使用 spring 很长时间,对于 spring 使用非常频繁,实际上对于源码一直没有静下心来学习过。

但是 spring 源码存在一个问题,那就是过于抽象,导致学习起来成本上升。

所以本项目由渐入深,只实现 spring 的核心功能,便于自己和他人学习 spring 的核心原理。

spring 的核心

Spring 的核心就是 spring-beans,后面的一切 spring-boot,spring-cloud 都是建立在这个地基之上。

当别人问你 spring 的时候,希望你可以谈谈自己对于 spring ioc 自己更深层的见解,而不是网上人云亦云的几句话。

什么是 IOC

控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。

其中最常见的方式叫做依赖注入(Dependency Injection,简称DI)。

通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体,将其所依赖的对象的引用传递给它。

也可以说,依赖被注入到对象中。

为什么需要 IOC

IoC 是解耦的一种方法。

我们知道Java 是一门面向对象的语言,在 Java 中 Everything is Object,我们的程序就是由若干对象组成的。

当我们的项目越来越大,合作的开发者越来越多的时候,我们的类就会越来越多,类与类之间的引用就会成指数级的增长。

这样的工程简直就是灾难,如果我们引入 Ioc 框架。

由框架来维护类的生命周期和类之间的引用。

我们的系统就会变成这样:

这个时候我们发现,我们类之间的关系都由 IoC 框架负责维护类,同时将类注入到需要的类中。

也就是类的使用者只负责使用,而不负责维护。

把专业的事情交给专业的框架来完成,大大的减少开发的复杂度。

快速开始

maven 引入

<dependency>
<groupId>com.github.houbb</groupId>
<artifactId>ioc</artifactId>
<version>0.1.11</version>
</dependency>

测试准备

全部测试代码,见 test 模块。

  • Apple.java
public class Apple {

    public void color() {
System.out.println("Apple color: red. ");
} }
  • apple.json

类似于 xml 的配置,我们暂时使用 json 进行配置验证。

[
{"name":"apple","className":"com.github.houbb.ioc.test.service.Apple"}
]

执行测试

  • 测试
BeanFactory beanFactory = new JsonApplicationContext("apple.json");
Apple apple = (Apple) beanFactory.getBean("apple");
apple.color();
  • 日志
Apple color: red.

spring 基本实现流程

说明

spring-beans 一切都是围绕 bean 展开的。

BeanFactory 负责对 bean 进行生命周期的相关管理,本节展示第一小节的简单实现流程。

spring 核心流程

Spring IoC 主要是以下几个步骤。

  1. 初始化 IoC 容器。

  2. 读取配置文件。

  3. 将配置文件转换为容器识别对的数据结构(这个数据结构在Spring中叫做 BeanDefinition)

  4. 利用数据结构依次实例化相应的对象

  5. 注入对象之间的依赖关系

BeanDefinition 的抽象

BeanDefinition 是 spring 对 java bean 属性的一个抽象,经过这一层抽象,配置文件可以是 xml/json/properties/yaml 等任意一种,甚至包括注解扫包。

为 spring 的拓展带来极大的灵活性。

本框架考虑到实现的简单性,初步只实现了 json 和基于注解扫包两种方式。

后期如果有时间可以考虑添加 xml 的实现,其实更多是 xml 的解析工作量,核心流程已经全部实现。

实现源码节选

BeanDefinition 相关

包含了对于 java bean 的基本信息抽象。

  • BeanDefinition.java

其默认实现为 DefaultBeanDefinition.java,就是对接口实现的最基本的 java POJO

参见 DefaultBeanDefinition

/**
* 对象定义属性
* @author binbin.hou
* @since 0.0.1
*/
public interface BeanDefinition { /**
* 名称
* @return 名称
* @since 0.0.1
*/
String getName(); /**
* 设置名称
* @param name 名称
* @since 0.0.1
*/
void setName(final String name); /**
* 类名称
* @return 类名称
*/
String getClassName(); /**
* 设置类名称
* @param className 类名称
* @since 0.0.1
*/
void setClassName(final String className); }

BeanFactory 核心管理相关

  • BeanFactory.java
/**
* bean 工厂接口
* @author binbin.hou
* @since 0.0.1
*/
public interface BeanFactory { /**
* 根据名称获取对应的实例信息
* @param beanName bean 名称
* @return 对象信息
* @since 0.0.1
*/
Object getBean(final String beanName); /**
* 获取指定类型的实现
* @param beanName 属性名称
* @param tClass 类型
* @param <T> 泛型
* @return 结果
* @since 0.0.1
*/
<T> T getBean(final String beanName, final Class<T> tClass); }
  • DefaultBeanFactory.java

为接口最基础的实现,源码如下:

/**
* bean 工厂接口
* @author binbin.hou
* @since 0.0.1
*/
public class DefaultBeanFactory implements BeanFactory { /**
* 对象信息 map
* @since 0.0.1
*/
private Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(); /**
* 对象 map
* @since 0.0.1
*/
private Map<String, Object> beanMap = new ConcurrentHashMap<>(); /**
* 注册对象定义信息
* @since 0.0.1
*/
protected void registerBeanDefinition(final String beanName, final BeanDefinition beanDefinition) {
// 这里可以添加监听器
this.beanDefinitionMap.put(beanName, beanDefinition);
} @Override
public Object getBean(String beanName) {
Object bean = beanMap.get(beanName);
if(ObjectUtil.isNotNull(bean)) {
// 这里直接返回的是单例,如果用户指定为多例,则每次都需要新建。
return bean;
} // 获取对应配置信息
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
if(ObjectUtil.isNull(beanDefinition)) {
throw new IocRuntimeException(beanName + " not exists in bean define.");
} // 直接根据
Object newBean = createBean(beanDefinition);
// 这里可以添加对应的监听器
beanMap.put(beanName, newBean);
return newBean;
} /**
* 根据对象定义信息创建对象
* @param beanDefinition 对象定义信息
* @return 创建的对象信息
* @since 0.0.1
*/
private Object createBean(final BeanDefinition beanDefinition) {
String className = beanDefinition.getClassName();
Class clazz = ClassUtils.getClass(className);
return ClassUtils.newInstance(clazz);
} @Override
@SuppressWarnings("unchecked")
public <T> T getBean(String beanName, Class<T> tClass) {
Object object = getBean(beanName);
return (T)object;
} }

其中 ClassUtils 是基于 class 的反射工具类,详情见 ClassUtils.java

JsonApplicationContext

基于 json 配置文件实现的基本实现,使用方式见开始种的例子代码。

  • JsonApplicationContext.java
/**
* JSON 应用上下文
* @author binbin.hou
* @since 0.0.1
*/
public class JsonApplicationContext extends DefaultBeanFactory { /**
* 文件名称
* @since 0.0.1
*/
private final String fileName; public JsonApplicationContext(String fileName) {
this.fileName = fileName; // 初始化配置
this.init();
} /**
* 初始化配置相关信息
*
* <pre>
* new TypeReference<List<BeanDefinition>>(){}
* </pre>
*
* 读取文件:https://blog.csdn.net/feeltouch/article/details/83796764
* @since 0.0.1
*/
private void init() {
InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName);
final String jsonConfig = FileUtil.getFileContent(is);
List<DefaultBeanDefinition> beanDefinitions = JsonBs.deserializeArray(jsonConfig, DefaultBeanDefinition.class);
if(CollectionUtil.isNotEmpty(beanDefinitions)) {
for (BeanDefinition beanDefinition : beanDefinitions) {
super.registerBeanDefinition(beanDefinition.getName(), beanDefinition);
}
}
} }

小结

至此,一个最基本的 spring ioc 就基本实现了。

如果你想继续学习,可以分别参考以下代码分支。

分支说明

v0.0.1-BeanFactory 基本实现

v0.0.2-ListBeanFactory 基本实现

v0.0.3-单例和延迟加载

v0.0.4-初始化和销毁方法

v0.0.5-RespCode 添加和代码优化

v0.0.6-构造器和 factoryMethod 新建对象

v0.0.7-property 属性设置

v0.0.8-Aware 监听器及 PostProcessor

v0.0.9-Parent 属性继承

v0.1.0-循环依赖检测

v0.1.1-@Configuration-java 代码配置

v0.1.2-@Bean-java 对象定义

v0.1.3-@Lazy-@Scope-java 对象属性配置

v0.1.4-@Import 配置导入

v0.1.5-@Bean 参数构造以及 @Description

v0.1.6-@Autowired 自动装配注解支持

v0.1.7-@Primary 指定优先级注解

v0.1.8-@Conditional 条件注解支持

v0.1.9-Environment 和 @Profile 实现

v0.1.10-Property 配置文件相关和 @Value/@PropertyResource 实现

v0.1.11-@ComponentScan 文件包扫描支持

拓展阅读

Java IOC-00-ioc 是什么

从零开始手写 spring ioc 框架,深入学习 spring 源码的更多相关文章

  1. 从零开始手写 dubbo rpc 框架

    rpc rpc 是基于 netty 实现的 java rpc 框架,类似于 dubbo. 主要用于个人学习,由渐入深,理解 rpc 的底层实现原理. 前言 工作至今,接触 rpc 框架已经有很长时间. ...

  2. java 从零开始手写 RPC (04) -序列化

    序列化 java 从零开始手写 RPC (01) 基于 socket 实现 java 从零开始手写 RPC (02)-netty4 实现客户端和服务端 java 从零开始手写 RPC (03) 如何实 ...

  3. java 从零开始手写 RPC (05) reflect 反射实现通用调用之服务端

    通用调用 java 从零开始手写 RPC (01) 基于 socket 实现 java 从零开始手写 RPC (02)-netty4 实现客户端和服务端 java 从零开始手写 RPC (03) 如何 ...

  4. 剖析手写Vue,你也可以手写一个MVVM框架

    剖析手写Vue,你也可以手写一个MVVM框架# 邮箱:563995050@qq.com github: https://github.com/xiaoqiuxiong 作者:肖秋雄(eddy) 温馨提 ...

  5. java 从零开始手写 RPC (03) 如何实现客户端调用服务端?

    说明 java 从零开始手写 RPC (01) 基于 socket 实现 java 从零开始手写 RPC (02)-netty4 实现客户端和服务端 写完了客户端和服务端,那么如何实现客户端和服务端的 ...

  6. java 从零开始手写 RPC (07)-timeout 超时处理

    <过时不候> 最漫长的莫过于等待 我们不可能永远等一个人 就像请求 永远等待响应 超时处理 java 从零开始手写 RPC (01) 基于 socket 实现 java 从零开始手写 RP ...

  7. 手写开源ORM框架介绍

    手写开源ORM框架介绍 简介 前段时间利用空闲时间,参照mybatis的基本思路手写了一个ORM框架.一直没有时间去补充相应的文档,现在正好抽时间去整理下.通过思路历程和代码注释,一方面重温下知识,另 ...

  8. 闭关修炼180天--手写持久层框架(mybatis简易版)

    闭关修炼180天--手写持久层框架(mybatis简易版) 抛砖引玉 首先先看一段传统的JDBC编码的代码实现: //传统的JDBC实现 public static void main(String[ ...

  9. 手牵手,从零学习Vue源码 系列二(变化侦测篇)

    系列文章: 手牵手,从零学习Vue源码 系列一(前言-目录篇) 手牵手,从零学习Vue源码 系列二(变化侦测篇) 陆续更新中... 预计八月中旬更新完毕. 1 概述 Vue最大的特点之一就是数据驱动视 ...

随机推荐

  1. Java基础(二十三)集合(6)Map集合

    Map接口作为Java集合框架中的第二类接口,其子接口为SortedMap接口,SortedMap接口的子接口为NavigableMap接口. 实现了Map接口具体类有:HashMap(子类Linke ...

  2. Java多线程编程(六)单例模式与多线程

    在使用多线程技术的单例模式时会出现一些意想不到的情况,这样的代码如果在生产环境中出现异常,有可能造成灾难性的后果. 一.立即加载/“饿汉模式” 立即加载就是使用类的时候已经将对象创建完毕,常见的实现方 ...

  3. fenby C语言 P18

    #include <stdio.h> int main(){ int i; for(i=1;i<=15;i++) { if((i%2==1)) { printf("%d\n ...

  4. SpringBoot 2.0 开发案例之百倍级减肥瘦身之旅

    前言 为了存我的小黄图,最近在做一款图床服务,集成了各种第三方云存储服务,目前正在内部测试阶段.项目是以Jar的形式运行在腾讯云上,不要问我为什么使用腾讯云了,因为阿里云老用户和狗不得入内. 问题凸显 ...

  5. 第八篇 Flask中的蓝图

    随着业务代码的增加,将所有代码都放在单个程序文件中,是非常不合适的.这不仅会让代码阅读变得困难,而且会给后期维护带来麻烦.如下示例:我们在一个文件中写入多个路由,这会使代码维护变得困难. 如图所示,如 ...

  6. 2018-7-30 python基本数据类型

    python基本数据类型 int      整数 str      字符串. 一般不存放大量的数据 bool   布尔值. 用来判断. True, False list      列表.用来存放大量数 ...

  7. Windows 10 + kali Linux 双系统安装教程(详细版)

    准备工具如下: kali Linux 镜像 准备一4G以上的U盘 制作U盘启动盘工具- Win32DiskImager 添加引导工具-EasyBCD 留出一个空的盘,哪个盘的空间比较大可以压缩出大概2 ...

  8. web.xml 配置文件 超详细说明!!!

    一.web.xml是什么? 首先 web.xml 是java web 项目的一个重要的配置文件,但是web.xml文件并不是Java web工程必须的. web.xml文件是用来配置:欢迎页.serv ...

  9. Java连接MySQL简单步骤及常见问题解析

    这篇文章跟读者们介绍Java语言如何连接到MySQL数据库相关操作,及FAQ(Frequently Asked Questions)介绍. 选择工具和准备数据集 IDE:Eclipse 数据库:使用的 ...

  10. 基于UGUI的框架

    这个框架简单易懂,上手就可以直接拿来用,主要是单例管理类,界面和界面之间的互相交流通过单例去实现,个人感觉不是很好,但是我特别喜欢他的管理层级非常分明. 之后会发一个广播机制,结合上这套UI框架,但是 ...