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

2024-07-28 21:32:47 浏览数 (1)

模拟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

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

0 人点赞