﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-kapok-随笔分类-Struts</title><link>http://www2.blogjava.net/kapok/category/984.html</link><description>垃圾桶,嘿嘿，我藏的这么深你们还能找到啊，真牛！</description><language>zh-cn</language><lastBuildDate>Wed, 28 Feb 2007 03:26:04 GMT</lastBuildDate><pubDate>Wed, 28 Feb 2007 03:26:04 GMT</pubDate><ttl>60</ttl><item><title>struts源代码阅读（struts 初始化）</title><link>http://www.blogjava.net/kapok/archive/2005/05/02/3987.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Mon, 02 May 2005 01:28:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/05/02/3987.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/3987.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/05/02/3987.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/3987.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/3987.html</trackback:ping><description><![CDATA[<FONT face=宋体><A href="http://www.matrix.org.cn/article/1071.html">http://www.matrix.org.cn/article/1071.html</A><BR>我在几个月前曾经发表过一个帖子，就是和大家一起学习struts<BR>源代码。成为一名合格的程序员，阅读大量的优秀程序是必不可少的。只看书是不会让你水平有很大提高的，要多看多写。<BR>本来是打算等下面几篇文章写好后一起发布的，这样大家可能才能看得明白些，但是根据我现在的状况，估计还要一、两个月。所以，为了防止在struts源代码发生过大变化后我的文章就没有太大价值了，所以就提前发表了，霍霍~~~<BR>我的email为：mariah_fan@hotmail.com，有什么不对的地方请大家指正：）<BR>struts作为J2EE的MVC框架已经取得了很大的成功，下面将分几篇文章说明struts源程序的结构。<BR>第一篇&nbsp;&nbsp;struts的初始化<BR><BR>struts&nbsp;的核心类是org.apache.struts.action.ActionServlet，这个类将会在struts第一次使用时，<BR>作为servlet初始化并存入tomcat容器。很显然的，初始化将会调用init方法初始化相应的数据。<BR><BR>一、initInternal()方法：<BR>&nbsp;&nbsp;&nbsp;&nbsp;通过调用MessageResources.getMessageResources(internalName)方法生成一个<BR>&nbsp;&nbsp;&nbsp;&nbsp;MessageResources类，getMessageResources是通过调用MessageResourcesFactory.<BR>&nbsp;&nbsp;&nbsp;&nbsp;createResources(config)来实现的。至于MessageResourcesFactory是一个abstract类，任何<BR>&nbsp;&nbsp;&nbsp;&nbsp;继承自它的类都要实现createResources方法，生成MessageResources对象。整个程序生成<BR>&nbsp;&nbsp;&nbsp;&nbsp;MessageResourcesFactory使用了如下技巧：<BR>&nbsp;&nbsp;&nbsp;&nbsp;MessageResourcesFactory.factoryClass&nbsp;=&nbsp;factoryClass;<BR>&nbsp;&nbsp;&nbsp;&nbsp;MessageResourcesFactory.clazz&nbsp;=&nbsp;null;<BR>&nbsp;&nbsp;&nbsp;&nbsp;首先会通过factoryClass来定义一个类全名，然后通过ClassLoader.loadClass<BR>&nbsp;&nbsp;&nbsp;&nbsp;(factoryClass)方法来生成这个类，并赋给clazz，然后通过newInstance来生成一个对象。<BR>&nbsp;&nbsp;&nbsp;&nbsp;在本程序中，生成MessageResources对象实际就是对如下属性进行了初始化：<BR>&nbsp;&nbsp;&nbsp;&nbsp;this.factory&nbsp;=&nbsp;factory;("org.apache.struts.util.PropertyMessageResourcesFactory")<BR>&nbsp;&nbsp;&nbsp;&nbsp;this.config&nbsp;=&nbsp;config;("org.apache.struts.action.ActionResources")<BR>&nbsp;&nbsp;&nbsp;&nbsp;this.returnNull&nbsp;=&nbsp;returnNull;(true/false)<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;对于MessageResources类的作用是根据不同的Locate来格式化相应的string。或者把你需要改变<BR>&nbsp;&nbsp;&nbsp;&nbsp;的string存放到数组中，然后通过getMessage(Locale&nbsp;locale,&nbsp;String&nbsp;key,&nbsp;Object&nbsp;args[])<BR>&nbsp;&nbsp;&nbsp;&nbsp;方法来格式化。然后把格式好的string存放到HashMap里，这样就可以为以后重用。这里的key是<BR>&nbsp;&nbsp;&nbsp;&nbsp;使用的locale.toString()&nbsp;+&nbsp;"."&nbsp;+&nbsp;key<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;在PropertyMessageResources中的loadLocale方法用来读取resource的初始化信息。首先它会<BR>&nbsp;&nbsp;&nbsp;&nbsp;通过一个HashMap检测这个localKey相关的message是否已经被初始化了，如果被初始化过就跳<BR>&nbsp;&nbsp;&nbsp;&nbsp;出，检测的方法是locales.get(localeKey)&nbsp;!=&nbsp;null。<BR>&nbsp;&nbsp;&nbsp;&nbsp;然后会读取如下一个文件：<BR>&nbsp;&nbsp;&nbsp;&nbsp;org/apache/struts/action/ActionResources_(localKey).properties，然后进行如下操作：<BR>&nbsp;&nbsp;&nbsp;&nbsp;Properties&nbsp;props&nbsp;=&nbsp;new&nbsp;Properties();<BR>&nbsp;&nbsp;&nbsp;&nbsp;ClassLoader&nbsp;classLoader&nbsp;=&nbsp;Thread.currentThread().getContextClassLoader();<BR>&nbsp;&nbsp;&nbsp;&nbsp;is&nbsp;=&nbsp;classLoader.getResourceAsStream(name);<BR>&nbsp;&nbsp;&nbsp;&nbsp;props.load(is);<BR>&nbsp;&nbsp;&nbsp;&nbsp;Iterator&nbsp;names&nbsp;=&nbsp;props.keySet().iterator();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;while&nbsp;(names.hasNext())&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;String&nbsp;key&nbsp;=&nbsp;(String)&nbsp;names.next();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(log.isTraceEnabled())&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;log.trace("&nbsp;&nbsp;Saving&nbsp;message&nbsp;key&nbsp;'"&nbsp;+&nbsp;messageKey(localeKey,&nbsp;key));<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;messages.put(messageKey(localeKey,&nbsp;key),&nbsp;props.getProperty(key));<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;PropertyMessageResources&nbsp;就是通过上面的loadLocale方法查找与Locale&nbsp;locale,&nbsp;String&nbsp;key<BR>&nbsp;&nbsp;&nbsp;&nbsp;相对对应的Message.查找的次序如下locale.toString()，然后是<BR>&nbsp;&nbsp;&nbsp;&nbsp;localeKey&nbsp;=&nbsp;localeKey.substring(0,&nbsp;underscore)，然后是defaultLocale，然后是key。<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;最后，resource类的结构如下：<BR>&nbsp;&nbsp;&nbsp;&nbsp;PropertyMessageResources&nbsp;extends&nbsp;MessageResources<BR>&nbsp;&nbsp;&nbsp;&nbsp;PropertyMessageResourcesFactory&nbsp;extends&nbsp;MessageResourcesFactory<BR><BR>二、initOther()方法：<BR>&nbsp;&nbsp;&nbsp;&nbsp;从servlet中获取config和debug两个参数，然后初始化ConvertUtils对象。由于<BR>&nbsp;&nbsp;&nbsp;&nbsp;ConvertUtils.deregister()的初始化，所有的Converter都是有初始值的，所以这里Struts自己<BR>&nbsp;&nbsp;&nbsp;&nbsp;把这些初始值设置为null，即转换出错的时候返回null，而不是初始值。使用ConvertUtils类的<BR>&nbsp;&nbsp;&nbsp;&nbsp;原因是由于从form传输过来的都是String类型的值，所以我们要把它们转换成相应的类型。<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;提到几个技巧：<BR>&nbsp;&nbsp;&nbsp;&nbsp;*public&nbsp;boolean&nbsp;isIndexed()&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(type&nbsp;==&nbsp;null)&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;(false);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//技巧一：判断是否是一个Array类的方法<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;else&nbsp;if&nbsp;(type.isArray())&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;(true);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//技巧二：判断type是否是List的一个父类或者父接口，或者与List为同一个类<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//要注意如果List是另一个primitive的TYPE类，那么type必须也是这个类才会<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//返回true，否则都是false。注意long.TYPE与Long.class是不同的<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;else&nbsp;if&nbsp;(List.class.isAssignableFrom(type))&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;(true);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;else&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;(false);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;*//componentType为Array类所存储的元素的类别&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Class&nbsp;componentType&nbsp;=&nbsp;indexedProperty.getClass().getComponentType();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//生成一个新的Array<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Object&nbsp;newArray&nbsp;=&nbsp;Array.newInstance(componentType,&nbsp;(index&nbsp;+&nbsp;1));<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.arraycopy(indexedProperty,&nbsp;0,&nbsp;newArray,&nbsp;0,&nbsp;length);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;indexedProperty&nbsp;=&nbsp;newArray;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;set(name,&nbsp;indexedProperty);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;newLength&nbsp;=&nbsp;Array.getLength(indexedProperty);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for&nbsp;(int&nbsp;i&nbsp;=&nbsp;length;&nbsp;i&nbsp;&lt;&nbsp;newLength;&nbsp;i++)&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Array.set(indexedProperty,&nbsp;i,&nbsp;createProperty(name+"["+i+"]",&nbsp;componentType));<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR><BR>三、initServlet()方法：<BR>&nbsp;&nbsp;&nbsp;&nbsp;这个方法主要是通过digester类解析web.xml，对String&nbsp;servletMapping属性进行初始化。对于<BR>&nbsp;&nbsp;&nbsp;&nbsp;digester说明如下：这是一个基于DOM的SAX实现的类，它是事件触发的，根据xml文件的结构，<BR>&nbsp;&nbsp;&nbsp;&nbsp;每次读到一个节点元素就会触发一个事件。<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;InputStream&nbsp;input&nbsp;=&nbsp;getServletContext().getResourceAsStream("/WEB-INF/web.xml");<BR>&nbsp;&nbsp;&nbsp;&nbsp;这是一个比较少见的方法。首先通过this.servletName&nbsp;=&nbsp;getServletConfig().<BR>&nbsp;&nbsp;&nbsp;&nbsp;getServletName()获取servlet的名称，然后根据<BR>&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(servletName.equals(this.servletName))&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this.servletMapping&nbsp;=&nbsp;urlPattern;<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;来判断当前读到的servlet名称是否是我们运行的servlet的名称，如果是，就把url-pattern作为<BR>&nbsp;&nbsp;&nbsp;&nbsp;我们的servletMapping。<BR><BR>四、getServletContext().setAttribute(Globals.ACTION_SERVLET_KEY,&nbsp;this)<BR>&nbsp;&nbsp;&nbsp;&nbsp;把自己存储到servletContext中，属性名为Globals.ACTION_SERVLET_KEY。<BR><BR>五、ModuleConfig&nbsp;moduleConfig&nbsp;=&nbsp;initModuleConfig("",&nbsp;config)<BR>&nbsp;&nbsp;&nbsp;&nbsp;这个方法使用由initOther()方法获取的config值为要解析的xml路径，用来初始化ModuleConfig。<BR>&nbsp;&nbsp;&nbsp;&nbsp;它首先采用与生成MessageResourcesFactory同样的方法产生一个MessageResourcesFactory对象：<BR>&nbsp;&nbsp;&nbsp;&nbsp;MessageResourcesFactory为一个抽象类，每一个继承它的类都要实现<BR>&nbsp;&nbsp;&nbsp;&nbsp;createModuleConfig(String&nbsp;prefix)方法。本程序使用的缺省的MessageResourcesFactory类为<BR>&nbsp;&nbsp;&nbsp;&nbsp;org.apache.struts.config.impl.DefaultModuleConfigFactory，它<BR>&nbsp;&nbsp;&nbsp;&nbsp;的createModuleConfig(String&nbsp;prefix)方法会生成一个ModuleConfigImpl类。<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;ModuleConfigImpl类相当于一个JavaBean，用来存放一个web模块运行时所需要的配置信息。当<BR>&nbsp;&nbsp;&nbsp;&nbsp;然，一个web模块可以拥有多个ModuleConfig，但是缺省的是prefix长度为0的ModuleConifg。它<BR>&nbsp;&nbsp;&nbsp;&nbsp;的每个属性几乎都是由HashMap组成的，它通过一个configured布尔值来描述当前的ModuleConfig<BR>&nbsp;&nbsp;&nbsp;&nbsp;是否已经被初始化完毕，在每存放一个属性的时候都会监测这个值。如果初始化完毕而还要改变<BR>&nbsp;&nbsp;&nbsp;&nbsp;里面的属性值，则会报出IllegalStateException("Configuration&nbsp;is&nbsp;frozen")异常，现在对它<BR>&nbsp;&nbsp;&nbsp;&nbsp;的属性简单说明如下：<BR>&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;protected&nbsp;HashMap&nbsp;actionConfigs：<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;这个HashMap用来存储ActionConfig对象。<BR>&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;protected&nbsp;HashMap&nbsp;dataSources<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;这个HashMap用来存储DataSourceConfig对象。<BR>&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;protected&nbsp;HashMap&nbsp;exceptions<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;这个HashMap用来存储ExceptionConfig对象。<BR>&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;protected&nbsp;HashMap&nbsp;formBeans<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;这个HashMap用来存储FormBeanConfig对象。<BR>&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;protected&nbsp;HashMap&nbsp;forwards<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;这个HashMap用来存储ForwardConfig对象。<BR>&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;protected&nbsp;HashMap&nbsp;messageResources<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;这个HashMap用来存储MessageResourcesConfig对象。<BR>&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;protected&nbsp;ArrayList&nbsp;plugIns<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;这个HashMap用来存储PlugInConfig对象。<BR>&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;protected&nbsp;ControllerConfig&nbsp;controllerConfig<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ControllerConfig类<BR>&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;protected&nbsp;boolean&nbsp;configured<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;标志这个ModuleConfig是(true)否(false)配置完成。<BR>&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;protected&nbsp;String&nbsp;prefix<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;用来标志和区分ModuleConfig类，同时在使用上面的config类初始化相应的资源以后，也是通<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;过这个prefix来区分所属的不同的web模块。<BR>&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;protected&nbsp;String&nbsp;actionMappingClass&nbsp;=&nbsp;"org.apache.struts.action.ActionMapping"<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ActionMapping类名，缺省为org.apache.struts.action.ActionMapping。<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;初始化ModuleConfig的方法如下：<BR>&nbsp;&nbsp;&nbsp;&nbsp;首先是使用getServletConfig().getInitParameter("mapping")来获取设定的ActionMapping类<BR>&nbsp;&nbsp;&nbsp;&nbsp;名，然后通过initConfigDigester()方法来生成一个digester。最后用","分隔config，对每一<BR>&nbsp;&nbsp;&nbsp;&nbsp;块调用parseModuleConfigFile(prefix,&nbsp;paths,&nbsp;config,&nbsp;digester,&nbsp;path)方法解析。注意，这<BR>&nbsp;&nbsp;&nbsp;&nbsp;个方法实际上只有两个参数是有意义的：path为我们要解析的xml文件，config用来初始化完成<BR>&nbsp;&nbsp;&nbsp;&nbsp;后保存到servletContext中。<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;如果ModuleConfig中存放的FormBeanConfig为Dydamic类型，那么就调用<BR>&nbsp;&nbsp;&nbsp;&nbsp;DynaActionFormClass.createDynaActionFormClass(FormBeanConfig)初始化<BR>&nbsp;&nbsp;&nbsp;&nbsp;DynaActionFormClass，并存放到DynaActionFormClass.dynaClasses&nbsp;的&nbsp;static&nbsp;HashMap中。这<BR>&nbsp;&nbsp;&nbsp;&nbsp;里的key为FormBeanConfig.getName()&nbsp;+&nbsp;moduleConfig.getPrefix()。<BR>&nbsp;&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;如果当前的ModuleConfig为缺省的ModuleConfig，那么将会调用如下几个方法：<BR>&nbsp;&nbsp;&nbsp;&nbsp;defaultControllerConfig(config)<BR>&nbsp;&nbsp;&nbsp;&nbsp;defaultMessageResourcesConfig(config)<BR>&nbsp;&nbsp;&nbsp;&nbsp;defaultFormBeansConfig(config)<BR>&nbsp;&nbsp;&nbsp;&nbsp;defaultForwardsConfig(config)<BR>&nbsp;&nbsp;&nbsp;&nbsp;defaultMappingsConfig(config)<BR>&nbsp;&nbsp;&nbsp;&nbsp;在struts1.1以后，这个特例将会被废弃：<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;defaultControllerConfig(config)为ControllerConfig通过getInitParameter(s)方法初始化如<BR>&nbsp;&nbsp;&nbsp;&nbsp;下几个属性：bufferSize，content，locale(true/false)，maxFileSize，nocache(true/false)<BR>&nbsp;&nbsp;&nbsp;&nbsp;，multipartClass，tempDir。<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;defaultMessageResourcesConfig(config)为MessageResourcesConfig通过getInitParameter(s)<BR>&nbsp;&nbsp;&nbsp;&nbsp;方法初始化如下几个属性：application，factory，null(true/false)。<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;其它的几个方法就是获取不同的对象，然后把它们相应的存储到servlet中。关心如下：<BR>&nbsp;&nbsp;&nbsp;&nbsp;ActionFormBeans=&gt;FormBeanConfig，ActionForwards=&gt;ForwardConfig，<BR>&nbsp;&nbsp;&nbsp;&nbsp;ActionMappings=&gt;ActionConfig。<BR><BR>六、initModuleMessageResources(ModuleConfig&nbsp;config)<BR>&nbsp;&nbsp;&nbsp;&nbsp;通过存储在ModuleConfig中的MessageResourcesConfig对象，逐个初始化MessageResource，<BR>&nbsp;&nbsp;&nbsp;&nbsp;然后再把初始化好的MessageResources存放到ServletContext中，attributeName为<BR>&nbsp;&nbsp;&nbsp;&nbsp;MessageResourcesConfig.getKey()&nbsp;+&nbsp;ModuleConfig.getPrefix()。<BR><BR>七、initModuleDataSources(ModuleConfig&nbsp;config)<BR>&nbsp;&nbsp;&nbsp;&nbsp;通过存储在ModuleConfig中的DataSourceConfig对象，逐个初始化DataSource。然后对于每一个<BR>&nbsp;&nbsp;&nbsp;&nbsp;DateSource通过BeanUtils.populate(ds,&nbsp;dscs[i].getProperties())方法初始化其属性。再把初<BR>&nbsp;&nbsp;&nbsp;&nbsp;始化好的DateSource存放到ServletContext中，attributeName为<BR>&nbsp;&nbsp;&nbsp;&nbsp;DataSourceConfig.getKey()&nbsp;+&nbsp;ModuleConfig.getPrefix()。同时也存放到名位dataSources的<BR>&nbsp;&nbsp;&nbsp;&nbsp;FastHashMap中，key为DataSourceConfig.getKey()。<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;这里还会根据生成的DateSource对象是否是GenericDataSource类型，如果是则调用<BR>&nbsp;&nbsp;&nbsp;&nbsp;GenericDataSource.open()方法。GenericDataSource是一个非常简单的数据库连接池，它的<BR>&nbsp;&nbsp;&nbsp;&nbsp;open()方法用来初始化连接池，生成最小数目的GenericConnection，这里的open()方法根据<BR>&nbsp;&nbsp;&nbsp;&nbsp;String&nbsp;driver变量是否为null来判断是否已经被初始化过。需要仔细说明的是getConnection()<BR>&nbsp;&nbsp;&nbsp;&nbsp;方法，它首先从连接池中取出GenericConnection对象，然后检查其是否是可链接的，如果是就<BR>&nbsp;&nbsp;&nbsp;&nbsp;返回，否则继续取出，同时activeCount-1。如果没有取到，则会检查当前可使用的<BR>&nbsp;&nbsp;&nbsp;&nbsp;GenericConnection是否达到最大值(activeCount&nbsp;&lt;&nbsp;maxCount)，如果没有，调用<BR>&nbsp;&nbsp;&nbsp;&nbsp;createConnection()方法声成一个新的GenericConnection，然后检查其是否是可链接，如果可以<BR>&nbsp;&nbsp;&nbsp;&nbsp;则返回。returnConnection(GenericConnection&nbsp;conn)方法则是通过把GenericConnection放回到<BR>&nbsp;&nbsp;&nbsp;&nbsp;连接池，然后activeCount-1。<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;这个方法中使用到了ServletContextWriter类，DateSource的log信息就通过这个类写入。对这个<BR>&nbsp;&nbsp;&nbsp;&nbsp;类说明如下：<BR>&nbsp;&nbsp;&nbsp;&nbsp;它继承自PrintWriter，而PrintWriter又继承自Writer。Writer类所作的事情就是在同步的情况下<BR>&nbsp;&nbsp;&nbsp;&nbsp;调用abstract方法：abstract&nbsp;public&nbsp;void&nbsp;write(char&nbsp;cbuf[],&nbsp;int&nbsp;off,&nbsp;int&nbsp;len)，这个方法<BR>&nbsp;&nbsp;&nbsp;&nbsp;将会根据调用者的需要由调用者实现。<BR>&nbsp;&nbsp;&nbsp;&nbsp;PrintWriter则首先通过ensureOpen()方法检验这个类中是否有写入的对象(Writer类或其子类)，<BR>&nbsp;&nbsp;&nbsp;&nbsp;如果有则根据不同的情况调用这个写入对象的write方法(out.write(....))。这个类的print(...)<BR>&nbsp;&nbsp;&nbsp;&nbsp;方法就是据不同的情况调用相应的write(...)方法。而println(...)与之的区别就是每次多写入一<BR>&nbsp;&nbsp;&nbsp;&nbsp;个换行字符串。还有一个区别是println(...)会根据是否需要autoflush进行flush，而write(...)<BR>&nbsp;&nbsp;&nbsp;&nbsp;方法不会。<BR>&nbsp;&nbsp;&nbsp;&nbsp;ServletContextWriter类的作用是把字符写入ServletContext中。ServletContextWriter类方法中<BR>&nbsp;&nbsp;&nbsp;&nbsp;真正实现了write方法：<BR>&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;void&nbsp;write(char&nbsp;c)&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(c&nbsp;==&nbsp;'\n')<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;flush();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else&nbsp;if&nbsp;(c&nbsp;!=&nbsp;'\r')<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;buffer.append(c);<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;void&nbsp;flush()&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(buffer.length()&nbsp;&gt;&nbsp;0)&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;context.log(buffer.toString());<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;buffer.setLength(0);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR><BR>八、initModulePlugIns(moduleConfig)<BR>&nbsp;&nbsp;&nbsp;&nbsp;通过存储在ModuleConfig中的PlugInConfig对象，逐个初始化PlugIn对象，存放到一个数组中，<BR>&nbsp;&nbsp;&nbsp;&nbsp;然后再把这个数组存放到ServletContext中，attributeName为<BR>&nbsp;&nbsp;&nbsp;&nbsp;Globals.PLUG_INS_KEY&nbsp;+&nbsp;ModuleConfig.getPrefix()。<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;对每一个生成的PlugIn对象通过<BR>&nbsp;&nbsp;&nbsp;&nbsp;BeanUtils.populate(plugIns[i],&nbsp;plugInConfigs[i].getProperties())方法初始化其属性。然后<BR>&nbsp;&nbsp;&nbsp;&nbsp;再把PlugInConfig对象存放到由其生成的PlugIn对象中。<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;最后，通过plugIns[i].init(this,&nbsp;(ModuleConfig)&nbsp;config)初始化这个plugIn对象。<BR><BR>九、初始化结束<BR>&nbsp;&nbsp;&nbsp;&nbsp;完成了这个初始化以后，会调用ModuleConfig.freeze()令这个ModuleConfig变得不可改变。然后<BR>&nbsp;&nbsp;&nbsp;&nbsp;会遍历ServletConfig中的initParameterNames，如果有以"config/"开头的，则通过这个parameter<BR>&nbsp;&nbsp;&nbsp;&nbsp;的值继续初始化其它的ModuleConfig，且这个ModuleConfig的prefix为"config/"后的字符串。<BR>&nbsp;&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;同样调用如下方法：<BR>&nbsp;&nbsp;&nbsp;&nbsp;initModuleMessageResources(moduleConfig);<BR>&nbsp;&nbsp;&nbsp;&nbsp;initModuleDataSources(moduleConfig);<BR>&nbsp;&nbsp;&nbsp;&nbsp;initModulePlugIns(moduleConfig);<BR>&nbsp;&nbsp;&nbsp;&nbsp;moduleConfig.freeze();<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;最后调用destroyConfigDigester()释放内存。</FONT><img src ="http://www.blogjava.net/kapok/aggbug/3987.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-05-02 09:28 <a href="http://www.blogjava.net/kapok/archive/2005/05/02/3987.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Struts Config的DTD</title><link>http://www.blogjava.net/kapok/archive/2005/05/01/3984.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Sun, 01 May 2005 14:27:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/05/01/3984.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/3984.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/05/01/3984.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/3984.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/3984.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: &lt;!--&nbsp;&nbsp;&nbsp;&nbsp; DTD for the Struts Application Configuration File, Version 1.1&nbsp;&nbsp;&nbsp;&nbsp; To support validation of your configuration file, include the following&nbsp;&n...&nbsp;&nbsp;<a href='http://www.blogjava.net/kapok/archive/2005/05/01/3984.html'>阅读全文</a><img src ="http://www.blogjava.net/kapok/aggbug/3984.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-05-01 22:27 <a href="http://www.blogjava.net/kapok/archive/2005/05/01/3984.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Struts下的MapForm</title><link>http://www.blogjava.net/kapok/archive/2005/04/30/3966.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Sat, 30 Apr 2005 05:55:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/04/30/3966.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/3966.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/04/30/3966.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/3966.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/3966.html</trackback:ping><description><![CDATA[<P><A href="http://www.jetmaven.net/documents/j_mapformInStruts.php">http://www.jetmaven.net/documents/j_mapformInStruts.php</A><BR><BR>我们知道Struts的ActionForm一直被大家视为缺陷，觉得多余，但我个人认为ActionForm还是有它存在的理由。我们建立ActionForm通常和Web页面的Form元素绑定，用于数据的收集和校验等。ActionForm的属性必须声明，然后才能用于和Web页面中，我们经常遇到一些属性不需要全部声明，如查询条件等，而且ActionForm的属性太多时管理也是个问题，再另一些情况下，如采购单，使用master/detail方式，ActionForm的创建变的困难，好多属性均不确定，如采购明细为对条记录，这样处理比较麻烦，在这篇文章中，我们将向你讲述如何使用Struts的MapForm机制实现这样的功能。<BR>我们希望ActionForm能够接收Map数据，这样我们的管理就变的容易多啦，在Struts 1.1以后版本这样的处理变得非常简单，我们在ActionForm中声明一个Map变量。</P>
<P><BR>public class MapForm extends ActionForm<BR>{<BR>private Map map = null;<BR>public void setMap(Map map) {<BR>this.map = map;<BR>}<BR>public Map getMap() {<BR>return this.map;<BR>}<BR>同时增加一个属性方法去设置和获取Map中的数据。<BR>public void setAttribute(String attributeKey, Object attributeValue)<BR>{<BR>getMap().put(attributeKey, attributeValue);<BR>}<BR>public Object getAttribute(String attributeKey)<BR>{<BR>Object keyValue = getMap().get(attributeKey);<BR>return keyValue;<BR>}<BR>这样我们在jsp页面中，我们就可以使用Struts的标签接触这些Map数据。<BR>&lt;html:text property="attribute(key)"/&gt;<BR>这样这些数据就可维护啦，这对查询条件较多的情况非常适用，你无需在维护这些查询信息在各个页面的过渡，Struts帮您完成了一切。</P>
<P>下面我们就看一下如何用MapForm组织master/detail方式的数据，我们将以一个订单做为样例。</P>
<P>1 首先建立一个Form对象，继承MapForm，同时声明主要的属性，如订单编码、定购人等。<BR>public class OrderForm extends MapForm<BR>{<BR>private Integer id;<BR>private String orderMan;</P>
<P>2 我们拟定以Map方式保存采购项信息，同一采购项采用统一前缀，可选择行编码，如row123_ productCode,row123_ productId,row123_ amount等，这样某一采购项信息将被输入到Map中，不同的采购项的前缀不一样，前缀由row+行编码组成，同时编写可获取行编码的函数，这样可取得某一采购项的所有信息，参数rowPrefix为某一字符串，如“row”、“item”等，不包含数字编码信息，同时你可以编写Comparator，进行排序。 <BR>public Collection getRowIdList(String rowPrefix)<BR>{<BR>if (map.isEmpty()) return new ArrayList();<BR>Collection allRowId = new TreeSet(new RowIdComparator(rowPrefix));<BR>Iterator allKey = map.keySet().iterator();<BR>while (allKey.hasNext())<BR>{<BR>String key = (String) allKey.next();<BR>if (key.indexOf(rowPrefix) != -1)<BR>{<BR>key = key.substring(0, key.indexOf('_'));<BR>allRowId.add(key);<BR>}<BR>}<BR>return allRowId;<BR>}</P>
<P>3 在jsp页面中你可以通过jstl，就可以完成采购明细的显示。<BR>&lt;c:forEach var="rowId" items="${OrderForm.getRowIdList('row')}"&gt;<BR>&lt;tr align="center" id="${rowId}" onclick="clickRow()"&gt;<BR>&lt;td &gt;&lt;html:text property="attribute(${rowId}_productCode)" size="8" onkeydown="fillProductInfoWithKeyDown('${rowId}',this)" /&gt;&lt;html:hidden property="attribute(${rowId}_productId)"/&gt; &amp;nbsp;&lt;a href="javascript:selectproduct('${rowId}')"&gt;选择&lt;/a&gt;&lt;/td&gt;<BR>&lt;td &gt;&lt;html:text property="attribute(${rowId}_productQty)" size="8" /&gt; &lt;/td&gt;<BR>&lt;td &gt;&lt;html:text property="attribute(${rowId}_productPrice)" size="8" /&gt;&lt;/td&gt;<BR>&lt;td &gt;&lt;html:text property="attribute(${rowId}_productName)" readonly="true" size="16" /&gt;&lt;/td&gt;<BR>&lt;td &gt;&lt;html:text property="attribute(${rowId}_productPackaging)" readonly="true" size="12"/&gt;&lt;/td&gt;<BR>&lt;td &gt;&lt;html:text property="attribute(${rowId}_productUnit)" size="6" readonly="true" /&gt;&lt;/td&gt;<BR>&lt;/tr&gt;<BR>&lt;/c:forEach&gt;</P>
<P>4 这样Struts帮你完成了所有的信息处理，你需要完成你的保存就可以啦。</P>
<P>提示：Map中的数据值默认都是String类型的，如果你想转换成你想要的类型，可以在你的Form编写一个工具方法，完成类型的转换。<BR>public Map getTypedMap() {<BR>Map map = this.getMap();<BR>String keyString = (String) map.get("key");<BR>Integer keyInteger = new Integer(keyString);<BR>map.put("key",keyInteger);<BR>return map;<BR>}</P>
<P>总结：MapForm各个功能很少被开发人员使用，如果使用得当，功能非常强大。ActionForm个人认为并非是个设计缺陷，结合BeanUtils等工具包，转换和信息处理非常方便。</P><img src ="http://www.blogjava.net/kapok/aggbug/3966.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-04-30 13:55 <a href="http://www.blogjava.net/kapok/archive/2005/04/30/3966.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Digester学习笔记(二)</title><link>http://www.blogjava.net/kapok/archive/2005/04/15/3300.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Fri, 15 Apr 2005 01:28:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/04/15/3300.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/3300.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/04/15/3300.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/3300.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/3300.html</trackback:ping><description><![CDATA[<DIV class=blogbody>
<H3 class=title id=startcontent>Digester学习笔记(二)</H3>
<P class=title><A href="http://www.infomall.cn/cgi-bin/mallgate/20040310/http://hedong.3322.org/archives/000336.html">http://www.infomall.cn/cgi-bin/mallgate/20040310/http://hedong.3322.org/archives/000336.html</A></P>
<P>　　为便于理解，将笔记的内容结构作了一些调整。</P><A name=more></A>
<P>
<H4>对象栈</H4>　　对digester技术最普通的应用，是用来动态创建一个由Java对象构成的树结构，各对象的属性以及对象间的关系，基于XML文档的内容来设置(XML文档就是一棵树)。为实现这种应用，Digester提供了一个对象栈，以供在相关的模板识别后被激活的处理规则操作。此栈的基本操作包括：<BR>
<OL>
<LI>clear(),清空栈的内容<BR>
<LI>peek(),返回对栈顶对象的引用<BR>
<LI>pop(),将栈顶对象弹出并返回<BR>
<LI>push(),将一个新的对象压入栈顶</LI></OL><BR>　　用栈的原因，就是当识别出一个XML元素的“开始”时，将相关对象生成并压入栈顶，这个对象在处理该元素的子元素的过程中一直在栈中，当所有子元素都处理完后，解析器遇到这个元素的“结束”时，则弹出此对象，并进行相关的处理。<BR>　　如何描述对象间的关系呢？将栈顶的对象做为一个参数，传递给第二栈顶(即先于栈顶对象入栈的那个对象，在栈顶对象的下面)的一个方法，就可以简单地建立起一种“父子关系”，从而可以简单地建立起1：1的关系(第二栈顶对象与栈顶对象之间)和1：N的关系(第二栈顶对象不动，N次压栈顶弹栈顶对象).<BR>　　如果取得生成的第一个对象呢？可以让parse()方法返回，或者在调用parse()方法前，先行压入一个对象，在parse()方法结束后弹出这个对象，则其子对象即为我们想要的第一个对象。<BR>
<H4>日志(logging)</H4><BR>　　日志是一个调试Digester规则集的非常重要的工具，它可以记录非常丰富的信息，因它在使用Digester之前有必要了解日志是如何工作的。<BR>　　Digester使用Jakarta Commons Logging，这个模块并不是具体的日志实现，而只是一个可设置的接口。可以设置它将各种日志信息传递它自身带的基本记录器，或者传递给其它的更复杂的日志工具。具体请参考commons logging的文档，或<A href="http://www.infomall.cn/cgi-bin/mallgate/20040310/http://hedong.3322.org/archives/000316.html">Jakarta Commons Logging学习笔记</A><BR>　　Digester主要使用两个记录器： 
<OL>
<LI>SAX相关的信息，被送往org.apache.commons.digester.Digester.sax记录器，记录了Digester收到的SAX的事件的信息。 
<LI>其它的所有信息，都被送往org.apache.commons.digester.Digester记录器，这个记录器在调试Digester时打开而在产品中常将其关闭</LI></OL><BR>　　假定用commons logging自带的基本日志工具，并以DEBUG级别记录Digester调试信息以及INFO级别记录SAX事件信息，则对logging的配置文件设置如下： 
<DIV class=code>org.apache.commons.logging.Log=org.apache.commons.logging.impl.SimpleLog org.apache.commons.logging.simplelog.log.org.apache.commons.digester.Digester=debug org.apache.commons.logging.simplelog.log.org.apache.commons.digester.Digester.sax=info</DIV><BR>
<H4>Digester包中的例子</H4>
<DIV class=code>***********Example.xml**********<BR>&lt;address-book&gt;<BR>&nbsp;&nbsp;&lt;person&nbsp;id="1"&nbsp;category="acquaintance"&nbsp;try="would&nbsp;be&nbsp;ignored"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;name&gt;Gonzo&lt;/name&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;email&nbsp;type="business"&gt;gonzo@muppets.com&lt;/email&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;gender&nbsp;result="the&nbsp;whole&nbsp;tag&nbsp;would&nbsp;be&nbsp;ignored"&gt;male&lt;/gender&gt;<BR>&nbsp;&nbsp;&lt;/person&gt;<BR>&nbsp;&nbsp;&lt;person&nbsp;id="2"&nbsp;category="rolemodel"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;name&gt;Kermit&lt;/name&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;email&nbsp;type="business"&gt;kermit@muppets.com&lt;/email&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;email&nbsp;type="home"&gt;kermie@acme.com&lt;/email&gt;<BR>&nbsp;&nbsp;&lt;/person&gt;<BR>&lt;/address-book&gt;**********Person.java************<BR>import&nbsp;java.util.HashMap;<BR>import&nbsp;java.util.Iterator;<BR>public&nbsp;class&nbsp;Person&nbsp;{<BR>&nbsp;&nbsp;private&nbsp;int&nbsp;id;<BR>&nbsp;&nbsp;private&nbsp;String&nbsp;category;<BR>&nbsp;&nbsp;private&nbsp;String&nbsp;name;<BR>&nbsp;&nbsp;private&nbsp;HashMap&nbsp;emails&nbsp;=&nbsp;new&nbsp;HashMap();<BR>&nbsp;&nbsp;//下面的两个方法的名字中set以后的部分，与&lt;person&gt;的属性名字对映。当从xml文件中识别出&lt;person&gt;的属性时，如果有要求(即调用过addSetProperties方法),Digester会依据这种对映关系自动调用相应的方法。<BR>&nbsp;&nbsp;public&nbsp;void&nbsp;setId(int&nbsp;id)&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this.id&nbsp;=&nbsp;id;<BR>&nbsp;&nbsp;}<BR>&nbsp;&nbsp;public&nbsp;void&nbsp;setCategory(String&nbsp;category)&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this.category&nbsp;=&nbsp;category;<BR>&nbsp;&nbsp;}<BR>&nbsp;&nbsp;//对name而言，因为其值来自&lt;name&gt;标签的内容而非属性值，需要用addCallMethod指定识别&lt;name&gt;后的要调用此方法(想自动调用也要可以，需要addBeanPropertySetter，参见第下一个例子)。<BR>&nbsp;&nbsp;public&nbsp;void&nbsp;setName(String&nbsp;name)&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this.name&nbsp;=&nbsp;name;<BR>&nbsp;&nbsp;}<BR>&nbsp;&nbsp;//同name，此时还要一一指定addEmail的参数值的来源。<BR>&nbsp;&nbsp;public&nbsp;void&nbsp;addEmail(String&nbsp;type,&nbsp;String&nbsp;address)&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;emails.put(type,&nbsp;address);<BR>&nbsp;&nbsp;}<BR>&nbsp;&nbsp;public&nbsp;void&nbsp;print()&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println("Person&nbsp;#"&nbsp;+&nbsp;id);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println("&nbsp;&nbsp;category="&nbsp;+&nbsp;category);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println("&nbsp;&nbsp;name="&nbsp;+&nbsp;name);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for(Iterator&nbsp;i&nbsp;=&nbsp;emails.keySet().iterator();&nbsp;i.hasNext();&nbsp;)&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;String&nbsp;type&nbsp;=&nbsp;(String)&nbsp;i.next();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;String&nbsp;address&nbsp;=&nbsp;(String)&nbsp;emails.get(type);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println("&nbsp;&nbsp;email&nbsp;(type&nbsp;"&nbsp;+&nbsp;type&nbsp;+&nbsp;")&nbsp;:&nbsp;"&nbsp;+&nbsp;address);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;}<BR>}<BR>**********AddressBook.java***********<BR>import&nbsp;java.util.LinkedList;<BR>import&nbsp;java.util.Iterator;<BR>public&nbsp;class&nbsp;AddressBook&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;LinkedList&nbsp;people&nbsp;=&nbsp;new&nbsp;LinkedList();<BR>&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;void&nbsp;addPerson(Person&nbsp;p)&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;people.addLast(p);<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;void&nbsp;print()&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println("Address&nbsp;book&nbsp;has&nbsp;"&nbsp;+&nbsp;people.size()&nbsp;+&nbsp;"&nbsp;entries"); 
<P></P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for(Iterator&nbsp;i&nbsp;=&nbsp;people.iterator();&nbsp;i.hasNext();&nbsp;)&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Person&nbsp;p&nbsp;=&nbsp;(Person)&nbsp;i.next();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;p.print();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>}<BR>************AddressBookDigester*********<BR>import&nbsp;org.apache.commons.digester.Digester;<BR>/**<BR>&nbsp;*&nbsp;Usage:&nbsp;java&nbsp;Example1&nbsp;example.xml<BR>&nbsp;*/<BR>public&nbsp;class&nbsp;AddressBookDigester&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;static&nbsp;void&nbsp;main(String[]&nbsp;args)&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(args.length&nbsp;!=&nbsp;1)&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;usage();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.exit(-1);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;String&nbsp;filename&nbsp;=&nbsp;args[0];<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;创建一个Digester实例<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Digester&nbsp;d&nbsp;=&nbsp;new&nbsp;Digester();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;创建AddressBook实例，并将其压入栈顶。<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;AddressBook&nbsp;book&nbsp;=&nbsp;new&nbsp;AddressBook();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;d.push(book);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;增加规则<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;addRules(d);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;处理输入的xml文件<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;try&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;java.io.File&nbsp;srcfile&nbsp;=&nbsp;new&nbsp;java.io.File(filename);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;d.parse(srcfile);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;catch(java.io.IOException&nbsp;ioe)&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println("Error&nbsp;reading&nbsp;input&nbsp;file:"&nbsp;+&nbsp;ioe.getMessage());<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.exit(-1);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;catch(org.xml.sax.SAXException&nbsp;se)&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println("Error&nbsp;parsing&nbsp;input&nbsp;file:"&nbsp;+&nbsp;se.getMessage());<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.exit(-1);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;将解析出的地址数据打印出来<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;book.print();<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;private&nbsp;static&nbsp;void&nbsp;addRules(Digester&nbsp;d)&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;当遇到&lt;person&gt;时，创建类Person的一个实例，并将其压入栈顶<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;d.addObjectCreate("address-book/person",&nbsp;Person.class);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;将&lt;person&gt;标签的属性(attribute)与栈顶Person类对象的属性(property)设置方法根据各自的名字进行映射，(例如，将标签属性id与属性设置方法setId进行映射，将标签属性category与属性设置方法setCategory进行映射)，然后将属性的值作参数传递给执行相应的方法。<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;如果某标签属性没法通过名字找到相应的属性设置方法，则此标签属性被忽略(如example.xml中第一个&lt;person&gt;的try属性)。<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;d.addSetProperties("address-book/person");</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;调用第二栈顶对象(AddressBook实例)的addPerson方法，以栈对象(Person实例)的对象为参数<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;d.addSetNext("address-book/person",&nbsp;"addPerson");&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;当遇到&lt;person&gt;的子元素&lt;name&gt;时，调用栈顶对象(Person实例)的setName方法。<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;此处addCallMethod方法的第一参数是规则，第二个参数是方法的名字，第三个是参数的数量(为0时，表示只有一个参数，且参数的值是元素的内容)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;d.addCallMethod("address-book/person/name",&nbsp;"setName",&nbsp;0);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;当遇到&lt;person&gt;的子元素&lt;email&gt;时，调用栈顶对象(Person实例)的addEmail方法,addEmail方法有两个参数，取值分别来自&lt;email&gt;的属性type的值和&lt;email&gt;本身的内容。<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;此处addCallParam方法的第一参数是规则，第二个参数是指明被调用方法(addEmail)参数的序号，第三个是参数为字符串时指属性的名字)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;d.addCallMethod("address-book/person/email",&nbsp;"addEmail",&nbsp;2);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;d.addCallParam("address-book/person/email",&nbsp;0,&nbsp;"type");<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;d.addCallParam("address-book/person/email",&nbsp;1);<BR>&nbsp;&nbsp;&nbsp;&nbsp;}</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;private&nbsp;static&nbsp;void&nbsp;usage()&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println("Usage:&nbsp;java&nbsp;Example1&nbsp;example.xml");<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>}</P></DIV>运行结果如下（运行时可能需要xml-crimson,一个源sun的XML解析器，可到http://xml.apache.org/crimson/下载) 
<DIV class=code>Address&nbsp;book&nbsp;has&nbsp;2&nbsp;entries<BR>Person&nbsp;#1<BR>&nbsp;&nbsp;category=acquaintance<BR>&nbsp;&nbsp;name=Gonzo<BR>&nbsp;&nbsp;email&nbsp;(type&nbsp;business)&nbsp;:&nbsp;gonzo@muppets.com<BR>Person&nbsp;#2<BR>&nbsp;&nbsp;category=rolemodel<BR>&nbsp;&nbsp;name=Kermit<BR>&nbsp;&nbsp;email&nbsp;(type&nbsp;business)&nbsp;:&nbsp;kermit@muppets.com<BR>&nbsp;&nbsp;email&nbsp;(type&nbsp;home)&nbsp;:&nbsp;kermie@acme.com</DIV>
<P></P><SPAN class=posted>Posted by Hilton at October 26, 2003 11:46 PM | <A onclick="OpenTrackback(this.href); return false" href="http://www.infomall.cn/cgi-bin/mallgate/20040310/http://hedong.3322.org/mt/mt-tb.cgi?__mode=view&amp;entry_id=336">TrackBack</A> <BR></SPAN></DIV><img src ="http://www.blogjava.net/kapok/aggbug/3300.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-04-15 09:28 <a href="http://www.blogjava.net/kapok/archive/2005/04/15/3300.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Digester</title><link>http://www.blogjava.net/kapok/archive/2005/04/14/3294.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Thu, 14 Apr 2005 10:07:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/04/14/3294.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/3294.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/04/14/3294.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/3294.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/3294.html</trackback:ping><description><![CDATA[<A href="http://www.infomall.cn/cgi-bin/mallgate/20040310/http://hedong.3322.org/archives/000337.html">http://www.infomall.cn/cgi-bin/mallgate/20040310/http://hedong.3322.org/archives/000337.html</A><BR>总觉得，Digester不仅仅能作配置文件解析，而且可以作得更多。<A name=more></A>
<P>
<H4>配置属性</H4>　　Digester用来解析应用系统的配置文件，其本身也有很可配置的属性。<BR>
<TABLE border=1>
<TBODY>
<TR>
<TD>属性</TD>
<TD>描述</TD></TR>
<TR>
<TD>classLoader </TD>
<TD>指定类装载器(class loader)。ObjectCreateRule 和 FactoryCreateRule两个规则中，需要动态加载一些类（如那些盛放XML解析出来的数据的javaBean等），装载器可以在次指定。如果不指定，对这此类的加载将会利用线程上下文中的加载器（当useContextClassLoader值为真时）或利用加载Digester的那个加载器。</TD></TR>
<TR>
<TD>errorHandler </TD>
<TD>指定 SAX ErrorHandler，以在出现此类错误时调用。默认情况下，任何解析错误都会被记入日志，Digest会继续进行解析。</TD></TR>
<TR>
<TD>namespaceAware </TD>
<TD>一个布尔值，为真时对XML文件的解析时会考虑元素的域名空间（如不同的域名空间的同名元素会视为不同的元素）</TD></TR>
<TR>
<TD>ruleNamespaceURI </TD>
<TD>指定后续加入的规则所属的命名空间，如果此值为null,则加入的规则不与任何命名空间相联系。</TD></TR>
<TR>
<TD>rules </TD>
<TD>设定规则模板与XML元素的匹配处理程序。由于这个匹配程序是插件式的，所以匹配工作的完成可以用用户定义的匹配程序未完成。默认情况下，使用Digester提供的匹配器。</TD></TR>
<TR>
<TD>useContextClassLoader </TD>
<TD>一个布尔值，为真时FactoryCreateRule 和 ObjectCreateRule 两个规则中对类的装载将会采用当前线程上下文中指定的加载器。默认情况下，对类的动态加载会利用加载Digester的那个装载器。</TD></TR>
<TR>
<TD>validating </TD>
<TD>一个布尔值，为真时解析器会根据DTD内容对XML文档进行合法性检查，默认值是假，解析器只是检查XML是否格式良好(well formed).</TD></TR></TBODY></TABLE><BR>　　除了上述属性外，还可以注册一个本地DTD，以供DOCTYPE声明引用。这样的注册告诉XML解析器，当遇到DOCTYPE声明时，应使用刚注册的DTD的内容，而不是DOCTYPE声明中的标识符(identifier)。<BR>　　例如，Struect框架控制器中，使用下述的注册，告诉Structs使用一个本地的DTD中的相关内容来处理Structs配置文件，这样可以适用于那些没有连接到互联网的应用环境，而在连到互联网的环境中可以加快运行速度(因为它避免了通过网络去取相关的资源)。 
<DIV class=”code”>URL url = new URL("/org/apache/struts/resources/struts-config_1_0.dtd");<BR>digester.register("-//Apache Software Foundation//DTD Struts Configuration 1.0//EN",url.toString()); 
<DIV><BR>
<H4>规则集打包</H4><BR>　　通常情况下，一个规则被创建后，接着便注册，然后等在event时被调用，这些规则集很难为其它应用程序直接复用。一个解决方法是将所有规则都放在一个类中，此由这些规则可以很简单地被装载然后被注册使用。RuleSet接口就是为些而设计，一般是通过扩展RuleSetBase类来开发规则集类。如例： 
<DIV class=code>public&nbsp;class&nbsp;MyRuleSet&nbsp;extends&nbsp;RuleSetBase&nbsp;{<BR>&nbsp;&nbsp;public&nbsp;MyRuleSet()&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;this("");<BR>&nbsp;&nbsp;}<BR>&nbsp;&nbsp;public&nbsp;MyRuleSet(String&nbsp;prefix)&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;super();<BR>&nbsp;&nbsp;&nbsp;&nbsp;this.prefix&nbsp;=&nbsp;prefix;<BR>&nbsp;&nbsp;&nbsp;&nbsp;this.namespaceURI&nbsp;=&nbsp;"http://www.mycompany.com/MyNamespace";<BR>&nbsp;&nbsp;}<BR>&nbsp;&nbsp;protected&nbsp;String&nbsp;prefix&nbsp;=&nbsp;null;<BR>&nbsp;&nbsp;public&nbsp;void&nbsp;addRuleInstances(Digester&nbsp;digester)&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;digester.addObjectCreate(prefix&nbsp;+&nbsp;"foo/bar",<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"com.mycompany.MyFoo");<BR>&nbsp;&nbsp;&nbsp;&nbsp;digester.addSetProperties(prefix&nbsp;+&nbsp;"foo/bar");<BR>&nbsp;&nbsp;}<BR>}</DIV>可以这样使用这个规则集 
<DIV class=code>Digester digester = new Digester();<BR>... 一些配置Digester ...<BR>digester.addRuleSet(new MyRuleSet("baz/"));</DIV><BR>
<H4>带命名空间的XML解析</H4><BR>　　这种情况下，使用Digester的步骤为： 
<OL>
<LI>在Digester初始化部分，指明要考虑命名空间。 
<DIV class=code>digester.setNamespaceAware(true);</DIV><BR>
<LI>指明一些规则的命名空间,如 
<DIV class=code>digester.setRuleNamespaceURI("http://www.mycompany.com/MyNamespace");</DIV><BR>
<LI>接下来定义一些与此命名空间有关的规则，此时可以省却前缀，如 
<DIV class=code>digester.addObjectCreate("foo/bar", "com.mycompany.MyFoo");<BR>digester.addSetProperties("foo/bar");</DIV><BR>
<LI>对其它命名空间，重复前面的2步</LI></OL><BR>　　另外，在指明要digester考虑命名空间之后，在定义匹配模板时，可以将命名空间别名加“：”作为元素名称的一部分使用。这与无命名空间时是一致的。<BR>
<H4>开发定制的匹配处理过程</H4><BR>　　通过实现 org.apache.commons.digester.Rules接口或扩展org.apache.commons.digester.RulesBase类来达到定制匹配过程的目的。<BR>　　Digester提供ExtendedBaseRules来扩展了匹配模板的定义，引入了特殊通配字符?和＊以及!,提供RegexRules来支持以正则式的语法定义匹配模板，提供WithDefaultsRulesWrapper来支持默认规则（即其它规则都不匹配时的处理规则）。<BR>
<H4>一些认识</H4><BR>　　通过看说明材料，尤其在学习Digester包中的Catalog例子以后，有一些认识：<BR>　　1、由于xml对属性名字的定义要求，与Java中对方法名字的定义要求不一致，导致出现不能自动映射的情况，如year-made标签属性，就不可能有方法setYear-made;<BR>　　2、对于根元素，与其子元素建立联系，有几种办法：一种是先生成根元素实例，压入栈，然后解析，将调用方法规则建立联系；另一种是解析的过程中第一个创建它，然后用getRoot的方法得到。<BR>　　3、如果某对象类构造都要参数，则此时需要扩展AbstractObjectCreationFactory类为这种对象建立一个Factory,在这个Factory中取得初始化参数值然后再创建一个对象实例。<BR>　　4、设有某个标签，要想自动用该标签子元素的内容填充该标签对应的对象的属性，则需要用digester.setRules(new ExtendedBaseRules()),然后addRules(),然后再调用addBeanPropertySetter("bala/lala/?");进行规则定义,注意此模板中有通配符。<BR>　　5、如果对象的属性是整型，则Digester自动将xml文件中字符串值转换为整型。<BR>　　6、在指明要digester考虑命名空间之后，如果不会引起歧义，完全可以忽略命名空间的存在，除非你要针对特定的命名空间进行特定的处理。<BR></DIV></DIV><img src ="http://www.blogjava.net/kapok/aggbug/3294.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-04-14 18:07 <a href="http://www.blogjava.net/kapok/archive/2005/04/14/3294.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Struts-menu源码分析(转贴)</title><link>http://www.blogjava.net/kapok/archive/2005/04/14/3293.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Thu, 14 Apr 2005 10:00:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/04/14/3293.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/3293.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/04/14/3293.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/3293.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/3293.html</trackback:ping><description><![CDATA[<FONT color=#a52a2a size=4><A href="http://dev.csdn.net/article/43/43114.shtm">http://dev.csdn.net/article/43/43114.shtm</A><BR><BR>&nbsp;&nbsp;好的代码读起来让人如饮醍醐，读完以后神清气爽。如果你想提高你的编程水平，如果你想提高你的设计能力，如果你也想成为大师，那么就去阅读代码吧。以本人十几年来的编程经验，阅读代码能让你得到的比阅读文章(那怕是大师的文章)得到的更多。优秀而且实用的代码有很多，比如Junit,比如Jive，比如petStore，甚至是tomcat的Example、Log4j的Example。<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Struts-Menu也来自一位大师的作品， Matt Raible。有很多优秀的作品，比如使用struts和hibernate的struts-resume。官方网站是</FONT><A href="http://raibledesigns.com/wiki/Wiki.jsp?page=Main" target=_blank><FONT color=#a52a2a size=4>http://raibledesigns.com/wiki/Wiki.jsp?page=Main</FONT></A><FONT color=#a52a2a size=4>。Struts-Menu的最新版本是2.1。功能是使用struts技术，构建树形菜单。应该说是一个非常实用的技术，极大的方便了广大的开发人员。与此同时，个人认为它的作用还不止于些。比如，同时它也是一个使用</FONT><A href="http://maven.apache.org/" target=_blank><FONT color=#a52a2a size=4>Maven</FONT></A><FONT color=#a52a2a size=4>和</FONT><A href="http://jakarta.apache.org/velocity/index.html" target=_blank><FONT color=#a52a2a size=4>velocity</FONT></A><FONT color=#a52a2a size=4>的一个很好的例子。<BR><BR></FONT>
<P><FONT color=#a52a2a size=4>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;首先，我们去看一下它的效果。</FONT><A href="http://www.raibledesigns.com/struts-menu/" target=_blank><FONT color=#a52a2a size=4>http://www.raibledesigns.com/struts-menu/</FONT></A><FONT color=#a52a2a size=4>。可以看到，如此丰富多彩的菜单效果，都是在演示一个配置文件里的内容。这是一个非常好的数据与表示相分离的实现。我们打开它的源码来看。首先看一下它的包图</FONT></P>
<P><A href="http://champion.ewuxi.com/old/opensource/struts-new/images/2.jpg" target=_blank><FONT color=#a52a2a size=4><IMG height=425 src="http://champion.ewuxi.com/old/opensource/struts-new/images/2.jpg" width=851 border=0></FONT></A></P>
<P><FONT color=#a52a2a size=4>共有五个包，其中menu自然是完成数据组织功能，是核心之一，displayer是显示方式包，完成数据显示大部分功能。也是核心之一。taglib意义明显。example自然是一些example。util是读取资源文件的包。因些，我们重点研究的包只有三个menu,displayer和taglib。</FONT></P>
<P><FONT color=#a52a2a size=4>首先我们来看menu包的类图</FONT></P>
<P><A href="http://champion.ewuxi.com/old/opensource/struts-new/images/1.jpg" target=_blank><FONT color=#a52a2a size=4><IMG height=768 src="http://champion.ewuxi.com/old/opensource/struts-new/images/1.jpg" width=1024 border=0></FONT></A></P>
<P><FONT color=#a52a2a size=4>首先是MenuPlugIn这个类。这个类的功能很明显，就是一个struts的plug-in。可以看到，它只有一个参数menuConfig，就是menu的配置文件路径。果然，在struts-conf文件中有这么一段</FONT></P>
<TABLE width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><FONT color=#a52a2a size=4>  &lt;!-- ========== Plug Ins Configuration ================================== --&gt;</FONT></PRE>
<P><FONT color=#a52a2a size=4>&lt;plug-in className="net.sf.navigator.menu.MenuPlugIn"&gt;<BR>&lt;set-property property="menuConfig" value="/WEB-INF/menu-config.xml"/&gt;<BR>&lt;/plug-in&gt;</FONT></P></TD></TR></TBODY></TABLE>
<P><FONT color=#a52a2a size=4></FONT>&nbsp;</P>
<P><FONT size=4><FONT color=#a52a2a>说明配置文件来自于/WEB-INF/menu-config.xml，当然，我们可以找到相应路径下找到这个文件。如果你以前没有做过struts的plug-in，现在该知道怎么做了吧，就这么简单。通过阅读初始化函数，知道它的功能就是调用<SPAN class=style1><SPAN class=style3><FONT face=Arial>MenuRepository来建立菜单。因此。我们知道MenuRepository必然是一个组织管理管理菜单的组织类。</FONT></SPAN></SPAN></FONT></FONT></P>
<TABLE width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE class=style1><FONT color=#a52a2a size=4>public void init(ActionServlet servlet, ModuleConfig config)
		throws ServletException {
		if (log.isDebugEnabled()) {
</FONT><SPAN class=style3><FONT color=#a52a2a size=4>			log.debug("Starting struts-menu initialization");
		}

		this.servlet = servlet;
		repository = new MenuRepository();
		repository.setLoadParam(menuConfig);
		repository.setServlet(servlet);

		try {
			repository.load();
			servlet.getServletContext().setAttribute(
				MenuRepository.MENU_REPOSITORY_KEY,
				repository);

			if (log.isDebugEnabled()) {
				log.debug("struts-menu initialization successfull");
			}
		} catch (LoadableResourceException lre) {
			throw new ServletException(
				"Failure initializing struts-menu: " + lre.getMessage());
		}
	}</FONT></SPAN></PRE></TD></TR></TBODY></TABLE>
<P><FONT color=#a52a2a size=4>打开<SPAN class=style1><SPAN class=style3><FONT face=Arial>MenuRepository</FONT></SPAN></SPAN>类，我们可以看到这个类也很简单，不过已经有少可以学习的了。首先是FastHashMap，可以看到，这个类里有三个FastHashMap。顾名思议，是快速HashMap了，再看一下，它来自org.apache.commons.collections.FastHashMap;。看到org.apache.commons这个著名的包了?如果你以前从没使用过它，那么建议你花上一段时间去研究使用它，我保证物有所值。</FONT></P>
<TABLE width="100%" border=1>
<TBODY>
<TR>
<TD><FONT color=#a52a2a size=4>protected FastHashMap menus = new FastHashMap();<BR>protected FastHashMap displayers = new FastHashMap();<BR>protected FastHashMap templates = new FastHashMap();</FONT></TD></TR></TBODY></TABLE>
<P><FONT color=#a52a2a size=4>接下来我们看到log的定义。对了，log，调试的核心之一。而下面这一句则是commons log的最常用的使用方法。快快让你的程序使用上commons log吧，第一，它功能强大，第二，它使用简单，就是这么简单。</FONT></P>
<TABLE width="100%" border=1>
<TBODY>
<TR>
<TD><FONT color=#a52a2a size=4>private Log log = LogFactory.getLog(getClass().getName());</FONT></TD></TR></TBODY></TABLE>
<P><FONT color=#a52a2a size=4>下面看一个的函数</FONT></P>
<TABLE width="100%" border=1>
<TBODY>
<TR>
<TD><PRE class=style1><FONT color=#a52a2a size=4> protected Digester initDigester() {
        Digester digester = new Digester();
        digester.setClassLoader(Thread.currentThread().getContextClassLoader());
        digester.push(this);

        //digester.setDebug(getDebug());
        // 1
        digester.addObjectCreate("MenuConfig/Menus/Menu",
            "net.sf.navigator.menu.MenuComponent", "type");
        digester.addSetProperties("MenuConfig/Menus/Menu");
        digester.addSetNext("MenuConfig/Menus/Menu", "addMenu");

        // 2
        digester.addObjectCreate("MenuConfig/Menus/Menu/Item",
            "net.sf.navigator.menu.MenuComponent", "type");
        digester.addSetProperties("MenuConfig/Menus/Menu/Item");
        digester.addSetNext("MenuConfig/Menus/Menu/Item", "addMenuComponent",
            "net.sf.navigator.menu.MenuComponent");

        // 3        
        digester.addObjectCreate("MenuConfig/Menus/Menu/Item/Item",
            "net.sf.navigator.menu.MenuComponent", "type");
        digester.addSetProperties("MenuConfig/Menus/Menu/Item/Item");
        digester.addSetNext("MenuConfig/Menus/Menu/Item/Item",
            "addMenuComponent", "net.sf.navigator.menu.MenuComponent");

        // 4
        digester.addObjectCreate("MenuConfig/Menus/Menu/Item/Item/Item",
            "net.sf.navigator.menu.MenuComponent", "type");
        digester.addSetProperties("MenuConfig/Menus/Menu/Item/Item/Item");
        digester.addSetNext("MenuConfig/Menus/Menu/Item/Item/Item",
            "addMenuComponent", "net.sf.navigator.menu.MenuComponent");

        // 5
        digester.addObjectCreate("MenuConfig/Menus/Menu/Item/Item/Item/Item",
            "net.sf.navigator.menu.MenuComponent", "type");
        digester.addSetProperties("MenuConfig/Menus/Menu/Item/Item/Item/Item");
        digester.addSetNext("MenuConfig/Menus/Menu/Item/Item/Item/Item",
            "addMenuComponent", "net.sf.navigator.menu.MenuComponent");

        // 6
        digester.addObjectCreate("MenuConfig/Menus/Menu/Item/Item/Item/Item",
            "net.sf.navigator.menu.MenuComponent", "type");
        digester.addSetProperties("MenuConfig/Menus/Menu/Item/Item/Item/Item");
        digester.addSetNext("MenuConfig/Menus/Menu/Item/Item/Item/Item",
            "addMenuComponent", "net.sf.navigator.menu.MenuComponent");

        // 7
        digester.addObjectCreate("MenuConfig/Menus/Menu/Item/Item/Item/Item",
            "net.sf.navigator.menu.MenuComponent", "type");
        digester.addSetProperties("MenuConfig/Menus/Menu/Item/Item/Item/Item");
        digester.addSetNext("MenuConfig/Menus/Menu/Item/Item/Item/Item",
            "addMenuComponent", "net.sf.navigator.menu.MenuComponent");

        digester.addObjectCreate("MenuConfig/Displayers/Displayer",
            "net.sf.navigator.displayer.MenuDisplayerMapping", "mapping");
        digester.addSetProperties("MenuConfig/Displayers/Displayer");
        digester.addSetNext("MenuConfig/Displayers/Displayer",
            "addMenuDisplayerMapping",
            "net.sf.navigator.displayer.MenuDisplayerMapping");
        digester.addSetProperty("MenuConfig/Displayers/Displayer/SetProperty",
            "property", "value");
            
        return digester;
    }</FONT></PRE></TD></TR></TBODY></TABLE>
<P><FONT color=#a52a2a size=4>这里又是一个经典,<SPAN class=style1><FONT face=Arial>digester</FONT></SPAN>，Digester的使用，如果你需要读一个XML配置文件，并且不想与DOM直接打交道的话，Digester将是一个很好的选择。实际上我们看到load函数调用一句 digester.parse(input);就已经把menu-config.xml建立到内存里了，就这么简单。如果你想要初始化你的系统，这种方法是不是可以学习呢？"工欲善其事，必先利其器"。我们可以看到Raible是怎么样利用现有的工具来减轻开发量的。</FONT></P>
<P><FONT color=#a52a2a size=4>由于MenuRepository举重若轻的初始化过程，甚至都没有让我们看到树形结构是怎么建立到内存里去的。不过不要着急，类图给我们了明示。</FONT></P>
<P><FONT color=#a52a2a size=4>看到MenuBase类了吗？对了，看名字就知道是一个Menu的基类。可以看到，它是一个简单的JavaBean。而且相信它的每个属性大家根据名字也能猜出来。所以重点讲解是MenuComponent，一个简化的 "Composite"模式。</FONT></P>
<P><FONT color=#a52a2a size=4><IMG height=257 src="http://champion.ewuxi.com/old/opensource/struts-new/images/pattern_3.gif" width=571></FONT></P>
<P><FONT color=#a52a2a size=4>如上图所示。由于此处的Leaf没有任何方法，只有属性。因此Leaf和Composite收缩成了一个MenuComponent类。大家都知道，Composite模式是实现树形结构最好的方法。如果你以前没有机会实现或者没有从Composite模式得到好处，那么，从这里看一下用Composite模式得到的好处。首先看它的简单，MenuComponet的实际代码很少，加起来不到十行。</FONT></P>
<TABLE width="100%" border=1>
<TBODY>
<TR>
<TD><PRE class=style1><FONT color=#a52a2a size=4>public void addMenuComponent(MenuComponent menuComponent) {
        menuComponents.add(menuComponent);
        menuComponent.setParent(this);

        if ((menuComponent.getName() == null) ||
                (menuComponent.getName().equals(""))) {
            menuComponent.setName(this.name + menuComponents.size());
        }
    }

    public MenuComponent[] getMenuComponents() {
        MenuComponent[] menus =
            (MenuComponent[]) menuComponents.toArray(_menuComponent);

        return menus;
    }</FONT></PRE></TD></TR></TBODY></TABLE>
<P><FONT color=#a52a2a size=4>如果你用十行来实现一个树型结构(并且还是通用的)，你愿不愿意？就是通过简单的这么一些代码，实现的在内存中建立树型结构的目标。</FONT></P>
<P><FONT color=#a52a2a size=4>下面我们来看DispLay包，这个包的功能也是很清楚的，就是用来显示啦。这个包的类图非常漂亮，遗憾的是也非常大。只能缩小了给大家看了。</FONT></P>
<P><A href="http://champion.ewuxi.com/old/opensource/struts-new/images/3.jpg" target=_blank><FONT color=#a52a2a size=4><IMG style="WIDTH: 855px; HEIGHT: 652px" height=652 src="http://champion.ewuxi.com/old/opensource/struts-new/images/3.jpg" width=831 border=0></FONT></A></P>
<P><FONT color=#a52a2a size=4>从类图中可以看到一个非常极漂亮的面象对象的设计思路。通过一个接口，利用模板方法。最后具体实现树型结构的显示。其主要方法是displayComponents和display这两方法，init方法则实现了初始化的工作，读取javascript和图片等文件。displayComponents是一个迭代函数。从而可以遍历一个MenuCompont树。并将其显示出来。</FONT></P>
<P><FONT color=#a52a2a size=4>应该说，Menu包是一个M层，而Dispplya包是一个view层，而加上TagLib包，就实现了MVC的完整结构。</FONT></P>
<P><FONT color=#a52a2a size=4>两个Tag类很清楚，首先我们从怎么使用它来看它们实现的功能</FONT></P>
<TABLE width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><FONT color=#a52a2a size=4>&lt;menu:useMenuDisplayer name="ListMenu" <BR>bundle="org.apache.struts.action.MESSAGE"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;menu:displayMenu name="ToDoListMenuFile"/&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;menu:displayMenu name="ToDoListMenuEdit"/&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;menu:displayMenu name="CaseDetailMenuCase"/&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;menu:displayMenu name="Standalone"/&gt;<BR>&lt;/menu:useMenuDisplayer&gt;</FONT></TD></TR></TBODY></TABLE>
<P><FONT color=#a52a2a size=4>显而易见。useMenuDisplayer这个类是实现使用哪一种显示方式。在menu-config里我们看到ListMenu的定义</FONT></P>
<TABLE width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><FONT color=#a52a2a size=4>&lt;Displayer name="ListMenu"<BR>type="net.sf.navigator.displayer.ListMenuDisplayer"/&gt;</FONT></TD></TR></TBODY></TABLE>
<P><FONT color=#a52a2a size=4>displayMenu则是取得一菜单，并将其显示出来，同样在menu-config也能找到。</FONT></P>
<TABLE width="100%" border=1>
<TBODY>
<TR>
<TD><PRE><FONT color=#a52a2a size=4>&lt;Menu  name="ToDoListMenuEdit"  title="EDIT"&gt;<BR>         &lt;Item  name="TDLselect" title="SELECT_ALL"       image="images/select-all.png"<BR>                    location="index.jsp" width="100" /&gt;<BR>         &lt;Item  name="TDLprefs"  title="USER_PREFERENCES" image="images/prefs.png"<BR>                    location="index.jsp" width="150" /&gt;<BR>         &lt;Item  title="Action Test" action="setPermissions?displayer=${displayer}"/&gt;<BR>&lt;/Menu&gt;</FONT></PRE></TD></TR></TBODY></TABLE>
<P><FONT color=#a52a2a size=4>查看 DisplayMenu的代码，可以看到。它完成的功能只是从context里取得MenuComponent对象，然后通过 displayer.display(menu);把它交给一个MenuDisplayer的实例来负责画出来。</FONT></P>
<P><FONT color=#a52a2a size=4>因此，Control层很好的完成了控制的功能。</FONT></P>
<P><FONT color=#a52a2a size=4>综上所述。通过这样一个优美的设计，把各个功能都他离开来了。如果我们需要增加一种显示方式，只要继承MenuDisplayer或它的一个子类，然后写出我们的方法，而不需要修改系统的其他部分。同样的，如果我们的菜单不准备存放在ServletContext而准备存放在比如Session里了，那么我们也只需要修改control部分和生成部分(即<SPAN class=style1><SPAN class=style3><FONT face=Arial>MenuRepository</FONT></SPAN></SPAN>)部分。而不影响Display部分。</FONT></P>
<P><FONT color=#a52a2a size=4>OK，对struts-menu的介绍结束了，下一篇文章将是如果使用struts-menu和数据库技术动态生成菜单了。请大家继续关注我的Blog。</FONT></P><BR>
<DIV style="FONT-SIZE: 14px; LINE-HEIGHT: 25px"><STRONG>作者Blog：</STRONG><A id=ArticleContent1_ArticleContent1_AuthorBlogLink href="http://blog.csdn.net/chensheng913/" target=_blank>http://blog.csdn.net/chensheng913/</A></DIV><img src ="http://www.blogjava.net/kapok/aggbug/3293.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-04-14 18:00 <a href="http://www.blogjava.net/kapok/archive/2005/04/14/3293.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>基于struts+spring+ibatis的 J2EE 开发 </title><link>http://www.blogjava.net/kapok/archive/2005/04/10/3064.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Sun, 10 Apr 2005 03:40:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/04/10/3064.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/3064.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/04/10/3064.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/3064.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/3064.html</trackback:ping><description><![CDATA[<A href="http://tech.ccidnet.com/pub/article/c1112_a216699_p5.html">http://tech.ccidnet.com/pub/article/c1112_a216699_p5.html</A><BR><BR>1. 前言<BR><BR>Struts 是目前Java Web MVC框架中不争的王者。经过长达五年的发展，Struts已经逐渐成长为一个稳定、成熟的框架，并且占有了MVC框架中最大的市场份额。但是Struts某些技术特性上已经落后于新兴的MVC框架。面对Spring MVC、Webwork2 这些设计更精密，扩展性更强的框架，Struts受到了前所未有的挑战。但站在产品开发的角度而言，Struts仍然是最稳妥的选择。本文的原型例子JpetStore 4.0就是基于Struts开发的，但是不拘泥于Struts的传统固定用法，例如只用了一个自定义Action类，并且在form bean类的定义上也是开创性的，令人耳目一新，稍后将具体剖析一下。<BR><BR>Spring Framework 实际上是Expert One-on-One J2EE Design and Development 一书中所阐述的设计思想的具体实现。Spring Framework的功能非常多。包含AOP、ORM、DAO、Context、Web、MVC等几个部分组成。Web、MVC暂不用考虑，JpetStore 4.0用的是更成熟的Struts和JSP；DAO由于目前Hibernate、JDO、ibatis的流行，也不考虑，JpetStore 4.0用的就是ibatis。因此最需要用的是AOP、ORM、Context。Context中，最重要的是Beanfactory，它能将接口与实现分开，非常强大。目前AOP应用最成熟的还是在事务管理上。<BR><BR>ibatis 是一个功能强大实用的SQL Map工具，不同于其他ORM工具（如hibernate），它是将SQL语句映射成Java对象，而对于ORM工具，它的SQL语句是根据映射定义生成的。ibatis 以SQL开发的工作量和数据库移植性上的让步，为系统设计提供了更大的自由空间。有ibatis代码生成的工具，可以根据DDL自动生成ibatis代码，能减少很多工作量。<BR><BR>2. JpetStore简述<BR><BR>2.1. 背景<BR><BR>最初是Sun公司的J2EE petstore，其最主要目的是用于学习J2EE，但是其缺点也很明显，就是过度设计了。接着Oracle用J2EE petstore来比较各应用服务器的性能。微软推出了基于.Net平台的 Pet shop，用于竞争J2EE petstore。而JpetStore则是经过改良的基于struts的轻便框架J2EE web应用程序，相比来说，JpetStore设计和架构更优良，各层定义清晰，使用了很多最佳实践和模式，避免了很多"反模式"，如使用存储过程，在java代码中嵌入SQL语句，把HTML存储在数据库中等等。最新版本是JpetStore 4.0。<BR><BR>2.2. JpetStore开发运行环境的建立<BR><BR>1、开发环境<BR><BR>Java SDK 1.4.2<BR>Apache Tomcat 4.1.31<BR>Eclipse-SDK-3.0.1-win32<BR>HSQLDB 1.7.2<BR><BR>2、Eclipse插件<BR><BR>EMF SDK 2.0.1：Eclipse建模框架，lomboz插件需要，可以使用runtime版本。<BR>lomboz 3.0：J2EE插件，用来在Eclipse中开发J2EE应用程序<BR>Spring IDE 1.0.3：Spring Bean配置管理插件<BR>xmlbuddy_2.0.10：编辑XML，用免费版功能即可<BR>tomcatPluginV3：tomcat管理插件<BR>Properties Editor：编辑java的属性文件,并可以预览以及自动存盘为Unicode格式。免去了手工或者ANT调用native2ascii的麻烦。<BR>2.3. 架构<BR><BR>
<CENTER><IMG src="http://tech.ccidnet.com/pub/attachment/2005/2/397089.jpg"></CENTER><BR><BR>
<CENTER>图1 JpetStore架构图</CENTER><BR><BR>图1 是JPetStore架构图。参照这个架构图，让我们稍微剖析一下源代码，得出JpetStore 4.0的具体实现图（图2），思路一下子就豁然开朗了。前言中提到的非传统的struts开发模式，关键就在struts Action类和form bean类上。<BR><BR>struts Action类只有一个：BeanAction。没错，确实是一个！与传统的struts编程方式很不同。再仔细研究BeanAction类，发现它其实是一个通用类，利用反射原理，根据URL来决定调用formbean的哪个方法。BeanAction大大简化了struts的编程模式，降低了对struts的依赖（与struts以及WEB容器有关的几个类都放在com.ibatis.struts包下，其它的类都可以直接复用）。利用这种模式，我们会很容易的把它移植到新的框架如JSF，spring。<BR><BR>这样重心就转移到form bean上了，它已经不是普通意义上的form bean了。查看源代码，可以看到它不仅仅有数据和校验/重置方法，而且已经具有了行为，从这个意义上来说，它更像一个BO(Business Object)。这就是前文讲到的，BeanAction类利用反射原理，根据URL来决定调用form bean的哪个方法（行为）。form bean的这些方法的签名很简单，例如：<BR><BR>
<CENTER><CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE><CCID_CODE>public String myActionMethod() 
 {
   //..work
   return "success";
 }</CCID_CODE></PRE></TD></TR></TBODY></TABLE></CCID_NOBR></CENTER><BR><BR>方法的返回值直接就是字符串，对应的是forward的名称，而不再是ActionForward对象，创建ActionForward对象的任务已经由BeanAction类代劳了。另外，程序还提供了ActionContext工具类，该工具类封装了request 、response、form parameters、request attributes、session attributes和 application attributes中的数据存取操作，简单而线程安全，form bean类使用该工具类可以进一步从表现层框架解耦。在这里需要特别指出的是，BeanAction类是对struts扩展的一个有益尝试，虽然提供了非常好的应用开发模式，但是它还非常新，一直在发展中。 <BR><BR>
<CENTER><IMG src="http://tech.ccidnet.com/pub/attachment/2005/2/397091.gif"></CENTER><BR><BR>
<CENTER>图2 JpetStore 4.0具体实现<BR><BR><BR>.4. 代码剖析<BR><BR>下面就让我们开始进一步分析JpetStore4.0的源代码，为下面的改造铺路。BeanAction.java是唯一一个Struts action类，位于com.ibatis.struts包下。正如上文所言，它是一个通用的控制类，利用反射机制，把控制转移到form bean的某个方法来处理。详细处理过程参考其源代码，简单明晰。<BR><BR>Form bean类位于com.ibatis.jpetstore.presentation包下，命名规则为***Bean。Form bean类全部继承于BaseBean类，而BaseBean类实际继承于ActionForm，因此，Form bean类就是Struts的 ActionForm，Form bean类的属性数据就由struts框架自动填充。而实际上，JpetStore4.0扩展了struts中ActionForm的应用： Form bean类还具有行为，更像一个BO,其行为（方法）由BeanAction根据配置（struts-config.xml）的URL来调用。虽然如此，我们还是把Form bean类定位于表现层。Struts-config.xml的配置里有3种映射方式，来告诉BeanAction把控制转到哪个form bean对象的哪个方法来处理。以这个请求连接为例http://localhost/jpetstore4/shop/viewOrder.do<BR><BR>1. URL Pattern<BR><BR>
<CENTER><CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE><CCID_CODE>&lt;action path="/shop/viewOrder" type="com.ibatis.struts.BeanAction"
    name="orderBean" scope="session"
    validate="false"&gt;
    &lt;forward name="success" path="/order/ViewOrder.jsp"/&gt;
  &lt;/action&gt;</CCID_CODE></PRE></TD></TR></TBODY></TABLE></CCID_NOBR></CENTER><BR><BR>此种方式表示，控制将被转发到"orderBean"这个form bean对象 的"viewOrder"方法（行为）来处理。方法名取"path"参数的以"/"分隔的最后一部分。 <BR><BR>2. Method Parameter <BR><BR>
<CENTER><CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE><CCID_CODE>&lt;action path="/shop/viewOrder" type="com.ibatis.struts.BeanAction"
    name="orderBean" parameter="viewOrder" scope="session"
    validate="false"&gt;
    &lt;forward name="success" path="/order/ViewOrder.jsp"/&gt;
  &lt;/action&gt;</CCID_CODE></PRE></TD></TR></TBODY></TABLE></CCID_NOBR></CENTER><BR><BR>此种方式表示，控制将被转发到"orderBean"这个form bean对象的"viewOrder"方法（行为）来处理。配置中的"parameter"参数表示form bean类上的方法。"parameter"参数优先于"path"参数。 <BR><BR>3. No Method call <BR><BR>
<CENTER><CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE><CCID_CODE>&lt;action path="/shop/viewOrder" type="com.ibatis.struts.BeanAction"
    name="orderBean" parameter="*" scope="session"
    validate="false"&gt;
    &lt;forward name="success" path="/order/ViewOrder.jsp"/&gt;
  &lt;/action&gt;</CCID_CODE></PRE></TD></TR></TBODY></TABLE></CCID_NOBR></CENTER><BR><BR>此种方式表示，form bean上没有任何方法被调用。如果存在"name"属性，则struts把表单参数等数据填充到form bean对象后，把控制转发到"success"。否则，如果name为空，则直接转发控制到"success"。这就相当于struts内置的org.apache.struts.actions.ForwardAction的功能 <BR><BR>
<CENTER><CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE><CCID_CODE>&lt;action path="/shop/viewOrder" type="org.apache.struts.actions.ForwardAction"
    parameter="/order/ViewOrder.jsp " scope="session" validate="false"&gt;
 &lt;/action&gt;</CCID_CODE></PRE></TD></TR></TBODY></TABLE></CCID_NOBR></CENTER><BR><BR>Service类位于com.ibatis.jpetstore.service包下，属于业务层。这些类封装了业务以及相应的事务控制。Service类由form bean类来调用。 <BR><BR>com.ibatis.jpetstore.persistence.iface包下的类是DAO接口，属于业务层，其屏蔽了底层的数据库操作，供具体的Service类来调用。DaoConfig类是工具类（DAO工厂类），Service类通过DaoConfig类来获得相应的DAO接口，而不用关心底层的具体数据库操作，实现了如图2中{耦合2}的解耦。 <BR><BR>com.ibatis.jpetstore.persistence.sqlmapdao包下的类是对应DAO接口的具体实现，在JpetStore4.0中采用了ibatis来实现ORM。这些实现类继承BaseSqlMapDao类，而BaseSqlMapDao类则继承ibatis DAO 框架中的SqlMapDaoTemplate类。ibatis的配置文件存放在com.ibatis.jpetstore.persistence.sqlmapdao.sql目录下。这些类和配置文件位于数据层 <BR><BR>Domain类位于com.ibatis.jpetstore.domain包下，是普通的javabean。在这里用作数据传输对象（DTO），贯穿视图层、业务层和数据层，用于在不同层之间传输数据。剩下的部分就比较简单了，请看具体的源代码，非常清晰。 <BR><BR></CCID_NOBR>2.5. 需要改造的地方<BR><BR>JpetStore4.0的关键就在struts Action类和form bean类上，这也是其精华之一（虽然该实现方式是试验性，待扩充和验证），在此次改造中我们要保留下来，即控制层一点不变，表现层获取相应业务类的方式变了（要加载spring环境），其它保持不变。要特别关注的改动是业务层和持久层，幸运的是JpetStore4.0设计非常好，需要改动的地方非常少，而且由模式可循，如下：<BR><BR>1. 业务层和数据层用Spring BeanFactory机制管理。<BR>2. 业务层的事务由spring 的aop通过声明来完成。<BR>3. 表现层（form bean）获取业务类的方法改由自定义工厂类来实现（加载spring环境）。<BR><BR>3. JPetStore的改造<BR><BR>3.1. 改造后的架构<BR><BR>
<CENTER><IMG src="http://tech.ccidnet.com/pub/attachment/2005/2/397093.gif"></CENTER><BR><BR>其中红色部分是要增加的部分，蓝色部分是要修改的部分。下面就让我们逐一剖析。<BR><BR>3.2. Spring Context的加载<BR>为了在Struts中加载Spring Context，一般会在struts-config.xml的最后添加如下部分：<BR><BR>
<CENTER><CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE><CCID_CODE>&lt;plug-in className="org.springframework.web.struts.ContextLoaderPlugIn"&gt;
&lt;set-property property="contextConfigLocation"
value="/WEB-INF/applicationContext.xml" /&gt;
&lt;/plug-in&gt;</CCID_CODE></PRE></TD></TR></TBODY></TABLE></CCID_NOBR></CENTER><BR><BR>Spring在设计时就充分考虑到了与Struts的协同工作，通过内置的Struts Plug-in在两者之间提供了良好的结合点。但是，因为在这里我们一点也不改动JPetStore的控制层(这是JpetStore4.0的精华之一)，所以本文不准备采用此方式来加载ApplicationContext。我们利用的是spring framework 的BeanFactory机制,采用自定义的工具类（bean工厂类）来加载spring的配置文件，从中可以看出Spring有多灵活，它提供了各种不同的方式来使用其不同的部分/层次，您只需要用你想用的，不需要的部分可以不用。 <BR><BR>具体的来说，就是在com.ibatis.spring包下创建CustomBeanFactory类，spring的配置文件applicationContext.xml也放在这个目录下。以下就是该类的全部代码，很简单： <BR><BR>
<CENTER><CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE><CCID_CODE>public final class CustomBeanFactory {
	static XmlBeanFactory factory = null;
	static {
		Resource is = new
InputStreamResource( CustomBeanFactory.class.getResourceAsStream("applicationContext.xml"));
		factory = new XmlBeanFactory(is);			
	}
	public static Object getBean(String beanName){
		return factory.getBean(beanName);
	}
}</CCID_CODE></PRE></TD></TR></TBODY></TABLE></CCID_NOBR></CENTER><BR><BR>实际上就是封装了Spring 的XMLBeanFactory而已，并且Spring的配置文件只需要加载一次，以后就可以直接用CustomBeanFactory.getBean("someBean")来获得需要的对象了(例如someBean)，而不需要知道具体的类。CustomBeanFactory类用于{耦合1}的解耦。CustomBeanFactory类在本文中只用于表现层的form bean对象获得service类的对象，因为我们没有把form bean对象配置在applicationContext.xml中。但是，为什么不把表现层的form bean类也配置起来呢，这样就用不着这CustomBeanFactory个类了，Spring会帮助我们创建需要的一切？问题的答案就在于form bean类是struts的ActionForm类！如果大家熟悉struts，就会知道ActionForm类是struts自动创建的：在一次请求中，struts判断，如果ActionForm实例不存在，就创建一个ActionForm对象，把客户提交的表单数据保存到ActionForm对象中。因此formbean类的对象就不能由spring来创建，但是service类以及数据层的DAO类可以，所以只有他们在spring中配置。所以，很自然的，我们就创建了CustomBeanFactory类，在表现层来衔接struts和spring。就这么简单，实现了另一种方式的{耦合一}的解耦。 <BR><BR>3.3. 表现层 <BR><BR>面分析到，struts和spring是在表现层衔接起来的，那么表现层就要做稍微的更改，即所需要的service类的对象创建上。以表现层的AccountBean类为例：原来的源代码如下 <BR><BR>
<CENTER><CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE><CCID_CODE>private static final AccountService accountService 
= AccountService.getInstance();
private static final CatalogService catalogService 
= CatalogService.getInstance();</CCID_CODE></PRE></TD></TR></TBODY></TABLE></CCID_NOBR></CENTER><BR><BR>改造后的源代码如下 <BR><BR>
<CENTER><CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE><CCID_CODE>private static final AccountService accountService 
= (AccountService)CustomBeanFactory.getBean("AccountService");
private static final CatalogService catalogService
= (CatalogService)CustomBeanFactory.getBean("CatalogService");</CCID_CODE></PRE></TD></TR></TBODY></TABLE></CCID_NOBR></CENTER><BR><BR>其他的几个presentation类以同样方式改造。这样，表现层就完成了。关于表现层的其它部分如JSP等一概不动。也许您会说，没有看出什么特别之处的好处啊？你还是额外实现了一个工厂类。别着急，帷幕刚刚开启，spring是在表现层引入，但您发没发现： <BR><BR>presentation类仅仅面向service类的接口编程，具体"AccountService"是哪个实现类，presentation类不知道，是在spring的配置文件里配置。（本例中，为了最大限度的保持原来的代码不作变化，没有抽象出接口）。Spring鼓励面向接口编程，因为是如此的方便和自然，当然您也可以不这么做。 <BR><BR>CustomBeanFactory这个工厂类为什么会如此简单，因为其直接使用了Spring的BeanFactory。Spring从其核心而言，是一个DI容器，其设计哲学是提供一种无侵入式的高扩展性的框架。为了实现这个目标，Spring 大量引入了Java 的Reflection机制，通过动态调用的方式避免硬编码方式的约束，并在此基础上建立了其核心组件BeanFactory，以此作为其依赖注入机制的实现基础。org.springframework.beans包中包括了这些核心组件的实现类，核心中的核心为BeanWrapper和BeanFactory类。 <BR><BR></CCID_NOBR>3.4. 持久层在讨论业务层之前，我们先看一下持久层，如下图所示：<BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR>
<CENTER><IMG src="http://tech.ccidnet.com/pub/attachment/2005/2/397095.jpg"></CENTER><BR><BR>在上文中，我们把iface包下的DAO接口归为业务层，在这里不需要做修改。ibatis的sql配置文件也不需要改。要改的是DAO实现类，并在spring的配置文件中配置起来。<BR><BR>1、修改基类<BR><BR>所有的DAO实现类都继承于BaseSqlMapDao类。修改BaseSqlMapDao类如下：<BR><BR>
<CENTER><CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE><CCID_CODE>public class BaseSqlMapDao extends SqlMapClientDaoSupport
{
  protected static final int PAGE_SIZE = 4;
  protected SqlMapClientTemplate smcTemplate 
  = this.getSqlMapClientTemplate();
  public BaseSqlMapDao()
  { 
	}
}</CCID_CODE></PRE></TD></TR></TBODY></TABLE></CCID_NOBR></CENTER><BR><BR>使BaseSqlMapDao类改为继承于Spring提供的SqlMapClientDaoSupport类，并定义了一个保护属性smcTemplate，其类型为SqlMapClientTemplate。 <BR><BR>2、修改DAO实现类 <BR><BR>所有的DAO实现类还是继承于BaseSqlMapDao类，实现相应的DAO接口，但其相应的DAO操作委托SqlMapClientTemplate来执行，以AccountSqlMapDao类为例，部分代码如下： <BR><BR>
<CENTER><CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE><CCID_CODE>public List getUsernameList() 
{
    return smcTemplate.queryForList("getUsernameList", null);
}
  public Account getAccount(String username, String password)
{
    Account account = new Account();
    account.setUsername(username);
    account.setPassword(password);
    return (Account)
	smcTemplate.queryForObject
	("getAccountByUsernameAndPassword", account);
}
  public void insertAccount(Account account)
{
 smcTemplate.update("insertAccount", account);
 smcTemplate.update("insertProfile", account);
 smcTemplate.update("insertSignon", account);
}</CCID_CODE></PRE></TD></TR></TBODY></TABLE></CCID_NOBR></CENTER><BR><BR>就这么简单，所有函数的签名都是一样的，只需要查找替换就可以了！ <BR><BR>3、除去工厂类以及相应的配置文件<BR><BR>除去DaoConfig.java这个DAO工厂类和相应的配置文件dao.xml，因为DAO的获取现在要用spring来管理。<BR><BR>4、DAO在Spring中的配置（applicationContext.xml）<BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR>
<CENTER><CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE><CCID_CODE>&lt;bean id="dataSource" 
        class="org.springframework.jdbc.datasource.DriverManagerDataSource"&gt;
        &lt;property name="driverClassName"&gt;
            &lt;value&gt;org.hsqldb.jdbcDriver&lt;/value&gt;
        &lt;/property&gt;
        &lt;property name="url"&gt;
            &lt;value&gt;jdbc:hsqldb:hsql://localhost/xdb&lt;/value&gt;
        &lt;/property&gt;
        &lt;property name="username"&gt;
            &lt;value&gt;sa&lt;/value&gt;
        &lt;/property&gt;
        &lt;property name="password"&gt;
            &lt;value&gt;&lt;/value&gt;
        &lt;/property&gt;
    &lt;/bean&gt;    
    &lt;!-- ibatis sqlMapClient config --&gt;
    &lt;bean id="sqlMapClient" 
        class="org.springframework.orm.ibatis.SqlMapClientFactoryBean"&gt;
        &lt;property name="configLocation"&gt;
            &lt;value&gt; 
   classpath:com\ibatis\jpetstore\persistence\sqlmapdao\sql\sql-map-config.xml
            &lt;/value&gt;
        &lt;/property&gt;
        &lt;property name="dataSource"&gt;
            &lt;ref bean="dataSource"/&gt;
        &lt;/property&gt;    
    &lt;/bean&gt;
    &lt;!-- Transactions --&gt;
    &lt;bean id="TransactionManager" 
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager"&gt;
        &lt;property name="dataSource"&gt;
            &lt;ref bean="dataSource"/&gt;
        &lt;/property&gt;
    &lt;/bean&gt;
    &lt;!-- persistence layer --&gt;
    &lt;bean id="AccountDao" 
        class="com.ibatis.jpetstore.persistence.sqlmapdao.AccountSqlMapDao"&gt;
        &lt;property name="sqlMapClient"&gt;
            &lt;ref local="sqlMapClient"/&gt;
        &lt;/property&gt;
    &lt;/bean&gt;</CCID_CODE></PRE></TD></TR></TBODY></TABLE></CCID_NOBR></CENTER><BR><BR>1. 我们首先创建一个数据源dataSource，在这里配置的是hsqldb数据库。如果是ORACLE数据库，driverClassName的值是"oracle.jdbc.driver.OracleDriver"，URL的值类似于"jdbc:oracle:thin:@wugfMobile:1521:cdcf"。数据源现在由spring来管理，那么现在我们就可以去掉properties目录下database.properties这个配置文件了；还有不要忘记修改sql-map-config.xml，去掉<PROPERTIES resource="properties/database.properties" />对它的引用。 <BR><BR>2. sqlMapClient节点。这个是针对ibatis SqlMap的SqlMapClientFactoryBean配置。实际上配置了一个sqlMapClient的创建工厂类。configLocation属性配置了ibatis映射文件的名称。dataSource属性指向了使用的数据源，这样所有使用sqlMapClient的DAO都默认使用了该数据源，除非在DAO的配置中另外显式指定。 <BR><BR>3. TransactionManager节点。定义了事务，使用的是DataSourceTransactionManager。 <BR><BR>4. 下面就可以定义DAO节点了，如AccountDao，它的实现类是com.ibatis.jpetstore.persistence.sqlmapdao.AccountSqlMapDao，使用的SQL配置从sqlMapClient中读取，数据库连接没有特别列出，那么就是默认使用sqlMapClient配置的数据源datasource。 <BR><BR>这样，我们就把持久层改造完了，其他的DAO配置类似于AccountDao。怎么样？简单吧。这次有接口了：） AccountDao接口－&gt;AccountSqlMapDao实现。 <BR><BR>3.5. 业务层 <BR><BR>业务层的位置以及相关类，如下图所示：在这个例子中只有3个业务类，我们以OrderService类为例来改造，这个类是最复杂的，其中涉及了事务。 <BR><BR>1、在ApplicationContext配置文件中增加bean的配置： <BR><BR>
<CENTER><CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE><CCID_CODE>&lt;bean id="OrderService" 
        class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"&gt;
        &lt;property name="transactionManager"&gt;
            &lt;ref local="TransactionManager"&gt;&lt;/ref&gt;
        &lt;/property&gt;
        &lt;property name="target"&gt;
            &lt;bean class="com.ibatis.jpetstore.service.OrderService"&gt;
                &lt;property name="itemDao"&gt;
                    &lt;ref bean="ItemDao"/&gt;
                &lt;/property&gt;
                &lt;property name="orderDao"&gt;
                    &lt;ref bean="OrderDao"/&gt;
                &lt;/property&gt;
                &lt;property name="sequenceDao"&gt;
                    &lt;ref bean="SequenceDao"/&gt;
                &lt;/property&gt;
            &lt;/bean&gt;
        &lt;/property&gt;
        &lt;property name="transactionAttributes"&gt;
            &lt;props&gt;
                &lt;prop key="insert*"&gt;PROPAGATION_REQUIRED&lt;/prop&gt;
            &lt;/props&gt;
        &lt;/property&gt;
    &lt;/bean&gt;</CCID_CODE></PRE></TD></TR></TBODY></TABLE></CCID_NOBR></CENTER><BR><BR>定义了一个OrderService，还是很容易懂的。为了简单起见，使用了嵌套bean，其实现类是com.ibatis.jpetstore.service.OrderService，分别引用了ItemDao，OrderDao，SequenceDao。该bean的insert*实现了事务管理(AOP方式)。TransactionProxyFactoryBean自动创建一个事务advisor， 该advisor包括一个基于事务属性的pointcut,因此只有事务性的方法被拦截。 <BR><BR>2、业务类的修改，以OrderService为例： <BR><BR>
<CENTER><CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE><CCID_CODE>public class OrderService {

   /* Private Fields */
  private ItemDao itemDao;
  private OrderDao orderDao;
  private SequenceDao sequenceDao;

  /* Constructors */

  public OrderService() {
  }

/**
 * @param itemDao 要设置的 itemDao。
 */
public final void setItemDao(ItemDao itemDao) {
	this.itemDao = itemDao;
}
/**
 * @param orderDao 要设置的 orderDao。
 */
public final void setOrderDao(OrderDao orderDao) {
	this.orderDao = orderDao;
}
/**
 * @param sequenceDao 要设置的 sequenceDao。
 */
public final void setSequenceDao(SequenceDao sequenceDao) {
	this.sequenceDao = sequenceDao;
}
//剩下的部分
…….
}</CCID_CODE></PRE></TD></TR></TBODY></TABLE></CCID_NOBR></CENTER><BR><BR>红色部分为修改部分。Spring采用的是Type2的设置依赖注入，所以我们只需要定义属性和相应的设值函数就可以了，ItemDao，OrderDao，SequenceDao的值由spring在运行期间注入。构造函数就可以为空了，另外也不需要自己编写代码处理事务了（事务在配置中声明），daoManager.startTransaction();等与事务相关的语句也可以去掉了。和原来的代码比较一下，是不是处理精简了很多！可以更关注业务的实现。 <BR><BR>4. 结束语 <BR><BR>ibatis是一个功能强大实用的SQL Map工具，可以直接控制SQL,为系统设计提供了更大的自由空间。其提供的最新示例程序JpetStore 4.0,设计优雅，应用了迄今为止很多最佳实践和设计模式，非常适于学习以及在此基础上创建轻量级的J2EE WEB应用程序。JpetStore 4.0是基于struts的，本文在此基础上，最大程度保持了原有设计的精华以及最小的代码改动量，在业务层和持久化层引入了Spring。在您阅读了本文以及改造后的源代码后，会深切的感受到Spring带来的种种好处：自然的面向接口的编程，业务对象的依赖注入，一致的数据存取框架和声明式的事务处理，统一的配置文件…更重要的是Spring既是全面的又是模块化的，Spring有分层的体系结构，这意味着您能选择仅仅使用它任何一个独立的部分，就像本文，而它的架构又是内部一致。 <BR></CENTER><img src ="http://www.blogjava.net/kapok/aggbug/3064.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-04-10 11:40 <a href="http://www.blogjava.net/kapok/archive/2005/04/10/3064.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>J2EE组件间共享对象技术</title><link>http://www.blogjava.net/kapok/archive/2005/04/08/2994.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Fri, 08 Apr 2005 06:41:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/04/08/2994.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/2994.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/04/08/2994.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/2994.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/2994.html</trackback:ping><description><![CDATA[<TABLE cellSpacing=0 cellPadding=0 width=760 align=center border=0>
<TBODY>
<TR>
<TD class=title vAlign=center align=middle height=56><B><FONT color=#ff0000 size=3><SPAN class=p16><A href="http://www.uml.org.cn/j2ee/j2ee003.htm">http://www.uml.org.cn/j2ee/j2ee003.htm</A><BR><BR>J2EE组件间共享对象技术</SPAN><!-- #EndEditable --></FONT></B></TD></TR>
<TR>
<TD class=formtitle align=middle height=40>作者：龚永生 &nbsp;&nbsp;&nbsp;本文选自：开放系统世界—赛迪网&nbsp;&nbsp;2003年02月26日</TD></TR></TBODY></TABLE>
<TABLE height=565 cellSpacing=0 cellPadding=0 width=760 align=center border=0>
<TBODY>
<TR>
<TD class=content vAlign=top height=350><SPAN class=p11b>想要用好Struts应用框架，必须了解J2EE Web级JSP和Servlet技术存放共享对象的几种方式。同时，要利用J2EE 开发Web应用程序也必须掌握组件间对象共享的机制。<BR><BR>像Java程序有类级别变量、方法级别变量一样，J2EE Web应用程序有四个对象存放共享对象。这些共享对象存放在那里，以便存放者或者其它程序代码日后使用。这四个对象分别是页面、请求、会话和应用程序，它们都是以数据结构键/值对的形式保存的。同时这四个对象形成了四个级别的共享对象存放地，即应用程序对象中的共享对象是全局性的，在整个应用程序的生命周期内有效（当然主动去掉除外），属于所有的上网用户；会话对象中的共享对象是在一个会话期内有效，属于用户的当前会话；请求对象中的共享对象在一个请求期内有效，属于用户发送的当前请求；页面对象中的共享对象只属于当前页面的执行实例。本文主要分析共享对象的设置和访问方法，包括共享对象的有效范围、具体访问方法、辅助显示手段和多线程下的实现策略。<BR><BR>
<CENTER><FONT color=#000099><STRONG>在JSP中访问共享对象</STRONG></FONT></CENTER><BR><BR>Servlet运行时已经准备好了这些范围对象，如表1所示。<BR><BR>
<CENTER>表1 JSP中的共享对象</CENTER><BR><BR>
<CENTER><CCID_NOBR>
<TABLE class=content width=502 border=1>
<TBODY>
<TR>
<TD>　　</TD>
<TD>变量名</TD>
<TD>变量类名</TD>
<TD>对象可访问范围</TD></TR>
<TR>
<TD>页面</TD>
<TD>pageContext</TD>
<TD>javax.servlet.jsp.PageContext</TD>
<TD>在执行某一个JSP时，Servlet运行时会为它初始化pageContext变量，这个变量可以被整个JSP代码访问，包括INCLUDE指示符插进来的代码。</TD></TR>
<TR>
<TD>请求</TD>
<TD>ruquest</TD>
<TD>javax.servlet.http.HttpServletRequest</TD>
<TD>用户提交一个HTTP请求给Servlet容量，Servlet运行时会把请求封装成HttpServletRequest的一个实例，在JSP中表现为request变量。能访问pageContext的JSP代码也能访问request，另外被处理这个请求的JSP代码FORWARD到的JSP代码也能访问。</TD></TR>
<TR>
<TD>会话</TD>
<TD>session</TD>
<TD>javax.servlet.http.HttpSession</TD>
<TD>一个HttpSession会话由被创建到关闭或失效期间的用户请求组成。处理这些请求的JSP可以访问到这期间的session对象中的共享对象。在会话关闭或失效时，这些对象会丢失。</TD></TR>
<TR>
<TD>应用程序</TD>
<TD>application</TD>
<TD>javax.servlet.ServletContext</TD>
<TD>这个对象在应用程序的整个生命周期间都有效，存放在这个对象内的数据任何JSP都能访问到。</TD></TR></TBODY></TABLE></CCID_NOBR></CENTER>
<P><BR><BR>
<CENTER><FONT color=#000099><STRONG>在Servlet中访问共享对象</STRONG></FONT></CENTER><BR><BR>Servlet中的共享对象如表2。<BR><BR>
<CENTER>表2 Servlet中的共享对象</CENTER><BR><BR>
<CENTER><CCID_NOBR>
<TABLE class=content width=502 border=1>
<TBODY>
<TR>
<TD>请求</TD>
<TD>SERVLET类的一系列服务方法的request参数。</TD>
<TD>javax.servlet.http.HttpServletRequest</TD>
<TD>用户提交一个HTTP请求给Servlet容器，Servlet运行时会把请求封装成HttpServletRequest的一个实例，并作为Servlet服务方法的request参数传递给Servlet。这个Servlet也可以把这个实例传递给其它Web组件。</TD></TR>
<TR>
<TD>会话</TD>
<TD>request.getSession()或者request.getSession(boolesn)方法获得。</TD>
<TD>javax.servlet.http.HttpSession</TD>
<TD>一个HttpSession会话由被创建到关闭或失败期间的用户请求组成。处理这些请求的Servlet可以访问到这期间的session对象中的共享对象。在会话关闭或失效时，这些共享对象会丢失。</TD></TR>
<TR>
<TD>应用程序</TD>
<TD>SERVLET的.getServletContext()</TD>
<TD>javax.servlet.ServletContext</TD>
<TD>这个对象在应用程序的整个生命周期间都有效，存放在这个对象内的数据任何Web组件都能访问到。</TD></TR></TBODY></TABLE></CCID_NOBR></CENTER>
<P><BR><BR>
<CENTER><FONT color=#000099><STRONG>资源组合</STRONG></FONT></CENTER><BR><BR>在JSP技术中，有两种把资源片断组合成一个资源的技术：include 指示符和jsp:include元素。指示符的语法为：<BR><BR><CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=550 align=center borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code bgColor=#e6e6e6><PRE><CCID_CODE>
&lt;%@ include file="fragmentresource.jsp" %&gt;</CCID_CODE>
</PRE></TD></TR></TBODY></TABLE></CCID_NOBR><BR><BR>当一个JSP被翻译成Servlet时，它会被处理。jsp:include元素的语法是：<BR><BR><CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=550 align=center borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code bgColor=#e6e6e6><PRE><CCID_CODE>
&lt;jsp:include page="included.jsp"/&gt;</CCID_CODE>
</PRE></TD></TR></TBODY></TABLE></CCID_NOBR><BR><BR>当这个JSP页面被执行时，它会被处理。指示符是代码的组合，元素则是结果的组合。fragmentresource.jsp和主页面具有一样的上下文，如页面对象；而included.jsp不具有和主页面一样的页面对象，但请求对象是同一个。<BR><BR>在Servlet中，RequestDispatcher.include(request,response)实现结果的整合,示例代码如下：<BR><BR><CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=550 align=center borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code bgColor=#e6e6e6><PRE><CCID_CODE>
RequestDispatcher dispatcher =request.getRequestDispatcher("/template.jsp");
if (dispatcher !=null)
 dispatcher.include(request,response);</CCID_CODE>
</PRE></TD></TR></TBODY></TABLE></CCID_NOBR><BR><BR>
<CENTER><FONT color=#000099><STRONG>控制传递</STRONG></FONT></CENTER><BR><BR>在利用RequestDispatcher.forware(request,response)把控制传给另一个Web组件设置形成一个控制管道时，要严格遵循“前面的组件处理request，最后的组件处理response”的准则。前面的组件甚至不能企图获取response输出流的引用。这个控制管道中的组件具有同一个request对象，不具有相同的pageContext对象（针对JSP）。Servlet中传递控制的例子如下：<BR><BR><CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=550 align=center borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code bgColor=#e6e6e6><PRE><CCID_CODE>
RequestDispatcher dispatcher =request.getRequestDispatcher("/template.jsp");
if (dispatcher !=null)
dispatcher.forward(request,response);</CCID_CODE>
</PRE></TD></TR></TBODY></TABLE></CCID_NOBR><BR><BR>JSP中传递控制的例子如下：<BR><BR><CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=550 align=center borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code bgColor=#e6e6e6><PRE><CCID_CODE>
&lt;jsp:forward page="/main.jsp"/&gt;</CCID_CODE>
</PRE></TD></TR></TBODY></TABLE></CCID_NOBR><BR><BR>在JSP中，可以通过jsp:param元素来增加请求对象的参数，适用于jsp:include和jsp:forward两元素。 示例如下：<BR><BR><CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=550 align=center borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code bgColor=#e6e6e6><PRE><CCID_CODE>
&lt;jsp: forward page="included.jsp"&gt;
&lt;jsp:param name="param1 " value=="value1"/&gt;
&lt;/jsp:include&gt;</CCID_CODE>
</PRE></TD></TR></TBODY></TABLE></CCID_NOBR><BR><BR>
<CENTER><FONT color=#000099><STRONG>显示共享对象</STRONG></FONT></CENTER><BR><BR>
<CENTER><IMG height=437 src="http://www.uml.org.cn/j2ee/images/97842.jpg" width=400></CENTER><BR><BR>
<CENTER>图1 显示共享信息类图</CENTER><BR><BR>在网页上显示各个级别的共享对象是一个非常不错的调试手段。下面的显示共享对象类图（如图1）实现了这个功能。它以类org.i18.struts.AttributeUtils为核心，这个类负责把四个对象的共享对象保存为键/值对的HashMap对象。通过它可以得到请求对象中的共享对象及参数信息，以及页面对象、会话对象、应用对象这些共享对象的函数接口。在JSP页面中，通过下面的代码可以给这个类的对象提供输入：<BR><BR><CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=550 align=center borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code bgColor=#e6e6e6><PRE><CCID_CODE>
&lt;jsp:useBean id="bean3" scope="application"
class="org.i18.struts.AttributeUtils" /&gt;
&lt;jsp:setProperty name="bean0" property="object" value="&lt;%= pageContext %&gt;" /&gt;</CCID_CODE>
</PRE></TD></TR></TBODY></TABLE></CCID_NOBR><BR><BR>JSP页面可以利用Struts提供的logic标签库显示这些共享对象：<BR><BR><CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=550 align=center borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code bgColor=#e6e6e6><PRE><CCID_CODE>
&lt;li&gt; 这是页对象内的共享对象 &lt;/li&gt;
&lt;table align="center" cellpadding="5" border="0"&gt;
&lt;tbody valign="center"&gt;
&lt;tr&gt;
&lt;td class="header"&gt; 共享对象名 &lt;/td&gt;
&lt;td class="header"&gt; 共享对象相关内容 &lt;/td&gt;
&lt;/tr&gt;
 &lt;logic:iterate id="element" name="bean0" property="pageProp"  &gt;
&lt;tr&gt;&lt;td&gt;
 &lt;bean:write name="element" property="key"/&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;bean:write name="element" property="value"/&gt;
&lt;/tr&gt;
&lt;/logic:iterate&gt;
&lt;/tbody&gt;
&lt;/table&gt;</CCID_CODE>
</PRE></TD></TR></TBODY></TABLE></CCID_NOBR><BR><BR>在Servlet中，AttributeDisplayHelper帮助者类完成JSP中logic标签库相应的功能。帮助者类完成工作后，AttrServlet把帮助者类完成的结果放在request中的一个共享属性Attr中，接着把控制传给servletAttr.jsp，再由它访问AttrServlet在request中设置的共享属性Attr，并显示结果。 
<P>使用者可以通过attr.jsp、AttrServlet的url映射和index.jsp的提交按钮来查看当前上下文所有级别的共享对象。<BR><BR>
<CENTER><FONT color=#000099><STRONG>关于多线程问题</STRONG></FONT></CENTER><BR><BR>J2EE系列规范中，EJB规范保证了组件开发者在单线程的环境下编程，但Servlet规范没有规定Servlet的系列服务方法在单线程模式下运作，所以开发者在使用共享对象时要注意线程同步问题。一个比较通用的原则是：在一个专职的组件中设置共享对象，当设置和访问破坏数据的一致性时，使用Java的同步控制。<BR><BR>一个Servlet(包括JSP)的生命周期由其所在的容器控制。当有一个Servlet请求时，容器执行如下步骤：<BR><BR>1.如果此Servlet的实例不存在，容器先装载Servlet的类代码，创建一个Servlet实例，接着调用这个实例的init方法。<BR><BR>2.如果此Servlet的实例存在，容器分配一个处理用户请求的工作线程。如果Servler实现了SingleThreadModel接口，工作线程会在这个实例上同步，并且在取得访问权限后调用实例的service方法，否则直接调用实例的service方法。javax.servlet.http.HttpServlet的service方法会根据用户的HTTP请求类型调用相应的doxxx方法。<BR><BR>3.只有当所有的线程从这个实例中退出，容器在回收这个实例时才会调用这个实例的destroy方法。<BR><BR>Servlet实例线程图（如图2）体现了这个生命周期模型。<BR><BR>
<CENTER><IMG height=215 src="http://www.uml.org.cn/j2ee/images/97843.jpg" width=334></CENTER><BR><BR>
<CENTER>图2 Servlet实例线程图</CENTER><BR><BR>虽然可以让所有的Servlet实现SingleThreadModel接口，但这会严重影响程序的性能。要解决多线程的同步问题，我们首先要分析共享对象的访问模式。在一个Web程序中，共享对象按访问模式可以分为以下两类：一次设置、多次读取的共享对象和多次设置、多次读取的共享对象。<BR><BR><B>一次设置、多次读取</B><BR><BR>对于这种共享对象，可以开发一个事件监听器，监听程序启动和停止事件，代码如下：<BR><BR><CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=550 align=center borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code bgColor=#e6e6e6><PRE><CCID_CODE>
package org.i18.listen
import org.i18.utils.*;
import javax.servlet.*;
import util.Counter;
public final class ContextListener implements ServletContextListener {
 private ServletContext context =null;
 public void contextInitialized(ServletContextEvent event){
  context =event.getServletContext();
  SynObject synObject = new SynObject();
  //在这里把共享对象放在应用对象中
  context.setAttribute("SYNOBJECT",synObject);
 }
 public void contextDestroyed(ServletContextEvent event){
  context =event.getServletContext();
  //清除保存在应用对象中的共享属性
  context.removeAttribute("SYNOBJECT ");
 }
}</CCID_CODE>
</PRE></TD></TR></TBODY></TABLE></CCID_NOBR><BR><BR>这样，当Web应用程序启动时，容器会调用监听器，从而设置共享对象。共享对象的访问只需直接调用getAttribute方法即可。<BR><BR><B>多次设置、多次读取</B><BR><BR>对于多次设置、多次读取的共享对象，必须利用Java的同步机制，访问步骤如下：<BR><BR>第一步，首先设计一个同步类。这个同步类的代码非常简单：<BR><BR><CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=550 align=center borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code bgColor=#e6e6e6><PRE><CCID_CODE>
package org.i18.utils
public final class SynObject{
}</CCID_CODE>
</PRE></TD></TR></TBODY></TABLE></CCID_NOBR><BR><BR>以上代码可以看出，它其实什么都没做，但继承了Object关于同步的方法和机制。<BR><BR>第二步，把这个类的实例放到应用对象中作为一个共享对象。可以看出这个同步对象属于一次设置、多次使用的共享对象。在应用程序启动事件监听器中设置它，请参见前面的代码。<BR><BR>第三步，设置共享对象。如果有对象需要整个应用程序共享，可以在Servlet的service中利用同步机制来设置：<BR><BR><CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=550 align=center borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code bgColor=#e6e6e6><PRE><CCID_CODE>
public void doGet (HttpServletRequest request,HttpServletResponse response)
throws ServletException,IOException {
 ServletContext context=   getServletContext();
 //得到同步对象
 Object obj = context.getAttribute("SYNOBJECT");  
 //获取同步钥匙
 synchronized(obj){
  //先检查是否存在这个共享对象
  Object obj2 = context.getAttribute("Attr");
  if (obj2 == null) {
   //不存在，设置共享对象
   obj2 = new TestBean();
   context.setAttribute("Attr",obj2);
  }
 }
}</CCID_CODE>
</PRE></TD></TR></TBODY></TABLE></CCID_NOBR><BR><BR>第四步，读取共享对象。当需要访问多次设置、多次访问的共享对象时，同样需要利用同步机制，代码如下：<BR><BR><CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=550 align=center borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code bgColor=#e6e6e6><PRE><CCID_CODE>
public void doGet (HttpServletRequest request,HttpServletResponse response)
throws ServletException,IOException {
 ServletContext context=   getServletContext();
 //得到同步对象，因为这个对象是一次设置、多次读取型的，不需要同步
 Object obj = context.getAttribute("SYNOBJECT");  
 //获取同步钥匙
 synchronized(obj){
  //得到需要的共享对象
  Object obj2 = context.getAttribute("Attr");
 }
}</CCID_CODE>
</PRE></TD></TR></TBODY></TABLE></CCID_NOBR><BR><BR>会话对象内共享对象的多线程问题可以和应用对象一样处理。请求对象和页面对象正常情况下是线程安全的，除非开发者自己引入了额外的线程。<BR><BR>本文分析了开发好的J2EE应用必须掌握的、组件间对象共享的技术，这种分析技术同样适用于EJB中。在EJB容器中，信息存放的位置变成了实现JNDI的服务提供者，大家通过JNDI的接口方法查询、绑定共享对象。需要注意的是，同步对象不能在JNDI中实现，因为大家搜索出来的不是同一个内存对象。<BR><BR></SPAN></TD></TR></TBODY></TABLE><img src ="http://www.blogjava.net/kapok/aggbug/2994.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-04-08 14:41 <a href="http://www.blogjava.net/kapok/archive/2005/04/08/2994.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>深入Struts 1.1</title><link>http://www.blogjava.net/kapok/archive/2005/04/08/2990.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Fri, 08 Apr 2005 03:57:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/04/08/2990.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/2990.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/04/08/2990.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/2990.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/2990.html</trackback:ping><description><![CDATA[<A href="http://www-900.ibm.com/developerWorks/cn/java/l-struts1-1/">http://www-900.ibm.com/developerWorks/cn/java/l-struts1-1/</A><BR><BR>
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR vAlign=top>
<TD width="100%">
<P>王和全（<A href="mailto:ok_winnerboy@sina.com">ok_winnerboy@sina.com</A>）<BR>2003年 8 月</P><!-- author inof end-->
<BLOCKQUOTE>作为基于MVC模式的Web应用最经典框架，Struts已经正式推出了1.1版本，该版本在以往版本的基础上，提供了许多激动人心的新功能。本文就将带你走进Struts 1.1去深入地了解这些功能。</BLOCKQUOTE>
<P><B>说明：</B>希望本文的读者能有一定的Struts使用基础。</P>
<P><A name=0><SPAN class=atitle2>1、Model 2</SPAN></A></P>
<P>Struts是基于Model 2之上的，而Model 2是经典的MVC（模型－视图－控制器）模型的Web应用变体，这个改变主要是由于网络应用的特性--HTTP协议的无状态性引起的。Model 2的目的和MVC一样，也是利用控制器来分离模型和视图，达到一种层间松散耦合的效果，提高系统灵活性、复用性和可维护性。在多数情况下，你可以将Model 2与MVC等同起来。</P>
<P>下图表示一个基于Java技术的典型网络应用，从中可以看出Model 2中的各个部分是如何对应于Java中各种现有技术的。</P>
<P align=center><IMG src="http://www-900.ibm.com/developerWorks/cn/java/l-struts1-1/image001.gif"></P>
<P>在利用Model 2之前，我们是把所有的表示逻辑和业务逻辑都集中在一起（比如大杂烩似的JSP），有时也称这种应用模式为Model 1，Model 1的主要缺点就是紧耦合，复用性差以及维护成本高。 </P>
<P><A name=1><SPAN class=atitle2>2、Struts 1.1 和Model 2 </SPAN></A></P>
<P>既然Struts 1.1是基于Model 2之上，那它的底层机制也就是MVC，下面是Struts 1.1中的MVC实现示意图：</P>
<P align=center><IMG src="http://www-900.ibm.com/developerWorks/cn/java/l-struts1-1/image002.jpg"><BR><I>图解说明：其中不同颜色代表MVC的不同部分：红色（控制器）、紫色（模型）和绿色（视图）</I></P>
<P>首先，控制器（ActionServlet）进行初始化工作，读取配置文件（struts-config.xml），为不同的Struts模块初始化相应的ModuleConfig对象。比如配置文件中的Action映射定义都保存在ActionConfig集合中。相应地有ControlConfig集合、FormBeanConfig集合、ForwardConfig集合和MessageResourcesConfig集合等。</P>
<P><I><B>提示：</B>模块是在Struts 1.1中新提出的概念，在稍后的内容中我们将详细介绍，你现在可以简单地把模块看作是一个子系统，它们共同组成整个应用，同时又各自独立。Struts 1.1中所有的处理都是在特定模块环境中进行的。模块的提出主要是为了解决Struts 1.0中单配置文件的问题。</I></P>
<P>控制器接收HTTP请求，并从ActionConfig中找出对应于该请求的Action子类，如果没有对应的Action，控制器直接将请求转发给JSP或者静态页面。否则控制器将请求分发至具体Action类进行处理。</P>
<P>在控制器调用具体Action的execute方法之前，ActionForm对象将利用HTTP请求中的参数来填充自己（可选步骤，需要在配置文件中指定）。具体的ActionForm对象应该是ActionForm的子类对象，它其实就是一个JavaBean。此外，还可以在ActionForm类中调用validate方法来检查请求参数的合法性，并且可以返回一个包含所有错误信息的ActionErrors对象。如果执行成功，ActionForm自动将这些参数信息以JavaBean（一般称之为form bean）的方式保存在Servlet Context中，这样它们就可以被其它Action对象或者JSP调用。</P>
<P>Struts将这些ActionForm的配置信息都放在FormBeanConfig集合中，通过它们Struts能够知道针对某个客户请求是否需要创建相应的ActionForm实例。</P>
<P>Action很简单，一般只包含一个execute方法，它负责执行相应的业务逻辑，如果需要，它也进行相应的数据检查。执行完成之后，返回一个ActionForward对象，控制器通过该ActionForward对象来进行转发工作。我们主张将获取数据和执行业务逻辑的功能放到具体的JavaBean当中，而Action只负责完成与控制有关的功能。遵循该原则，所以在上图中我将Action对象归为控制器部分。</P>
<P><I><B>提示：</B>其实在Struts 1.1中，ActionMapping的作用完全可以由ActionConfig来替代，只不过由于它是公共API的一部分以及兼容性的问题得以保留。ActionMapping通过继承ActionConfig来获得与其一致的功能，你可以等同地看待它们。同理，其它例如ActionForward与ForwardConfig的关系也是如此。</I></P>
<P>下图给出了客户端从发出请求到获得响应整个过程的图解说明。</P>
<P align=center><IMG src="http://www-900.ibm.com/developerWorks/cn/java/l-struts1-1/image003.jpg"></P>
<P>下面我们就来详细地讨论一下其中的每个部分，在这之前，先来了解一下模块的概念。</P>
<P><A name=2><SPAN class=atitle2>3、模块</SPAN></A></P>
<P>我们知道，在Struts 1.0中，我们只能在web.xml中为ActionServlet指定一个配置文件，这对于我们这些网上的教学例子来说当然没什么问题，但是在实际的应用开发过程中，可能会有些麻烦。因为许多开发人员都可能同时需要修改配置文件，但是配置文件只能同时被一个人修改，这样肯定会造成一定程度上的资源争夺，势必会影响开发效率和引起开发人员的抱怨。</P>
<P>在Struts 1.1中，为了解决这个并行开发的问题，提出了两种解决方案：<BR>
<OL>
<LI>多个配置文件的支持 
<LI>模块的支持 </LI></OL>
<P></P>
<P>支持多个配置文件，是指你能够为ActionServlet同时指定多个xml配置文件，文件之间以逗号分隔，比如Struts提供的MailReader演示例子中就采用该种方法。</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>
  &lt;!-- Action Servlet Configuration --&gt;
  &lt;servlet&gt;
	&lt;servlet-name&gt;action&lt;/servlet-name&gt;
	&lt;servlet-class&gt;org.apache.struts.action.ActionServlet&lt;/servlet-class&gt;
	&lt;init-param&gt;
		&lt;param-name&gt;config&lt;/param-name&gt;
		&lt;param-value&gt;/WEB-INF/struts-config.xml, /WEB-INF/struts-config-registration.xml&lt;/param-value&gt;
	&lt;/init-param&gt; 
	&lt;load-on-startup&gt;1&lt;/load-on-startup&gt;
  &lt;/servlet&gt;

</CODE></PRE></TD></TR></TBODY></TABLE>
<P>这种方法可以很好地解决修改冲突的问题，不同的开发人员可以在不同的配置文件中设置自己的Action、ActionForm等等（当然不是说每个开发人员都需要自己的配置文件，可以按照系统的功能模块进行划分）。但是，这里还是存在一个潜在的问题，就是可能不同的配置文件之间会产生冲突，因为在ActionServlet初始化的时候这几个文件最终还是需要合并到一起的。比如，在struts-config.xml中配置了一个名为success的&lt;forward&gt;，而在struts-config-registration.xml中也配置了一个同样的&lt;forward&gt;，那么执行起来就会产生冲突。</P>
<P>为了彻底解决这种冲突，Struts 1.1中引进了模块（Module）的概念。一个模块就是一个独立的子系统，你可以在其中进行任意所需的配置，同时又不必担心和其它的配置文件产生冲突。因为前面我们讲过，ActionServlet是将不同的模块信息保存在不同的ModuleConfig对象中的。要使用模块的功能，需要进行以下的准备工作：</P>
<P>1、为每个模块准备一个配置文件</P>
<P>2、配置web.xml文件，通知控制器</P>
<P>决定采用多个模块以后，你需要将这些信息告诉控制器，这需要在web.xml文件进行配置。下面是一个典型的多模块配置：</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>
&lt;init-param&gt;
	&lt;param-name&gt;config&lt;/param-name&gt;
	&lt;param-value&gt;/WEB-INF/struts-config.xml&lt;/param-value&gt;
&lt;/init-param&gt;
&lt;init-param&gt;
	&lt;param-name&gt;config/customer&lt;/param-name&gt; 
	&lt;param-value&gt;/WEB-INF/struts-config-customer.xml&lt;/param-value&gt;
&lt;/init-param&gt;
&lt;init-param&gt; 
	&lt;param-name&gt;config/order&lt;/param-name&gt;
	&lt;param-value&gt;/WEB-INF/struts-config-order.xml&lt;/param-value&gt;
&lt;/init-param&gt;

</CODE></PRE></TD></TR></TBODY></TABLE>
<P>要配置多个模块，你需要在原有的一个&lt;init-param&gt;（在Struts 1.1中将其对应的模块称为缺省模块）的基础之上，增加模块对应的&lt;init-param&gt;。其中&lt;param-name&gt;表示为config/XXX的形式，其中XXX为对应的模块名，&lt;param-value&gt;中还是指定模块对应的配置文件。上面这个例子说明该应用有三个模块，分别是缺省模块、customer和order，它们分别对应不同的配置文件。</P>
<P>3、准备各个模块所需的ActionForm、Action和JSP等资源</P>
<P>但是要注意的是，模块的出现也同时带来了一个问题，即如何在不同模块间进行转发？有两种方法可以实现模块间的转发，一种就是在&lt;forward&gt;（全局或者本地）中定义，另外一种就是利用org.apache.struts.actions.SwitchAction。</P>
<P>下面就是一个全局的例子：</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>
    ... 
    &lt;struts-config&gt;
	... 
	&lt;global-forwards&gt;
		&lt;forward name="toModuleB"
			contextRelative="true"  
			path="/moduleB/index.do" 
		redirect="true"/&gt;   
	... 
	&lt;/global-forwards&gt;  
	...   
    &lt;/struts-config&gt;
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>可以看出，只需要在原有的path属性前加上模块名，同时将contextRelative属性置为true即可。此外，你也可以在&lt;action&gt;中定义一个类似的本地&lt;forward&gt;。</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>
  &lt;action-mappings&gt;
	&lt;!-- Action mapping for profile form --&gt;
	&lt;action path="/login" 
	type="com.ncu.test.LoginAction"  
	name="loginForm"     
	scope="request"      
	input="tile.userLogin"
	validate="true"&gt;     
	&lt;forward name="success" contextRelative="true" path="/moduleA/login.do"/&gt; 
	&lt;/action&gt; 
  &lt;/action-mappings&gt;
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>如果你已经处在其他模块，需要转回到缺省模块，那应该类似下面这样定义，即模块名为空。</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>
&lt;forward name="success" contextRelative="true" path="/login.do"/&gt;

</CODE></PRE></TD></TR></TBODY></TABLE>
<P>此外，你也可以使用org.apache.struts.actions.SwitchAction，例如：</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>
    ...
    &lt;action-mappings&gt; 
	&lt;action path="/toModule" 
	type="org.apache.struts.actions.SwitchAction"/&gt;  
	...    
    &lt;/action-mappings&gt;  
    ...
</CODE></PRE></TD></TR></TBODY></TABLE>
<P><A name=3><SPAN class=atitle2>4、ActionServlet</SPAN></A></P>
<P>我们首先来了解MVC中的控制器。在Struts 1.1中缺省采用ActionServlet类来充当控制器。当然如果ActionServlet不能满足你的需求，你也可以通过继承它来实现自己的类。这可以在/WEB-INF/web.xml中来具体指定。</P>
<P>要掌握ActionServlet，就必须了解它所扮演的角色。首先，ActionServlet表示MVC结构中的控制器部分，它需要完成控制器所需的前端控制及转发请求等职责。其次，ActionServlet被实现为一个专门处理HTTP请求的Servlet，它同时具有servlet的特点。在Struts 1.1中它主要完成以下功能：<BR>
<UL>
<LI>接收客户端请求 
<LI>根据客户端的URI将请求映射到一个相应的Action类 
<LI>从请求中获取数据填充Form Bean（如果需要） 
<LI>调用Action类的execute()方法获取数据或者执行业务逻辑 
<LI>选择正确的视图响应客户 </LI></UL>
<P></P>
<P>此外，ActionServlet还负责初始化和清除应用配置信息的任务。ActionServlet的初始化工作在init方法中完成，它可以分为两个部分：初始化ActionServlet自身的一些信息以及每个模块的配置信息。前者主要通过initInternal、initOther和initServlet三个方法来完成。</P>
<P>我们可以在/WEB-INF/web.xml中指定具体的控制器以及初始参数，由于版本的变化以及Struts 1.1中模块概念的引进，一些初始参数被废弃或者移入到/WEB-INF/struts-config.xml中定义。下面列出所有被废弃的参数，相应地在web.xml文件中也不鼓励再使用。</P>
<UL>
<LI>application 
<LI>bufferSize 
<LI>content 
<LI>debug 
<LI>factory 
<LI>formBean 
<LI>forward 
<LI>locale 
<LI>mapping 
<LI>maxFileSize 
<LI>multipartClass 
<LI>nocache 
<LI>null 
<LI>tempDir </LI></UL>
<P>ActionServlet根据不同的模块来初始化ModuleConfig类，并在其中以XXXconfig集合的方式保存该模块的各种配置信息，比如ActionConfig，FormBeanConfig等。</P>
<P>初始化工作完成之后，ActionServlet准备接收客户请求。针对每个请求，方法process(HttpServletRequest request, HttpServletResponse response)将被调用。该方法指定具体的模块，然后调用该模块的RequestProcessor的process方法。</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>
protected void process(HttpServletRequest request, 
		HttpServletResponse response) 
		throws IOException, ServletException {

	RequestUtils.selectModule(request, getServletContext());        
	getRequestProcessor(getModuleConfig(request)).process(request, response);
}
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>RequestProcessor包含了Struts控制器的所有处理逻辑，它调用不同的processXXX方法来完成不同的处理。下表列出其中几个主要的方法：</P>
<P>
<DIV align=center>
<TABLE border=1>
<TBODY>
<TR>
<TD>方法</TD>
<TD>功能</TD></TR>
<TR>
<TD>processPath</TD>
<TD>获取客户端的请求路径</TD></TR>
<TR>
<TD>processMapping</TD>
<TD>利用路径来获得相应的ActionMapping</TD></TR>
<TR>
<TD>processActionForm</TD>
<TD>初始化ActionForm（如果需要）并存入正确的scope中</TD></TR>
<TR>
<TD>processActionCreate</TD>
<TD>初始化Action</TD></TR>
<TR>
<TD>processActionPerform</TD>
<TD>调用Action的execute方法</TD></TR>
<TR>
<TD>processForwardConfig</TD>
<TD>处理Action返回的ActionForward</TD></TR></TBODY></TABLE></DIV>
<P></P>
<P><A name=4><SPAN class=atitle2>5、ActionForm</SPAN></A></P>
<P>对于ActionForm你可以从以下几个方面来理解它：<BR>
<OL>
<LI>ActionForm表示HTTP窗体中的数据，可以将其看作是模型和视图的中介，它负责保存视图中的数据供模型或者视图使用。Struts 1.1文档中把它比作HTTP和Action之间的防火墙，这体现了ActionForm具有的过滤保护的作用，只有通过ActionForm验证的数据才能够发送到Action处理。 
<LI>ActionForm是与一个或多个ActionConfig关联的JavaBean，在相应的action的execute方法被调用之前，ActionForm会自动利用请求参数来填充自己（初始化属性）。 
<LI>ActionForm是一个抽象类，你必须通过继承来实现自己的类。 </LI></OL>
<P></P>
<P>ActionForm首先利用属性的getter和setter方法来实现初始化，初始化完毕后，ActionForm的validate方法被调用，你可以在其中来检查请求参数的正确性和有效性，并且可以将错误信息以ActionErrors的形式返回到输入窗体。否则，ActionForm将被作为参数传给action的execute方法以供使用。</P>
<P>ActionForm bean的生命周期可以设置为session（缺省）和request，当设置为session时，记得在reset方法中将所有的属性重新设置为初始值。</P>
<P>由于ActionForm对应于HTTP窗体，所以随着页面的增多，你的ActionForm将会急速增加。而且可能同一类型页面字段将会在不同的ActionForm中出现，并且在每个ActionForm中都存在相同的验证代码。为了解决这个问题，你可以为整个应用实现一个ActionForm或者至少一个模块对应于一个ActionForm。</P>
<P>但是，聚合的代价就是复用性很差，而且难维护。针对这个问题，在Struts 1.1中提出了DynaActionForm的概念。</P>
<P><B>DynaActionForm类</B></P>
<P>DynaActionForm的目的就是减少ActionForm的数目，利用它你不必创建一个个具体的ActionForm类，而是在配置文件中配置出所需的虚拟ActionForm。例如，在下表中通过指定&lt;form-bean&gt;的type为"org.apache.struts.action.DynaActionForm"来创建一个动态的ActionForm--loginForm。</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>
&lt;form-beans&gt;
	&lt;form-bean name="loginForm" type="org.apache.struts.action.DynaActionForm"&gt;  
		&lt;form-property name="actionClass" type="java.lang.String"/&gt;
		&lt;form-property name="username" type="java.lang.String"/&gt;
		&lt;form-property name="password" type="java.lang.String"/&gt; 
	&lt;/form-bean&gt; 
&lt;/form-beans&gt;

</CODE></PRE></TD></TR></TBODY></TABLE>
<P>动态的ActionForm的使用方法跟普通的ActionForm相同，但是要注意一点。普通的ActionForm对象需要为每个属性提供getter和setter方法，以上面的例子而言，我们需要提供getUsername() 和 setUsername()方法取得和设置username属性，同样地有一对方法用于取得和设置password属性和actionClass属性。</P>
<P>如果使用DynaActionForm，它将属性保存在一个HashMap类对象中，同时提供相应的get(name) 和 set(name)方法，其中参数name是要访问的属性名。例如要访问DynaActionForm中username的值，可以采用类似的代码：</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>
String username = (String)form.get("username")；

</CODE></PRE></TD></TR></TBODY></TABLE>
<P>由于值存放于一个HashMap对象，所以要记得对get()方法返回的Object对象做强制性类型转换。正是由于这点区别，如果你在Action中非常频繁地使用ActionForm对象，建议还是使用普通的ActionForm对象。</P>
<P>在Struts 1.1中，除了DynaActionForm以外，还提供了表单输入自动验证的功能，在包org.apache.struts.validator中提供了许多有用的类，其中最常见的就是DynaValidatorForm类。</P>
<P><B>DynaValidatorForm类</B></P>
<P>DynaValidatorForm是DynaActionForm的子类，它能够提供动态ActionForm和自动表单输入验证的功能。和使用DynaActionForm类似，你必须首先在配置文件中进行配置：</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>
&lt;form-beans&gt;
	&lt;form-bean name="loginForm" type="org.apache.struts.validator.DynaValidatorForm"&gt; 
		&lt;form-property name="actionClass" type="java.lang.String"/&gt;     
		&lt;form-property name="username" type="java.lang.String"/&gt; 
		&lt;form-property name="password" type="java.lang.String"/&gt;  
	&lt;/form-bean&gt;
&lt;/form-beans&gt;

</CODE></PRE></TD></TR></TBODY></TABLE>
<P>同时要定义验证的插件：</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>
  &lt;plug-in className="org.apache.struts.validator.ValidatorPlugIn"&gt;
	&lt;set-property property="pathnames"  
	value="/WEB-INF/validator-rules.xml,  
	/WEB-INF/validation.xml"/&gt;
  &lt;/plug-in&gt;

</CODE></PRE></TD></TR></TBODY></TABLE>
<P>其中的validator.xml和validator-rules.xml分别表示验证定义和验证规则的内容（可以合并在一起），比如针对上例中的DynaValidatorForm，我们有如下验证定义（validator.xml）：</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>
&lt;?xml version="1.0" encoding="ISO-8859-1" ?&gt;
&lt;!DOCTYPE form-validation PUBLIC  
"-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.0//EN"  
"http://jakarta.apache.org/commons/dtds/validator_1_0.dtd"&gt;
&lt;!--    Validation Rules    $Id: validation.xml--&gt;

&lt;form-validation&gt;  
&lt;!-- ========== Default Language Form Definitions ===================== --&gt;
&lt;formset&gt;  
	&lt;form name="loginForm"&gt;     
		&lt;field property="username" depends="required, minlength,maxlength"&gt; 
			&lt;arg0   key="prompt.username"/&gt;          
			&lt;arg1   key="${var:minlength}" name="minlength" resource="false"/&gt;       
			&lt;arg2   key="${var:maxlength}" name="maxlength" resource="false"/&gt;              
			&lt;var&gt;                
				&lt;var-name&gt;maxlength&lt;/var-name&gt;    
				&lt;var-value&gt;16&lt;/var-value&gt;         
			&lt;/var&gt;          
			&lt;var&gt;      
				&lt;var-name&gt;minlength&lt;/var-name&gt;     
				&lt;var-value&gt;3&lt;/var-value&gt;         
			&lt;/var&gt;       
		&lt;/field&gt;     
		&lt;field property="password" depends="required, minlength,maxlength" bundle="alternate"&gt;          
			&lt;arg0   key="prompt.password"/&gt;   
			&lt;arg1   key="${var:minlength}" name="minlength" resource="false"/&gt;          
			&lt;arg2   key="${var:maxlength}" name="maxlength" resource="false"/&gt;  
			&lt;var&gt;              
				&lt;var-name&gt;maxlength&lt;/var-name&gt;     
				&lt;var-value&gt;16&lt;/var-value&gt;        
			&lt;/var&gt;          
			&lt;var&gt;      
				&lt;var-name&gt;minlength&lt;/var-name&gt; 
				&lt;var-value&gt;3&lt;/var-value&gt;       
			&lt;/var&gt;        
		&lt;/field&gt;    
	&lt;/form&gt;   
&lt;/formset&gt;
&lt;/form-validation&gt;
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>从上述定义中，我们可以看到对于字段username有三项验证：required, minlength, maxlength，意思是该字段不能为空，而且长度在3和16之间。而validator-rules.xml文件则可以采用Struts提供的缺省文件。注意在&lt;form-bean&gt;中定义的form是如何与validation.xml中的form关联起来的。最后，要启动自动验证功能，还需要将Action配置的validate属性设置为true。</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>

&lt;action path="/login"  
type="com.ncu.test.LoginAction"
name="loginForm"          
scope="request"         
input="tile.userLogin"validate="true"&gt;

</CODE></PRE></TD></TR></TBODY></TABLE>
<P>此时，Struts将根据xml配置文件中的定义来检验表单输入，并将不符合要求的错误信息输出到页面。但是你可能会想：这个功能虽然好，可是什么检验都跑到服务器端执行，效率方面和用户易用性方面是不是有些问题？你可能会怀念起那简单的JavaScript客户端验证。</P>
<P>不用担心，在Struts 1.1中也支持JavaScript客户端验证。如果你选择了客户端验证，当某个表单被提交以后，Struts 1.1启动客户端验证，如果浏览器不支持JavaScript验证，则服务器端验证被启动，这种双重验证机制能够最大限度地满足各种开发者的需要。JavaScript验证代码也是在validator-rules.xml文件中定义的。要启动客户端验证，你必须在相应的JSP文件中做如下设置：<BR>
<OL>
<LI>为&lt;html:form&gt;增加onsubmit属性 
<LI>设置Javascript支持 </LI></OL>
<P></P>
<P>下表中列出了一JSP文件的示例代码，红字部分为Javascript验证所需代码。</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>
&lt;%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %&gt;
&lt;table bgcolor="#9AFF9A" cellspacing="0" cellpadding="10" border="1" width="100%"&gt;
	&lt;tr&gt;
	&lt;td&gt; 
	&lt;table cellspacing="0" cellpadding="0" border="0" width="100%"&gt; 
	&lt;tr bgcolor="#696969"&gt; 
		&lt;td align="center"&gt;     
		&lt;font color="#FFFFFF"&gt;Panel 3: Profile&lt;/font&gt;  
		&lt;/td&gt;
		&lt;/tr&gt; 
	&lt;tr&gt;  
		&lt;td&gt;&lt;br&gt; 
		&lt;html:errors/&gt;  
		&lt;html:form action="/login.do" focus="username"  onsubmit="return validateLoginForm(this);"&gt;  
		&lt;html:hidden property="actionClass"/&gt;   
		&lt;center&gt;      
		&lt;table&gt;      
			&lt;tr&gt;        
			&lt;td&gt;UserName:&lt;/td&gt;   
			&lt;td&gt;&lt;html:text property="username" size="20"/&gt;&lt;/td&gt; 
			&lt;/tr&gt; 
			&lt;tr&gt;  
			&lt;td&gt;Password:&lt;/td&gt;   
			&lt;td&gt;&lt;html:password property="password" size="20"/&gt;&lt;/td&gt;    
			&lt;/tr&gt;  
			&lt;tr&gt;  
			&lt;td colspan=2&gt;&lt;html:submit property="submitProperty" value="Submit"/&gt;&lt;/td&gt;     
		&lt;/table&gt;   
		&lt;/center&gt;  
		&lt;/html:form&gt; 
		&lt;html:javascript formName="loginForm" dynamicJavascript="true" staticJavascript="false"/&gt;  
	
	&lt;script language="Javascript1.1" src="staticJavascript.jsp"&gt;&lt;/script&gt;  
	&lt;/td&gt; 
	&lt;/tr&gt; 
	&lt;/table&gt;
	&lt;/td&gt;
	&lt;/tr&gt;
&lt;/table&gt;
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>其中onsubmit的值为"return validateLoginForm(this);"，它的语法为：</P>
<P>return validate + struts-config.xml中定义的form-bean名称 + (this);</P>
<P>staticJavascript.jsp的内容为：</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>
&lt;%@ page language="java" %&gt;
&lt;%-- set document type to Javascript (addresses a bug in Netscape according to a web resource --%&gt;
&lt;%@ page contentType="application/x-javascript" %&gt;
&lt;%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %&gt;
&lt;html:javascript dynamicJavascript="false" staticJavascript="true"/&gt;


</CODE></PRE></TD></TR></TBODY></TABLE>
<P>如果validator-rules.xml中定义的基本验证功能不能满足你的需求，你可以自己添加所需的验证类型。</P>
<P><A name=5><SPAN class=atitle2>6、Action</SPAN></A></P>
<P>我们通过继承Action类来实现具体的执行类。具体Action类的功能一般都在execute（以前是perform方法）方法中完成，其中主要涉及到以下几个方面：<BR>
<OL>
<LI>辅助ActionForm进行一些表单数据的检查。 
<LI>执行必要的业务逻辑，比如存取数据库，调用实体bean等。 
<LI>更新服务器端的bean数据，后续对象中可能会用到这些数据，比如在JSP中利用bean:write来获得这些数据。 
<LI>根据处理结果决定程序的去处，并以ActionForward对象的形式返回给ActionServlet。 </LI></OL>
<P></P>
<P><I><B>提示：</B>由于在Action和ActionForm中都可以实现验证方法，那么如何来安排它们之间的分工呢？一般来说，我们秉着MVC分离的原则，也就是视图级的验证工作放在ActionForm来完成，比如输入不能为空，email格式是否正确，利用ValidatorForm可以很轻松地完成这些工作。而与具体业务相关的验证则放入Action中，这样就可以获得最大ActionForm重用性的可能。</I></P>
<P>前面我们提到过，我们主张将业务逻辑执行分离到单独的JavaBean中，而Action只负责错误处理和流程控制。而且考虑到重用性的原因，在执行业务逻辑的JavaBean中不要引用任何与Web应用相关的对象，比如HttpServletRequest，HttpServletResponse等对象，而应该将其转化为普通的Java对象。关于这一点，可以参考Petstore中WAF框架的实现思路。</P>
<P>此外，你可能还注意到execute与perform的一个区别：execute方法简单地掷出Exception异常，而perform方法则掷出ServletException和IOException异常。这不是说Struts 1.1在异常处理功能方面弱化了，而是为了配合Struts 1.1中一个很好的功能--宣称式异常处理机制。</P>
<P><A name=6><SPAN class=atitle2>7、宣称式异常处理</SPAN></A></P>
<P>和EJB中的宣称式事务处理概念类似，宣称式异常处理其实就是可配置的异常处理，你可以在配置文件中指定由谁来处理Action类中掷出的某种异常。你可以按照以下步骤来完成该功能：<BR>
<OL>
<LI>实现org.apache.struts.action.ExceptionHandler的子类，覆盖execute方法，在该方法中处理异常并且返回一个ActionForward对象 
<LI>在配置文件中配置异常处理对象，你可以配置一个全局的处理类或者单独为每个Action配置处理类 </LI></OL>
<P></P>
<P>下表就定义了一个全局的处理类CustomizedExceptionHandler，它被用来处理所有的异常。</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>
&lt;global-exceptions&gt; 
&lt;exception 
	handler="com.yourcorp.CustomizedExceptionHandler" 
	key="global.error.message" 
	path="/error.jsp"    
	scope="request"    
	type="java.lang.Exception"/&gt;
&lt;/global-exceptions&gt;

</CODE></PRE></TD></TR></TBODY></TABLE>
<P>其中具体的参数含义，可以参考ExceptionHandler.java源文件。</P>
<P><A name=7><SPAN class=atitle2>8、taglib</SPAN></A></P>
<P>讲完了模型和控制器，接下来我们要涉及的是视图。视图的角色主要是由JSP来完成，从JSP的规范中可以看出，在视图层可以"折腾"的技术不是很多，主要的就是自定义标记库的应用。Struts 1.1在原有的四个标记库的基础上新增了两个标记库--Tiles和Nested。</P>
<P>其中Tiles除了替代Template的基本模板功能外，还增加了布局定义、虚拟页面定义和动态页面生成等功能。Tiles强大的模板功能能够使页面获得最大的重用性和灵活性，此外可以结合Tiles配置文件中的页面定义和Action的转发逻辑，即你可以将一个Action转发到一个在Tiles配置文件中定义的虚拟页面，从而减少页面的数量。比如，下表中的Action定义了一个转发路径，它的终点是tile.userMain，而后者是你在Tiles配置文件中定义的一个页面。</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>
&lt;!-- ========== Action Mapping Definitions ============================== --&gt;
&lt;action-mappings&gt;  
&lt;!-- Action mapping for profile form --&gt; 
	&lt;action path="/login"   
		type="com.ncu.test.LoginAction"      
		name="loginForm"    
		scope="request"     
		input="tile.userLogin"
		validate="true"&gt;     
		&lt;forward name="success" path="tile.userMain"/&gt;   
	&lt;/action&gt; 
&lt;/action-mappings&gt;
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>Tiles配置文件：tiles-defs.xml</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>
&lt;!DOCTYPE tiles-definitions PUBLIC 
"-//Apache Software Foundation//DTD Tiles Configuration//EN"       "http://jakarta.apache.org/struts/dtds/tiles-config.dtd"&gt;
&lt;tiles-definitions&gt;  
&lt;!-- =======================================================  --&gt; 
&lt;!-- Master definitions                                       --&gt;
&lt;!-- =======================================================  --&gt; 
&lt;!-- Page layout used as root for all pages. --&gt; 

&lt;definition name="rootLayout" path="/tiles-layouts/rootLayout.jsp"&gt; 
	&lt;put name="titleString" value="CHANGE-ME"/&gt;   
	&lt;put name="topMenu" value="/tiles-components/topMenu.jsp"/&gt; 
	&lt;put name="leftMenu" value="/tiles-components/panel1.jsp"/&gt;  
	&lt;put name="body" value="CHANGE-ME"/&gt;   
	&lt;put name="footer" value="/tiles-components/footer.jsp"/&gt; 
&lt;/definition&gt; 

&lt;!-- =======================================================  --&gt; 
&lt;!-- Page definitions 					--&gt;  
&lt;!-- =======================================================  --&gt; 

&lt;!-- User Login page --&gt; 
&lt;definition name="tile.userLogin" extends="rootLayout"&gt; 
	&lt;put name="titleString" value="User Login"/&gt;  
	&lt;put name="body" value="/src/userLogin.jsp"/&gt; 
&lt;/definition&gt;  
&lt;!-- User Main page --&gt; 
&lt;definition name="tile.userMain" extends="rootLayout"&gt; 
	&lt;put name="titleString" value="User Main"/&gt;  
	&lt;put name="body" value="/src/userMain.jsp"/&gt; 
&lt;/definition&gt;
&lt;/tiles-definitions&gt;
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>而Nested标记库的作用是让以上这些基本标记库能够嵌套使用，发挥更大的作用。</P>
<P><A name=8><SPAN class=atitle2>9、Commons Logging 接口</SPAN></A></P>
<P>所谓的Commons Logging接口，是指将日志功能的使用与日志具体实现分开，通过配置文件来指定具体使用的日志实现。这样你就可以在Struts 1.1中通过统一的接口来使用日志功能，而不去管具体是利用的哪种日志实现，有点于类似JDBC的功能。Struts 1.1中支持的日志实现包括：Log4J，JDK Logging API， LogKit，NoOpLog和SimpleLog。</P>
<P>你可以按照如下的方式来使用Commons Logging接口（可以参照Struts源文中的许多类实现）：</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>
package com.foo;
// ...
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
//...
	public class Foo {    
	// ...    
	private static Log log = LogFactory.getLog(Foo.class);
	// ...    
	public void setBar(Bar bar) {       
		if (log.isTraceEnabled()) {         
			log.trace("Setting bar to " + bar);   
		}      
	this.bar = bar;   
	}
// ...
}
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>而开启日志功能最简单的办法就是在WEB-INF/classes目录下添加以下两个文件：</P>
<P>commons-logging.properties文件：</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>
# Note: The Tiles framework now uses the commons-logging package to output different information or debug statements. 
Please refer to this package documentation to enable it. The simplest way to enable logging is to create two files in 
WEB-INF/classes:
# commons-logging.properties
# org.apache.commons.logging.Log=org.apache.commons.logging.impl.SimpleLog
# simplelog.properties
# # Logging detail level,
# # Must be one of ("trace", "debug", "info", "warn", "error", or "fatal").
#org.apache.commons.logging.simplelog.defaultlog=trace
org.apache.commons.logging.Log=org.apache.commons.logging.impl.SimpleLog

</CODE></PRE></TD></TR></TBODY></TABLE>
<P>simplelog.properties文件：</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>
# Logging detail level,
# Must be one of ("trace", "debug", "info", "warn", "error", or "fatal").
org.apache.commons.logging.simplelog.defaultlog=fatal

</CODE></PRE></TD></TR></TBODY></TABLE>
<P>这里我们采用的日志实现是SimpleLog，你可以在simplelog.properties文件指定日志明细的级别：trace，debug，info，warn，error和fatal，从trace到fatal错误级别越来越高，同时输出的日志信息也越来越少。而这些级别是和org.apache.commons.logging.log接口中的方法一一对应的。这些级别是向后包含的，也就是前面的级别包含后面级别的信息。</P><!-- AUTHOR BIOS--><!-- Make author heading singular or plural as needed--><!--
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr><td>
<a name="authorname"><span class="atitle2">About the author</span></a>
<br />

</td></tr></table>
--></TD>
<TD width=10><IMG height=1 alt="" src="http://www-900.ibm.com/developerWorks/cn/i/c.gif" width=10 border=0></TD></TR></TBODY></TABLE><!-- END PAPER BODY--><img src ="http://www.blogjava.net/kapok/aggbug/2990.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-04-08 11:57 <a href="http://www.blogjava.net/kapok/archive/2005/04/08/2990.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>