前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >模拟Spring实现一个简易的IOC容器

模拟Spring实现一个简易的IOC容器

原创
作者头像
半月无霜
发布2024-07-28 21:32:47
1220
发布2024-07-28 21:32:47

模拟Spring实现一个简易的IOC容器

一、介绍

在模拟实现IOC容器之前,我们必须要掌握反射相关的知识,毕竟IOC容器采用的是反射进行的查找创建。

反射可以看我以前的这篇文章

Java注解的介绍和反射使用 | 半月无霜 (banmoon.top)

二、步骤

spring的包中,有这么一个接口ApplicationContext.java,他是一个容器接口,一切的开始都是由它开始,spring中定义了各种场景下使用的实现类,其中常见有以下几种实现类

  • AnnotationConfigApplicationContext:通过注解配置初始化容器
  • ClassPathXmlApplicationContext:通过xml配置文件初始化容器
  • AnnotationConfigServletWebApplicationContext:注解配置加载web环境的容器

既然我们要自己写一个容器,我们也需要一个这样的容器类,接口不接口的无所谓,我们可以不考虑结构,只考虑效果。故我们只需要一个容器类就好了。

除了容器外,我们还需要一个注解,分别作用于类上,作为标识此类要进行初始化被容器管理

  • @bean:标注为需要实例化的类,被容器管理

那么步骤如下

  1. 先创建一个容器类,这个容器类中有初始化bean,获取bean的方法
  2. 初始化bean时,需要传入一个包路径,自动扫描这个包路径下的类
  3. 如果类上面要是有@bean注解,我们就进行实例化类,并加入到容器中

三、实现

我们先把注解写了

代码语言:java
复制
package com.banmoon.test.mockioc.annotation;
​
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
​
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Bean {
​
    String value() default "";
​
}

然后,我们可以开始写容器了,代码一步到位,看构造器初始化方法,查看是如何加载到类,并将其实例化的。

以及容器中有个Map<String, Object> singleObjects = new HashMap<>();,这才是用来存储实例化后的对象的

代码语言:java
复制
package com.banmoon.test.mockioc.core;
​
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.banmoon.test.mockioc.annotation.Bean;
​
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
​
public class MyApplicationContext {
​
    /**
     * 真正的容器存储集合
     */
    private static final Map<String, Object> singleObjects = new HashMap<>();
​
    /**
     * 当前运行环境的路径
     */
    private static String currentAbsPath = null;
​
    public MyApplicationContext(String packagePath) throws Exception {
        // 1、将包路径中的.变成\
        String basePackage = StrUtil.replace(packagePath, ".", "\\");
        // 2、获取包的绝对路径,我们要获取class包的绝对路径,也就是target里面的那些
        URL url = Thread.currentThread().getContextClassLoader().getResource(basePackage);
        // 3、得到url后还需要进行转码
        if (Objects.nonNull(url)) {
            String filePath = URLDecoder.decode(url.getFile(), "utf-8");
            // 4、为了方便,此处记录target包的绝对路径
            currentAbsPath = filePath.substring(0, filePath.length() - basePackage.length());
            // 5、扫描包里面所有的类
            scanBean(new File(filePath));
        }
    }
​
    private void scanBean(File file) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        // 1、判断当前是否文件夹
        if (file.isDirectory()) {
            // 2、获取文件夹中所有的内容,如果为空直接返回
            File[] children = file.listFiles();
            // 3、遍历内容
            if (ArrayUtil.isEmpty(children)) {
                return;
            }
            for (File child : children) {
                // 4.1、如果是文件夹,则进行递归
                if (child.isDirectory()) {
                    scanBean(child);
                } else {
                    // 5.1、如果是文件,则进行判断是否为class文件
                    String pathWithClass = child.getAbsolutePath().substring(currentAbsPath.length() - 1);
                    if (pathWithClass.endsWith(".class")) {
                        // 5.2、反射得到当前文件的Class对象
                        String classPath = StrUtil.replace(pathWithClass, "\\", ".")
                                .replace(".class", "");
                        Class<?> clazz = Class.forName(classPath);
                        // 5.3、判断上面是否有@Bean注解,有的话进行实例化
                        Bean annotation = clazz.getAnnotation(Bean.class);
                        if (!clazz.isInterface() && Objects.nonNull(annotation)) {
                            Constructor<?> constructor = clazz.getConstructor();
                            Object obj = constructor.newInstance();
                            // 5.4、将实例化后的对象,放入map容器中
                            String beanName = generateBeanName(annotation, clazz);
                            singleObjects.put(beanName, obj);
                        }
                    }
                }
            }
        }
    }
​
    @SuppressWarnings("all")
    public <T> T getBean(String beanName, Class<T> clazz) {
        Object o = singleObjects.get(beanName);
        if (!clazz.isInstance(o)) {
            throw new UnsupportedOperationException("获取的类型错误");
        }
        return (T) o;
    }
​
    /**
     * 获取bean的名字
     * @param annotation bean注解
     * @param clazz class对象
     * @return bean的名字
     */
    private String generateBeanName(Bean annotation, Class<?> clazz) {
        String value = annotation.value();
        if (StrUtil.isBlank(value)) {
            Class<?>[] interfaces = clazz.getInterfaces();
            if (ArrayUtil.isNotEmpty(interfaces)) {
                value = StrUtil.lowerFirst(interfaces[0].getSimpleName());
            } else {
                value = StrUtil.lowerFirst(clazz.getSimpleName());
            }
        }
        return value;
    }
}

四、测试

写一个service、一个dao,以及对应的实现类

代码语言:java
复制
package com.banmoon.test.mockioc.service;
​
public interface TestService {
​
}
代码语言:java
复制
package com.banmoon.test.mockioc.service.impl;
​
import com.banmoon.test.mockioc.annotation.Bean;
import com.banmoon.test.mockioc.annotation.Di;
import com.banmoon.test.mockioc.dao.TestDao;
import com.banmoon.test.mockioc.service.TestService;
​
@Bean
public class TestServiceImpl implements TestService {
​
}
代码语言:java
复制
package com.banmoon.test.mockioc.dao;
​
public interface TestDao {
​
}
代码语言:java
复制
package com.banmoon.test.mockioc.dao.impl;
​
import com.banmoon.test.mockioc.annotation.Bean;
import com.banmoon.test.mockioc.dao.TestDao;
​
@Bean
public class TestDaoImpl implements TestDao {
​
}

再编写我们的主程序,来初始化这个容器

代码语言:java
复制
package com.banmoon.test.mockioc;
​
import com.banmoon.test.mockioc.core.MyApplicationContext;
import com.banmoon.test.mockioc.dao.TestDao;
import com.banmoon.test.mockioc.service.TestService;
​
public class Test {
​
    public static void main(String[] args) throws Exception {
        MyApplicationContext context = new MyApplicationContext("com.banmoon.test.mockioc");
        TestService testService = context.getBean("testService", TestService.class);
        System.out.println(testService);
        TestDao testDao = context.getBean("testDao", TestDao.class);
        System.out.println(testDao);
    }
​
}

运行结果如下

如此,一个简易的IOC容器就已经搭建完成了

五、最后

想象很美好,实际问题会很多,在此只展示基本的原理。

如果真的这么简单的话,spring也就不会这么庞大了,555!!!

上面这段代码存在不少的问题,后续总结一下spring中是如何解决这些个问题的吧。

  • 实例化的bean存在多个接口,该如何存储
  • 上述代码没有完成依赖注入,也就是DI
  • spring是如何存储,才完成了既可以通过名称获取,又可以通过类型获取bean

我是半月,你我一同共勉!!!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 模拟Spring实现一个简易的IOC容器
    • 一、介绍
      • 二、步骤
        • 三、实现
          • 四、测试
            • 五、最后
            相关产品与服务
            容器服务
            腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档