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. PowerBI开发 第十五篇:Power BI的行级安全

    Power BI支持行级安全(Row-Level Security,RLS)的权限控制,用于限制用户对Dashboard.报表和DataSet的访问.用户浏览的报表是相同的,但是看到的数据却是不同的. ...

  2. JVM三部曲之运行时数据区 (第一部)

    在接下来的几天想总结下,JVM相关的一些内容,比如下面的这三个内容算是比较核心知识点了 1.运行时数据区域: 在运行时数据区里存储类Class文件元数据(方法区),对象和数组(堆),方法参数局部变量( ...

  3. plsql + instantclient 连接oracle ( 超简单)

    1.instantclient 下载并解压 instantclient 下载地址 https://www.oracle.com/technetwork/cn/database/features/ins ...

  4. Python安装cx_Oracle与操作数据测试小结

    这里简单总结一下Python操作Oracle数据库这方面的相关知识.只是简单的整理一下之前的实验和笔记.这里的测试服务器为CentOS Linux release 7.5. 个人实验.测试.采集数据的 ...

  5. CSP-S模拟57

    这次的T1是来送温暖的. T2T3挺神的. T1. 不会 T2. 容斥,挺神的 T3. 考场上被卡常卡掉10分让我很难受.....(虽然说$O(n)$过$1e8$本来就不太行) 考场上:疯狂化简式子, ...

  6. Hbase与Oracle的比较

    http://blog.csdn.net/lucky_greenegg/article/details/47070565 转自:http://www.cnblogs.com/chay1227/arch ...

  7. 『题解』洛谷P5015 标题统计

    题意描述 给你一个字符串,求所有字符的总数. 字符只包含数字,大小写字母. 分析 字符串的长度还是\(\le5\)的. 直接枚举就可以了. AC代码: NOIP官方标准程序是这样的 #include ...

  8. 如何使用24行JavaScript代码实现Redux

    作者:Yazeed Bzadough 译者:小维FE 原文:freecodecamp 为了保证文章的可读性,本文采用意译而非直译. 90%的规约,10%的库. Redux是迄今为止创建的最重要的Jav ...

  9. 使用vue-cli搭建项目开发环境

    一.前言 本篇文章主要是使用vue-cli搭建一个简单的vue项目,这个项目在其他文章中作为代码演示的环境会一直使用. 注意:默认大家的电脑已经安装nodejs,所以这里不总结nodejs的安装. 二 ...

  10. Angular前端优化思路

    简单总结接下我这边angular前端优化步骤都是满满的干货,各位客官有好的改进欢迎留言~ 1. 动静分离 项目里面前端比较占用带宽的一般都是加载静态资源,请求后台接口一般占用带宽都是1kb左右,但是在 ...