徒手撸框架--实现IoC

原文地址:www.xilidou.com/2018/01/08/…java

Spring 做为 J2ee 开发事实上的标准,是每一个Java开发人员都须要了解的框架。可是Spring 的 IoC 和 Aop 的特性,对于初级的Java开发人员来讲仍是比较难于理解的。因此我就想写一系列的文章给你们讲解这些特性。从而可以进一步深刻了解 Spring 框架。git

读完这篇文章,你将会了解:github

  • 什么是依赖注入和控制反转
  • Ioc有什么用
  • Spring的 Ioc 是怎么实现的
  • 按照Spring的思路开发一个简单的Ioc框架

IoC 是什么?

wiki百科的解释是:spring

控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,能够用来减低计算机代码之间的耦合度。其中最多见的方式叫作依赖注入(Dependency Injection,简称DI)。经过控制反转,对象在被建立的时候,由一个调控系统内全部对象的外界实体,将其所依赖的对象的引用传递给它。也能够说,依赖被注入到对象中。shell

Ioc 有什么用?

看完上面的解释你必定没有理解什么是 IoC,由于是第一次看见上面的话也以为云里雾里。编程

不过经过上面的描述咱们能够大概的了解到,使用IoC的目的是为了解耦。也就是说IoC 是解耦的一种方法。json

咱们知道Java 是一门面向对象的语言,在 Java 中 Everything is Object,咱们的程序就是由若干对象组成的。当咱们的项目愈来愈大,合做的开发者愈来愈多的时候,咱们的类就会愈来愈多,类与类之间的引用就会成指数级的增加。以下图所示:api

这样的工程简直就是灾难,若是咱们引入IoC框架。由框架来维护类的生命周期和类之间的引用。咱们的系统就会变成这样:markdown

这个时候咱们发现,咱们类之间的关系都由 IoC 框架负责维,同时将类注入到须要的类中。也就是说类的使用者只负责使用,而不负责维护。把专业的事情交给专业的框架来完成。大大的减小开发的复杂度。数据结构

用一个类比来理解这个问题。Ioc框架就是咱们生活中的房屋中介,首先中介会收集市场上的房源,分别和各个房源的房东创建联系。当咱们须要租房的时候,并不须要咱们四处寻找各种租房信息。咱们直接找房屋中介,中介就会根据你的需求提供相应的房屋信息。大大提高了租房的效率,减小了你与各种房东之间的沟通次数。

Spring 的 IoC 是怎么实现的

了解Spring框架最直接的方法就阅读Spring的源码。可是Spring的代码抽象的层次很高,且处理的细节很高。对于大多数人来讲不是太容易理解。我读了Spirng的源码之后以个人理解作一个总结,Spirng IoC 主要是如下几个步骤。

1. 初始化 IoC 容器。
2. 读取配置文件。
3. 将配置文件转换为容器识别对的数据结构(这个数据结构在Spring中叫作 BeanDefinition) 
4. 利用数据结构依次实例化相应的对象
5. 注入对象之间的依赖关系
复制代码

本身实现一个IoC框架

为了方便,咱们参考 Spirng 的 IoC 实现,去除全部与核心原理无关的逻辑。极简的实现 IoC 的框架。 项目使用 json 做为配置文件。使用 maven 管理 jar 包的依赖。

在这个框架中咱们的对象都是单例的,并不支持Spirng的多种做用域。框架的实现使用了cglib 和 Java 的反射。项目中我还使用了 lombok 用来简化代码。

下面咱们就来编写 IoC 框架吧。

首先咱们看看这个框架的基本结构:

从宏观上观察一下这个框架,包含了3个package、在包 bean 中定义了咱们框架的数据结构。core 是咱们框架的核心逻辑所在。utils 是一些通用工具类。接下来咱们就逐一讲解一下:

1. bean 定义了框架的数据结构

BeanDefinition 是咱们项目的核心数据结构。用于描述咱们须要 IoC 框架管理的对象。

@Data
@ToString
public class BeanDefinition {

    private String name;

    private String className;

    private String interfaceName;

    private List<ConstructorArg> constructorArgs;

    private List<PropertyArg> propertyArgs;

}
复制代码

包含了对象的 name,class的名称。若是是接口的实现,还有该对象实现的接口。以及构造函数的传参的列表 constructorArgs 和须要注入的参数列表 propertyArgs

2. 再看看咱们的工具类包里面的对象:

ClassUtils 负责处理 Java 类的加载,代码以下:

public class ClassUtils {
    public static ClassLoader getDefultClassLoader(){
        return Thread.currentThread().getContextClassLoader();
    }
    public static Class loadClass(String className){
        try {
            return getDefultClassLoader().loadClass(className);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }
}
复制代码

咱们只写了一个方法,就是经过 className 这个参数获取对象的 Class。

BeanUtils 负责处理对象的实例化,这里咱们使用了 cglib 这个工具包,代码以下:

public class BeanUtils {
    public static <T> T instanceByCglib(Class<T> clz,Constructor ctr,Object[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(clz);
        enhancer.setCallback(NoOp.INSTANCE);
        if(ctr == null){
            return (T) enhancer.create();
        }else {
            return (T) enhancer.create(ctr.getParameterTypes(),args);
        }
    }
}
复制代码

ReflectionUtils 主要经过 Java 的反射原理来完成对象的依赖注入:

public class ReflectionUtils {

    public static void injectField(Field field,Object obj,Object value) throws IllegalAccessException {
        if(field != null) {
            field.setAccessible(true);
            field.set(obj, value);
        }
    }
}
复制代码

injectField(Field field,Object obj,Object value) 这个方法的做用就是,设置 obj 的 field 为 value。

JsonUtils 的做用就是为了解析咱们的json配置文件。代码比较长,与咱们的 IoC 原理关系不大,感兴趣的同窗能够自行从github上下载代码看看。

有了这几个趁手的工具,咱们就能够开始完成 Ioc 框架的核心代码了。

3. 核心逻辑

个人 IoC 框架,目前只支持一种 ByName 的注入。因此咱们的 BeanFactory 就只有一个方法:

public interface BeanFactory {
    Object getBean(String name) throws Exception;
}
复制代码

而后咱们实现了这个方法:

public class BeanFactoryImpl implements BeanFactory{

    private static final ConcurrentHashMap<String,Object> beanMap = new ConcurrentHashMap<>();

    private static final ConcurrentHashMap<String,BeanDefinition> beanDefineMap= new ConcurrentHashMap<>();

    private static final Set<String> beanNameSet = Collections.synchronizedSet(new HashSet<>());

    @Override
    public Object getBean(String name) throws Exception {
        //查找对象是否已经实例化过
        Object bean = beanMap.get(name);
        if(bean != null){
            return bean;
        }
        //若是没有实例化,那就须要调用createBean来建立对象
        bean =  createBean(beanDefineMap.get(name));
        
        if(bean != null) {

            //对象建立成功之后,注入对象须要的参数
            populatebean(bean);
            
            //再把对象存入Map中方便下次使用。
            beanMap.put(name,bean;
        }

        //结束返回
        return bean;
    }

    protected void registerBean(String name, BeanDefinition bd){
        beanDefineMap.put(name,bd);
        beanNameSet.add(name);
    }

    private Object createBean(BeanDefinition beanDefinition) throws Exception {
        String beanName = beanDefinition.getClassName();
        Class clz = ClassUtils.loadClass(beanName);
        if(clz == null) {
            throw new Exception("can not find bean by beanName");
        }
        List<ConstructorArg> constructorArgs = beanDefinition.getConstructorArgs();
        if(constructorArgs != null && !constructorArgs.isEmpty()){
            List<Object> objects = new ArrayList<>();
            for (ConstructorArg constructorArg : constructorArgs) {
                objects.add(getBean(constructorArg.getRef()));
            }
            return BeanUtils.instanceByCglib(clz,clz.getConstructor(),objects.toArray());
        }else {
            return BeanUtils.instanceByCglib(clz,null,null);
        }
    }

    private void populatebean(Object bean) throws Exception {
        Field[] fields = bean.getClass().getSuperclass().getDeclaredFields();
        if (fields != null && fields.length > 0) {
            for (Field field : fields) {
                String beanName = field.getName();
                beanName = StringUtils.uncapitalize(beanName);
                if (beanNameSet.contains(field.getName())) {
                    Object fieldBean = getBean(beanName);
                    if (fieldBean != null) {
                        ReflectionUtils.injectField(field,bean,fieldBean);
                    }
                }
            }
        }
    }
}
复制代码

首先咱们看到在 BeanFactory 的实现中。咱们有两 HashMap,beanMap 和 beanDefineMap。 beanDefineMap 存储的是对象的名称和对象对应的数据结构的映射。beanMap 用于保存 beanName和实例化以后的对象。

容器初始化的时候,会调用 BeanFactoryImpl.registerBean 方法。把对象的 BeanDefination 数据结构,存储起来。

当咱们调用 getBean() 的方法的时候。会先到 beanMap 里面查找,有没有实例化好的对象。若是没有,就会去beanDefineMap查找这个对象对应的 BeanDefination。再利用DeanDefination去实例化一个对象。

对象实例化成功之后,咱们还须要注入相应的参数,调用 populatebean()这个方法。在 populateBean 这个方法中,会扫描对象里面的Field,若是对象中的 Field 是咱们IoC容器管理的对象,那就会调用 咱们上文实现的 ReflectionUtils.injectField来注入对象。

一切准备稳当以后,咱们对象就完成了整个 IoC 流程。最后这个对象放入 beanMap 中,方便下一次使用。

因此咱们能够知道 BeanFactory 是管理和生成对象的地方。

4. 容器

咱们所谓的容器,就是对BeanFactory的扩展,负责管理 BeanFactory。咱们的这个IoC 框架使用 Json 做为配置文件,因此咱们容器就命名为 JsonApplicationContext。固然以后你愿意实现 XML 做为配置文件的容器你就能够本身写一个 XmlApplicationContext,若是基于注解的容器就能够叫AnnotationApplcationContext。这些实现留个你们去完成。

咱们看看 ApplicationContext 的代码:

public class JsonApplicationContext extends BeanFactoryImpl{
    private String fileName;
    public JsonApplicationContext(String fileName) {
        this.fileName = fileName;
    }
    public void init(){
        loadFile();
    }
    private void loadFile(){
        InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName);
        List<BeanDefinition> beanDefinitions = JsonUtils.readValue(is,new TypeReference<List<BeanDefinition>>(){});
        if(beanDefinitions != null && !beanDefinitions.isEmpty()) {
            for (BeanDefinition beanDefinition : beanDefinitions) {
                registerBean(beanDefinition.getName(), beanDefinition);
            }
        }
    }
}
复制代码

这个容器的做用就是 读取配置文件。将配置文件转换为容器可以理解的 BeanDefination。而后使用 registerBean 方法。注册这个对象。

至此,一个简单版的 IoC 框架就完成。

5. 框架的使用

咱们写一个测试类来看看咱们这个框架怎么使用:

首先咱们有三个对象

public class Hand {
    public void waveHand(){
        System.out.println("挥一挥手");
    }
}

public class Mouth {
    public void speak(){
        System.out.println("say hello world");
    }
}

public class Robot {
    //须要注入 hand 和 mouth 
    private Hand hand;
    private Mouth mouth;

    public void show(){
        hand.waveHand();
        mouth.speak();
    }
}
复制代码

咱们须要为咱们的 Robot 机器人注入 hand 和 mouth。

配置文件:

[
  {
    "name":"robot",
    "className":"com.xilidou.framework.ioc.entity.Robot"
  },
  {
    "name":"hand",
    "className":"com.xilidou.framework.ioc.entity.Hand"
  },
  {
    "name":"mouth",
    "className":"com.xilidou.framework.ioc.entity.Mouth"
  }
]
复制代码

这个时候写一个测试类:

public class Test {
    public static void main(String[] args) throws Exception {
        JsonApplicationContext applicationContext = new JsonApplicationContext("application.json");
        applicationContext.init();
        Robot aiRobot = (Robot) applicationContext.getBean("robot");
        aiRobot.show();
    }
}
复制代码

运行之后输出:

挥一挥手
say hello world

Process finished with exit code 0
复制代码

能够看到咱们成功的给个人 aiRobot 注入了 hand 和 mouth。

至此咱们 Ioc 框架开发完成。

总结

这篇文章读完之后相信你必定也实现了一个简单的 IoC 框架。

虽说阅读源码是了解框架的最终手段。可是 Spring 框架做为一个生产框架,为了保证通用和稳定,源码一定是高度抽象,且处理大量细节。因此 Spring 的源码阅读起来仍是至关困难。但愿这篇文章可以帮助理解 Spring Ioc 的实现。

下一篇文章 应该会是 《徒手撸框架--实现AOP》。

github 地址:https://github.com/diaozxin007/xilidou-framework

相关文章
相关标签/搜索
本站公众号
   欢迎关注本站公众号,获取更多信息