简单IOC容器实现

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

前言

本文是为了学习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仓库获取

  • 发表于 2020-02-10 23:40
  • 阅读 ( 186 )
  • 分类:网络文章

条评论

请先 登录 后评论
不写代码的码农
小编

篇文章

作家榜 »

  1. 小编 文章
返回顶部
部分文章转自于网络,若有侵权请联系我们删除