struts源代码阅读

 

第一篇 struts的初始化

struts 的核心类是org.apache.struts.action.ActionServlet,这个类将会在struts第一次使用时,
作为servlet初始化并存入tomcat容器。很显然的,初始化将会调用init方法初始化相应的数据。

一、initInternal()方法:
通过调用MessageResources.getMessageResources(internalName)方法生成一个
MessageResources类,getMessageResources是通过调用MessageResourcesFactory.
createResources(config)来实现的。至于MessageResourcesFactory是一个abstract类,任何
继承自它的类都要实现createResources方法,生成MessageResources对象。整个程序生成
MessageResourcesFactory使用了如下技巧:
MessageResourcesFactory.factoryClass = factoryClass;
MessageResourcesFactory.clazz = null;
首先会通过factoryClass来定义一个类全名,然后通过ClassLoader.loadClass
(factoryClass)方法来生成这个类,并赋给clazz,然后通过newInstance来生成一个对象。
在本程序中,生成MessageResources对象实际就是对如下属性进行了初始化:
this.factory = factory;("org.apache.struts.util.PropertyMessageResourcesFactory")
this.config = config;("org.apache.struts.action.ActionResources")
this.returnNull = returnNull;(true/false)

对于MessageResources类的作用是根据不同的Locate来格式化相应的string。或者把你需要改变
的string存放到数组中,然后通过getMessage(Locale locale, String key, Object args[])
方法来格式化。然后把格式好的string存放到HashMap里,这样就可以为以后重用。这里的key是
使用的locale.toString() + "." + key

在PropertyMessageResources中的loadLocale方法用来读取resource的初始化信息。首先它会
通过一个HashMap检测这个localKey相关的message是否已经被初始化了,如果被初始化过就跳
出,检测的方法是locales.get(localeKey) != null。
然后会读取如下一个文件:
org/apache/struts/action/ActionResources_(localKey).properties,然后进行如下操作:
Properties props = new Properties();
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
is = classLoader.getResourceAsStream(name);
props.load(is);
Iterator names = props.keySet().iterator();
while (names.hasNext()) {
String key = (String) names.next();
if (log.isTraceEnabled()) {
log.trace(" Saving message key '" + messageKey(localeKey, key));
}
messages.put(messageKey(localeKey, key), props.getProperty(key));
}

PropertyMessageResources 就是通过上面的loadLocale方法查找与Locale locale, String key
相对对应的Message.查找的次序如下locale.toString(),然后是
localeKey = localeKey.substring(0, underscore),然后是defaultLocale,然后是key。

最后,resource类的结构如下:
PropertyMessageResources extends MessageResources
PropertyMessageResourcesFactory extends MessageResourcesFactory

二、initOther()方法:
从servlet中获取config和debug两个参数,然后初始化ConvertUtils对象。由于
ConvertUtils.deregister()的初始化,所有的Converter都是有初始值的,所以这里Struts自己
把这些初始值设置为null,即转换出错的时候返回null,而不是初始值。使用ConvertUtils类的
原因是由于从form传输过来的都是String类型的值,所以我们要把它们转换成相应的类型。

提到几个技巧:
*public boolean isIndexed() {
if (type == null) {
return (false);
//技巧一:判断是否是一个Array类的方法
} else if (type.isArray()) {
return (true);
//技巧二:判断type是否是List的一个父类或者父接口,或者与List为同一个类
//要注意如果List是另一个primitive的TYPE类,那么type必须也是这个类才会
//返回true,否则都是false。注意long.TYPE与Long.class是不同的
} else if (List.class.isAssignableFrom(type)) {
return (true);
} else {
return (false);
}
}

*//componentType为Array类所存储的元素的类别
Class componentType = indexedProperty.getClass().getComponentType();
//生成一个新的Array
Object newArray = Array.newInstance(componentType, (index + 1));
System.arraycopy(indexedProperty, 0, newArray, 0, length);
indexedProperty = newArray;
set(name, indexedProperty);
int newLength = Array.getLength(indexedProperty);
for (int i = length; i < newLength; i++) {
Array.set(indexedProperty, i, createProperty(name+"["+i+"]", componentType));
}

三、initServlet()方法:
这个方法主要是通过digester类解析web.xml,对String servletMapping属性进行初始化。对于
digester说明如下:这是一个基于DOM的SAX实现的类,它是事件触发的,根据xml文件的结构,
每次读到一个节点元素就会触发一个事件。

InputStream input = getServletContext().getResourceAsStream("/WEB-INF/web.xml");
这是一个比较少见的方法。首先通过this.servletName = getServletConfig().
getServletName()获取servlet的名称,然后根据
if (servletName.equals(this.servletName)) {
this.servletMapping = urlPattern;
}
来判断当前读到的servlet名称是否是我们运行的servlet的名称,如果是,就把url-pattern作为
我们的servletMapping。

四、getServletContext().setAttribute(Globals.ACTION_SERVLET_KEY, this)
把自己存储到servletContext中,属性名为Globals.ACTION_SERVLET_KEY。

五、ModuleConfig moduleConfig = initModuleConfig("", config)
这个方法使用由initOther()方法获取的config值为要解析的xml路径,用来初始化ModuleConfig。
它首先采用与生成MessageResourcesFactory同样的方法产生一个MessageResourcesFactory对象:
MessageResourcesFactory为一个抽象类,每一个继承它的类都要实现
createModuleConfig(String prefix)方法。本程序使用的缺省的MessageResourcesFactory类为
org.apache.struts.config.impl.DefaultModuleConfigFactory,它
的createModuleConfig(String prefix)方法会生成一个ModuleConfigImpl类。

ModuleConfigImpl类相当于一个JavaBean,用来存放一个web模块运行时所需要的配置信息。当
然,一个web模块可以拥有多个ModuleConfig,但是缺省的是prefix长度为0的ModuleConifg。它
的每个属性几乎都是由HashMap组成的,它通过一个configured布尔值来描述当前的ModuleConfig
是否已经被初始化完毕,在每存放一个属性的时候都会监测这个值。如果初始化完毕而还要改变
里面的属性值,则会报出IllegalStateException("Configuration is frozen")异常,现在对它
的属性简单说明如下:
* protected HashMap actionConfigs:
这个HashMap用来存储ActionConfig对象。
* protected HashMap dataSources
这个HashMap用来存储DataSourceConfig对象。
* protected HashMap exceptions
这个HashMap用来存储ExceptionConfig对象。
* protected HashMap formBeans
这个HashMap用来存储FormBeanConfig对象。
* protected HashMap forwards
这个HashMap用来存储ForwardConfig对象。
* protected HashMap messageResources
这个HashMap用来存储MessageResourcesConfig对象。
* protected ArrayList plugIns
这个HashMap用来存储PlugInConfig对象。
* protected ControllerConfig controllerConfig
ControllerConfig类
* protected boolean configured
标志这个ModuleConfig是(true)否(false)配置完成。
* protected String prefix
用来标志和区分ModuleConfig类,同时在使用上面的config类初始化相应的资源以后,也是通
过这个prefix来区分所属的不同的web模块。
* protected String actionMappingClass = "org.apache.struts.action.ActionMapping"
ActionMapping类名,缺省为org.apache.struts.action.ActionMapping。

初始化ModuleConfig的方法如下:
首先是使用getServletConfig().getInitParameter("mapping")来获取设定的ActionMapping类
名,然后通过initConfigDigester()方法来生成一个digester。最后用","分隔config,对每一
块调用parseModuleConfigFile(prefix, paths, config, digester, path)方法解析。注意,这
个方法实际上只有两个参数是有意义的:path为我们要解析的xml文件,config用来初始化完成
后保存到servletContext中。

如果ModuleConfig中存放的FormBeanConfig为Dydamic类型,那么就调用
DynaActionFormClass.createDynaActionFormClass(FormBeanConfig)初始化
DynaActionFormClass,并存放到DynaActionFormClass.dynaClasses 的 static HashMap中。这
里的key为FormBeanConfig.getName() + moduleConfig.getPrefix()。

如果当前的ModuleConfig为缺省的ModuleConfig,那么将会调用如下几个方法:
defaultControllerConfig(config)
defaultMessageResourcesConfig(config)
defaultFormBeansConfig(config)
defaultForwardsConfig(config)
defaultMappingsConfig(config)
在struts1.1以后,这个特例将会被废弃:

defaultControllerConfig(config)为ControllerConfig通过getInitParameter(s)方法初始化如
下几个属性:bufferSize,content,locale(true/false),maxFileSize,nocache(true/false)
,multipartClass,tempDir。

defaultMessageResourcesConfig(config)为MessageResourcesConfig通过getInitParameter(s)
方法初始化如下几个属性:application,factory,null(true/false)。

其它的几个方法就是获取不同的对象,然后把它们相应的存储到servlet中。关心如下:
ActionFormBeans=>FormBeanConfig,ActionForwards=>ForwardConfig,
ActionMappings=>ActionConfig。

六、initModuleMessageResources(ModuleConfig config)
通过存储在ModuleConfig中的MessageResourcesConfig对象,逐个初始化MessageResource,
然后再把初始化好的MessageResources存放到ServletContext中,attributeName为
MessageResourcesConfig.getKey() + ModuleConfig.getPrefix()。

七、initModuleDataSources(ModuleConfig config)
通过存储在ModuleConfig中的DataSourceConfig对象,逐个初始化DataSource。然后对于每一个
DateSource通过BeanUtils.populate(ds, dscs[i].getProperties())方法初始化其属性。再把初
始化好的DateSource存放到ServletContext中,attributeName为
DataSourceConfig.getKey() + ModuleConfig.getPrefix()。同时也存放到名位dataSources的
FastHashMap中,key为DataSourceConfig.getKey()。

这里还会根据生成的DateSource对象是否是GenericDataSource类型,如果是则调用
GenericDataSource.open()方法。GenericDataSource是一个非常简单的数据库连接池,它的
open()方法用来初始化连接池,生成最小数目的GenericConnection,这里的open()方法根据
String driver变量是否为null来判断是否已经被初始化过。需要仔细说明的是getConnection()
方法,它首先从连接池中取出GenericConnection对象,然后检查其是否是可链接的,如果是就
返回,否则继续取出,同时activeCount-1。如果没有取到,则会检查当前可使用的
GenericConnection是否达到最大值(activeCount < maxCount),如果没有,调用
createConnection()方法声成一个新的GenericConnection,然后检查其是否是可链接,如果可以
则返回。returnConnection(GenericConnection conn)方法则是通过把GenericConnection放回到
连接池,然后activeCount-1。

这个方法中使用到了ServletContextWriter类,DateSource的log信息就通过这个类写入。对这个
类说明如下:
它继承自PrintWriter,而PrintWriter又继承自Writer。Writer类所作的事情就是在同步的情况下
调用abstract方法:abstract public void write(char cbuf[], int off, int len),这个方法
将会根据调用者的需要由调用者实现。
PrintWriter则首先通过ensureOpen()方法检验这个类中是否有写入的对象(Writer类或其子类),
如果有则根据不同的情况调用这个写入对象的write方法(out.write(....))。这个类的print(...)
方法就是据不同的情况调用相应的write(...)方法。而println(...)与之的区别就是每次多写入一
个换行字符串。还有一个区别是println(...)会根据是否需要autoflush进行flush,而write(...)
方法不会。
ServletContextWriter类的作用是把字符写入ServletContext中。ServletContextWriter类方法中
真正实现了write方法:
public void write(char c) {
if (c == '/n')
flush();
else if (c != '/r')
buffer.append(c);
}
public void flush() {
if (buffer.length() > 0) {
context.log(buffer.toString());
buffer.setLength(0);
}
}

八、initModulePlugIns(moduleConfig)
通过存储在ModuleConfig中的PlugInConfig对象,逐个初始化PlugIn对象,存放到一个数组中,
然后再把这个数组存放到ServletContext中,attributeName为
Globals.PLUG_INS_KEY + ModuleConfig.getPrefix()。

对每一个生成的PlugIn对象通过
BeanUtils.populate(plugIns[i], plugInConfigs[i].getProperties())方法初始化其属性。然后
再把PlugInConfig对象存放到由其生成的PlugIn对象中。

最后,通过plugIns[i].init(this, (ModuleConfig) config)初始化这个plugIn对象。

九、初始化结束
完成了这个初始化以后,会调用ModuleConfig.freeze()令这个ModuleConfig变得不可改变。然后
会遍历ServletConfig中的initParameterNames,如果有以"config/"开头的,则通过这个parameter
的值继续初始化其它的ModuleConfig,且这个ModuleConfig的prefix为"config/"后的字符串。

同样调用如下方法:
initModuleMessageResources(moduleConfig);
initModuleDataSources(moduleConfig);
initModulePlugIns(moduleConfig);
moduleConfig.freeze();

最后调用destroyConfigDigester()释放内存。


  第二篇 struts的执行
 
本篇详细介绍struts在初始化之后是如何处理一个请求,并返回数据的。这里最核心的类是
RequestProcessor以及RequestUtils。RequestProcessor类通过RequestDispatcher实现页面的跳转,
而RequestProcessor负责处理request中传来的请求信息,存放到FormBeanConfig中,以及对要跳转的
url进行处理。

struts 在初始化完成之后,会根据请求调用doGet(...)或者doPost(...)方法,这两个方法直接
调用process(request, response)方法。process(...)方法首先判断当前的request属于
哪一个ModuleConfig,然后生成与这个ModuleConifg相对应的RequestProcessor,最后调用这个
RequestProcessor的process(...)方法,执行request的请求。

一、RequestUtils.selectModule(String prefix, HttpServletRequest,ServletContext)方法:
这个方法,根据prefix,从ServletContext中选择相应的ModuleConfig,然后把这个ModuleConfig
保存到request中。ServletContext对应的key值为Globals.MODULE_KEY + prefix,保存到request
中使用的key值为Globals.MODULE_KEY。如果在ServletContext中不存在这样的一个ModuleConfig,
那么调用request.removeAttribute(Globals.MODULE_KEY)方法。然后以同样的方法查找、保存
MessageResources对象。当prefix为空时,会调用下面的方法选择ModuleConfig。

二、RequestUtils.selectModule(HttpServletRequest, ServletContext)
这个方发首先使用getModuleName(HttpServletRequest, ServletContext)获取相应的path,然后
通过调用getModuleName(String matchPath, ServletContext)获取相应的prefix。以这prefix为
参数调用(一)中的selectModule(...)选择ModuleConfig。
获取path的过程为:首先从request中查找名称为
INCLUDE_SERVLET_PATH(javax.servlet.include.servlet_path)的属性,如果为空,就调用
request.getServletPath()方法获取servletPath。

获取prefix的过程为:它首先调用getModulePrefixes(ServletContext),获取所有已存在的
module前缀。 然后通过分析上面获取的path来判断当前的url属于哪一个module,方法是截取
path中最后一个"/"字符前面的字符串,然后与有上面方法中获取的prefixes[]逐个对比,如果
prefixes[]中存在这样的一个值,则返回这个截取的字符串,否则继续截取path最后面的"/"前面
的字符串,然后对比。如果始终找不到,则返回""。

getModulePrefixes(ServletContext)的执行过程是:首先通过
context.getAttribute(PREFIXES_KEY)查找是否存在这样的一个保存所有的prefix的string array,
如果存在,就说明已经解析过一次了,就直接返回这个string array,否则遍历ServletContext中
所有的attribute name,查找以MODULE_KEY(org.apache.struts.action.MODULE)开头的
atrribute name,然后截取这个atrribute name中MODULE_KEY后面的字符串,作为一个prefix. 最
后把通过这个这个方式获取的所有的prefix作为一个ArrayList存储到ServletContext中,属性key
值为PREFIXES_KEY(org.apache.struts.util.PREFIXES),这样下次再次查找的时候就可以直接从这
个attribute中获取了。

三、getModuleConfig(HttpServletRequest)
这个方法就是获取上面的selectModule(...)方法所得到的ModuleConfig。如果找不到这样的
ModuleConfig,那么就把ServletContext中缺省的ModuleConfig返回(调用
getServletContext().getAttribute(Globals.MODULE_KEY))

四、getRequestProcessor(ModuleConfig config)
这个方法从根据ModuleConfig的prefix作为key,从ServletContext中获取RequestProcessor。这
个key值为Globals.REQUEST_PROCESSOR_KEY + config.getPrefix()。

如果ServletContext中不存在这样的一个RequestProcessor,那么就生成一个新的
RequestProcessor的实例,完成初始化(保存ActionServlet以及ModuleConfig到这个新的实例中)后
将其保存到ServletContext中。

五、RequestProcessor.process(HttpServletRequest, HttpServletResponse)
这是真正执行HttpServletRequst请求的方法。

这个方法首先判断但前的HttpServletRequest是否是Multipart类型的request,也就是说当前
的request是否有字段是"file"类型。这样做的原因是这种类型的request中getParameter()等
相类似的方法都是无法执行的,所以要区分对待。如果是Multipart类型的,那么把这个request包
装成MultipartRequestWrapper类。

MultipartRequestWrapper 与 HttpServletRequest不同的就是重新实现了
setParameter(String name, String value),getParameter(String name),getParameterNames()
以及getParameterValues(String name)方法。
这些方法的思想是所有的变量名、值对都保存到一个HashMap里:key为变量名,value为变量值,但
是注意value都是String[]格式的。当有一个新的值加入的时候,通过setParameter(...)
方法,把值加到数组的最后。在setParameter(...)中有一个比较少见的保存值的方法,记录如下:
public void setParameter(String name, String value) {
String[] mValue = (String[]) parameters.get(name);
if (mValue == null) {
mValue = new String[0];
}
String[] newValue = new String[mValue.length + 1];
System.arraycopy(mValue, 0, newValue, 0, mValue.length);
newValue[mValue.length] = value;

parameters.put(name, newValue);
}

然后是调用processPath(HttpServletRequest, HttpServletResponse)获取地址,以这个地址作为
从ModuleConfig获取ActionMapping的Key。这里首先查询
request.getAttribute(INCLUDE_PATH_INFO)中是否有这样的一个值,如果为空就调用
request.getPathInfo()方法获取path。如果这样还是获取不了,就从
request.getAttribute(INCLUDE_SERVLET_PATH)方法中获取path,找不到就使用
request.getServletPath()得到的path进行分析。分析过程如下:

然后如果这个path不是属于当前的ModuleConfig的话,直接返回null。截取prefix后面的字符串,
如果这个字符串以.XX结尾,那么就截取"."前面的字符,比如
http://localhost:8080/servlet/where/go.do。where为module的名称(prefix),那么我们获取的
path值为/go。

然后是通过processLocale(HttpServletRequest ,HttpServletResponse)为当前用户设定一个Local
对象,它查找的顺序是:moduleConfig.getControllerConfig().getLocale(),
session.getAttribute(Globals.LOCALE_KEY),request.getLocale()。你可以在config XML文件
中通过<controller><set-property..../></controller>执行相关的定义。

调用processContent(HttpServletRequest, HttpServletResponse)为response设定contentType。
这个contentType是从moduleConfig.getControllerConfig().getContentType()获取的。你可以
在config XML文件中通过<controller><set-property..../></controller>执行相关的定义。

通过processNoCache(HttpServletRequest, HttpServletResponse)方法设置response的缓存。
你可以在config XML文件中通过<controller><set-property..../></controller>执行相关的定义。

processPreprocess(HttpServletRequest, HttpServletResponse)预处理request,这个方法是预
留的,用户可以根据自己的需要加一些预处理的程序。

通过processMapping(HttpServletRequest, HttpServletResponse, String path)以
processPath(...)的返回值为key从ModuleConfig中查找ActionMapping 对象。如果找到了,那么保
存这个Mapping到request中,key值为Globals.MAPPING_KEY。如果不存在,那么遍历ModuleConfig
中所有的ActionMapping,查找哪一个是缺省的ActionMapping。然后把它保存到request中,key值
为Globals.MAPPING_KEY。

通过processRoles(HttpServletRequest,HttpServletResponse,ActionMapping)检查当前用户是
否有权限执行这个请求。如果request.isUserInRole(roles[i])返回true,则代表有。

通过processActionForm(HttpServletRequest, HttpServletResponse, ActionMapping)生成一个
ActionForm,然后根据ActionMapping所属的scope,保存到request或者session中。key值为这个
ActionMapping中的attribute属性。ActionForm是通过RequestUtils.createActionForm(...)方法
获取的,在下一篇将会对这个方法进行详细的说明。这里只说明ActionForm与FormBeanConfig以及
FormPropertyConfig之间的区别。每个FormPropertyConfig代表Form表单的一个字段,表单中的所
有FormPropertyConfig以一个HashMap保存到FormBeanConfig中。而FormBeanConfig是在
Actionservet初始化的时候生成的:
<form-beans>
<form-bean name="一个名称,作为action选择的key" type="实际对应的ActionForm类"/>
<form-beans>
名称用来在MoudleConfig中的formBeans HashMap中查找相应的FormBeanConfig,而FormBeanConfig
中有一个type,它保存了上面XML文件中定义的值,用来生成ActionForm。

通过processPopulate(HttpServletRequest, HttpServletResponse, ActionForm,
ActionMapping)方法初始化ActionForm 以及 FormBean,这个方法首先会设定这个ActionForm所属
于的ActionServlet,然后对这个ActionForm进行初始化。判断当前的reqest是否Multipart类型,
如果是就把相应的MultipartClass类全名保存到request中,key值为Globals.MULTIPART_KEY。调
用RequestUtils.populate(...)对request中的参数进行处理,并保存到相应的FormBean中。在下
一篇将会对这个方法进行详细的说明。最后根据request的parameter判断当前的请求是否是被取
消,然后把相关信息保存到request中:
if ((request.getParameter(Constants.CANCEL_PROPERTY) != null) ||
(request.getParameter(Constants.CANCEL_PROPERTY_X) != null)) {
request.setAttribute(Globals.CANCEL_KEY, Boolean.TRUE);
}

六、下面说明这个request是如何真正被处理的,下面的方法都是在RequestProcessor中定义。

通过processValidate(HttpServletRequest, HttpServletResponse, ActionForm,
ActionMapping)判断request中的parameter是否合法。如果合法就返回true,否则取消这次请求,
并且返回到指定的输入页面。它判断合法的过程是:如果这次的请求被取消,那么直接返回true;
如果没有要求对request中的parameter进行合法性检验,也直接返回true;最后通过调用
form.validate(mapping, request)执行检验,如果返回的ActionErrors为null,那么代表通过
检验,返回true,否则取消这次request。取消的过程是:如果这个请求是Multipart类型的,那么
要对这个MultipartRequestHandler进行回滚;而后,获取当前的ActionMapping的input值,即在
config XML 中<action.../>定义的input属性。如果ActionMapping没有这个input值,那么调用
response.sendError(...)方法,然后返回false;如果存在,就保存验证出错信息到request中,
key为Globals.ERROR_KEY,跳转到input所指定的地址中。

processForward(HttpServletRequest, HttpServletResponse, ActionMapping)以及
processInclude(HttpServletRequest, HttpServletResponse, ActionMapping)分别通过执行
RequestDispatcher.forward(request, response) 以及 RequestDispatcher.include(request,
response)实现对页面的跳转。

但是如果当前的请求还有其它操作要执行,那么ActionMapping中的include或者forward属性值就
会为空。这时需要通过调用processActionCreate(HttpServletRequest, HttpServletResponse,
ActionMapping)方法根据config XML文件的配置信息生成一个Action类,然后通过
processActionPerform(...)调用生成的Action中的execute(...)方法。最后根据execute(...)
方法返回的ActionForword类,执行跳转。在整个过程中有一个url的计算方法,这个将在下一篇
中说明。


七、对ActionMaping结构的说明:
在ActionConfig为保存Moudle Action属性的一个javabean,它有以下属性:
* boolean configured 这个对象的所有属性是否已经被配置完。如果已经完成,那么在改变其中
任何属性都会抛出IllegalStateException("Configuration is frozen")
* ModuleConfig moduleConfig 本ActionConfig类所属于的ModuleConfig。
* String attribute request范围 或者 session范围 的属性名称,我们将通过这个属性名称来
获取相应的form bean,注意,这个属性与form bean中定义的名称是不一样的。注意以下的方
法:
public String getAttribute() {
if (this.attribute == null) {
return (this.name);
} else {
return (this.attribute);
}
}
* String forward 调用RequestDispatcher.forward()方法时所需要的上下文相关的地址。
* String include 调用RequestDispatcher.include()方法时所需要的上下文相关的地址。
* String type Action类的类全名,如果上面的两个属性都没有定义的话,就会用它来处理
request。
* String input 如果输入数据没有通过验证,将会以这个值为地址,跳转到相应的页面。
* String multipartClass MultipartRequestHandler实现类的全名
* String name 与本类相关的 form bean 的名称。
* String parameter 用于struts的扩展,存储其它的配置信息。struts自己不会用到这个属性。
* String path 上下文相关的地址,这个地址为下一个将会进入的地址,这个地址必须要以"/"
开头。如果使用了extension mapping的话,这个地址将不会带有扩展名。
* String roles 一个以","分隔的角色名称。这些角色将会有权限访问这个request。
* String scope 缺省为session。
* String prefix,String suffix后缀和前缀。
* boolean unknown 标志当前的ActionConfig类是否是为了未知的request path而准备的,
ActionMappings将会根据这个标志返回这个ActionConfig。
* boolean validate 是否调用validate()方法进行数据校验。

ActionMapping 继承自 ActionConfig,它增加了从ModuleConfig查找ActionForword的方法。同
时它还提供了从ModuleConfig查找ExceptionConfig的方法,如果找不到,会通过
type.getSuperclass()方法,根据父类的类名称继续查找,直到最后。

八、关于request.getServletPath()的解释:
request.getServletPath() 的返回值就是在下面url中定义的值,比如如果按照下面的定义
<url-pattern>
*.do
</url-pattern>
那么:
http://localhost:8080/servlet/go.do ---------->/go.do
http://localhost:8080/servlet/where/go.do ---------->/where/go.do
这里有一个小常识,如果
<url-pattern>
/where/*.do
</url-pattern>
那么地址中只有http://localhost:8080/servlet/where/*.do有效(tomcat 4.0)


在ActionConfig为保存Moudle Action属性的一个javabean,它有以下属性:
* boolean configured 这个对象的所有属性是否已经被配置完。如果已经完成,那么在改变其中
任何属性都会抛出IllegalStateException("Configuration is frozen")
* ModuleConfig moduleConfig 本ActionConfig类所属于的ModuleConfig。
* String attribute request范围 或者 session范围 的属性名称,我们将通过这个属性名称来
获取相应的form bean,注意,这个属性与form bean中定义的名称是不一样的。注意以下的方
法:
public String getAttribute() {
if (this.attribute == null) {
return (this.name);
} else {
return (this.attribute);
}
}
* String forward 调用RequestDispatcher.forward()方法时所需要的上下文相关的地址。
* String include 调用RequestDispatcher.include()方法时所需要的上下文相关的地址。
* String type Action类的类全名,如果上面的两个属性都没有定义的话,就会用它来处理
request。
* String input
* String multipartClass MultipartRequestHandler实现类的全名
* String name 与本类相关的 form bean 的名称。
* String parameter 用于struts的扩展,存储其它的配置信息。struts自己不会用到这个属性。
* String path 上下文相关的地址,这个地址为下一个将会进入的地址,这个地址必须要以"/"
开头。如果使用了extension mapping的话,这个地址将不会带有扩展名。
* String roles 一个以","分隔的角色名称。这些角色将会有权限访问这个request。
* String scope 缺省为session。
* String prefix,String suffix后缀和前缀。
* boolean unknown 标志当前的ActionConfig类是否是为了未知的request path而准备的,
ActionMappings将会根据这个标志返回这个ActionConfig。
* boolean validate 是否调用validate()方法进行数据校验。

ActionMapping 继承自 ActionConfig,它增加了从ModuleConfig查找ActionForword的方法。同
时它还提供了从ModuleConfig查找ExceptionConfig的方法,如果找不到,会通过
type.getSuperclass()方法,根据父类的类名称继续查找,直到最后。
 
 
 第三篇 Commons-Validator

Commons-Validator(一)
Commons-Validator包用来把验证规则程序提取出来,以供重复使用。这个包可以使用在Struts中,也可以独立的应用在任何其它的应用中。用户可以通过java类的方式自定义验证方法,也可以在配置文件中通过正则表达式配置验证方法。它不但支持服务器端的验证,客户端的验证也支持,具体需要使用tag把相应的js方法写入相应的页面中。

一、综述:
整个Validator框架可以有若干个FormSet,而每个FormSet又可以有若干个Form,每个Form中可以有若干个Field。FormSet的process(...)方法,逐个调用其中的Form的process(...)方法,而Form的process(...)方法又是逐个调用Field的process(...)方法。Validator类作为验证的起始点,调用与其一一对应的Form的validate(...)方法,而Form的validate(...)方法又是逐个调用其中Field的validate(...)方法实现的。

二、配置文件说明:

<form-validation>
<global>
<constant>
<constant-name>验证方法的标志名</constant-name>
<constant-value>正则表达式</constant-value>
</constant>
<validator name="这个验证方法的标志名,供下面的depends调用"
classname="这个验证方法在哪个类中,为类全名"
method="验证方法的名称"
methodParams="这个验证方法需要的参数类型,依次以逗号格开,为类全名"
depends="基于什么验证之上,可以为多个值,以逗号格开,值为方法的标志名"
jsFunction="js的方法全名,格式为文件路径.方法名。文件路径以点隔开,
如果不填,默认为org.apache.commons.validator.javascript.xxxx"
msg="对应于properties文件中的一条,作为不通过验证时返回的信息"/>
</global>
<formset language="语言" country="城市" variant="方言?">
<constant>
<constant-name>验证方法的标志名</constant-name>
<constant-value>正则表达式</constant-value>
</constant>
<form name="bean 对象名称">
<field property="bean中的属性名" depends="需要什么样的验证,可以为多个值,以逗号格开,值为方法的标志名">
<arg name = "变量名" key = "properties文件的key,或者来自Var的name" resource = "是/否来自资源文件"/>
<var>
<var-name>变量名</var-name>
<var-value>变量值</var-value>
</var>
</field>
</form>
</formset>
</form-validation>


Commons-Validator(二)

在Validator的配置文件中,一共有如下几个基本元素。

一、org.apache.commons.validator.Var
它的作用是为配置文件(validator.xml)中的其它标签提供可取用的变量,为Field提供执行验证所需要的其它参数值,比如最大长度。这个类有如下属性:name,变量的名称;value,变量的值;jsType,当要自动生成js的时候,js的类型。

二、org.apache.commons.validator.Arg
它的作用是替换信息中的某一部分,或者为验证方法提供必需的参数值。这个类有如下属性:bundle,资源文件名,用来存放所需要的信息。key,表示Arg的key或者value。name,表示Arg的名称。position,这个Arg中的值用来替换信息中的哪一部分,需要替换的部分以{n}标志。resource:key所指定的信息是否来自外部的资源文件,默认为true;如果为true,则代表key为buddle属性所指定的资源文件中的key。

三、org.apache.commons.validator.Msg
它的作用是在验证不通过时,应该返回什么的信息。这个类有如下属性:bundle,资源文件名,用来存放所需要的信息。key,表示Msg的key或者value。name,表示Msg的名称。resource:key所指定的信息是否来自外部的资源文件,默认为true;如果为true,则代表key为buddle属性所指定的资源文件中的key。

四、org.apache.commons.validator.FormSet
这个类管理通过一个Map所有要检验的Form对象,key为Form的name;同时通过一个Map管理在<formset/>中定义的Constant,key为<constant-name>。同时其内部有language,country, variant这几个属性,用来实现国际化。一个配置文件可以有多个FormSet,这些FormSet的区别是要求不同的本地化。

五、org.apache.commons.validator.Form
这个类有如下属性:name,这个form的名称。lFields,一个保存所有Field的List。hFields,一个保存所有Field的FastHashMap,这个FastHashMap的key值是对应Field的key属性(对应配置文件中的property)。这个类通过validate(...)方法用来对这个Form中的所有位置低于page的Field进行验证。它实际是在一个循环中逐个调用每个field的validate(...)方法,然后把结果保存在一个ValidatorResults对象中。

六、org.apache.commons.validator.Field
这个类有如下属性:depends,依赖于什么验证规则。dependencyList保存了以逗号为分隔符把depends分割生成的list。page,如果是多步提交的话当前是第几步,值等于或小于表单中page性质的值,JavaBean 才会得到处理;args,是一个数组;这个数组的元素为HashMap,每个HashMap在数组的位置就是其中的Arg中的position属性的值;HashMap中的key值为Arg的name,如果这个name为null则为默认的值:DEFAULT_ARG(org.apache.commons.validator.Field.DEFAULT),value为Arg对象。hVars,是一个FastHashMap,用来管理所有的Var对象,key为Var的name,值为Var对象。getIndexedListProperty表明这个property为JavaBean中的一个数组元素的index。

1、process(Map globalConstants, Map constants)
这个方法用来执行配置文件中变量的替换。它的两个参数分别为在<global/>和<formset/>中定义的constant。在这个Map中,key为<constant/>标签中的constant-name,value为<constant-value>。在配置文件中,可以实现如下的变量使用方式:Form的property属性,Var中的value属性,Arg中的key属性,Msg的key属性,他们均可以通过{constant-name}的方式,引用在<global/>或者<formset/>标签中定义的constant。Arg中的key属性,可以通过{Var:var-name}的方式引用在<var/>中定义的Var。

FormSet中的process(...)方法依次调用其中的Form的process(...)方法,而Form的process(...)方法又依次调用其中的Field的process(...)方法。

2、validate(Map params, Map actions)
执行验证,其中actions保存了所有的ValidatorAction对象。它首先会从params取出key为Validator.BEAN_PARAM(java.lang.Object)的值作为要验证的JavaBean。然后通过generateKey()方法判断当前要验证的Field是否是IndexedList。如果是,则需要分别对这个List中的各个元素进行验证;否则直接执行对与JavaBean的特定属性(property)执行验证。

3、validateForRule(...)
接受要执行的ValidatorAction对象的同时,还是会接受Map actions参数,这是因为这个要执行的ValidatorAction可能会依赖于其它的ValidatorAction。它会先查找以前的验证结果,如果以前没有执行过这个验证,那么执行runDependentValidators(...)方法,执行它所依赖于的ValidatorAction;如果通过,那么再对要执行的ValidatorAction,执行验证。


Commons-Validator(三)

在Validator包中提供了一些Util类,同时提供了对基本数据类型,时间,E-mail,信用卡等格式的验证方法。

一、org.apache.commons.validator.util.Flags
这个类用来管理一系列的Flag,其中的每个Flag的值都是2的N次方。然后每个Flag之间位与(&)就得到了整个Flags的值。

二、org.apache.commons.validator.util.ValidatorUtils
这个类为Validator提供一些Utility的操作,共有三个方法。

1、replace(...)
这个方法用来将一个字符串中某个特性的字符串替换为另一个字符串,注意这是一个全局替换方法。

2、getValueAsString(...)
这个方法用来获取某个Bean中的一个特定的属性,然后把属性值转换为字符串返回。注意,String[]和Collection类型的值,如果里面不包含任何值,则直接返回""。

3、copyFastHashMap(...)
很显然的是对一个特定的FashHashMap执行copy。这里要注意的是,如果value为Msg,Arg,Var类型的时候需要执行clone,然后再保存。

三、org.apache.commons.validator.ValidatorUtil
这个类完全通过调用上面的ValidatorUtils方法,实现自己的同名方法。只是多了一个getDelimitedRegExp(...)方法,用来生成一个正则表达式。

四、org.apache.commons.validator.DateValidator
实现了单例模式。这个类用来检查日期类型是否合法,日期的类型要通过参数传递给这个Validator。同时还有一个strict属性,表示时候检查过渡匹配所要求的日期格式。这个日期检查就是通过formatter.parse(value)方法检查时候有异常抛出。

五、org.apache.commons.validator.EmailValidator
实现了单例模式。这个类用来检查Email类型是否合法。它通过正则表达式实现验证。

六、org.apache.commons.validator.GenericTypeValidator
这个类实现了对基本类型(Byte,Short,Int,Long,Float, Date)的检测,方法很简单,就是通过每个类型的构造函数创建相应的对象,如果抛出异常就说明不符合,否则符合。同时,这个类还提供对CreditCard验证的实现。


Commons-Validator(四)

通过调用Validator的validate(...)方法,启动验证。

一、org.apache.commons.validator.Validator
这个类是对一个Form执行验证的起点,通过调用这个类的validate(...)方法,启动验证。这个类中的formName属性,对应于Form中的name属性。resources属性,记录这个Validator属于哪一个ValidatorResources。parameters用来管理执行validateXXX(...)方法时所需要的参数,它是一个Map,key为类全名,value为这个类全名所指的类的一个对象。注意,这个parameters是这个Form中所有Field所要执行的所有validateXXX(...)方法时所需要的参数集合。每个validateXXX(...)方法,根据自己需要的参数名(类全名),从这个parameters中查取自己所需要的参数。

二、org.apache.commons.validator.ValidatorAction
每个ValidatorAction对应于配置文件中的一个<validator/>。它有如下属性:name,这个验正动作的标志名,用在depends属性的配置中。classname,这个验证方法在哪个类中,为类全名。validationClass,由classname生成的Class。method,这个验证方法的名称。validationMethod,由method生成的Method。methodParams,执行这个验证方法所需要的参数,值为逗号隔开的类全名,默认值为Validator.BEAN_PARAM,Validator.VALIDATOR_ACTION_PARAM,Validator.FIELD_PARAM。parameterClasses,由methodParams生成的Class数组。depends,依赖何ValidatorAction之上。msg,如果没有通过此验证,应该返回什么消息。javascript,为对应的javascript方法的代码内容。jsFunction,为对应的js文件名称。jsFunctionName,对应的js中方法的名称。

1、executeValidationMethod(...)
这个方法用对Field中的值进行检测。它实际上就是通过反射,根据类名、方法名和参数类型执行相应的验证方法。要注意的是,如果这个Field为indexList类型的,则需要把Java Bean替换为对应的Array中pos指定的元素。同时Field的key属性中的[]要变为[pos]。

2、这个类还提供了对js的基本操作,解释如下:
*generateJsFunction()
用来生成名为org.apache.commons.validator.javascript.validateXxxx的js文件名。其中Xxxx与name相对应。

*readJavascriptFile(...)
用来读取js文件。由于一个js文件只包含一个方法,因此会读取所有的内容后,直接返回所读取的所有内容。

三、org.apache.commons.validator.ValidatorResults
这个类管理对一个Form中的所有Field执行验证的结果。它有如下属性:hResults,用来管理对所有Field验证的结果,它的key为field的kye属性,它的value为ValidatorResult对象。

四、org.apache.commons.validator.ValidatorResult
这个类用来管理对于一个Field执行验证的时候,所有的验证方法的结果。它有如下属性:field,所要验证的Field对象。hAction,用来保存所有验证结果的Map,它的key为ValidatorAction中的name属性,value为ResultStatus对象。

五、org.apache.commons.validator.ResultStatus
它封装了对某个Field执行某个验证后的结果。它有如下属性:valid,表示是否通过验证。result:为执行验证后的结果。

 

Commons-Validator(五)

最后,说明Validator是如何初始化的。

一、org.apache.commons.validator.ValidatorResourcesInitializer
这个类用来初始化Validator框架,通过Digester包解析用户自定义的xml配置文件。然后把解析的结果保存到ValidatorResources对象中,最后返回这个ValidatorResources对象。

二、org.apache.commons.validator.ValidatorResources
这个类用来管理Validator框架中的资源。它包含如下属性:hFormSets,一个FastHashMap,用来管理所有的FormSet,这个FastHashMap的key为根据FormSet中的本地信息生成的。hConstants,一个FastHashMap,用来管理<global/>中定义的constant。hActions,一个FastHashMap,用来管理ValidatorAction,它的key为ValidatorAction的name属性。

三、与struts整合时需要增加的配置:
在struts-config.xml文件中加入:

<plug-in className="org.apache.struts.validator.ValidatorPlugIn">? ? ? ? <set-property property="pathnames" value="/WEB-INF/validator-rules.xml,/WEB-INF/validation.xml" />? ? </plug-in>

其中validator-rules.xml,为Struts自带的配置文件,配置了所有的验证方法;而validation.xml为用户自己配置,配置要验证的对象。

四、org.apache.struts.validator.FieldChecks
这个是Struts使用的核心验证类,用来实现各种验证规则。其中,validateXXX(...)就是想要创建的方法名,只要规则名没有重复即可。validateXXX(...)方法的参数说明:
Object 验证是在此JavaBean 对象上完成的
ValidatorAction 当前所执行的ValidatorAction
Field 被验证的字段对象
ActionErrors 如果验证失败,新增ActionError 对象的错误对象
HttpServletRequest 当前的请求对象

五、org.apache.commons.validator.ValidatorResources
这个类用来通过Arg替换Msg中格式为{N}的字符串

六、org.apache.commons.validator.ValidatorPlugIn
这个类用来初始化Validator,然后把初始化生成的ValidatorResources对象保存到ServletContext中,key为VALIDATOR_KEY + config.getPrefix()。

七、org.apache.struts.validator.Resources
这个类用来初始化一个Validator对象,它首先通过key值VALIDATOR_KEY + config.getPrefix()从ServletContext取得相应的ValidatorResources对象。它再以这个对象为参数构造一个Validator对象,然后把验证方法需要的参数保存到Validator对象的parameters属性中

 


  第四篇 Commons-Pool包)
 
这些类用来生成相应的XXXObjectPool,比如GenericKeyedObjectPoolFactory:这个类用来生成一个新的GenericKeyedObjectPool对象。它实际的作用只是保存创建一个GenericKeyedObjectPool对象所需要的配置参数。如果使用默认的配置参数生成一个GenericKeyedObjectPool对象,那么可以调用GenericKeyedObjectPoolFactory(KeyedPoolableObjectFactory factory, GenericKeyedObjectPool.Config config),其中Config类里保存所有的缺省配置参数。

二、GenericKeyedObjectPool
通过_poolMap来管理相应的对象链,key作为寻找不同对象链的线索。也就是说,一个GenericKeyedObjectPool中可以管理多个不同key的对象集合,每个集合通过_activeMap用来保存各个不同的Key当前的活动对象的数量(闲置对象的数量可以直接通过相应的CursorableLinkedList的size来获取),可以通过getNumActive(Object key)和getNumIdle(Object key)方法获取这
两个值。这个对象集合通过CursorableLinkedList来管理。

这个类里的_maxIdle和_maxActive属性是针对单个key对应的CursorableLinkedList而言的最大值,而_maxTotal属性则是对所有的key表示的对象集合的总和的最大值。_totalActive+_totalIdle用来和_maxTotal的值进行对比,它们可以通过getNumActive()和getNumIdle()获取。_poolList存放的值与 _poolMap 中的key值一样,注意这个对象不是没有用处的,因为CursorableLinkedList是一个允许多个线程同步迭代的类。

1、几个内置的static类型的变量:
WHEN_EXHAUSTED_FAIL 这个属性表明当对象池已经用尽时(比如说池中的对象数目已经达到了最大值),borrowObject方法应该立刻抛出NoSuchElementException异常。

WHEN_EXHAUSTED_BLOCK 这个属性表明当对象池已经用尽时(比如说池中的对象数目已经达到了最大值),borrowObject方法应该等待,直到对象池中有一个对象可以让它返回,或者达到了最大等待时间(DEFAULT_MAX_WAIT)。

WHEN_EXHAUSTED_GROW 这个属性表明即使在对象池已经用尽时(比如说池中的对象数目已经达到了最大值),borrowObject方法应该仍旧返回一个对象。
DEFAULT_WHEN_EXHAUSTED_ACTION 这个属性表明当对象池用尽的时候(比如说池中的对象数目已经达到了最大值)应该采取上面的那种策略。

DEFAULT_MAX_IDLE 和 DEFAULT_MAX_ACTIVE分别指定了在池中对大的空闲和活动对象的数目。DEFAULT_MAX_TOTAL表明对象池允许存在的最大对象数量。

2、CursorableLinkedList
它实际是一个双向列表,与LinkedList的区别是:它允许多个线程执行迭代。它的实现思路是:在每次生成一个Cursor时:CursorableLinkedList.Cursor cursor(int i),都会吧这个新生成的Cursor对象包装到一个 WeakReference 中:_cursors.add( new WeakReference(cur) );在不需要的的时候需要调用Cursor. close()方法取消注册。而Cursor实际是一个以内嵌类的方式的实现了ListIterator接口的类。WeakReference 不会增加被其包装的类的引用次数,换句话说,它不会影响JVM的垃圾收集。

3、borrowObject(Object key)
这个方法用来从相应的key表示的对象集合中取出一个对象。如果对应的对象集合不存在,会创建一个这样的集合。如果集合当前没有闲置的对象可以返回,并且当前的active < _maxActive && totalActive + _totalIdle < _maxTotal,那么通过KeyedPoolableObjectFactory创建一个新的对象;否则就根据_whenExhaustedAction的设定执行相应的操作。要注意每次都会执行_factory.activateObject(key,pair.value)方法初始化这个对象。在返回这个对象之前,如果设定了_testOnBorrow标志则调用_factory.validateObject(Object key, Object obj)方法,如果不符合要求会抛出NoSuchElementException异常,否则返回这个对象。

4、returnObject(Object key, Object obj)
这个方法用来把一个对象返回给key表示的对象集合,这个方法的执行过程与borrowObject(Object key)方法相似。它会根据_testOnReturn标志执行_factory.validateObject(Object key, Object obj)方法检查返回的对象是否合格。要注意每次都会执行 _factory.passivateObject(key, obj)方法“消除”这个对象。

5、过期类的回收:
Evictor 为一个内嵌类,实现自Runnable接口。它每过_timeBetweenEvictionRunsMillis毫秒会调用一次evict()方法,在后台收集垃圾寿命超过_minEvictableIdleTimeMillis毫秒的对象。每当我们创建一个GenericKeyedObjectPool对象的时候,这个类会被自动创建,并且被启动。evict() 方法很简单,它首先创建一个_evictionKeyCursor = _poolList.cursor()对象,然后根据key遍历每一个对象集合。
查找到过期的就会删除。

三、SoftReferenceObjectPool
通过SoftReference进行对象的保存,这个SoftReference的好处是你不需要自己进行收集,它会在JVM的内存用尽时被自动回收。这种对象池的特色是:
a、可以保存任意多个对象,不会有容量已满的情况发生。

b、在对象池已空的时候,调用它的borrowObject方法,会自动返回新创建的实例。

c、可以在初始化同时,在池内预先创建一定量的对象。

d、当内存不足的时候,池中的对象可以被Java虚拟机回收。


四、StackObjectPool 和 StackKeyedObjectPool
通过一个Stack对对象进行管理,要注意这个Stack是继承自Vector对象的。因此虽然线程安全,但是效率较低。这种对象池的特色是:
a、可以为对象池指定一个初始的参考大小(当空间不够时会自动增长)。

b、在对象池已空的时候,调用它的borrowObject方法,会自动返回新创建的实例。

c、可以为对象池指定一个可保存的对象数目的上限。达到这个上限之后,再向池里送回的对象会被自动送去回收。

 

五、PoolableObjectFactory
Pool组件利用PoolableObjectFactory来照看被池化的对象。ObjectPool的实例在需要处理被池化的对象的产生、激活、挂起、校验和销毁工作时,就会调用跟它关联在一起的PoolableObjectFactory实例的相应方法来操作。 用户需要自己实现这个接口。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值