来自 http://www.blogjava.net/ldd600/archive/2008/09/25/231171.html
最近在完成一个小小的framework项目,由于项目中不使用spring,juice,自己实现了一个简易的依赖注入框架。
- 写一个xml文件作为配置的实际例子
<?
xml version="1.0" encoding="UTF-8"
?>
<
beans
xmlns
="http://www.ldd600.com/beanIoc"
xmlns:xsi
="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation
="http://www.ldd600.com/beanIoc beanIoc.xsd"
>
<
bean
id
="ftpSend"
class
="com.ldd600.bean.ioc.beans.impl.FTPTransporter"
>
<
params
>
<
param
value
="/ldd600"
key
="uploadFolder"
></
param
>
<
param
value
="com.ldd600.bean.ioc.beans.impl.JugUUIDGenerator"
key
="generator"
converter
="com.ldd600.bean.ioc.converters.UUIDGeneratorConverter"
></
param
>
</
params
>
</
bean
>
<
bean
id
="jmsSend"
class
="com.ldd600.bean.ioc.beans.impl.JMSTransporter"
>
<
params
>
<
param
value
="Ldd600Queue"
key
="destName"
></
param
>
</
params
>
</
bean
>
</
beans
>
2.根据这个
xml
文件,定义一个
schema
文件,验证配置文件
<?
xml version="1.0" encoding="UTF-8"
?>
<
xsd:schema
xmlns:sample
="http://www.ldd600.com/beanIoc"
xmlns:xsd
="http://www.w3.org/2001/XMLSchema"
targetNamespace
="http://www.ldd600.com/beanIoc"
elementFormDefault
="qualified"
attributeFormDefault
="unqualified"
>
<
xsd:complexType
name
="paramsType"
>
<
xsd:annotation
>
<
xsd:documentation
>
of java class
</
xsd:documentation
>
</
xsd:annotation
>
<
xsd:sequence
maxOccurs
="unbounded"
>
<
xsd:element
name
="param"
>
<
xsd:complexType
>
<
xsd:annotation
>
<
xsd:documentation
>
key is property name, value is property value
</
xsd:documentation
>
</
xsd:annotation
>
<
xsd:simpleContent
>
<
xsd:extension
base
="sample:leafType"
>
<
xsd:attribute
name
="key"
type
="sample:notEmptyToken"
use
="required"
/>
<
xsd:attribute
name
="value"
type
="sample:notEmptyToken"
use
="required"
/>
<
xsd:attribute
name
="converter"
type
="sample:classAttributeType"
use
="optional"
/>
</
xsd:extension
>
</
xsd:simpleContent
>
</
xsd:complexType
>
</
xsd:element
>
</
xsd:sequence
>
</
xsd:complexType
>
<
xsd:complexType
name
="idClassType"
>
<
xsd:all
minOccurs
="0"
>
<
xsd:element
name
="params"
type
="sample:paramsType"
>
<
xsd:key
name
="outerParamsKey"
>
<
xsd:selector
xpath
="sample:param"
/>
<
xsd:field
xpath
="@key"
/>
</
xsd:key
>
</
xsd:element
>
</
xsd:all
>
<
xsd:attribute
name
="id"
type
="sample:notEmptyToken"
use
="required"
/>
<
xsd:attribute
name
="class"
type
="sample:classAttributeType"
>
<
xsd:annotation
>
<
xsd:documentation
>
class name
</
xsd:documentation
>
</
xsd:annotation
>
</
xsd:attribute
>
</
xsd:complexType
>
<
xsd:simpleType
name
="leafType"
>
<
xsd:restriction
base
="xsd:string"
>
<
xsd:pattern
value
="(/s)*"
/>
</
xsd:restriction
>
</
xsd:simpleType
>
<
xsd:simpleType
name
="classAttributeType"
>
<
xsd:restriction
base
="sample:notEmptyToken"
>
<
xsd:pattern
value
="([a-z|0-9|_]+/.)*[A-Z]([A-Z|a-z|0-9|_])*"
/>
</
xsd:restriction
>
</
xsd:simpleType
>
<
xsd:simpleType
name
="notEmptyToken"
>
<
xsd:restriction
base
="xsd:token"
>
<
xsd:pattern
value
="(/S+/s*)+"
/>
</
xsd:restriction
>
</
xsd:simpleType
>
<
xsd:element
name
="beans"
>
<
xsd:complexType
>
<
xsd:sequence
maxOccurs
="unbounded"
>
<
xsd:element
name
="bean"
type
="sample:idClassType"
/>
</
xsd:sequence
>
</
xsd:complexType
>
<
xsd:key
name
="beanIdkey"
>
<
xsd:selector
xpath
="./sample:bean"
></
xsd:selector
>
<
xsd:field
xpath
="@id"
></
xsd:field
>
</
xsd:key
>
</
xsd:element
>
</
xsd:schema
>
类型
| 解释
|
paramType
| 表示类的字段,key是字段名,value字段值,converter如果有用来将String转换为所需要的实例。
|
idClassType
| 用来表示bean实例,并且该实例有唯一的id
|
notEmptyToken
| 非空token
|
leafType
| 叶子类型比如<a> </a>,中间允许有space,比如空格、换行符等,但是不能出现其他任何字符。
|
classAttributeType
| 负责java中class全名的string类型,比如com.ldd600.BeanClass
|
- 解析这个文件,并marshall成java对象,用xmlbeans实现
使用xmlbeans生成schema对应的java类和xmlbeans jaxb需要的元数据。
· 创建xsdConfig文件,内容主要是生成的java类的package名,类simple name的前缀和后缀
<
xb:config
xmlns:xb
=
"http://xml.apache.org/xmlbeans/2004/02/xbean/config"
>
<
xb:namespace
uri
="http://www.ldd600.com/beanIoc"
>
<
xb:package
>
com.ldd600.bean.ioc.config.jaxb
</
xb:package
>
</
xb:namespace
>
<
xb:namespace
uriprefix
="http://www.ldd600.com/beanIoc"
>
<
xb:prefix
>
Xml
</
xb:prefix
>
</
xb:namespace
>
<
xb:namespace
uriprefix
="http://www.ldd600.com/beanIoc"
>
<
xb:suffix
>
Bean
</
xb:suffix
>
</
xb:namespace
>
</
xb:config
>
· 创建一个bat可执行文件,不用每次跑都去cmd下敲一堆命令,累啊。bat文件的内容:
scomp -d classes/beanIoc -src src/beanIoc -out beanIoc.jar beanIoc.xsd beanIoc.xsdconfig
· 双击bat,所需要的java类的.java文件,class文件和xmlbeans jaxb需要的元数据都横空出世了,这些元数据xmlbeans会用来对xml文件进行验证,检查是否符合schema定义的语义规则。
· 把java文件和元数据信息都拷贝到eclipse中吧,或者直接把生成的jar包发布到maven repository中,然后再pom中dependency它。
- 解析的过程中,需要生成被依赖注入的对象,并完成属性的动态设定。
4.1 生成依赖注入的对象
生成依赖注入的对象是通过反射直接生成类的实例,在这里要求有public的参数为空的构造函数。
Class clazz
=
Class.forName(sNamclassNamee);

return
clazz.newInstance();
4.2 属性的动态设定
Commons-BeanUtils就是专门免费做这件事的好同志,我们可以利用它来完成,基本类型和一些经常使用的类型,Commons-BeanUtils责无旁贷的提供了自动转换的功能,beanutils不需要我们提供参数的类型,它可以自动完成转换,它是根据get和set方法的类型来决定类型的,可参见PropertyDescriptor.getPropertyType()方法。使用方法如下:
if
(
!
PropertyUtils.isWriteable(object, key))
{
throw new ConfigParseException(object.getClass()
+ " doesn't have the property " + key
+ "'s setter method!");
}
String paramVal
=
paramBean.getValue();
BeanUtils.setProperty(object, key, paramVal);
isWriteable方法判断是否有可用的set方法,如果有就完成属性的动态设置。paramBean就是xml文件中定义的那个param。
但是Beanutils默认帮我们转换的类型为基本类型和所有它已经提供了Converter的class类型,如果我们有特殊的类需要进行动态设定,必须自己提供converter,注册到它的converters map中。这样beanutils兄弟在动态设定属性值的时候,就会根据属性的类型去converter map中把取出该属性类型对应的自定义converter来转换。因为在这里配置文件中配置的都是String, 所以我们对Converter接口做了修改:
public
abstract
class
BeanConverter
implements
Converter
{
public abstract Object convert(Class type, String value) throws ConfigParseException;


public Object convert(Class type, Object value)
{
return this.convert(type, (String)value);
}
}
我们强制规定了convert方法的参数必须是String,自己提供的converter必须继承BeanConverter抽象类。
String key
=
paramBean.getKey();
String converterClsName
=
paramBean.getConverter();

if
(StringUtils.isNotEmpty(converterClsName))
{
Class converterClazz = Class.forName(converterClsName);
if (!BeanConverter.class

.isAssignableFrom(converterClazz))
{
throw new ConfigParseException(
"converter must extend BeanConverter!");
}

if(!this.converters.containsKey(converterClsName))
{
this.converters.put(converterClsName, converterClazz);
// get property type
Class propertyClazz = PropertyUtils.getPropertyType(
object, key);
// register converter
ConvertUtils.register((Converter) converterClazz
.newInstance(), propertyClazz);
}
}
}
4.3属性逻辑规则的检查
在设置好属性以后,这个属性的值并不一定配置的正确,也不一定满足逻辑规则,比如希望int值在3到5之间,比如希望String 不要为空等等。为了在动态设定完属性后进行逻辑规则的校验,提供了InitializingBean接口

public
interface
InitializingBean
{
void afterPropertiesSet() throws Exception;
}
实现该接口的类,它们的逻辑规则需要在afterProperties提供,不符合规则的请抛异常,并会在属性设定完后检查
public
void
afterPropertiesSet()
throws
Exception
{

if (StringUtils.isEmpty(uploadFolder))
{
throw new IllegalArgumentException(
"upload folder is an empty string!");
}

if (null == generator)
{
throw new IllegalArgumentException("generator is null!");
}
}
if
(object
instanceof
InitializingBean)
{
((InitializingBean) object).afterPropertiesSet();
}
4.4将bean注册到BeanContext中
String id
=
idClassTypeBean.getId();
BeanContextFactory.getBeanContext().setBean(id, object);
4.5清理环境
完成属性的动态注入后,还需要清理环境
private
void
cleanConfig()
{
ConvertUtils.deregister();
this.converters = null;
}
5.如何做到基于接口设计
· Converter提供了基于接口设计的功能:我们可以动态的设置不同的实现。
· 用了该框架,本身就基于接口,我们可以在配置文件中修改bean的实现类。应用程序代码它不关心具体的实现类,它只关心id
Transporter transporter
=
(Transporter) BeanContextFactory.getBeanContext().getBean(TransporterParser.getTransportName());
不过,这里没有象spring和juice那么强大的bean factory功能。因为这个东东只是一个小项目的一小部分,所以功能上满足小项目的需求就足够了。
6. Test
就简单的测了一下,可以看源代码。
7.总结
主要是项目是基于接口设计的,所以一些类的实现需要在配置文件里设定,实现类的实例属性也要是可以扩展的,并且提供属性值的逻辑校验,所以就有了这么一个东东。