struts1源码分析(二)初始化主线

Struts1整体概览和核心组件一文中,我们提到了Struts1框架的两条主线:初始化主线和请求处理主线,本文将探寻Struts1框架初始化这条主线。本文使用的Struts版本为1.2.8, 不同版本会略有差异,1.3.x系列对请求处理进行优化,差异性将另文叙述。

 

[问题]

在介绍初始化过程之前,我们先来思考几个问题。

1. 如何在web应用中植入框架的初始化过程?

2. 如何便捷地读取框架配置文件,完成配置文件到Java对象的映射?

3. 哪些组件需要完成初始化,如果完成?

4. 如何存储初始化阶段的成果?

 

[初始化入口]

在上文中我们提到,Struts1框架的入口和核心是ActionServlet类。通常在web.xml中会有如下配置:

Xml代码   收藏代码
  1. <servlet>  
  2.     <servlet-name>action</servlet-name>  
  3.     <servlet-class>org.apache.struts.action.ActionServlet</servlet-class>  
  4.     <init-param>  
  5.           <param-name>config</param-name>  
  6.           <param-value>  
  7.               /WEB-INF/struts-config.xml  
  8.           </param-value>  
  9.     </init-param>  
  10. </servlet>  
  11.   
  12. <servlet-mapping>  
  13.     <servlet-name>action</servlet-name>  
  14.     <url-pattern>*</url-pattern>  
  15. </servlet-mapping>  

上述配置可以看出,所有的请求都交由ActionServlet进行处理。ActionServlet本身作为一个Servlet(继承HttpServlet),自然符合Servlet的生命周期,容器在调用service()方法处理请求之前,需要提前调用init()方法完成Servlet初始化工作。init()方法在Servlet生命周期中只会被调用一次。由于Servlet生命周期中的这些特性,使得框架的初始化入口一般都在init()方法中。其他框架也不例外,比如SpringMVC,框架初始化交由DispatcherServlet的init()方法完成。

 

[初始化概览]

从整体上先预览一下init()方法:

 

Java代码   收藏代码
  1. public void init() throws ServletException {  
  2.   
  3.     // 此处省略了方法前后的异常处理  
  4.     // 第一阶段,准备阶段  
  5.     initInternal();  
  6.     initOther();  
  7.     initServlet();  
  8.       
  9.     // 第二阶段, 默认模块配置解析  
  10.     getServletContext().setAttribute(Globals.ACTION_SERVLET_KEY, this);  
  11.     initModuleConfigFactory();  
  12.     // Initialize modules as needed  
  13.     ModuleConfig moduleConfig = initModuleConfig("", config);  
  14.   
  15.     // 第三阶段,默认模块组件初始化  
  16.     initModuleMessageResources(moduleConfig);  
  17.     initModuleDataSources(moduleConfig);  
  18.     initModulePlugIns(moduleConfig);  
  19.     moduleConfig.freeze();  
  20.   
  21.     // 第四阶段, 自定义模块初始化  
  22.     Enumeration names = getServletConfig().getInitParameterNames();  
  23.     while (names.hasMoreElements()) {  
  24.         String name = (String) names.nextElement();  
  25.         if (!name.startsWith("config/")) {  
  26.             continue;  
  27.         }  
  28.         String prefix = name.substring(6);  
  29.         moduleConfig = initModuleConfig  
  30.             (prefix, getServletConfig().getInitParameter(name));  
  31.         initModuleMessageResources(moduleConfig);  
  32.         initModuleDataSources(moduleConfig);  
  33.         initModulePlugIns(moduleConfig);  
  34.         moduleConfig.freeze();  
  35.     }  
  36.   
  37.     // 第五阶段,收尾阶段  
  38.     this.initModulePrefixes(this.getServletContext());  
  39.     this.destroyConfigDigester();  
  40.   
  41. }  
 

 

这里将初始化过程分为五个阶段,通过分析每个阶段做了那些事来解读整个初始化过程。下面简要介绍一下每个阶段所做的工作。
第一阶段:准备阶段。这个阶段是为后续工作做准备,如读取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的方法如下:

Java代码   收藏代码
  1. if (convertNull) {  
  2.     // 1. 注销所有的转换器  
  3.     ConvertUtils.deregister();  
  4.     // 2. 依次设置各包装类转换器,初始值置为null  
  5.     ConvertUtils.register(new BigDecimalConverter(null), BigDecimal.class);  
  6.     ConvertUtils.register(new BigIntegerConverter(null), BigInteger.class);  
  7.     ConvertUtils.register(new BooleanConverter(null), Boolean.class);  
  8.       
  9.     // 此处省略其他包装类转换器           
  10. }  

 

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()时读取的配置文件信息。该方法的实现如下:

 

Java代码   收藏代码
  1. protected ModuleConfig initModuleConfig(String prefix, String paths)  
  2.     throws ServletException {  
  3.   
  4.     // 此处省略日志信息  
  5.   
  6.     // 1. 初始化工厂对象并创建ModuleConfig实例  
  7.     ModuleConfigFactory factoryObject = ModuleConfigFactory.createFactory();  
  8.     ModuleConfig config = factoryObject.createModuleConfig(prefix);  
  9.   
  10.     // 2. 创建Digester对象,用于配置文件解析  
  11.     Digester digester = initConfigDigester();  
  12.   
  13.     // 3. 依次完成每个配置文件解析,多个配置文件采用逗号隔开  
  14.     while (paths.length() > 0) {  
  15.         digester.push(config);  
  16.         String path = null;  
  17.         int comma = paths.indexOf(',');  
  18.         if (comma >= 0) {  
  19.             path = paths.substring(0, comma).trim();  
  20.             paths = paths.substring(comma + 1);  
  21.         } else {  
  22.             path = paths.trim();  
  23.             paths = "";  
  24.         }  
  25.   
  26.         if (path.length() < 1) {  
  27.             break;  
  28.         }  
  29.   
  30.         this.parseModuleConfigFile(digester, path);  
  31.     }  
  32.   
  33.     // 4. 保存解析结果,将结果存入ServletContext中  
  34.     getServletContext().setAttribute(  
  35.         Globals.MODULE_KEY + config.getPrefix(),  
  36.         config);  
  37.   
  38.     // 5. 创建动态Form Bean对应的DynaActionFormClass实例  
  39.     FormBeanConfig fbs[] = config.findFormBeanConfigs();  
  40.     for (int i = 0; i < fbs.length; i++) {  
  41.         if (fbs[i].getDynamic()) {  
  42.             fbs[i].getDynaActionFormClass();  
  43.         }  
  44.     }  
  45.   
  46.     return config;  
  47. }  
通过上面代码可以看出,整个过程包含五步:

 

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框架。

转载出处:http://learnworld.iteye.com/blog/1997760

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值