用简单代码手写一个简易IOC容器

手写IOC容器
我们先来看一下IOC容器的整体流程:

首先,我们先创建一个Maven项目,然后在项目的resources目录下添加一个配置文件application.properties,在配置文件中指定需要扫描的包路径

然后我们定义一些注解,分别表示访问控制层、业务服务层、数据持久层、依赖注入注解、获取配置文件注解,代码如下
依赖注入注解@MyAutowired

访问控制层注解@MyController

业务服务层注解@MyService

数据持久层注解@MyMapping

配置文件获取注解@Value

定义完注解后,我们可以开始编写代码了。根据上面的流程图,此时应该先获取读取配置文件,从配置文件中获取需要扫描的包路径

我们先写一个配置文件工具类ConfigurationUtils,代码如下:

package cn.xiaosong.platform.config;

import org.apache.commons.lang3.StringUtils;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

/**
 * 类 名: ConfigurationUtils
 * @author xiaosong
 */
public class ConfigurationUtils {

    /**
     * 项目配置文件信息
     */
    public static Properties properties;

    public ConfigurationUtils(String propertiesPath) {
        properties = this.getBeanScanPath(propertiesPath);
    }

    /**
     * @demand: 读取配置文件
     */
    private Properties getBeanScanPath(String propertiesPath) {
        if (StringUtils.isEmpty(propertiesPath)) {
            propertiesPath = "/application.properties";
        }
        Properties properties = new Properties();
        // 通过类的加载器获取具有给定名称的资源
        InputStream in = ConfigurationUtils.class.getResourceAsStream(propertiesPath);
        try {
            System.out.println("正在加载配置文件application.properties");
            properties.load(in);
            return properties;
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return properties;
    }

    /**
     * @demand: 根据配置文件的key获取value的值
     */
    public static Object getPropertiesByKey(String propertiesKey) {
        if (properties.size() > 0) {
            return properties.get(propertiesKey);
        }
        return null;
    }



}

上述代码中,我们通过读取配置文件获取到配置文件信息的key-value键值对;然后我们再根据配置文件中指定的扫描包路径进行包扫描 拿到包扫描路径后,我们就可以获取到当前路径下的文件信息及文件夹信息,我们将当前路径下所有以.class结尾的文件添加到一个Set集合中进行存储。代码如下:

private void classLoader() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
    // 加载配置文件所有配置信息
    new ConfigurationUtils(null);
    // 获取扫描包路径
    String classScanPath = (String) ConfigurationUtils.properties.get("ioc.scan.path");
    if (StringUtils.isNotEmpty(classScanPath)) {
        classScanPath = classScanPath.replace(".", "/");
    } else {
        throw new RuntimeException("请配置项目包扫描路径 ioc.scan.path");
    }
    // 扫描项目根目录中所有的class文件
    getPackageClassFile(classScanPath);
    for (String className : classSet) {
        addServiceToIoc(Class.forName(className));
    }
    // 获取带有MyService注解类的所有的带MyAutowired注解的属性并对其进行实例化
    Set<String> beanKeySet = iocBeanMap.keySet();
    for (String beanName : beanKeySet) {
        addAutowiredToField(iocBeanMap.get(beanName));
    }
}

我们需要扫描项目路径中所有以.class结尾的文件,将其添加到一个全局的Set集合中,代码如下:

private Set<String> classSet = new HashSet();
private void getPackageClassFile(String packageName) {
    URL url = this.getClass().getClassLoader().getResource(packageName);
    File file = new File(url.getFile());
    if (file.exists() && file.isDirectory()) {
        File[] files = file.listFiles();
        for (File fileSon : files) {
            if (fileSon.isDirectory()) {
                // 递归扫描
                getPackageClassFile(packageName + "/" + fileSon.getName());
            } else {
                // 是文件并且是以 .class结尾
                if (fileSon.getName().endsWith(".class")) {
                    System.out.println("正在加载: " + packageName.replace("/", ".") + "." + fileSon.getName());
                    classSet.add(packageName.replace("/", ".") + "." + fileSon.getName().replace(".class", ""));
                }
            }
        }
    } else {
        throw new RuntimeException("没有找到需要扫描的文件目录");
    }
}
我们将所有的类的字节码对象都存储到一个全局的Set集合中之后,遍历这个set集合,获取在类上有指定注解的类,并将其交给IOC容器;我们先定义一个安全的Map用来存储这些对象

```java

    /**
     * @demand: 控制反转
     */
    private void addServiceToIoc(Class classZ) throws IllegalAccessException, InstantiationException {
        // 预留位置,之后优化
        if (classZ.getAnnotation(MyController.class) != null) {
            iocBeanMap.put(toLowercaseIndex(classZ.getSimpleName()), classZ.newInstance());
            System.out.println("控制反转访问控制层:" + toLowercaseIndex(classZ.getSimpleName()));
        } else if (classZ.getAnnotation(MyService.class) != null) {
            // 将当前类交由IOC管理
            MyService myService = (MyService) classZ.getAnnotation(MyService.class);
            iocBeanMap.put(StringUtils.isEmpty(myService.value()) ? toLowercaseIndex(classZ.getSimpleName()) : toLowercaseIndex(myService.value()), classZ.newInstance());
            System.out.println("控制反转服务层:" + toLowercaseIndex(classZ.getSimpleName()));
        } else if (classZ.getAnnotation(MyMapping.class) != null) {
            MyMapping myMapping = (MyMapping) classZ.getAnnotation(MyMapping.class);
            iocBeanMap.put(StringUtils.isEmpty(myMapping.value()) ? toLowercaseIndex(classZ.getSimpleName()) : toLowercaseIndex(myMapping.value()), classZ.newInstance());
            System.out.println("控制反转持久层:" + toLowercaseIndex(classZ.getSimpleName()));
        }
    }
 /**
     * @demand: 类名首字母转小写
     */
    public static String toLowercaseIndex(String name) {
        if (StringUtils.isNotEmpty(name)) {
            return name.substring(0, 1).toLowerCase() + name.substring(1, name.length());
        }
        return name;
    }

然后我们再遍历这个IOC容器,获取到每一个类的实例,判断里面是有有依赖其他的类的实例,即依赖注入;

 /**
     * @demand: 依赖注入
     */
    private void addAutowiredToField(Object obj) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
        Field[] fields = obj.getClass().getDeclaredFields();
        for (Field field : fields) {
            if (field.getAnnotation(MyAutowired.class) != null) {
                field.setAccessible(true);
                MyAutowired myAutowired = field.getAnnotation(MyAutowired.class);
                Class<?> fieldClass = field.getType();
                // 接口不能被实例化,需要对接口进行特殊处理获取其子类,获取所有实现类
                if (fieldClass.isInterface()) {
                    // 如果有指定获取子类名
                    if (StringUtils.isNotEmpty(myAutowired.value())) {
                        field.set(obj, iocBeanMap.get(myAutowired.value()));
                    } else {
                        // 当注入接口时,属性的名字与接口实现类名一致则直接从容器中获取
                        Object objByName = iocBeanMap.get(field.getName());
                        if (objByName != null) {
                            field.set(obj, objByName);
                            // 递归依赖注入
                            addAutowiredToField(field.getType());
                        } else {
                            // 注入接口时,如果属性名称与接口实现类名不一致的情况下
                            List<Object> list = findSuperInterfaceByIoc(field.getType());
                            if (list != null && list.size() > 0) {
                                if (list.size() > 1) {
                                    throw new RuntimeException(obj.getClass() + "  注入接口 " + field.getType() + "   失败,请在注解中指定需要注入的具体实现类");
                                } else {
                                    field.set(obj, list.get(0));
                                    // 递归依赖注入
                                    addAutowiredToField(field.getType());
                                }
                            } else {
                                throw new RuntimeException("当前类" + obj.getClass() + "  不能注入接口 " + field.getType().getClass() + "  , 接口没有实现类不能被实例化");
                            }
                        }
                    }
                } else {
                    String beanName = StringUtils.isEmpty(myAutowired.value()) ? toLowercaseIndex(field.getName()) : toLowercaseIndex(myAutowired.value());
                    Object beanObj = iocBeanMap.get(beanName);
                    field.set(obj, beanObj == null ? field.getType().newInstance() : beanObj);
                    System.out.println("依赖注入" + field.getName());
//                递归依赖注入
                }
                addAutowiredToField(field.getType());
            }
            if (field.getAnnotation(Value.class) != null) {
                field.setAccessible(true);
                Value value = field.getAnnotation(Value.class);
                field.set(obj, StringUtils.isNotEmpty(value.value()) ? ConfigurationUtils.getPropertiesByKey(value.value()) : null);
                System.out.println("注入配置文件  " + obj.getClass() + " 加载配置属性" + value.value());
            }
        }
    }

上述代码中,我们通过判断带指定注解的类里面是否有注入其它的类,然后进行递归注入;但是有一个问题,接口和抽象类不能被实例化,所以在处理接口时,就出现了一个难题。通常我们习惯注入接口,但是接口不能被实例化,我们需要对接口赋值它的子类,如何获取到接口的实现类呢? 翻遍了JDK1.8的API,没有找到能够提供这样的方法。于是这里做了写了一个循环,遍历IOC容器中的每一个类是否有实现接口,如果是相同的接口则记录,但是这样做会非常消耗性能的,其代码如下:

 /**
     * @demand: 判断需要注入的接口所有的实现类
     */
    private List<Object> findSuperInterfaceByIoc(Class classz) {
        Set<String> beanNameList = iocBeanMap.keySet();
        ArrayList<Object> objectArrayList = new ArrayList<>();
        for (String beanName : beanNameList) {
            Object obj = iocBeanMap.get(beanName);
            Class<?>[] interfaces = obj.getClass().getInterfaces();
            if (useArrayUtils(interfaces, classz)) {
                objectArrayList.add(obj);
            }
        }
        return objectArrayList;
    }

对于接口的注入,暂时还没有想到有什么好的方式可以优化,也不知道Spring是怎么做的。当一个接口有多个实现类时,需要用过自定义名称进行交给IOC管理和注入注解进行获取。 截至到这里,我们就完成了整个IOC容器的创建以及依赖注入功能了。我们可以写一个简单的测试类来试一下我们写的这个IOC容器;测试代码:访问控制层

@MyController
public class LoginController {

    @Value(value = "ioc.scan.pathTest")
    private String test;

    @MyAutowired(value = "test")
    private LoginService loginService;

    public String login() {
        return loginService.login();
    }

}

测试代码:业务服务接口层

public interface LoginService {

    String login();
}
``
测试代码:具体服务层(这里尝试了写两个实现类,多态情况下)`

```java
@MyService(value = "test")
public class LoginServiceImpl implements LoginService {

    @MyAutowired
    private LoginMapping loginMapping;

    @Override
    public String login() {
        return loginMapping.login();
    }
}

@MyService
public class TestLoginServiceImpl implements LoginService {

    @Override
    public String login() {
        return "测试多态情况下依赖注入";
    }
}

测试代码:数据持久层接口层public interface LoginMapping {

String login();

}
测试代码:数据持久层具体持久层

@MyMapping
public class LoginMappingImpl implements LoginMapping {

    @Override
    public String login() {
        return "项目启动成功";
    }
}

然后我们写一个启动类PlatformApplication

public class PlatformApplication {

    public static void main(String[] args) throws Exception {
        // 从容器中获取对象(自动首字母小写)
        MyApplicationContext applicationContext = new MyApplicationContext();
        LoginController loginController = (LoginController) applicationContext.getIocBean("LoginController");
        String login = loginController.login();
        System.out.println(login);
    }

}

控制台输出日志信息如下:

正在加载配置文件application.properties
正在加载: cn.jiayao.platform.common.MyApplicationContext.class
正在加载: cn.jiayao.platform.config.ConfigurationUtils.class
正在加载: cn.jiayao.platform.core.annotation.MyAutowired.class
正在加载: cn.jiayao.platform.core.annotation.MyController.class
正在加载: cn.jiayao.platform.core.annotation.MyMapping.class
正在加载: cn.jiayao.platform.core.annotation.MyService.class
正在加载: cn.jiayao.platform.core.annotation.Value.class
正在加载: cn.jiayao.platform.modular.controller.LoginController.class
正在加载: cn.jiayao.platform.modular.dao.impl.LoginMappingImpl.class
正在加载: cn.jiayao.platform.modular.dao.LoginMapping.class
正在加载: cn.jiayao.platform.modular.service.impl.LoginServiceImpl.class
正在加载: cn.jiayao.platform.modular.service.impl.TestLoginServiceImpl.class
正在加载: cn.jiayao.platform.modular.service.LoginService.class
正在加载: cn.jiayao.platform.PlatformApplication.class
正在加载: cn.jiayao.platform.utils.MyArrayUtils.class
控制反转访问控制层:loginController
控制反转持久层:loginMappingImpl
控制反转服务层:loginServiceImpl
控制反转服务层:testLoginServiceImpl
注入配置文件  class cn.jiayao.platform.modular.controller.LoginController 加载配置属性ioc.scan.pathTest
项目启动成功

这样,一个简单的IOC容器就创建完成了;
Github地址:https://github.com/smile960207/IOC.git

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值