简单IOC容器实现
前言
本文是为了学习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的过程基本按照以下流程来走
- 扫描指定的包下所有带
@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();
}
}
- 遍历类,获取类的构造器以及所有字段。
Constructor constructor = beanClass.getDeclaredConstructor();
Object object = constructor.newInstance();
Field[] fields = beanClass.getDeclaredFields();
判断字段是依赖注入的还是普通字段。
如果是普通字段,通过字段类型初始化该字段,并尝试从
@Value
注解获取值塞给字段。
Value value = field.getAnnotation(Value.class);
if (value != null) {
// 注入
field.setAccessible(true);
// 需要做一些类型转换,从String转为对应的类型
field.set(object, value.value());
}
- 如果是依赖注入的字段,尝试从
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容器实现的更多相关文章
- 【最简单IOC容器实现】实现一个最简单的IOC容器
前面DebugLZQ的两篇博文: 浅谈IOC--说清楚IOC是什么 IoC Container Benchmark - Performance comparison 在浅谈IOC--说清楚IOC是什么 ...
- Spring源码分析 手写简单IOC容器
Spring的两大特性就是IOC和AOP. IOC Container,控制反转容器,通过读取配置文件或注解,将对象封装成Bean存入IOC容器待用,程序需要时再从容器中取,实现控制权由程序员向程序的 ...
- 自己动手实现一个简单的 IOC容器
控制反转,即Inversion of Control(IoC),是面向对象中的一种设计原则,可以用有效降低架构代码的耦合度,从对象调用者角度又叫做依赖注入,即Dependency Injection( ...
- Spring IoC容器的设计—3—次线
这里涉及的是主要接口关系,而具体的IoC容器都是在这个接口体系下实现的,比如DefaultListableBeanFactory,这个基本IoC容器的实现就是实现了ConfigurableBeanFa ...
- Spring IoC容器的设计—2—主线
第二条接口设计主线是,以ApplicationContext应用上下文接口为核心的接口设计,这里涉及的主要接口设计有,从BeanFactory到ListableBeanFactory,再到Applic ...
- Spring IoC容器的设计—1—主线
IoC容器的接口设计图 下面对接口关系做一些简要的分析,可以依据以下内容来理解这张接口设计图. 从接口BeanFactory到HierarchicalBeanFactory,再到Configurabl ...
- 解读Spring Ioc容器设计图
在Spring Ioc容器的设计中,有俩个主要的容器系列:一个是实现BeanFactory接口的简单容器系列,这系列容器只实现了容器最基本的功能:另外一个是ApplicationContext应用上下 ...
- Spring 源码剖析IOC容器(一)概览
目录 一.容器概述 二.核心类源码解读 三.模拟容器获取Bean ======================= 一.容器概述 spring IOC控制反转,又称为DI依赖注入:大体是先初始化bean ...
- IoC容器的接口设计
1.从接口BeanFactory---HierarchicalBeanFactory---ConfigurableBeanFactory,是一条主要的BeanFactory设计路径. 2.第二条接口设 ...
随机推荐
- Spring Boot定义系统启动任务的两种方式
Spring Boot定义系统启动任务的两种方式 概述 如果涉及到系统任务,例如在项目启动阶段要做一些数据初始化操作,这些操作有一个共同的特点,只在项目启动时进行,以后都不再执行,这里,容易想到web ...
- vue-cli3中引入图片的几种方式和注意事项
如果你是在数据中引入图片,他是从项目中引入的应该按第一种方式引入 如果不是在数据中引入图片,按第二种方式引入
- 解决AS加载gradle时出现的Could not find com.android.tools.build:gradle:3.5.0.的错误
时间:2019/12/7 最近在做安卓大作业时总是遇到从GitHub上下载下来的demo不能在本地Android studio中运行的问题,感觉真的被安卓中的各种版本给恶心到了,下面记录其中比较典型的 ...
- HTML5的web 存储localStorage、sessionStorage
说明 随着互联网的快速发展,基于网页的应用越来越普遍,同时也变的越来越复杂,为了满足各种各样的需求,会经常性在本地存储大量的数据,HTML5规范提出了相关解决方案.我们可以使用localStorage ...
- 阿里云Redis性能测试结果(1个集合存300万数据,查询能几秒返回结果)
现状: 1.买了一台主从的阿里云Redis,内存就1GB. 2.查询了阿里云的帮助,没有找到性能相关的说明, 有的也是4GB版本的并发性能 3.提工单问客服 一个集合里有300万数据,单次查询性能大概 ...
- 函数调用方式--__thiscall调用方式和__cdecl,__stdcall有什么区别
函数调用方式--__thiscall调用方式和__cdecl,__stdcall有什么区别 首先,__thiscall是关于类的一种调用方式,它与其他调用方式的最大区别是: __thiscall ...
- Windows下查看dll被哪个进程调用
卸载程序,结果没卸载干净---程序的安装目录中还剩下一个dll文件.想删,结果系统提示说dll文件被某个进程占用了,不让删. 先前碰到这种做法,我都是直接使用unlocker先unlock一下,然后删 ...
- ArrayList 并发操作 ConcurrentModificationException 异常
1.故障现象 ArrayList在迭代的时候如果同时对其进行修改就会抛出java.util.ConcurrentModificationException异常 2.故障代码 public class ...
- ARTS Week 3
Nov 11,2019 ~ Nov 17,2019 Algorithm 本周来介绍快速求一个数字n次方的余数. 理论基础 我们先定义运算$ x \bmod p = r \(与\) x \equiv r ...
- JumpServer部署与管理
一.JumpServer 堡垒机概述 JumpServer由Python/Django进行开发.使用GNU GPL v2.0开源协议.也是全球首款完全开源的堡垒机.同时配备了业界领先的Web Term ...