关于java:ByxContainer轻量级IOC容器

43次阅读

共计 7324 个字符,预计需要花费 19 分钟才能阅读完成。

ByxContainer 是一个用 Java 编写的轻量级 IOC 容器,具备以下个性:

  • 应用 JSON 格局的配置文件
  • 反对构造函数注入、动态工厂注入、实例工厂注入、属性注入、setter 注入、条件注入
  • 组件的提早加载和单例组件
  • 依据 id 注册、获取容器中的组件

我的项目地址:https://github.com/byx2000/By…

配置文件

ByxContainer 应用 JSON 作为配置文件的格局。你能够将配置文件命名为任何名字,放在任何你喜爱的门路下。

ByxContainer 配置文件的根本框架如下:

{
    "typeAlias":
    {// 配置 components 中应用的类型别名},
    "components":
    {// 定义容器中的所有组件}
}
类型 阐明 是否必须
typeAlias 对象 components中应用的类型别名
components 对象 定义容器中的所有组件

对于类型别名的应用,请参考这里。

组件

ByxContainer 应用组件来管理系统中的所有对象。组件代表了零碎中的一个对象,并封装了该对象的创立过程。每个组件都有一个惟一的 key,当咱们向 ByxContainer 注册一个组件时,须要为组件指定一个惟一的 key,同时还要定义组件的创立过程和依赖关系。

ByxContainer 的所有组件都定义在 components 中。组件以键值对的模式写在 components 对象中,键就是组件的 id,值就是组件的定义。

有很多种形式来定义组件,详见下文。

{
    "components":
    {
        "c1": ... // 组件 c1 的定义
        "c2": ... // 组件 c2 的定义
        "c3": ... // 组件 c3 的定义
        ...
    }
}

加载与应用 ByxContainer

在应用程序的初始化代码中,依照以下形式加载配置文件,并初始化容器:

InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("配置文件门路");
ContainerFactory factory = new JsonContainerFactory(inputStream);
Container container = factory.create();

容器初始化实现后,须要应用注册组件时的 id 来获取某个特定组件:

// 获取 id 为 c1 的组件
SomeType c1 = container.getComponent("c1");

组件的创立形式

在 ByxContainer 中,反对以下四种组件创立形式:

  • 常数
  • 构造函数(constructor)
  • 动态工厂(static factory)
  • 实例工厂(instance factor)

常数

ByxContainer 反对 JSON 中的所有根本类型常数,包含整数、浮点数、字符串、布尔、null,它们在 Java 中对应的类型别离是:intdoubleStringbooleannull

{
    "components":
    {
        "intValue": 123,
        "doubleValue": 3.14,
        "stringValue": "hello",
        "trueValue": true,
        "falseValue": false,
        "nullValue": null
    }
}

以上配置申明了几种不同类型的常数组件。

构造函数

配置构造函数创立形式时,须要指定全限定类名,如果要传递参数则指定参数数组。

类型 阐明 是否必须
class 字符串 全限定类名
parameter 数组 结构函数参数
{
    "components":
    {
        "a1":
        {"class": "byx.test.A"},
        "a2":
        {
            "class": "byx.test.A",
            "parameters": ["hello", 123]
        }
    }
}

以上配置等价于以下 Java 代码:

A a1 = new A();
A a2 = new A("hello", 123);

注:parameters数组中的元素既能够是常数,也能够嵌套其它组件的定义:

{
    "components":
    {
        "a3":
        {
            "class": "byx.test.A",
            "parameters": 
            [
                "hello", 
                123, 
                {"class": "byx.test.B"}
            ]
        }
    }
}

以上配置等价于以下 Java 代码:

A a3 = new A("hello", 123, new B());

动态工厂

配置动态工厂创立形式时,须要指定工厂类的类名和工厂函数名,如果要传递参数则指定参数数组。

类型 阐明 是否必须
factory 字符串 工厂类全限定类名
method 字符串 工厂类静态方法名
parameter 数组 办法参数
{
    "components":
    {
        "a1":
        {
            "factory": "byx.test.Factory",
            "method": "createDefault"
        },
        "a2":
        {
            "factory": "byx.test.Factory",
            "method": "create",
            "parameters": ["hello", 123]
        }
    }
}

以上配置等价于以下 Java 代码:

A a1 = Factory.createDefault();
A a2 = Factory.create("hello", 123);

实例工厂

配置实例工厂创立形式时,须要指定实例组件和工厂函数名,如果要传递参数则指定参数数组。

类型 阐明 是否必须
instance 对象或根本类型 实例组件定义
method 字符串 实例工厂办法名
parameter 数组 办法参数
{
    "components":
    {
        "a1":
        {"instance": {"class": "byx.test.B"},
            "method": "createDefault"
        },
        "a2":
        {"instance": {"class": "byx.test.B"},
            "method": "create",
            "parameters": ["hello", 123]
        }
    }
}

以上配置等价于以下 Java 代码:

A a1 = new B().createDefault();
A a2 = new B().create("hello", 123);

组件的依赖申明

申明完组件的创立形式后,还可持续申明组件的依赖。ByxContainer 反对以下两种依赖申明形式:

  • 属性设置
  • setter 办法

属性设置

ByxContainer 反对在组件创立实现之后对组件的属性进行设置。这里的属性等同于 JavaBean 的 Property。

类型 阐明 是否必须
properties 对象 (属性名, 属性值)键值对
{
    "components":
    {
        "a": 
        {
            "class": "byx.test.A",
            "properties":
            {
                "id": 1001,
                "name": "byx",
                "score": 97.5
            }
        }
    }
}

以上配置等价于以下 Java 代码:

A a = new A();
a.setId(1001);
a.setName("byx");
a.setScore(97.5);

注:properties中的属性值既能够是常数,也能够嵌套其它组件的定义:

{
    "components":
    {
        "a": 
        {
            "class": "byx.test.A",
            "properties":
            {
                "id": 1001,
                "b": {"class": "byx.test.B"}
            }
        }
    }
}

以上配置等价于以下 Java 代码:

A a = new A();
a.setId(1001);
a.setB(new B());

setter 办法

ByxContainer 反对在组件创立实现之后调用组件的 setter 办法,并传递参数。

类型 阐明 是否必须
setters 对象 (setter 办法名, 办法参数)键值对
{
    "components":
    {
        "a": 
        {
            "class": "byx.test.A",
            "setters":
            {"setId": [1001],
                "setNameAndScore": ["byx", 97.5]
            }
        }
    }
}

以上配置等价于以下 Java 代码:

A a = new A();
a.setId(1001);
a.setNameAndScore("byx", 97.5);

其它组件定义

ByxContainer 还反对以下非凡组件定义:

  • 汇合组件
  • 援用组件
  • 条件组件

汇合组件

ByxContainer 反对 ListSetMap这三种汇合组件。

上面是这三种汇合组件的定义:

{
    "components":
    {"c1": {"list": [1, 2, 3]},
        "c2": {"set": [4, 5, 6]},
        "c3": 
        {
            "map":
            {
                "k1": 100,
                "k2": 200,
                "k3": 300
            }
        },
        "c4":
        {
            "map":
            [{"key": 400, "value": "v1"},
                {"key": 500, "value": "v2"},
                {"key": 600, "value": "v3"}
            ]
        }
    }
}

以上配置等价于以下 Java 代码:

List<Object> c1 = List.of(1, 2, 3);
Set<Object> c2 = Set.of(4, 5, 6);
Map<Object> c3 = Map.of("k1", 100, "k2", 200, "k3", 300);
Map<Object> c4 = Map.of(400, "v1", 500, "v2", 600, "v3");

注:

  • 汇合组件的元素既能够是常数,也能够嵌套其它组件的定义
  • c3c4Map组件的两种不同定义形式。第一种只反对 String 类型的 key,第二种反对任意数据类型的 key

援用组件

ByxContainer 既反对对全局组件的援用,也反对对部分组件的援用。

类型 阐明 是否必须
ref 字符串 援用的组件 key
{
    "components":
    {"a1": {"class": "byx.test.A"},
        "a2": {"ref": "a1"}
    }
}

以上配置等价于以下 Java 代码:

A a1 = new A();
A a2 = a1;

注:对于部分组件,请参考这里。

条件组件

ByxContainer 反对依据不同条件产生不同的组件定义。

类型 阐明 是否必须
if 对象或根本类型 作为条件的组件定义
then 对象或根本类型 条件为真时产生的组件
else 对象或根本类型 条件为假时产生的组件
{
    "components":
    {
        "flag": true,
        "c":
        {"if": {"ref": "flag"},
            "then": {"class": "com.byx.A"},
            "else": {"class": "com.byx.B"}
        }
    }
}

以上配置等价于以下 Java 代码:

boolean flag = true;
Object c;
if (flag)
    c = new A();
else
    c = new B();

注:当 if 设置成一个组件定义时,容器将把该组件创立的对象解释成 boolean 值,如果该组件创立的对象不是 boolean 类型,则当成 false 解决。

高级个性

ByxContainer 还反对以下高级个性:

  • 部分组件
  • 单例组件
  • 自定义组件
  • 自定义转换器
  • 类型别名

部分组件

有时候,某些组件的创立过程中须要用到其它的组件,如上面这个例子:

A a = new A("hello", "123", new B());

咱们能够依照如下形式来配置:

{
    "components":
    {
        "msg": "hello",
        "val": 123,
        "b": {"class": "byx.test.B"},
        "a":
        {
            "class": "byx.test.A",
            "parameters": [{"ref": "msg"}, {"ref": "val"}, {"ref": "b"}]
        }
    }
}

A 的构造函数中传递的三个参数别离属于三个不同的组件,然而这三个组件仅仅用于构建 A 对象,咱们不心愿这些长期组件占用容器中的一个 key。当然,能够应用一种“就地初始化”的写法:

{
    "components":
    {
        "a":
        {
            "class": "byx.test.A",
            "parameters": 
            [
                "hello", 
                123, 
                {"class": "byx.test.B"}
            ]
        }
    }
}

这种写法能够暗藏长期组件,然而当组件创立过程比较复杂的时候,这种写法会造成深层次的嵌套构造,导致可读性变差。而且,这种写法也不能很好地应答某个长期组件被屡次应用的状况,例如:

B b = new B();
A a = new A(b, b);

以上问题能够应用 ByxContainer 提供的部分组件来解决:

{
    "components":
    {
        "a":
        {
            "class": "byx.test.A",
            "parameters": [{"ref": "msg"}, {"ref": "val"}, {"ref": "b"}],
            "locals":
            {
                "msg": "hello",
                "val": 123,
                "b": {"class": "byx.test.B"}
            }
        }
    }
}

a 组件定义的外部有一个 locals 块,外面蕴含了属于 a 的部分组件定义,这些部分组件不会占用容器的全局 key,它只能在组件 a 的作用域内被援用。如果部分组件的 key 与全局组件的 key 雷同,则部分组件将会笼罩全局组件。

单例组件

在默认状况下,ByxContainer 中的所有组件都是单例的,这意味着组件最多只会被创立一次。当你屡次获取同一个组件时,所失去的是同一个对象实例:

Object c1 = container.getComponent("c1");
Object c2 = container.getComponent("c2");
// c1 与 c2 指向同一个对象

如果想要扭转这个默认行为,只须要在组件定义中退出如下配置:

"singleton": false

自定义组件

ByxContainer 反对用户定义本人的组件,并在配置文件中应用。

用户能够通过实现 byx.container.component.Component 接口来创立本人的组件:

public interface Component
{Object create();
}

Component接口中的 create 办法封装了对象创立的细节:

public class MyComponent implements Component
{
    private final String msg;
    private final int val;

    public MyComponent()
    {MyComponent("hello", 123);
    }

    public MyComponent(String msg, int val)
    {
        this.msg = msg;
        this.val = val;
    }

    @Override
    public Object create()
    {return new A(msg, val);
    }
}

在配置文件中应用自定义组件,须要指定自定义组件的全限定类名,如果想传递结构函数参数,则需指定参数列表。

类型 阐明 是否必须
custom 字符串 自定义组件的全限定类名
parameters 数组 传递给自定义组件的结构函数参数
{
    "components":
    {
        "a1":
        {"custom": "byx.test.MyComponent"},
        "a2":
        {
            "custom": "byx.test.MyComponent",
            "parameters": ["hi", 456]
        }
    }
}

以上配置等价于以下 Java 代码:

A a1 = new MyComponent().create();
A a2 = new MyComponent("hi", 456).create();

自定义转换器

ByxContainer 反对对组件创立进去的对象进行进一步转换。用户须要通过实现 byx.container.component.Mapper 接口来创立自定义转换器:

public interface Mapper
{Object map(Object obj);
}

Mapper接口中的 map 办法封装了对组件创立后果的解决:

public class MyMapper implements Mapper
{
    private final int val;

    public MyMapper()
    {MyMapper(1);
    }

    public MyMapper(int val)
    {this.val = val;}

    @Override
    public Object map(Object obj)
    {if (obj instanceof Integer)
            return ((int) obj) + val;
        return obj;
    }
}

在配置文件中应用自定义转换器,须要在组件定义中指定 mapper 属性:

  • mapper 属性为字符串时,该字符串示意自定义 mapper 的全限定类名,同时用默认构造函数来创立该mapper
  • mapper 属性为对象时,对象中必须蕴含 classparameters属性,其中 class 示意 mapper 的全限定类名,parameters示意创立 mapper 时向构造函数传递的参数
{
    "components":
    {
        "c1":
        {
            "class": "java.lang.Integer",
            "parameters": [123],
            "mapper": "byx.test.MyMapper"
        },
        "c1":
        {
            "class": "java.lang.Integer",
            "parameters": [123],
            "mapper":
            {
                "class": "byx.test.MyMapper",
                "parameters": [100]
            }
        }
    }
}

以上配置等价于以下 Java 代码:

int c1 = (int) new MyMapper().map(container.getComponent("c1"));
int c2 = (int) new MyMapper(100).map(container.getComponent("c2"));

类型别名

为了防止键入大量简短的全限定类名,ByxContainer 提供了类型别名性能,只需在容器定义最外层增加 typeAlias 配置:

{
    "typeAlias":
    {
        "A": "byx.test.A",
        "B": "byx.test.B"
    },
    "components":
    {
        "a":
        {
            "class": "A",
            "parameters": ["hello", 123]
        },
        "b":
        {"class": "B"}
    }
}

以上配置等价于以下 Java 代码:

A a = new A("hello", 123);
B b = new B();

注:components中所有呈现类名的中央都能够应用 typeAlias 中定义的别名,包含 classfactorycustom 等等。

正文完
 0