前言

本文是为了学习Spring IOC容器的执行过程而写,不能完全代表Spring IOC容器,只是简单实现了容器的依赖注入控制反转功能,无法用于生产,只能说对理解Spring容器能够起到一定的作用。

开始

创建项目

创建Gradle项目,并修改build.gradle

plugins {
id 'java'
id "io.franzbecker.gradle-lombok" version "3.1.0"
} group 'io.github.gcdd1993'
version '1.0-SNAPSHOT' sourceCompatibility = 1.8 repositories {
mavenCentral()
} dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'
}

创建BeanFactory

BeanFactory是IOC中用于存放bean实例以及获取bean的核心接口,它的核心方法是getBean以及getBean的重载方法,这里简单实现两个getBean的方法。

package io.github.gcdd1993.ioc.bean;

/**
* bean factory interface
*
* @author gaochen
* @date 2019/6/2
*/
public interface BeanFactory { /**
* 通过bean名称获取bean
*
* @param name bean名称
* @return bean
*/
Object getBean(String name); /**
* 通过bean类型获取bean
*
* @param tClass bean类型
* @param <T> 泛型T
* @return bean
*/
<T> T getBean(Class<T> tClass); }

创建ApplicationContext上下文

ApplicationContext,即我们常说的应用上下文,实际就是Spring容器本身了。

我们创建ApplicationContext类,并实现BeanFactory接口。

public class ApplicationContext implements BeanFactory {
}

getBean方法

既然说是容器,那肯定要有地方装我们的bean实例吧,使用两个Map作为容器。

/**
* 按照beanName分组
*/
private final Map<String, Object> beanByNameMap = new ConcurrentHashMap<>(256); /**
* 按照beanClass分组
*/
private final Map<Class<?>, Object> beanByClassMap = new ConcurrentHashMap<>(256);

然后,我们可以先完成我们的getBean方法。

@Override
public Object getBean(String name) {
return beanByNameMap.get(name);
} @Override
public <T> T getBean(Class<T> tClass) {
return tClass.cast(beanByClassMap.get(tClass));
}

直接从Map中获取bean实例,是不是很简单?当然了,在真实的Spring容器中,是不会这么简单啦,不过我们这次是要化繁为简,理解IOC容器。

构造器

Spring提供了@ComponentScan来扫描包下的Component,我们为了简便,直接在构造器中指定要扫描的包。

private final Set<String> basePackages;
/**
* 默认构造器,默认扫描当前所在包
*/
public ApplicationContext() {
this(new HashSet<>(Collections.singletonList(ApplicationContext.class.getPackage().getName())));
} /**
* 全参构造器
* @param basePackages 扫描的包名列表
*/
public ApplicationContext(Set<String> basePackages) {
this.basePackages = basePackages;
}

refresh方法

refresh的过程基本按照以下流程来走

  1. 扫描指定的包下所有带@Bean注解(Spring中是@Component注解)的类。
List<Class> beanClasses = PackageScanner.findClassesWithAnnotation(packageName, Bean.class);
System.out.println("scan classes with Bean annotation : " + beanClasses.toString()); for (Class beanClass : beanClasses) {
try {
createBean(beanClass);
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException | InstantiationException e) {
e.printStackTrace();
}
}
  1. 遍历类,获取类的构造器以及所有字段。
Constructor constructor = beanClass.getDeclaredConstructor();
Object object = constructor.newInstance();
Field[] fields = beanClass.getDeclaredFields();
  1. 判断字段是依赖注入的还是普通字段。

  2. 如果是普通字段,通过字段类型初始化该字段,并尝试从@Value注解获取值塞给字段。

Value value = field.getAnnotation(Value.class);
if (value != null) {
// 注入
field.setAccessible(true);
// 需要做一些类型转换,从String转为对应的类型
field.set(object, value.value());
}
  1. 如果是依赖注入的字段,尝试从beanByClassMap中获取对应的实例,如果没有,就先要去实例化该字段对应的类型。
Autowired autowired = field.getAnnotation(Autowired.class);
if (autowired != null) {
// 依赖注入
String name = autowired.name();
// 按照名称注入
Object diObj;
if (!name.isEmpty()) {
diObj = beanByNameMap.get(name) == null ?
createBean(name) :
beanByNameMap.get(name);
} else {
// 按照类型注入
Class<?> aClass = field.getType();
diObj = beanByClassMap.get(aClass) == null ?
createBean(aClass) :
beanByClassMap.get(aClass);
}
// 注入
field.setAccessible(true);
field.set(object, diObj);
}

测试我们的IOC容器

创建Address

@Data
@Bean
public class Address {
@Value("2222")
private String longitude; @Value("1111")
private String latitude;
}

创建Person并注入Address

@Data
@Bean
public class Person {
@Autowired
private Address address; @Value("gaochen")
private String name; @Value("27")
private String age;
}

创建测试类ApplicationContextTest

public class ApplicationContextTest {

    @Test
public void refresh() {
Set<String> basePackages = new HashSet<>(1);
basePackages.add("io.github.gcdd1993.ioc");
ApplicationContext ctx = new ApplicationContext(basePackages);
ctx.refresh(); Person person = ctx.getBean(Person.class);
System.out.println(person); Object person1 = ctx.getBean("Person");
System.out.println(person1);
}
}

控制台将会输出:

scan classes with Bean annotation : [class io.github.gcdd1993.ioc.util.Address, class io.github.gcdd1993.ioc.util.Person]
scan classes with Bean annotation : [class io.github.gcdd1993.ioc.util.Address, class io.github.gcdd1993.ioc.util.Person]
Person(address=Address(longitude=2222, latitude=1111), name=gaochen, age=27)
Person(address=Address(longitude=2222, latitude=1111), name=gaochen, age=27)

可以看到,我们成功将Address实例注入到了Person实例中,并且将它们存储在了我们自己的IOC容器中。其实,Spring容器的原理大致就是如此,只不过为了应对企业级开发,提供了很多便捷的功能,例如bean的作用域、bean的自定义方法等等。

获取源码

完整源码可以在我的github仓库获取

简单IOC容器实现的更多相关文章

  1. 【最简单IOC容器实现】实现一个最简单的IOC容器

    前面DebugLZQ的两篇博文: 浅谈IOC--说清楚IOC是什么 IoC Container Benchmark - Performance comparison 在浅谈IOC--说清楚IOC是什么 ...

  2. Spring源码分析 手写简单IOC容器

    Spring的两大特性就是IOC和AOP. IOC Container,控制反转容器,通过读取配置文件或注解,将对象封装成Bean存入IOC容器待用,程序需要时再从容器中取,实现控制权由程序员向程序的 ...

  3. 自己动手实现一个简单的 IOC容器

    控制反转,即Inversion of Control(IoC),是面向对象中的一种设计原则,可以用有效降低架构代码的耦合度,从对象调用者角度又叫做依赖注入,即Dependency Injection( ...

  4. Spring IoC容器的设计—3—次线

    这里涉及的是主要接口关系,而具体的IoC容器都是在这个接口体系下实现的,比如DefaultListableBeanFactory,这个基本IoC容器的实现就是实现了ConfigurableBeanFa ...

  5. Spring IoC容器的设计—2—主线

    第二条接口设计主线是,以ApplicationContext应用上下文接口为核心的接口设计,这里涉及的主要接口设计有,从BeanFactory到ListableBeanFactory,再到Applic ...

  6. Spring IoC容器的设计—1—主线

    IoC容器的接口设计图 下面对接口关系做一些简要的分析,可以依据以下内容来理解这张接口设计图. 从接口BeanFactory到HierarchicalBeanFactory,再到Configurabl ...

  7. 解读Spring Ioc容器设计图

    在Spring Ioc容器的设计中,有俩个主要的容器系列:一个是实现BeanFactory接口的简单容器系列,这系列容器只实现了容器最基本的功能:另外一个是ApplicationContext应用上下 ...

  8. Spring 源码剖析IOC容器(一)概览

    目录 一.容器概述 二.核心类源码解读 三.模拟容器获取Bean ======================= 一.容器概述 spring IOC控制反转,又称为DI依赖注入:大体是先初始化bean ...

  9. IoC容器的接口设计

    1.从接口BeanFactory---HierarchicalBeanFactory---ConfigurableBeanFactory,是一条主要的BeanFactory设计路径. 2.第二条接口设 ...

随机推荐

  1. linux--->配置lamp环境(centos7 最小版)

    这篇博客写的很全,按照顺序敲代码即可 参考:https://www.cnblogs.com/me80/p/7218883.html

  2. WEB打印控件LODOP的使用

    有人说她是Web打印控件,因为她能打印.在浏览器中以插件的形式出现,用简单一行语句就把整个网页打印出来: 有人说她是打印编程接口,因为她介于浏览器和打印设备之间,是个通道和桥梁,几乎能想到的打印控制事 ...

  3. Android教程2020 - RecyclerView获取滑动距离

    获取RecyclerView滑动的距离. Android教程2020 - 系列总览 本文链接 前面我们已经用RecyclerView显示一些数据. 本文演示如何获取RecyclerView的滑动距离. ...

  4. Unreal Engine 4 蓝图完全学习教程(五)—— 关于数组

    Ⅰ.数组的含义及使用 数组是能统一保存若干数值的特殊变量.数组可以指定编号.运用其中的值,因此能够有序地管理大量的数据. 首先试图将上次创建的msg变量修改成数组,在细节栏点击修改: 并选择“修改变量 ...

  5. 教你5分钟做个手机APP[视频]

    天天宅在家里,没什么事做,录个教学视频吧! 发到了视频网站上去根本没人看,伤心ing啊! 不知cnblogs上面是否让我发! 先上一张效果图看看哈: 如果播放不正常请点这里:https://www.b ...

  6. Codeforces_711_B

    http://codeforces.com/problemset/problem/711/B 比较简单,过程有点繁琐,先找一行包含那个0的行,得到和,以此填出0位置的值,然后判断这个矩阵是否符合条件. ...

  7. Codeforces_451_B

    http://codeforces.com/problemset/problem/451/B 取前后第一个不满足条件的位置,逆序,判断. #include<cstdio> #include ...

  8. 《剑指Offer》第二章(一)题3-8

    为春招实习做准备,记录一下<剑指Offer>里面的面试题 第二章 面试题3:数组之中的重复数字. 这个题吧,虽然不难,但是不知道为什么就是看了很久,可能很久没有做算法题了.最后面一句话说的 ...

  9. Ceph 13.2.8 三节点部署

    bs-k8s-ceph eth1 mon osd mgr deploy 2c2g sdb sdc sdd 各20G bs-hk-hk01 eth1 mon osd mgr 2c2g sdb sdc s ...

  10. Linux 工作机初始化操作

    # apt 依赖包安装 sudo apt install build-essential zlib1g-dev libncurses5-dev libgdbm-dev libnss3-dev libs ...