为自己模拟的IOC容器添加上DI注入

2023-03-07 20:02:27 浏览数 (1)

为自己模拟的IOC容器添加上DI注入

一、介绍

上一篇中,模拟Spring实现了一个简易的IOC容器,完成了初始化bean的操作,统一交给了一个Map集合进行管理。

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

现在,将为这个IOC容器添加DI注入功能

二、实现

在编写之前,我们先加一个工具类,用来获取接口所实现的子类Class对象,也是通过子类

代码语言:javascript复制
<dependency>
    <groupId>org.reflections</groupId>
    <artifactId>reflections</artifactId>
    <version>0.10.2</version>
</dependency>

代码如下,对比上一次,稍稍做了一点封装,使得步骤更加清晰

代码语言:javascript复制
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.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Di {

}
代码语言:javascript复制
package com.banmoon.test.mockioc.core;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.banmoon.test.mockioc.annotation.Bean;
import com.banmoon.test.mockioc.annotation.Di;
import lombok.extern.slf4j.Slf4j;
import org.reflections.Reflections;

import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
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;
import java.util.Set;

@Slf4j
@SuppressWarnings("all")
public class MyApplicationContext {

    /**
     * 真正的容器存储集合
     */
    private static final Map<String, Object> singleObjects = new HashMap<>();

    /**
     * 当前运行环境的路径
     */
    private static String currentAbsPath = null;

    /**
     * 扫描的包路径
     */
    private String packagePath;

    /**
     * 扫描的包路径反射工具类
     */
    private Reflections packageReflections;

    public MyApplicationContext(String packagePath) throws Exception {
        this.packagePath = packagePath;
        this.packageReflections = new Reflections(packagePath);
        // 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);
                        initializeBean(clazz, false);
                    }
                }
            }
        }
    }

    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;
    }

    /**
     * 处理DI注解,实现注入属性
     *
     * @param currentObj
     */
    private void diField(Object currentObj, Class<?> clazz) {
        // 1、获取所有属性
        Field[] fields = clazz.getDeclaredFields();
        // 2、遍历属性
        for (Field field : fields) {
            // 3、找到有DI注解的属性
            Di annotation = field.getAnnotation(Di.class);
            if (Objects.nonNull(annotation)) {
                // 4、找到对应属性在容器中的实例对象
                Class<?> fieldClazz = field.getType();
                // 5、在容器中查找bean,没有则进行初始化bean
                Object o = initializeBean(fieldClazz, true);
                // 6、通过反射设置到属性中
                try {
                    field.set(currentObj, o);
                } catch (IllegalAccessException e) {
                    log.error("DI注入异常", e);
                }
            }
        }
    }

    /**
     * 初始化bean
     *
     * @param clazz beanClass对象
     * @param find  是否在容器中进行寻找
     * @return 返回bean
     */
    public Object initializeBean(Class<?> clazz, boolean find) {
        // 1、判断是否是接口,且判断是不是要查找子类
        if (clazz.isInterface() && find) {
            Set<Class<?>> set = packageReflections.getSubTypesOf((Class<Object>) clazz);
            clazz = CollUtil.get(set, 0);
        } else if (clazz.isInterface() && !find) {
            return null;
        }
        // 2、查找上面是否有@bean注解
        Bean beanAnnotation = clazz.getAnnotation(Bean.class);
        if (Objects.isNull(beanAnnotation)) {
            return null;
        }
        try {
            // 3、找到bean名字,获取在容器中的实例对象
            String beanName = generateBeanName(beanAnnotation, clazz);
            // 4、判断是不是需要查找bean
            Object o = null;
            if (find) {
                o = singleObjects.get(beanName);
            }
            // 5、如果在容器中没有找到,则进行初始化
            if (Objects.isNull(o)) {
                Constructor<?> constructor = clazz.getConstructor();
                Object obj = constructor.newInstance();
                // 6、将实例化后的对象,放入map容器中
                singleObjects.put(beanName, obj);
                // 7、处理属性,DI注入属性
                diField(obj, clazz);
            }
            return o;
        } catch (Exception exception) {
            log.error("初始化bean异常", exception);
            return null;
        }
    }

}

三、测试

同样,service及其实现类,dao及其实现类

代码语言:javascript复制
package com.banmoon.test.mockioc.service;

public interface TestService {

    void hello();
}
代码语言:javascript复制
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 {

    @Di
    public TestDao testDao;

    @Override
    public void hello() {
        System.out.println("service hello...");
        testDao.hello();
    }
}
代码语言:javascript复制
package com.banmoon.test.mockioc.dao;

public interface TestDao {

    void hello();
}
代码语言:javascript复制
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 {

    @Override
    public void hello() {
        System.out.println("dao hello...");
    }
}

service实现类上面有dao类型的属性,并加上了@Di注解,试试能不能成功注入

代码语言:javascript复制
package com.banmoon.test.mockioc;

import com.banmoon.test.mockioc.core.MyApplicationContext;
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);
        testService.hello();
    }

}

运行查看结果,成功

四、最后

其实,这也是最为简单的注入,我就问问

都说Spring使用了三级缓存,那么这三级缓存是怎么使用的,它有什么作用呢?

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

0 人点赞