java实现 ioc_简单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

* @return bean

*/

T getBean(Class tClass);

}

创建ApplicationContext上下文

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

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

public class ApplicationContext implements BeanFactory {

}

getBean方法

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

/**

* 按照beanName分组

*/

private final Map beanByNameMap = new ConcurrentHashMap<>(256);

/**

* 按照beanClass分组

*/

private final Map, Object> beanByClassMap = new ConcurrentHashMap<>(256);

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

@Override

public Object getBean(String name) {

return beanByNameMap.get(name);

}

@Override

public T getBean(Class tClass) {

return tClass.cast(beanByClassMap.get(tClass));

}

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

构造器

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

private final Set basePackages;

/**

* 默认构造器,默认扫描当前所在包

*/

public ApplicationContext() {

this(new HashSet<>(Collections.singletonList(ApplicationContext.class.getPackage().getName())));

}

/**

* 全参构造器

* @param basePackages 扫描的包名列表

*/

public ApplicationContext(Set basePackages) {

this.basePackages = basePackages;

}

refresh方法

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

096348397b1c440c2506ecb1311369f2.png

扫描指定的包下所有带@Bean注解(Spring中是@Component注解)的类。

List 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 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仓库获取

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值