自制IOC容器(3)

本系列文章介绍ByxContainer的实现思路。

ByxContainer是一个简单的轻量级IOC容器,具有以下特性:

  • 使用JSON格式的配置文件
  • 支持构造函数注入、静态工厂注入、实例工厂注入、属性注入、setter注入、条件注入
  • 组件的延迟加载和单例组件
  • 根据id注册、获取容器中的组件

ByxContainer的设计借鉴了ajoo大神的博客

项目地址:github 码云

本篇文章介绍ByxContainer中与对象初始化有关的设计。

回顾

在上一篇文章中,我们解决了对象创建的问题,但是在实际开发中,我们把一个对象创建出来后,还需要做一些其它的事情来完成对象的初始化:

Student s = new Student();
s.setName("XiaoMing");
s.setAge(17);
s.setScore(87.5);

在上面的代码中,使用默认构造函数创建了一个Student对象,在对象创建出来之后,还设置了Studentnameagescore这三个属性。那么,这种需求要怎么在ByxContainer中实现呢?

Mapper接口和MapperComponent

如果仅仅使用上一篇文章实现的ConstructorComponentStaticFactoryComponentInstanceFactoryComponent,是无法实现这个需求的,因为这几个Component最多只能表示如何将对象创建出来,但是在对象创建出来之后,我们还希望对这个对象进行更多的设置,比如调用setter方法设置属性,或者调用初始化方法等等。

首先,我们需要一个东西来封装对一个对象的操作:

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

Mapper接口是一个十分通用的接口,表示转换操作,它的map方法接受某个对象,然后返回经过处理后的对象。

接下来实现一个MapperComponent

public class MapperComponent implements Component
{
    private final Component component;
    private final Mapper mapper;

    public MapperComponent(Component component, Mapper mapper)
    {
        this.component = component;
        this.mapper = mapper;
    }

    @Override
    public Object create()
    {
        return mapper.map(component.create());
    }
}

MapperComponent创建时需要传入一个ComponentMapperMapperComponent可以在上一个Component的基础上,通过调用Mappermap方法,对上一个Component生成的对象进行进一步处理。

然后可以在Component中实现几个默认方法:

public interface Component
{
    ...
    default Component map(Mapper mapper)
    {
        return new MapperComponent(this, mapper);
    }

    default Component setProperty(String property, Component value)
    {
        return this.map(obj ->
        {
            // 获取属性值
            Object v = value.create();

            // 调用JavaBean的API将obj的property属性设置为v
            ...
        });
    }

    default Component invokeSetter(String setter, Component... params)
    {
        return this.map(obj ->
        {
            // 获取参数值
            Object[] p = Arrays.stream(params).map(Component::create).toArray();
            
            // 反射调用obj的setter方法,并传递参数
            ...
        });
    }
}

把这些常用操作封装成Component接口的默认方法后,所有的Component都能以链式调用的形式来组合这些操作,详见下面的使用示例。

setProperty用于声明对象属性的设置,property是属性名,value是用于生成属性值的组件。注意,这里value的类型是Component而不是Object,因为属性值可能是一个组件。

invokeSetter用于声明调用对象的setter方法,setter是setter方法名,params是传递的参数,setter方法的参数同样也可以是组件,所以params的类型为Component[]

实际上,设置属性也是通过调用相应的setter方法实现的,setPropertyinvokeSetter的区别在于,某些setter方法可以同时设置两个属性,如obj.setNameAndAge("XiaoMing", 17);另外,invokeSetter也可以用来调用某些初始化方法,如obj.init(...)

使用ByxContainer

到这里,对象初始化的需求就完成得差不多了,本篇文章实现的对象初始化方式结合上一篇文章实现的对象创建一起使用,可以表达十分多样的需求。下面给出一些使用示例,以加深理解:

/*
    Student s = new Student();
    s.setId(1001);
    s.setName("XiaoMing");
    s.setAge(17);
    s.setScore(87.5);
*/
Component s = constructor(Student.class)
        .setProperty("name", value("XiaoMing"))
        .setProperty("age", value(17))
        .setProperty("score", value(87.5));

/*
    Student s = new Student();
    s.setNameandAge("XiaoMing", 17);
    s.setScore(87.5);
*/
Component s = constructor(Student.class)
        .invokeSetter("setNameAndAge", value("XiaoMing"), value(17))
        .invokeSetter("setScore", value(87.5));

/*
    StringBuilder builder = new StringBuilder();
    builder.append("hello");
    builder.append(" world");
    String str = builder.toString();
*/
Component builder = constructor(StringBuilder.class)
        .invokeSetter("append", value("hello"))
        .invokeSetter("append", value(" world"));
Component str = instanceFactory(builder, "toString");
上一篇:前端学习笔记 - vue数据双向绑定的原理,用了什么设计模式?vue删除数据监测不到?


下一篇:字符串格式化输出方法&各种对齐&%s,format,f""