4

spring框架的基础核心和起点毫无疑问就是IOC,IOC作为spring容器提供的核心技术,成功完成了依赖的反转:从主类的对依赖的主动管理反转为了spring容器对依赖的全局控制。

这样做的好处是什么呢?

当然就是所谓的“解耦”了,可以使得程序的各模块之间的关系更为独立,只需要spring控制这些模块之间的依赖关系并在容器启动和初始化的过程中将依据这些依赖关系创建、管理和维护这些模块就好,如果需要改变模块间的依赖关系的话,甚至都不需要改变程序代码,只需要将更改的依赖关系进行修改即可,spring会在再次启动和初始化容器的过程中使得这些新的依赖关系重新建立符合新需求的模块,在这个过程中,需要注意的是代码本身不需要体现对于模块具体依赖情形的声明而只需要定义其所需模块的接口,所以这是一种典型的面向接口思想,同时最好将依赖关系以配置文件或者注解的形式表述出来,相关的spring处理类会根据这些外部的配置文件组装模块,或者扫描注解调用内部的注解处理器组装模块,以此完成IOC的过程。

IOC的目的是称为DI的依赖注入,通过IOC技术,最终容器将帮助我们完成模块间的依赖注入。

另外,最终的一点是,在spring IOC的过程中,我们必须始终清楚以上这条主线,即时语法和类的结构再复杂,但是其作用和目的都是一样的:就是通过依赖描述的配置文件这一装配“图纸”去完成模块的“组装”,复杂的语法只是完成这一目的的手段罢了。

所谓的IOC原型,为了展示最简单的IOC原理图,我们不妨做一个完全简单的原型来说明这个过程:

首先是我们定义的几个模块,包括主模块和两个接口定义的依赖模块:

class MainModule{
    private DependModuleA moduleA;
    private DependModuleB moduleB;
    public DependModuleA getModuleA() {
        return moduleA;
    }
    public void setModuleA(DependModuleA moduleA) {
        this.moduleA = moduleA;
    }
    public DependModuleB getModuleB() {
        return moduleB;
    }
    public void setModuleB(DependModuleB moduleB) {
        this.moduleB = moduleB;
    }
    
}

interface DependModuleA{
    public void funcFromModuleA();
}

interface DependModuleB{
    public void funcFromModuleB();
}

class DependModuleAImpl implements DependModuleA{

    @Override
    public void funcFromModuleA() {
        System.out.println("This is func from Module A");
    }
    
}

class DependModuleBImpl implements DependModuleB{

    @Override
    public void funcFromModuleB() {
        System.out.println("This is func from Module B");
    }
    
}

如果我们不采用IOC,而是依靠主模块本身去控制其依赖模块的创建,那么会是这样的:

public class SimpleIOCDemo {
    public static void main(String[] args) throws ClassNotFoundException {
        MainModule mainModule = new MainModule();
        mainModule.setModuleA(new DependModuleAImpl());
        mainModule.setModuleB(new DependModuleBImpl());
        mainModule.getModuleA().funcFromModuleA();
        mainModule.getModuleB().funcFromModuleB();
    }
}

这是我们经过简化定义的IOC容器原型,容器在启动后初始化的时候会读取用户写入的配置文件,这里我们以简单的properties配置文件为例,只有当用户调取getBean方法的时候才会真正地按照配置文件组装加载相应的bean,在我们定义的容器原型内部维护着一个用于保存装配好的bean 的map,如果在其中有满足要求的bean的话就不需要再新建了:

class SimpleIOCContainer{
    private Properties properties = new Properties();
    private Map<String, Object> moduleMap = new HashMap<>();
    {
        try {
            properties.load(new FileInputStream(new File("SimpleIOC.properties")));
        } catch (Exception e) {
            e.printStackTrace();
        }
        
    }
    public Object getBean(String moduleName) throws ClassNotFoundException {
        Object instanceObj;
        if(moduleMap.get(moduleName)!=null){
            System.out.println("return old bean");
            return moduleMap.get(moduleName);
        }
        System.out.println("create new bean");
        String fullClassName = properties.getProperty(moduleName);
        if(fullClassName == null)
            throw new ClassNotFoundException();
        else{
            Class<? extends Object> clazz = Class.forName(fullClassName);
            try {
                instanceObj = clazz.newInstance();
                instanceObj = buildAttachedModules(moduleName,instanceObj);
                moduleMap.put(moduleName, instanceObj);
                return instanceObj;
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
    private Object buildAttachedModules(String modulename , Object instanceObj) {
        Set<String> propertiesKeys = properties.stringPropertyNames();
        Field[] fields = instanceObj.getClass().getDeclaredFields();
        for (String key : propertiesKeys) {
            if(key.contains(modulename)&&!key.equals(modulename)){
                try {
                    Class<? extends Object> clazz = Class.forName(properties.getProperty(properties.getProperty(key)));
                    for (Field field : fields) {
                        if(field.getType().isAssignableFrom(clazz))
                            field.set(instanceObj, clazz.newInstance());
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
                
            }
        }
        return instanceObj;
    }
}

这是我们使用properties配置文件写成的依赖关系配置文件,这个配置文件是我们装配模块的“图纸”,这里的语法个是完全是我们定义的,在真实的spring IOC容器中,为了表达更为复杂的依赖逻辑,会使用更为发达的xml格式配置文件或者更新的注解配置,依靠注解处理器来完成图纸的解析:

mainModule=com.rocking.demo.MainModule
mainModule.moduleA=moduleA
mainModule.moduleB=moduleB
moduleA=com.rocking.demo.DependModuleAImpl
moduleB=com.rocking.demo.DependModuleBImpl

这是测试代码,可以看到的是我们可以完整的通过我们定义的IOC容器获取到符合要求的模块,同时也可以发现我们定义的容器可以为我们维护这些bean,当有bean已经组装创建出来之后就不需要再创建了。

public class SimpleIOCDemo {
    public static void main(String[] args) throws ClassNotFoundException {
        SimpleIOCContainer container = new SimpleIOCContainer();
        DependModuleA moduleA = (DependModuleA) container.getBean("moduleA");
        moduleA.funcFromModuleA();
        DependModuleB moduleB = (DependModuleB) container.getBean("moduleB");
        moduleB.funcFromModuleB();
        MainModule mainModule = (MainModule) container.getBean("mainModule");
        mainModule.getModuleA().funcFromModuleA();
        mainModule.getModuleB().funcFromModuleB();
        container.getBean("mainModule");
    }
}

这就是我依据IOC的基本思想创建的IOC容器原型,spring IOC虽然语法复杂,但是说到底完成的任务在核心上都是一样的,所谓的“万变不离其宗”。


JinhaoPlus
1.5k 声望92 粉丝

扎瓦程序员