最近在完成一个小小的framework项目,由于项目中不使用spring,juice,自己实现了一个简易的依赖注入框架。
1. 写一个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>
· 创建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</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. 解析的过程中,需要生成被依赖注入的对象,并完成属性的动态设定。
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.总结
主要是项目是基于接口设计的,所以一些类的实现需要在配置文件里设定,实现类的实例属性也要是可以扩展的,并且提供属性值的逻辑校验,所以就有了这么一个东东。
源代码: