手写Spring IOC容器

发现是好久都没有写博客了,不得不说有时候工作真的是温水煮青蛙。而且现在框架层出不穷,在技术浪潮里,找到一条路变得比学习新技术更为重要

Spring IOC

我曾经多次聊过Spring的底层原理,以及Spring Boot Operational Principle这篇文章中也详细描述了一些底层的运行原理,但真正从低往上聊,应该是第一次。现在课程,大多都讲的是应用,然后在摸一摸底层,深度是不够的。曾经老师就跟我讲过,现在你年轻,年前是你的资本,那等你老了呢,你的资本又是什么?用了多少API,会多少框架的配置嘛,绝对不是的,你的资本应该是你日积月累的,对整体的认知,对原理的探索,还有对工匠精神的敬畏。这不是一个初出茅庐的孩子所能拥有的,但也是你年龄增长之后必须要有的。

今天我把IOC这部分内容单独拿出来,也是希望能够让更多看到这篇博客的人,对底层和框架真正知识有一个大致的概念,当然,您要是前辈,我相信您在技术上的造诣比我高深,特别希望您能够对我的文章有所指正,您可以在首页的评论区、github链接中联系到我。

首先IOC容器指的究竟是什么,毋庸置疑ConcurrentHashMap

    /** Map from bean name to merged RootBeanDefinition. */
    private final Map<String, RootBeanDefinition> mergedBeanDefinitions = new ConcurrentHashMap<>(256);

    /** Names of beans that have already been created at least once. */
    private final Set<String> alreadyCreated = Collections.newSetFromMap(new ConcurrentHashMap<>(256));

至于查询的逻辑,简单概括就是,不论你传几个参数,最终走的都是doGetBean这个方法,这个方法会找找缓存里有没有,mergedBeanDefinitions里找一找,总之各个地方找一找,没找到,就抛异常,找到了类型不对,抛异常,大概就这样

那所以我们要做的事情很明确了

  • 第一件事,启动的时候通过注解或者配置的方式,把需要交给容器管理的类注册到容器中
  • 第二件事,获取的时候,能获取到

这两件事情搞定了,其他东西就都是可有可无的,有了方便使用,没有不影响主逻辑

Bean容器工厂

既然是IOC容器,那最关键的,必然是容器工厂,也就是我们存储Bean对象的地方。
ps:我是基于Spring上一个版本的容器实现的仿制(部分设计模式压缩)

    /**
    * 核心容器
    */
    private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
    /**
    * 别名辅助容器
    */
    private final Map<String, String> aliasesDefinitionMap = new ConcurrentHashMap<>(256);
    /**
    * 类型辅助容器
    */
    private final Map<String, Class<?>> typeDefinitionMap = new ConcurrentHashMap<>(256);
    /**
    * 动态重制容器
    */
    private final Map<Class<?>, Object> resolvableDependencies = new ConcurrentHashMap<>(256);

我设置的容器比较多,原版本中,更多的内容在BeanDefinition这个对象中,是对Bean的一个封装。那我这边希望尽可能的简化,所以并没有在BeanDefinition中增加太多的逻辑

这个容器类的声明为public class DefaultBeanFactory implements BeanFactory, BeanRegister
表明同时具有BeanFactory的管理获取能力,BeanRegister的注册能力,拍平了很多原本的设计
获取的方法和注册的方法都很简单了,如果大家好奇,可以到我Github中查看相关的代码。

现在,我们已经完成了容器,也就是存Bean的工具和方法,接下来,我们需要先把Bean对象注入进来

Bean注入

之前有提到说,Bean注入有两种方式,第一种,手动配置,第二种,注解声明。
我们先来看第二种,注解的方式

定义一个注解

/**
 * @author eddie
 * @createTime 2019-02-13
 * @description 自动注入
 */
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AutoWried {
    //没设置任何参数,需要的话可以在这里配置别名,是否单例,是否required等
}

注解处理器

/**
 * @author eddie
 * @createTime 2019-02-13
 * @description 自动注入注解处理器
 */
public class AutoWriedProcessor implements AutoWriedService {

    private BeanFactory factory;

    /**
     * 获取列上的注解 如果有AutoWried注解 则处理
     * @param clazz
     * @return
     * @throws NoSuchMethodException
     */
    private void classForAnnotation0(Class clazz) {
        Field[] fields = clazz.getDeclaredFields();
        for(Field field : fields){
          field.setAccessible(true);
          AutoWried autoWried = field.getAnnotation(AutoWried.class);
          if (autoWried != null){
              Object injectBean = factory.getBean(field.getType());
              if (Objects.isNull(injectBean)){
                  throw new RuntimeException("没有找到对应的Bean异常");
              }
              Object bean = factory.getBean(clazz);
              try {
                  field.set(bean, injectBean);
              } catch (IllegalAccessException e) {
                  e.printStackTrace();
              }
          }
        }
        return;
    }


    @Override
    public void start(String pkg, BeanFactory factory) {
        this.factory = factory;
        final List<Class<?>> allClassByPackageName = AnnotationUtils.getAllClassByPackageName(pkg);
        for (Class<?> clazz: allClassByPackageName){
            classForAnnotation0(clazz);
        }
    }
}

到此,注入的方式搞定了,诶,你会发现,容器和注入的类没有联系到一起啊,这各干各的没用的呀。
所以,我们还需要一个启动类,也就是Spring 中非常熟悉的XXXXXApplicationContext

ApplicationContext

作为一个集大成者,这个类涉及的继承实现关系就多了。先来看下类声明
public class ClassPathJsonApplicationContext extends AbstractApplicationContext implements ApplicationContext

AbstractApplicationContext的类声明
public abstract class AbstractApplicationContext implements Lifecycle, ConfigurableApplicationContext, BeanFactory

别害怕这个AbstractApplicationContext哈,他的实现非常简单

    @Override
    public Object getBean(String name) throws BeansException {
        return getBeanFactory().getBean(name);
    }

    @Override
    public <T> T getBean(Class<T> requiredType) throws BeansException {
        return getBeanFactory().getBean(requiredType);
    }

    @Override
    public boolean containsBean(String name) {
        return getBeanFactory().containsBean(name);
    }

    @Override
    public boolean isSingleton(String name) throws NoSuchBeanDefinitionException {
        return getBeanFactory().isSingleton(name);
    }

    @Override
    public boolean isPrototype(String name) throws NoSuchBeanDefinitionException {
        return getBeanFactory().isPrototype(name);
    }

    @Override
    public boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException {
        return getBeanFactory().isTypeMatch(name, typeToMatch);
    }

    @Override
    public Class<?> getType(String name) throws NoSuchBeanDefinitionException {
        return getBeanFactory().getType(name);
    }

    @Override
    public String getAliases(String name) {
        return getBeanFactory().getAliases(name);
    }

    /**
     * 获取bean工厂
     * @return
     * @throws IllegalStateException
     */
    @Override
    public abstract BeanFactory getBeanFactory() throws IllegalStateException;

都是去调的getBeanFactory()这个方法获取的BeanFactory,至于获取的是谁,由子类决定,最简单的模版模式

回到ClassPathJsonApplicationContext类,这个类的实现更加简单

    private static final Active active;

    private static final AutoWriedService autoWriedService;

    private ConfigLoader loader;

    static {
        /**
        * 获取类上的注解 如果有Bean注解 则处理
        */
        active = new DefaultBeanProcessor();
        /**
        * 获取列上的注解 如果有AutoWried注解 则处理
        */
        autoWriedService = new AutoWriedProcessor();
    }

    public ClassPathJsonApplicationContext (String configPath){
        log.info("start load context - 开始加载上下文 读取配置文件");
        loader = new ConfigLoader(configPath);
        log.info("start load annotation - 根据配置文件 加载所有注解类 bean类型");
        start();
    }

    @Override
    public BeanFactory getBeanFactory() {
        return DefaultBeanFactory.getInstance();
    }

    @Override
    public void start() {
        BeanRegister register= DefaultBeanFactory.getInstance();
        log.info("start load bean to IOC map - 开始注册bean对象到IOC容器");
        active.activate(loader.getBaseBeanPackage(), register);
        autoWriedService.start(loader.getBaseBeanPackage(), getBeanFactory());
    }

    @Override
    public void stop() {
    }

    @Override
    public boolean isRunning() {
        return true;
    }

致此,我们是不是实现了自动获取对象注入到这个Bean容器中了?OK。
获取完了,怎么用呢??
当然有个办法是通过ClassPathJsonApplicationContextgetBean方法,但我觉得很Low,Spring的Autowried就很不错。

那关注到的那个点,就是AutoWriedProcessor,我们初始化的时候,有个AutoWriedProcessor这个类,没错,就干的这事。

自动注入

实现同样非常简单,先上代码

    private BeanFactory factory;

    /**
     * 获取列上的注解 如果有AutoWried注解 则处理
     * @param clazz
     * @return
     * @throws NoSuchMethodException
     */
    private void classForAnnotation0(Class clazz) {
        Field[] fields = clazz.getDeclaredFields();
        for(Field field : fields){
          field.setAccessible(true);
          AutoWried autoWried = field.getAnnotation(AutoWried.class);
          if (autoWried != null){
              Object injectBean = factory.getBean(field.getType());
              if (Objects.isNull(injectBean)){
                  throw new RuntimeException("没有找到对应的Bean异常");
              }
              Object bean = factory.getBean(clazz);
              try {
                  field.set(bean, injectBean);
              } catch (IllegalAccessException e) {
                  e.printStackTrace();
              }
          }
        }
        return;
    }


    @Override
    public void start(String pkg, BeanFactory factory) {
        this.factory = factory;
        final List<Class<?>> allClassByPackageName = AnnotationUtils.getAllClassByPackageName(pkg);
        for (Class<?> clazz: allClassByPackageName){
            classForAnnotation0(clazz);
        }
    }

主体功能已经OK了,那我们还想要什么呢,基于配置文件嘛?
好吧,既然提到了,这个小功能就来看下这个小功能

我就不放代码了。。。。就是写个xxx.json,然后再ClassPathJsonApplicationContext启动的时候加载,解析,然后用就完了。。。
默认必填那几个项,没获取到或者获取为Null,“”就报个错出来,经济又实惠
好,这个问题就算过了哈,没懂的,自己翻github看一下

那再请大家思考下,这个事是不是做完了??

其实还有个问题,有参构造器的情况还没有考虑,这个问题就留给看到这篇文章的兄弟们来实现吧

当然,你们肯定有比我方案更优的设计,或者我有什么不正确的地方,可以在我Github中提出Issus哟。
我的Github && 这个项目的地址

感谢大家的阅读时间,有机会我把这个博客开通留言功能,[开心的表情]

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 作者:“安顺” 没有人在雪地里歌舞申声 楼道和楼梯绸缪着石海绵 拿起扫把,在市中心 无数的人已拿起扫把 白色垃圾就...
    彭先生10阅读 256评论 -3 3
  • 链接: https://pan.baidu.com/s/1o8OIz6E 密码: 2brm
    静_静_阅读 209评论 0 0
  • 营养是健康的基础,与美颜美形息息相关;女性的温柔之美,男性的阳刚之美,均靠气血充盈,而气血充盈来自于丰富的营养。如...
    苏州浪花阅读 1,589评论 0 49
  • 冯萍 焦点解决网络第14期 坚持分享第40天清零(17天) 如果你想被深度爱,就去找一个能消化你情绪的人,而不是努...
    糊涂妙阅读 353评论 0 0
  • 瘦人碗子阅读 134评论 0 3

友情链接更多精彩内容