手撸一个Spring IOC容器——渐进式实现

前言

本文原载于我的博客,地址:https://blog.guoziyang.top/archives/53/

最近阅读了Spring Framework中的IOC容器部分的实现,手痒,决定自己实现一个比较简单的版本。

和我一起手写一个,面试的时候丝毫不虚!

具体代码可以查看我的Github的仓库:https://github.com/CN-GuoZiyang/My-Spring-IOC

目前实现的功能有:

  • xml配置文件读取
  • 属性注入
  • 引用依赖注入
  • 递归引用注入
  • singleton与prototype模式注入
  • 注解配置
  • 基于该容器的SpringMVC的实现(下一篇)

待实现:

  • AOP实现
  • 循环依赖

基于xml配置文件的注入

该部分对应的提交在:

https://github.com/CN-GuoZiyang/My-Spring-IOC/tree/82967670e52fe66ad55a6b2a539dbb4d48b46805

最终效果

主要过程按自顶向下的方式实现,最终实现的是将以下的配置文件读取后,在容器中注入Bean:

<?xml version="1.0" encoding="UTF-8"?>
<beans>

    <bean id="helloWorldService" class="top.guoziyang.main.service.HelloWorldServiceImpl" scope="prototype">
        <property name="text" value="Hello World"></property>
    </bean>

    <bean id="wrapService" class="top.guoziyang.main.service.WrapService">
        <property name="helloWorldService" ref="helloWorldService"></property>
    </bean>

</beans>

该配置文件仿照Spring的配置文件格式,注入以下的两个Bean:

package top.guoziyang.main.service;

public class HelloWorldServiceImpl implements HelloWorldService {
    private String text;
    @Override
    public void saySomething() {
        System.out.println(text);
    }
}
package top.guoziyang.main.service;

public class WrapService {
    private HelloWorldService helloWorldService;
    public void say() {
        helloWorldService.saySomething();
    }
}

ApplicationContext的实现

ApplicationContext,即应用程序上下文,是Spring框架中最为核心的类,也是Spring的入口类。该接口继承自BeanFactory接口,实现了BeanFactory(实例工厂)的所有功能,还支持资源访问(如URL和文件)、事务传播等功能。但是我们还是只实现其核心的功能。

我们首先定义ApplicationContext接口:

package top.guoziyang.springframework.context;

/**
 * 应用程序上下文接口
 *
 * @author ziyang
 */
public interface ApplicationContext {
    Object getBean(Class clazz) throws Exception;
    Object getBean(String beanName) throws Exception;
}

这个接口只定义了两个方法,分别通过类对象和实例的名称从容器中获取对象。

我们接着仿照Spring,编写一个抽象类AbstractApplicationContext,来实现ApplicationContext接口,书写一些通用的方法。注意,在Spring中,ApplicationContext实现BeanFactory的方式,是在ApplicationContext对象的内部,保存了一个BeanFactory对象的实例,实质上类似一种代理模式:

package top.guoziyang.springframework.context;

import top.guoziyang.springframework.factory.BeanFactory;

public abstract class AbstractApplicationContext implements ApplicationContext {
  
    BeanFactory beanFactory;
  
    @Override
    public Object getBean(Class clazz) throws Exception {
        return beanFactory.getBean(clazz);
    }
    @Override
    public Object getBean(String beanName) throws Exception {
        return beanFactory.getBean(beanName);
    }
}

那么现在,从ApplicationContext中取出对象的方法都实现完了,那么ApplicationContext的具体实现类的工作,就是用某种方式读取配置,然后把对象信息存入到BeanFactory中,等待用户来取。

那么在我们查看ApplicationContext的具体实现类之前,我们先来看看BeanFactory,这个实例工厂。

从AbstractApplicationContext中,我们可以知道,这个接口,有getBean这两种方法,除此以外,我还定义了一个方法:void registerBeanDefinition(String name, BeanDefinition beanDefinition) throws Exception;,表示像工厂中注册Bean的定义,至于BeanDefinition的实现,后面再说。

BeanFactory的实现

BeanFactory,毫无疑问就是一个工厂,而且ApplicationContext就是从它这儿拿Bean的。根据名字来拿Bean,显而易见是一个类似Map的结构,这里我们采用ConcurrentHashMap来存储这个结构。那么这样,两个getBean的实现也就很显然了,仿照Spring的结构,我们还是先创建一个抽象类来实现BeanFactory接口:

package top.guoziyang.springframework.factory;

import top.guoziyang.springframework.entity.BeanDefinition;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public abstract class AbstractBeanFactory implements BeanFactory {

    ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();

    @Override
    public Object getBean(String name) throws Exception {
        BeanDefinition beanDefinition = beanDefinitionMap.get(name);
        if(beanDefinition == null) return null;
        if(!beanDefinition.isSingleton() || beanDefinition.getBean() == null) {
            return doCreateBean(beanDefinition);
        } else {
            return doCreateBean(beanDefinition);
        }
    }

    @Override
    public Object getBean(Class clazz) throws Exception {
        BeanDefinition beanDefinition = null;
        for(Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
            Class tmpClass = entry.getValue().getBeanClass();
            if(tmpClass == clazz || clazz.isAssignableFrom(tmpClass)) {
                beanDefinition = entry.getValue();
            }
        }
        if(beanDefinition == null) {
            return null;
        }
        if(!beanDefinition.isSingleton() || beanDefinition.getBean() == null) {
            return doCreateBean(beanDefinition);
        } else {
            return beanDefinition.getBean();
        }
    }

    @Override
    public void registerBeanDefinition(String name, BeanDefinition beanDefinition) {
        beanDefinitionMap.put(name, beanDefinition);
    }

    /**
     * 创建Bean实例
     * @param beanDefinition Bean定义对象
     * @return Bean实例对象
     * @throws Exception 可能出现的异常
     */
    abstract Object doCreateBean(BeanDefinition beanDefinition) throws Exception;

    public void populateBeans() throws Exception {
        for(Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
            doCreateBean(entry.getValue());
        }
    }
}

这里,我们留了一个doCreateBean方法作为抽象方法,表示真正创建Bean实例对象的操作,留给具体的实现类来实现。

我们要实现的BeanFactory,是一个可以自动注入属性的BeanFactory,可以创建完成实例对象后,注入其中的属性,如果属性是一个对象引用,那么就去创建那个被引用的实例对象,并递归地完成属性注入。在Spring中,这个实现类叫做AutowiredCapableBeanFactory。于是,我们的AutowiredCapableBeanFactory的实现是这样的:

package top.guoziyang.springframework.factory;

import top.guoziyang.springframework.entity.BeanDefinition;
import top.guoziyang.springframework.entity.BeanReference;
import top.guoziyang.springframework.entity.PropertyValue;
import java.lang.reflect.Field;

public class AutowiredCapableBeanFactory extends AbstractBeanFactory {

    @Override
    Object doCreateBean(BeanDefinition beanDefinition) throws Exception {
        if(beanDefinition.isSingleton() && beanDefinition.getBean() != null) {
            return beanDefinition.getBean();
        }
        Object bean = beanDefinition.getBeanClass().newInstance();
        if(beanDefinition.isSingleton()) {
            beanDefinition.setBean(bean);
        }
        applyPropertyValues(bean, beanDefinition);
        return bean;
    }

    /**
     * 为新创建了bean注入属性
     * @param bean 待注入属性的bean
     * @param beanDefinition bean的定义
     * @throws Exception 反射异常
     */
    void applyPropertyValues(Object bean, BeanDefinition beanDefinition) throws Exception {
        for(PropertyValue propertyValue : beanDefinition.getPropertyValues().getPropertyValues()) {
            Field field = bean.getClass().getDeclaredField(propertyValue.getName());
            Object value = propertyValue.getValue();
            if(value instanceof BeanReference) {
                BeanReference beanReference = (BeanReference) propertyValue.getValue();
                BeanDefinition refDefinition = beanDefinitionMap.get(beanReference.getName());
                if(refDefinition.getBean() == null) {
                    value = doCreateBean(refDefinition);
                }
            }
            field.setAccessible(true);
            field.set(bean, value);
        }
    }
}

这里主要还是使用了反射来创建对象实例,原理比较简单,就不过多说明。

那么说了这么多,BeanDefinition到底是什么呢,又从哪里来呢?

BeanDefinition的定义如下:

public class BeanDefinition {

    private Object bean;	// 实例化后的对象
    private Class beanClass;
    private String beanClassName;
    private Boolean singleton;	// 是否是单例模式
    private PropertyValues propertyValues;	// Bean的属性

}

PropertyValues实际上是一个List,表示一组属性的定义,内部存储的对象是PropertyValue对象,表示一个属性定义和其对应的注入属性:

public class PropertyValue {

    private final String name;
    private final Object value;
 
}

注意这里的value,如果是引用其他对象的话,value就是一个BeanReference实例,表示对一个对象的引用,而不是立即初始化,因为BeanDefinition是在读取配置文件时就被创建的,这时还没有任何Bean被初始化,BeanReference仅仅是一个记录而已:

public class BeanReference {
    private String name;
    private Object bean;
}

BeanDefinitionReader的实现

回到正题,BeanDefinition从哪里来?目前是从文件中读取的,定义一个抽象的AbstractBeanDefinitionReader,如下:

/**
 * BeanDefinitionReader实现的抽象类
 *
 * @author ziyang
 */
public abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader {
  
    private Map<String, BeanDefinition> registry;

    private ResourceLoader resourceLoader;

    public AbstractBeanDefinitionReader(ResourceLoader resourceLoader) {
        this.registry = new HashMap<>();
        this.resourceLoader = resourceLoader;
    }

    public Map<String, BeanDefinition> getRegistry() {
        return registry;
    }

    public ResourceLoader getResourceLoader() {
        return resourceLoader;
    }
}

registry也是一个Map,用于暂存Bean的名称和BeanDefinition的映射。

最终,最后的具体实现类实现了对配置文件的读取,由于我们读取的是Xml配置文件,所以我们的实现类名叫XmlBeanDefinitionReader,使用Java内置的XML解析器,可以将其解析为Document,具体的解析过程较长,不贴代码了,文件参考这里

回到ApplicationContext

这就是完整的,一个Bean从配置文件到被实例化的过程。那么,第一节的ApplicationContext的具体实现类所要做的,就很简单了,只需要创建一个BeanDefinitionReader读取配置文件,并且将读取到的配置存到BeanFactory中,并且由BeanFactory创建对应的实例对象即可。由于我们是读取xml文件,那么这个ApplicationContext的实现类,就叫ClassPathXmlApplicationContext,具体的逻辑在obtainBeanFactory()方法中:

private AbstractBeanFactory obtainBeanFactory() throws Exception {
    XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(new ResourceLoader());
    beanDefinitionReader.loadBeanDefinitions(location);
    AbstractBeanFactory beanFactory = new AutowiredCapableBeanFactory();
    for (Map.Entry<String, BeanDefinition> beanDefinitionEntry : beanDefinitionReader.getRegistry().entrySet()) {
        beanFactory.registerBeanDefinition(beanDefinitionEntry.getKey(), beanDefinitionEntry.getValue());
    }
    return beanFactory;
}

看看效果!

让我们书写一些测试代码,看看效果:

public class Main {

    public static void main(String[] args) throws Exception {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
        WrapService wrapService = (WrapService) applicationContext.getBean("wrapService");
        wrapService.say();
        HelloWorldService helloWorldService = (HelloWorldService) applicationContext.getBean("helloWorldService");
        HelloWorldService helloWorldService2 = (HelloWorldService) applicationContext.getBean("helloWorldService");
        System.out.println("prototype验证:" + (helloWorldService == helloWorldService2));
        WrapService wrapService2 = (WrapService) applicationContext.getBean("wrapService");
        System.out.println("singleton验证:" + (wrapService == wrapService2));
    }

}

运行结果如下:

Hello World
prototype验证:false
singleton验证:true

这里验证了一下prototype和singleton,这里首先获取了两次HelloWorldService的实例,由于这个Bean在配置文件中被标为prototype,所以两次获取到的都不是同一个对象,使用等号比较时得到了false。而后面获取的wrapService,和第一次获取的WrapService比较,由于是singleton的,所以使用等号比较时返回true。

基于注解的注入

该部分对应的提交在

https://github.com/CN-GuoZiyang/My-Spring-IOC/tree/8a3a9c640e532c5d4aa8d62f18b42fa336c94f2e

声明注解

首先我们需要自定义一些注解,仿照Spring,我们声明一下五个注解:Autowired、Component、Qualifier、Scope和Value,用过Spring的人应该都知道以下注解的作用。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Autowired{}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Component {
    String name() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Qualifier {
    String value();
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Scope {
    String value() default "singleton";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Value {
    public String value();
}

由于不是SpringBoot,我们仍然需要在配置文件中书写自动注入的扫描范围,配置文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans>
    <component-scan base-package="top.guoziyang.main"></component-scan>
</beans>

启动后,会自动扫描该包及其子包下所有使用注解标明的Bean,并注入容器。

扫描注解

由于配置文件发生了改变,自然我们需要改变xml文件的解析方式,在XmlBeanDefinitionReader的parseBeanDefinitions()方法中,一旦我们发现了component-scan标签,说明我们是使用注解来注入Bean的:

protected void parseBeanDefinitions(Element root) {
  ...
  for(int i = 0; i < nodeList.getLength(); i ++) {
      if(nodeList.item(i) instanceof Element) {
          Element ele = (Element)nodeList.item(i);
          if(ele.getTagName().equals("component-scan")) {
              basePackage = ele.getAttribute("base-package");
              break;
          }
      }
  }
  if(basePackage != null) {
      parseAnnotation(basePackage);
      return;
  }
  ...
}

我们增加了parseAnnotation方法,来对目标包进行注解扫描,实质上需要递归地扫描到该包下的所有类,并使用反射来查看该类是否使用了@Component注解,并获取相关的信息,如属性注入或者singleton或者prototype之类的信息。并将beanDefinition存入registry中:

    protected void processAnnotationBeanDefinition(Class<?> clazz) {
        if(clazz.isAnnotationPresent(Component.class)) {
            String name = clazz.getAnnotation(Component.class).name();
            if(name == null || name.length() == 0) {
                name = clazz.getName();
            }
            String className = clazz.getName();
            boolean singleton = true;
            if(clazz.isAnnotationPresent(Scope.class) && "prototype".equals(clazz.getAnnotation(Scope.class).value())) {
                singleton = false;
            }
            BeanDefinition beanDefinition = new BeanDefinition();
            processAnnotationProperty(clazz, beanDefinition);
            beanDefinition.setBeanClassName(className);
            beanDefinition.setSingleton(singleton);
            getRegistry().put(name, beanDefinition);
        }
    }

具体的实现可以看本文件

实际上,由于产生的结果一致(产生beanDefinition存入registry),可以仿照Spring的实现使用委托模式,这样耦合度就不会太高。但是由于使用注解同样还需要读取配置文件,较为繁琐,就没有解耦(实际上是我偷懒了)。

看看效果!

这时,我们就可以去测试一下。测试所用的两个类加上相应的注解即可:

@Component(name = "helloWorldService")
@Scope("prototype")
public class HelloWorldServiceImpl implements HelloWorldService {
    @Value("Hello, world")
    private String text;

    @Override
    public void saySomething() {
        System.out.println(text);
    }
}
@Component(name = "wrapService")
public class WrapService {
    @Autowired
    private HelloWorldService helloWorldService;

    public void say() {
        helloWorldService.saySomething();
    }
}

测试代码如下:

public class Main() {
  public static void annotationTest() throws Exception {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application-annotation.xml");
        WrapService wrapService = (WrapService) applicationContext.getBean("wrapService");
        wrapService.say();
        HelloWorldService helloWorldService = (HelloWorldService) applicationContext.getBean("helloWorldService");
        HelloWorldService helloWorldService2 = (HelloWorldService) applicationContext.getBean("helloWorldService");
        System.out.println("prototype验证:相等" + (helloWorldService == helloWorldService2));
        WrapService wrapService2 = (WrapService) applicationContext.getBean("wrapService");
        System.out.println("singleton验证:相等" + (wrapService == wrapService2));
    }
}

结果和第一次测试一致。

最后

到这里,自己手撸的Spring的控制反转容器的简单实现就完成了!还是挺有成就感的。使用体验和Spring基本没啥差别(误)。

下一篇文章,会基于已经实现的IOC容器,在其上层手撸一个SpringMVC的简单实现。

挺晚了,睡觉!

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 很高兴回答您的问题。首先,需要定义一个Bean类,然后使用反射机制创建Bean实例并将其存储在Map中。接着,需要解析Bean的依赖关系并进行注入。最后,需要实现Bean的生命周期管理,包括初始化和销毁。具体实现可以参考Spring源码。 ### 回答2: 实现一个简单的自定义Spring IOC容器需要以下步骤: 1. 定义一个bean类,该类包含需要注入的属性和对应的setter方法。 2. 创建一个容器类,用于管理bean的实例。在该容器类中,定义一个Map用来存储bean的名称和对应的实例。 3. 创建一个注入类,该类负责根据配置文件或注解来实例化和注入bean。可以使用反射机制来动态创建对象。 4. 编写一个配置类,该类用于读取配置文件或注解,获取需要创建的bean的信息。 5. 在容器类中,编写一个方法用于注册bean实例,将bean的名称和对应的实例放入Map中。 6. 在容器类中,编写一个方法用于从容器中获取bean的实例。 7. 将以上步骤整合到一个主程序中,测试容器类的功能。 以下是一个简单的实现示例: 1. 创建一个User类,包含name和age属性,并提供相应的setter方法。 ```java public class User { private String name; private int age; // Getter and Setter methods // ... } ``` 2. 创建一个容器类MyIOCContainer,使用Map来存储bean的名称和对应的实例。 ```java public class MyIOCContainer { private Map<String, Object> beans = new HashMap<>(); public void registerBean(String name, Object bean) { beans.put(name, bean); } public Object getBean(String name) { return beans.get(name); } } ``` 3. 创建一个注入类,用于实例化和注入bean。 ```java public class BeanInjector { public static void inject(Object bean) { // 通过反射机制实例化对象 // 通过反射机制注入属性 } } ``` 4. 创建一个配置类,使用注解来获取bean的信息。 ```java public class AppConfig { @Bean public User getUser() { User user = new User(); // 设置属性值 return user; } } ``` 5. 在容器类中实现注册和获取bean的方法。 ```java public class MyIOCContainer { // ... public void registerBean(String name, Object bean) { BeanInjector.inject(bean); // 注入属性 beans.put(name, bean); } public Object getBean(String name) { return beans.get(name); } // ... } ``` 6. 编写一个主程序,测试容器类的功能。 ```java public class Main { public static void main(String[] args) { MyIOCContainer container = new MyIOCContainer(); // 创建配置类实例 AppConfig config = new AppConfig(); // 获取bean的信息 Method[] methods = config.getClass().getMethods(); for (Method method : methods) { if (method.isAnnotationPresent(Bean.class)) { try { // 调用配置类的方法获取bean实例 Object bean = method.invoke(config); // 注册到容器中 container.registerBean(method.getName(), bean); } catch (Exception e) { e.printStackTrace(); } } } // 从容器中获取bean的实例 User user = (User) container.getBean("getUser"); System.out.println("Name: " + user.getName()); System.out.println("Age: " + user.getAge()); } } ``` 这是一个简单的自定义Spring IOC容器实现示例,可以根据需要进行扩展和优化。 ### 回答3: 自定义一个简单的Spring IOC容器需要包含几个基本的组件和功能。以下是一个简单的实现示例: ```java import java.util.HashMap; import java.util.Map; public class CustomIOCContainer { private Map<String, Object> beanMap; // 存储bean的map public CustomIOCContainer() { beanMap = new HashMap<>(); } public void registerBean(String beanName, Object bean) { beanMap.put(beanName, bean); // 注册bean到map中 } public Object getBean(String beanName) { return beanMap.get(beanName); // 通过beanName获取对应的bean对象 } public static void main(String[] args) { CustomIOCContainer container = new CustomIOCContainer(); // 创建bean对象并注册到容器中 UserService userService = new UserServiceImpl(); container.registerBean("userService", userService); // 从容器中获取bean对象并使用 UserService userServiceFromContainer = (UserService) container.getBean("userService"); userServiceFromContainer.sayHello(); // 调用方法 // 结果输出:Hello, World! } } interface UserService { void sayHello(); } class UserServiceImpl implements UserService { @Override public void sayHello() { System.out.println("Hello, World!"); } } ``` 以上代码实现一个简单的Spring IOC容器,核心思想是使用一个Map来存储注册的bean,通过bean的名称来获取对应的对象。在main方法中,我们创建了一个UserService对象并注册到容器中,然后通过容器的getBean方法获取UserService对象,并调用其sayHello方法进行输出。 这只是一个简化的实现示例,实际的Spring IOC容器功能还包括依赖注入、生命周期管理、AOP等复杂功能,不过以上代码可作为基本框架的起点。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值