手寫簡易IOC容器

前言

本文是爲了學習Spring IOC容器的執行過程而寫,不能徹底表明Spring IOC容器,只是簡單實現了容器的依賴注入控制反轉功能,沒法用於生產,只能說對理解Spring容器可以起到必定的做用。java

開始

建立項目

建立Gradle項目,並修改build.gradlegit

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的方法。github

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容器自己了。bash

咱們建立ApplicationContext類,並實現BeanFactory接口。maven

public class ApplicationContext implements BeanFactory {
}
複製代碼

getBean方法

既然說是容器,那確定要有地方裝咱們的bean實例吧,使用兩個Map做爲容器。ide

/** * 按照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,咱們爲了簡便,直接在構造器中指定要掃描的包。gradle

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的過程基本按照如下流程來走ui

  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倉庫獲取👉Simple-IOC-Container

相關文章
相關標籤/搜索