走自己的路

路漫漫其修远兮,吾将上下而求索

  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  50 随笔 :: 4 文章 :: 118 评论 :: 0 Trackbacks

 

最近在完成一个小小的framework项目,由于项目中不使用spring,guice,自己实现了一个简易的依赖注入框架。

 

  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>


 

类型

解释

paramType

表示类的字段,key是字段名,value字段值,converter如果有用来将String转换为所需要的实例。

idClassType

用来表示bean实例,并且该实例有唯一的id

notEmptyToken

非空token

leafType

叶子类型比如<a> </a>,中间允许有space,比如空格、换行符等,但是不能出现其他任何字符。

classAttributeType

负责javaclass全名的string类型,比如com.ldd600.BeanClass



 

 

  1. 解析这个文件,并marshalljava对象,用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中,然后再pomdependency它。


 
  1. 解析的过程中,需要生成被依赖注入的对象,并完成属性的动态设定。

4.1   生成依赖注入的对象

生成依赖注入的对象是通过反射直接生成类的实例,在这里要求有public的参数为空的构造函数。

    

  Class clazz = Class.forName(sNamclassNamee);

            
return clazz.newInstance();

 

 

4.2  属性的动态设定

Commons-BeanUtils就是专门免费做这件事的好同志,我们可以利用它来完成,基本类型和一些经常使用的类型,Commons-BeanUtils责无旁贷的提供了自动转换的功能,beanutils不需要我们提供参数的类型,它可以自动完成转换,它是根据getset方法的类型来决定类型的,可参见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默认帮我们转换的类型为基本类型和所有它已经提供了Converterclass类型,如果我们有特殊的类需要进行动态设定,必须自己提供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值在35之间,比如希望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.4bean注册到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());

 

不过,这里没有象springjuice那么强大的bean factory功能。因为这个东东只是一个小项目的一小部分,所以功能上满足小项目的需求就足够了。

 

6. Test

 就简单的测了一下,可以看源代码。

 

7.总结

主要是项目是基于接口设计的,所以一些类的实现需要在配置文件里设定,实现类的实例属性也要是可以扩展的,并且提供属性值的逻辑校验,所以就有了这么一个东东。

beanIoc源代码



posted on 2008-09-25 19:16 叱咤红人 阅读(2140) 评论(4)  编辑  收藏

评论

# re: 利用commons-BeanUtils实现简易的依赖注入框架 2008-09-26 09:10 Jarod Liu
既然是在仿spring,个人觉得没必要“不使用spring",只使用spring的ioc就OK了。
自己做一个框架,多人项目里别人的学习成本更高  回复  更多评论
  

# re: 利用commons-BeanUtils实现简易的依赖注入框架 2008-09-26 09:36 叱咤红人
@Jarod Liu
已有的framework经过评估,如果可以用,减少开发成本,我觉得也应该利用。
在这里遇到了一些情况,其实本来应用的环境并不象文章中的那样子,配置文件并不是bean什么的,因为想总结一下,就改成了bean。原来的配置文件是有自己语义的,下面是截取的一部分,

<receiver:app receiverId="gps">
<receiver:decorators>
<receiver:decorator class="com.ldd600.frm.mif.receiver.decorator.impl.mf.JAXBXmlBeansMFReceiverDecorator">
<receiver:params>
<receiver:param key="xmlDocClassName" value="com.ldd600.frm.mif.test.jaxb.tms.jo.XmlJobOrderDocumentBean"/>
</receiver:params>
</receiver:decorator>
</receiver:decorators>
<receiver:converter class="com.ldd600.frm.mif.test.receiver.converter.GPSJOVer2_0Converter">
</receiver:converter>
<receiver:processor class="com.ldd600.frm.mif.test.receiver.processor.GPSJOProcessor">
</receiver:processor>
</receiver:app>

这个配置文件是有自己语义的,并不需要bean什么的。虽然spring提供了namespacehandler,但namespacehandler用起来也有成本的,而且还要自己注册到spring的context中。这里只是为了增强一些基于接口的扩展功能怕麻烦就简单的用了一下beanutils,用spring beans module肯定是可以的。
而且,项目本来就有自己的配置文件,所以也不想再增加额外的配置文件。也不可能完全的就把这个配置整合到spring的applicationContext.xml中。  回复  更多评论
  

# re: 利用commons-BeanUtils实现简易的依赖注入框架 2008-09-28 08:49 隔叶黄莺
都做这么复杂了,为何不用 spring,用 spring 无疑可以减少学习和维护成本。  回复  更多评论
  

# re: 利用commons-BeanUtils实现简易的依赖注入框架[未登录] 2008-10-27 13:29 attend
支持做一些小轮子,自己写的自己清楚,还好维护点。  回复  更多评论
  


只有注册用户登录后才能发表评论。


网站导航: