自制IOC容器(3)
本系列文章介绍ByxContainer的实现思路。
ByxContainer是一个简单的轻量级IOC容器,具有以下特性:
- 使用JSON格式的配置文件
- 支持构造函数注入、静态工厂注入、实例工厂注入、属性注入、setter注入、条件注入
- 组件的延迟加载和单例组件
- 根据id注册、获取容器中的组件
ByxContainer的设计借鉴了ajoo大神的博客。
本篇文章介绍ByxContainer中与对象初始化有关的设计。
回顾
在上一篇文章中,我们解决了对象创建的问题,但是在实际开发中,我们把一个对象创建出来后,还需要做一些其它的事情来完成对象的初始化:
Student s = new Student();
s.setName("XiaoMing");
s.setAge(17);
s.setScore(87.5);
在上面的代码中,使用默认构造函数创建了一个Student
对象,在对象创建出来之后,还设置了Student
的name
、age
、score
这三个属性。那么,这种需求要怎么在ByxContainer中实现呢?
Mapper接口和MapperComponent
如果仅仅使用上一篇文章实现的ConstructorComponent
、StaticFactoryComponent
、InstanceFactoryComponent
,是无法实现这个需求的,因为这几个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
创建时需要传入一个Component
和Mapper
。MapperComponent
可以在上一个Component
的基础上,通过调用Mapper
的map
方法,对上一个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方法实现的,setProperty
与invokeSetter
的区别在于,某些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");