# ioc-Exercise **Repository Path**: wen-baolin/ioc-exercise ## Basic Information - **Project Name**: ioc-Exercise - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2022-01-10 - **Last Updated**: 2022-01-10 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # IoC练习 Spring、Guice等流行框架最核心的功能就是依赖注入,即Dependency Injection,也常常被简称为DI。 DI工作的基本原理是用一个容器来管理类的实例化。于是,程序员不需要再通过主动调用构造函数的方式来创建一个实例,而是: 1. 当需要这些类的实例时,直接从容器中查找即可。 2. 如果A依赖B,容器初始化A时,就会先找到B的实例,并将B注入A。 常见的依赖注入有3种:构造函数注入、Setter注入、字段注入。在本练习中,你需要实现的是**构造函数**注入。 ## 思路 开发一个类似Spring的简单DI框架可以分为两大步: - 实现一个容器,用来管理实例。在Spring中,这些实例被称为Bean。 - 实现扫描功能,从而使得用户不需要手动向容器注册实例,通过注解扫描即可自动发现要被管理的实例。在Spring中,这个功能叫做ComponentScan。 ## 一、容器 ### 1. `DIContainer::registerBean`和`DIContainer::lookupBean` - `registerBean(type, object)`向容器中注册一个类型为`type`的对象`object`。 - `lookupBean(type)`尝试从容器中获取一个注册类型为`type`的对象。如果已经注册,则返回之前注册的对象;如果该类型尚未注册,则返回`null`。 提示:DI容器保存的本质上是一个键值对——根据键(类型)来添加或查询值(实例对象)。不过,键和值的类型有点棘手。你也可以通过阅读`ContainerWrapper.java`中的,来反推可能的参数和返回类型。此外,为简化问题,你无需考虑同一个类型被多次注册的情况。 当你开发完成后,运行`Test1`,测试应该全部通过。 ### 2. `DIContainer::registerSupplier` 通过第一步中的`DIContainer::registerBean`注册的被称为单例(Singleton)Bean,即只有这一个对象,未来不管获取多少次,返回的都是这同一个对象。在Spring中,默认注册的都是单例Bean。 不过,Spring还提供了另外一种原型(prototype)Bean机制,允许你向容器中注册某一类型时,不是直接提供一个对象,而是提供一个函数。此函数返回的必须是一个该类型的对象。 每当向容器请求该类型的对象时,容器都会重新调用一遍那个函数,从而得到一个符合类型要求的Bean。因此,和整个程序只有一个实例的单例Bean不同,原型Bean有可能会产生同一类型的多个不同实例。 为了模拟这个功能,请你开发: - `@Inject`修改此注解,使得它只能作用于构造函数上。 - `registerSupplier(type, supplier)`,向容器中注册时,`type`类型对应的不再是一个实例,而是这个类型的一个[`Supplier`][supplier]。 - `lookupBean(type)`需要进行修改,从而使得两种方式注册的Bean都可以被获取到。 提示:为简化问题,你无需考虑一个类型多次被注册的情况。即无论第一次注册是实例还是`Supplier`,后续都不会再重复注册。 当你开发完成后,运行`Test2`,测试应该全部通过。 ### 3. `DIContainer::registerSupplierClass` 容器开发的最后一步是实现构造函数注入。我们将支持两种构造函数:被`@Inject`注解标记了的构造函数(必须只有一个),以及无参的构造函数。如果一个类有以上两种构造函数当中的一个,就可以将该类通过下面这的函数直接将该类注册进容器。 - `registerSupplierClass(type, class)`,向容器中注册`type`类型。你可以利用上一步开发的`registerSupplier`。注意,这会使得通过构造函数注入的不再是单例Bean,跟Spring框架的默认行为是不同的。但这会省去Bean之间的依赖分析,大大简化问题。 - `lookupBean(type)`需要进行修改,从而使得三种方式注册的Bean都可以被获取到。查询时,可能需要调用被`@Inject`标注了的有参构造函数,则参数也应该在容器中寻找。如果容器中返回的是`null`,说明依赖的参数并未注册,则应抛出错误。 提示:在`Helpers.java`中,我们为你准备了一些辅助函数,也许会对你有所帮助。此外,跟之前一样,无需考虑重复注册。 当你开发完成后,运行`Test3`,测试应该全部通过。 ## 二、扫描 Spring另一个非常好用的功能就是Component Scan。简单地说,如果某个类被`@Configuration`注解标记了,那么Spring就会对这个类所在的包进行扫描。这个包里下所有被`@Component`标记的类,都会自动被注册进DI容器。 事实上,这样的功能也并不复杂。不过,为了简化,与Spring不同,我们的框架只会扫描包里的类,包下面的子包将不会被扫描。例如: ```text . ├── C.java ├── subpackage1 │ └── C1.java └── subpackage2 └── C2.java ``` 在这个例子中,扫描当前目录的话,将只得到`C.java`,而不会扫描到`C1.java`和`C2.java`。 我们已经为你准备两个空函数。你只需要分别完成他们,即可实现扫描功能。 ### 4. `ElementScanner::findAllClassesInSamePackage` `findAllClassesInSamePackage(clazz)`会返回`clazz`所在的包里面所有的类。注意,子包中的类不应扫描进来。 提示:在`Helpers.java`中,我们为你准备了一些辅助函数,也许会对你有所帮助。 当你开发完成后,运行`Test4`,测试应该全部通过。 ### 5. `ElementScanner::scanForElements` `scanForElements(container, clazz)`会扫描`clazz`所在的包,并将所有被`@Element`标注的类都通过构造函数注册进容器中。 这里的`clazz`起到了类似Spring中`@Configuration`所标注类的作用。而`@Element`注解则对应Spring中的`@Component`。 有了第4步的基础,你已经可以找到跟`clazz`在同一个包的所有类。只需要再判断这些类是否被`@Element`标注、在容器中做相应的注册即可。 当你开发完成后,运行`Test5`,测试应该全部通过。 [supplier]:https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/function/Supplier.html