在Struts1整体概览和核心组件一文中,我们提到了Struts1框架的两条主线:初始化主线和请求处理主线,本文将探寻Struts1框架初始化这条主线。本文使用的Struts版本为1.2.8, 不同版本会略有差异,1.3.x系列对请求处理进行优化,差异性将另文叙述。
[问题]
在介绍初始化过程之前,我们先来思考几个问题。
1. 如何在web应用中植入框架的初始化过程?
2. 如何便捷地读取框架配置文件,完成配置文件到Java对象的映射?
3. 哪些组件需要完成初始化,如果完成?
4. 如何存储初始化阶段的成果?
[初始化入口]
在上文中我们提到,Struts1框架的入口和核心是ActionServlet类。通常在web.xml中会有如下配置:
<servlet>
<servlet-name>action</servlet-name>
<servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
<init-param>
<param-name>config</param-name>
<param-value>
/WEB-INF/struts-config.xml
</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>action</servlet-name>
<url-pattern>*</url-pattern>
</servlet-mapping>
上述配置可以看出,所有的请求都交由ActionServlet进行处理。ActionServlet本身作为一个Servlet(继承HttpServlet),自然符合Servlet的生命周期,容器在调用service()方法处理请求之前,需要提前调用init()方法完成Servlet初始化工作。init()方法在Servlet生命周期中只会被调用一次。由于Servlet生命周期中的这些特性,使得框架的初始化入口一般都在init()方法中。其他框架也不例外,比如SpringMVC,框架初始化交由DispatcherServlet的init()方法完成。
[初始化概览]
从整体上先预览一下init()方法:
public void init() throws ServletException {
// 此处省略了方法前后的异常处理
// 第一阶段,准备阶段
initInternal();
initOther();
initServlet();
// 第二阶段, 默认模块配置解析
getServletContext().setAttribute(Globals.ACTION_SERVLET_KEY, this);
initModuleConfigFactory();
// Initialize modules as needed
ModuleConfig moduleConfig = initModuleConfig("", config);
// 第三阶段,默认模块组件初始化
initModuleMessageResources(moduleConfig);
initModuleDataSources(moduleConfig);
initModulePlugIns(moduleConfig);
moduleConfig.freeze();
// 第四阶段, 自定义模块初始化
Enumeration names = getServletConfig().getInitParameterNames();
while (names.hasMoreElements()) {
String name = (String) names.nextElement();
if (!name.startsWith("config/")) {
continue;
}
String prefix = name.substring(6);
moduleConfig = initModuleConfig
(prefix, getServletConfig().getInitParameter(name));
initModuleMessageResources(moduleConfig);
initModuleDataSources(moduleConfig);
initModulePlugIns(moduleConfig);
moduleConfig.freeze();
}
// 第五阶段,收尾阶段
this.initModulePrefixes(this.getServletContext());
this.destroyConfigDigester();
}
这里将初始化过程分为五个阶段,通过分析每个阶段做了那些事来解读整个初始化过程。下面简要介绍一下每个阶段所做的工作。
第一阶段:准备阶段。这个阶段是为后续工作做准备,如读取Servlet参数信息和准备框架内部用到的工具等。
第二阶段:默认模块配置解析。这个阶段完成默认模块配置文件的解析工作,是整个初始化的核心阶段。
第三阶段:默认模块组件初始化。在第二阶段中,组件的配置信息(MessageResource/DataSource/PlugIn)已经读取,这里需要对各个组件进行进一步加工,完成各个组件的初始化。
第四阶段:自定义模块初始化。自定义模块初始化步骤和默认模块一样,完成配置解析和组件初始化。
第五阶段:收尾阶段。准备框架需要的其他信息和清理初始化过程中使用的辅助工具。
下面将对依次对每个阶段的工作做进一步解读。
[一、准备阶段]
这阶段主要完成ActionServlet参数读取和框架内部工具准备,总共三个方法。
1. initInternal()
用来初始化框架本身使用的MessageResource。框架自身处理过程中, 可能会出现很多的错误日志信息,这些错误信息需要做国际化,所以框架在启动初期首先完成该操作。框架自身提供的错误信息资源文件路径为org/apache/struts/action/ActionResources.properties,只支持英文和日文两种语言。
2. initOther()
从web.xml中读取ActionServlet 的初始化参数"config"和"convertNull"。
1) “config”参数为Struts默认模块的配置信息,包含Struts框架用到的配置文件列表信息。第二阶段将依据该参数值来解析配置文件。
2) "convertNull"参数用于指定FormBean参数转换时Java包装类的初始化默认值。如果设置为true,则使用null作为包装类的默认值。设置初始值为null的方法如下:
if (convertNull) {
// 1. 注销所有的转换器
ConvertUtils.deregister();
// 2. 依次设置各包装类转换器,初始值置为null
ConvertUtils.register(new BigDecimalConverter(null), BigDecimal.class);
ConvertUtils.register(new BigIntegerConverter(null), BigInteger.class);
ConvertUtils.register(new BooleanConverter(null), Boolean.class);
// 此处省略其他包装类转换器
}
3. initServlet()
该方法完成web.xml中ActionServlet信息的读取,包括ServletName和ServletMapping。ServletName信息通过getServletConfig().getServletName()直接获取。ServletMapping信息读取借助Apache Commons Digester Component类库(简称Digester)来完成。Digester类库用途是完成xml文件到Java对象的转换,可以配置规则,针对xml元素设定对应的“Java对象操作”,简化XML->Java对象的转换过程。Digester用户文档参考这里。这里的转换过程主要步骤如下:
1) 创建Digester对象,设定Digester属性值。
2) 在Digester对象中注册框架自带的五个DTD文件。
3) 设置Digester处理web.xml中ServletMapping元素的规则。
4) 执行解析操作,获取ServletMapping的URL pattern,将结果保存到ActionServlet的servletMapping属性中。
[二、默认模块配置解析]
这一阶段主要工作是解析Struts配置文件,也就是完成XML配置文件->Java配置类的转换过程。我们先从整体上看一下这个过程中涉及的类图。
1. 类图
Struts配置解析涉及的类图如下:
这里用不同颜色表示不同的功能集合,我们简要介绍一下各集合的功能。
红色(ModuleConfig等):ModuleConfig是配置数据的载体,存储着一个模块需要的所有配置数据。 初始化过程是解析每个模块配置文件的过程,也是构建每个模块对应的ModuleConfig实例的过程。
黄色(ModuleConfigFactory等):ModuleConfig的工厂类,用于构建ModuleConfig实例。
深蓝色(ActionServlet):初始化过程的实际执行者。
浅蓝色(Digester/ConfigRuleSet):解析配置文件使用的辅助类。Digester用于将XML转换为Java对象,ConfigRuleSet指定具体转换规则,既每个XML标签出现后如何操作Java对象。
绿色(ControllerConfig/ActionConfig等):配置文件中各元素对应的JavaBean。比如<Action>元素对应ActionConfig类, <forward>元素对应ForwardConfig类。在使用Digest类解析XML文件过程中,会构建每个元素对应的JavaBean类,最后统一保存在ModuleConfig对象中。
看完类图以后,我们来看一下第二阶段的两个方法调用。
2. initModuleConfigFactory()
从web.xml中读取ActionServlet的配置参数configFactory,该参数表示ModuleConfigFacotory的实现类信息,默认工厂实现类为"org.apache.struts.config.impl.DefaultModuleConfigFactory“。如果该参数存在,使用该参数值替换默认工厂实现类。
3. initModuleConfig("", config)
非常关键的一步,解析Struts配置文件并生成ModuleConfig对象。该方法的第一个参数是模块的前缀,因为是默认模块,所以前缀为空字符串。第二个参数config是第一阶段中initOther()时读取的配置文件信息。该方法的实现如下:
protected ModuleConfig initModuleConfig(String prefix, String paths)
throws ServletException {
// 此处省略日志信息
// 1. 初始化工厂对象并创建ModuleConfig实例
ModuleConfigFactory factoryObject = ModuleConfigFactory.createFactory();
ModuleConfig config = factoryObject.createModuleConfig(prefix);
// 2. 创建Digester对象,用于配置文件解析
Digester digester = initConfigDigester();
// 3. 依次完成每个配置文件解析,多个配置文件采用逗号隔开
while (paths.length() > 0) {
digester.push(config);
String path = null;
int comma = paths.indexOf(',');
if (comma >= 0) {
path = paths.substring(0, comma).trim();
paths = paths.substring(comma + 1);
} else {
path = paths.trim();
paths = "";
}
if (path.length() < 1) {
break;
}
this.parseModuleConfigFile(digester, path);
}
// 4. 保存解析结果,将结果存入ServletContext中
getServletContext().setAttribute(
Globals.MODULE_KEY + config.getPrefix(),
config);
// 5. 创建动态Form Bean对应的DynaActionFormClass实例
FormBeanConfig fbs[] = config.findFormBeanConfigs();
for (int i = 0; i < fbs.length; i++) {
if (fbs[i].getDynamic()) {
fbs[i].getDynaActionFormClass();
}
}
return config;
}
通过上面代码可以看出,整个过程包含五步:
1. 创建工厂对象并构建ModuleConfig实例,这里ModuleConfig属性被设置为默认值。
2. 创建Digester对象,用于后续的配置文件解析。在initConfigDigester()方法中,首先构建Digester对象,并设置解析规则,默认解析规则由ConfigRuleSet类指定,用户自定义解析规则也将被加入Digester对象中。
3. 依次完成每个配置文件解析工作。parseModuleConfigFile()读取配置文件信息并交由Digester对象进行解析,解析结果放入ModuleConfig对象中。
4. 将解析结果ModuleConfig放入ServletContext中,采用的key为"Globals.MODULE_KEY+模块前缀"。采用这种方式区分存储不同模块对应的ModuleConfig对象。
5. 根据FormBeanConfig配置,创建动态FormBean对应的DynaActionFormClass对象,用于请求处理过程中生成ActionForm对象。
完成以上五步,ModuleConfig对象已生成完毕,第二阶段工作已经完成。
[三、默认模块组件初始化]
ModuleConfig对象生成完毕后,框架配置信息已全部读取,第三阶段在这个基础上做进一步处理。这里包含对三个组件的进一步处理。
1. initModuleMessageResources(moduleConfig)
完成各MessageResource对象的初始化工作,用于处理资源国际化。关于MessageResource的初始化和使用,将另文详述,这里不做进一步说明。
2. initModuleDataSources(moduleConfig)
完成DataSource对象的初始化工作。首先读取配置的DataSource类型信息,生成对应的实例;读取配置文件中DataSource的properties信息,完成DataSource属性设置;将生成的DataSource对象放入dataSources列表中。
3. initModulePlugIns(moduleConfig)
完成PlugIn对象的初始化工作。首先读取PlugIn类型信息,生成PlugIn实例;读取配置文件中PlugIn的properties信息,完成PlugIn属性设置;调用PlugIn的init()方法完成初始化工作。
4. moduleConfig.freeze()
设置标志位,用来说明模块配置已初始化完毕,后续对ModuleConfig的修改操作将导致异常。
完成默认模块组件初始化后,默认模块初始化工作已经全部完毕,接下来将开始自定义模块的初始化工作。
[四、自定义模块初始化]
该阶段依次读取每个自定义模块的配置信息,完成各模块的初始化工作。初始化过程和默认模块一致,分为两个阶段:配置解析和组件初始化。如何区分默认模块和自定义模块配置信息? 这里采用模块前缀(prefix)作为模块的标识信息。比如在ServletContext中存储ModuleConfig对象时,默认模块key为"Globals.MODULE_KEY",而自定义模块key为"Globals.MODULE_KEY + 模块前缀“。其他地方都是做类似处理。
[五、收尾阶段]
收尾阶段完成其他属性设置和资源清理。主要包含两个方法:
1. initModulePrefixes(this.getServletContext())
将所有模块前缀信息存入ServletContext中,供后续使用。
2. destroyConfigDigester()
完成配置文件读取时使用的digester对象清理。
[小结]
本文详细分析Struts1框架整个初始化主线,回顾一下关键要点:
1. 初始化入口是借助ActionServlet生命周期中的init()方法完成。
2. 初始化的目标是准备框架使用的各个配置对象和组件,核心是围绕解析配置文件和生成ModuleConfig对象。
3. 整体过程分三个阶段:准备阶段,模块初始化和收尾阶段。模块初始化分为配置解析和组件初始化两个阶段,默认模块和自定义模块都需要经过这两个阶段。
通过学习初始化这条主线,我们已了解Struts1框架在启动阶段做了哪些工作,下文将从请求处理主线来解读Struts框架。
相关推荐
从 org.apache.struts2.dispatcher.FilterDispatcher 开始 Java 代码阅读,我们可以看到 FilterDispatcher 的 init 方法,它负责初始化 Dispatcher 对象,并创建了一个 ActionMapper 实例,以便对请求进行处理。...
本文将深入探讨Struts2的源码分析,特别是关于StrutsPrepareAndExecuteFilter的初始化过程,这是Struts2的核心组件之一,负责处理HTTP请求。 首先,我们来看`StrutsPrepareAndExecuteFilter`的初始化。这个过滤器...
struts2源码详细解析51CTO下载-struts2源代码分析(个人觉得非常经典)
`Dispatcher`类在Struts2中扮演着重要角色,它负责框架的初始化和Action的执行。`FilterDispatcher`则是一个Servlet Filter,根据配置文件来决定哪些请求需要经过Struts2处理。如果配置不当,可能会导致问题,如将...
下面我们将深入探讨Struts 1的源码分析,特别是针对Struts 1.2版本。 1. **架构概述** - **DispatcherServlet**:作为Struts的核心,DispatcherServlet负责接收HTTP请求并分发到相应的Action。 - **ActionMapping...
ActionServlet是Struts1的核心组件,它的生命周期分为初始化、拦截请求和销毁三个阶段。在初始化阶段,`init()`方法执行了一系列关键步骤: 1. `initInternal()`方法初始化内部资源,如国际化设置。它包含了英文和...
Interceptor链是由用户自定义的拦截器和Struts2内置的拦截器组成,例如PrepareInterceptor会确保Action实例被正确初始化。Action执行完成后,Result对象会根据配置的类型和值来决定如何处理返回的结果。 此外,...
ActionServlet的初始化过程是整个框架工作的基础,它负责加载配置、初始化模块并处理请求。通过深入理解Struts 1.2的源码,开发者可以更好地利用这个框架,解决实际项目中的问题,同时为自定义扩展和优化提供可能。
`ActionProxy`创建并初始化`ActionInvocation`对象,`ActionInvocation`则负责执行Action,并管理拦截器(Interceptors)的调用。 在请求流程中,`ActionInvocation`会按照预定义的顺序调用拦截器,然后执行Action...
### Struts框架初始化详解 #### 一、Struts框架简介 Struts是一个开源的MVC(Model-View-Controller)架构实现,用于简化Java Web应用的开发过程。它基于Servlet和JSP技术来构建Web应用程序,能够帮助开发者更加...
学习Struts1源码有助于开发者更好地理解Web应用的架构设计,以及MVC模式的实现细节,对于提升Java Web开发技能和解决问题具有很大帮助。同时,虽然Struts1已逐渐被更新的框架如Struts2和Spring MVC取代,但其设计...
- **StrutsPrepareAndExecuteFilter**:这是Struts2的核心过滤器,负责初始化Struts2框架并处理所有的HTTP请求。 - **ActionContext**:存储了请求期间的上下文信息,包括值栈、session、request和response对象等。...
struts2源代码及API帮助文档。...跟读struts2源码的时候注意两条主线:一条是初始化;另外一条是监听http请求。学习初始化过程是如何获取配置构造容器,其中的巧妙构思和设计模式的运用非常值得学习。
源码分析是提升编程技能和解决问题的关键,特别是对于复杂的框架如Struts2,理解其内部工作原理能够帮助我们优化应用性能,修复潜在问题,并进行定制化开发。 首先,Struts2的核心组件包括Action、Result、...
3. StrutsPrepareAndExecuteFilter:这是Struts2的过滤器,用于初始化和处理请求。它在web.xml中配置,负责拦截请求并将其交给Struts2的Dispatcher。 4. ActionMapper:ActionMapper根据请求URL映射到相应的Action...
然而,有一个常见的问题出现在尝试初始化`java.util.Date`类型的字段时,即“Struts的form不能初始化java.util.Date类型”。这个问题通常是由于日期对象的序列化和反序列化机制导致的,以及Struts默认的数据绑定策略...
Struts2源码分析--请求处理.pdf
11. **ActionServlet的生命周期**:从初始化到服务,再到销毁,理解这一过程有助于优化应用性能。 深入研究Struts1的源码,开发者可以学习到如何优雅地处理HTTP请求、如何组织业务逻辑、如何实现视图和模型的解耦,...
1. **Struts2的核心概念** - **Action**: Action是业务逻辑的载体,负责处理用户请求并返回结果。在Struts2中,Action可以通过实现`com.opensymphony.xwork2.Action`接口或继承`org.struts2.interceptor栈...