最近在完成一個(gè)小小的framework項(xiàng)目,由于項(xiàng)目中不使用spring,guice,自己實(shí)現(xiàn)了一個(gè)簡(jiǎn)易的依賴注入框架。
- 寫(xiě)一個(gè)xml文件作為配置的實(shí)際例子
<?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.根據(jù)這個(gè)xml文件,定義一個(gè)schema文件,驗(yàn)證配置文件
<?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>

類(lèi)型
|
解釋
|
paramType
|
表示類(lèi)的字段,key是字段名,value字段值,converter如果有用來(lái)將String轉(zhuǎn)換為所需要的實(shí)例。
|
idClassType
|
用來(lái)表示bean實(shí)例,并且該實(shí)例有唯一的id
|
notEmptyToken
|
非空token
|
leafType
|
葉子類(lèi)型比如<a> </a>,中間允許有space,比如空格、換行符等,但是不能出現(xiàn)其他任何字符。
|
classAttributeType
|
負(fù)責(zé)java中class全名的string類(lèi)型,比如com.ldd600.BeanClass
|
- 解析這個(gè)文件,并marshall成java對(duì)象,用xmlbeans實(shí)現(xiàn)
使用xmlbeans生成schema對(duì)應(yīng)的java類(lèi)和xmlbeans jaxb需要的元數(shù)據(jù)。
· 創(chuàng)建xsdConfig文件,內(nèi)容主要是生成的java類(lèi)的package名,類(lèi)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>
· 創(chuàng)建一個(gè)bat可執(zhí)行文件,不用每次跑都去cmd下敲一堆命令,累啊。bat文件的內(nèi)容:
scomp -d classes\beanIoc -src src\beanIoc -out beanIoc.jar beanIoc.xsd beanIoc.xsdconfig
· 雙擊bat,所需要的java類(lèi)的.java文件,class文件和xmlbeans jaxb需要的元數(shù)據(jù)都橫空出世了,這些元數(shù)據(jù)xmlbeans會(huì)用來(lái)對(duì)xml文件進(jìn)行驗(yàn)證,檢查是否符合schema定義的語(yǔ)義規(guī)則。
· 把java文件和元數(shù)據(jù)信息都拷貝到eclipse中吧,或者直接把生成的jar包發(fā)布到maven repository中,然后再pom中dependency它。
- 解析的過(guò)程中,需要生成被依賴注入的對(duì)象,并完成屬性的動(dòng)態(tài)設(shè)定。
4.1 生成依賴注入的對(duì)象
生成依賴注入的對(duì)象是通過(guò)反射直接生成類(lèi)的實(shí)例,在這里要求有public的參數(shù)為空的構(gòu)造函數(shù)。
Class clazz = Class.forName(sNamclassNamee);

return clazz.newInstance();

4.2 屬性的動(dòng)態(tài)設(shè)定
Commons-BeanUtils就是專(zhuān)門(mén)免費(fèi)做這件事的好同志,我們可以利用它來(lái)完成,基本類(lèi)型和一些經(jīng)常使用的類(lèi)型,Commons-BeanUtils責(zé)無(wú)旁貸的提供了自動(dòng)轉(zhuǎn)換的功能,beanutils不需要我們提供參數(shù)的類(lèi)型,它可以自動(dòng)完成轉(zhuǎn)換,它是根據(jù)get和set方法的類(lèi)型來(lái)決定類(lèi)型的,可參見(jiàn)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方法,如果有就完成屬性的動(dòng)態(tài)設(shè)置。paramBean就是xml文件中定義的那個(gè)param。
但是Beanutils默認(rèn)幫我們轉(zhuǎn)換的類(lèi)型為基本類(lèi)型和所有它已經(jīng)提供了Converter的class類(lèi)型,如果我們有特殊的類(lèi)需要進(jìn)行動(dòng)態(tài)設(shè)定,必須自己提供converter,注冊(cè)到它的converters map中。這樣beanutils兄弟在動(dòng)態(tài)設(shè)定屬性值的時(shí)候,就會(huì)根據(jù)屬性的類(lèi)型去converter map中把取出該屬性類(lèi)型對(duì)應(yīng)的自定義converter來(lái)轉(zhuǎn)換。因?yàn)樵谶@里配置文件中配置的都是String, 所以我們對(duì)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);
}
}

我們強(qiáng)制規(guī)定了convert方法的參數(shù)必須是String,自己提供的converter必須繼承BeanConverter抽象類(lèi)。
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屬性邏輯規(guī)則的檢查
在設(shè)置好屬性以后,這個(gè)屬性的值并不一定配置的正確,也不一定滿足邏輯規(guī)則,比如希望int值在3到5之間,比如希望String 不要為空等等。為了在動(dòng)態(tài)設(shè)定完屬性后進(jìn)行邏輯規(guī)則的校驗(yàn),提供了InitializingBean接口


public interface InitializingBean
{
void afterPropertiesSet() throws Exception;
}
實(shí)現(xiàn)該接口的類(lèi),它們的邏輯規(guī)則需要在afterProperties提供,不符合規(guī)則的請(qǐng)拋異常,并會(huì)在屬性設(shè)定完后檢查

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注冊(cè)到BeanContext中
String id = idClassTypeBean.getId();
BeanContextFactory.getBeanContext().setBean(id, object);

4.5清理環(huán)境
完成屬性的動(dòng)態(tài)注入后,還需要清理環(huán)境

private void cleanConfig()
{
ConvertUtils.deregister();
this.converters = null;
}

5.如何做到基于接口設(shè)計(jì)
· Converter提供了基于接口設(shè)計(jì)的功能:我們可以動(dòng)態(tài)的設(shè)置不同的實(shí)現(xiàn)。
· 用了該框架,本身就基于接口,我們可以在配置文件中修改bean的實(shí)現(xiàn)類(lèi)。應(yīng)用程序代碼它不關(guān)心具體的實(shí)現(xiàn)類(lèi),它只關(guān)心id
Transporter transporter = (Transporter) BeanContextFactory.getBeanContext().getBean(TransporterParser.getTransportName());
不過(guò),這里沒(méi)有象spring和juice那么強(qiáng)大的bean factory功能。因?yàn)檫@個(gè)東東只是一個(gè)小項(xiàng)目的一小部分,所以功能上滿足小項(xiàng)目的需求就足夠了。
6. Test
就簡(jiǎn)單的測(cè)了一下,可以看源代碼。
7.總結(jié)
主要是項(xiàng)目是基于接口設(shè)計(jì)的,所以一些類(lèi)的實(shí)現(xiàn)需要在配置文件里設(shè)定,實(shí)現(xiàn)類(lèi)的實(shí)例屬性也要是可以擴(kuò)展的,并且提供屬性值的邏輯校驗(yàn),所以就有了這么一個(gè)東東。
beanIoc源代碼