`
learnworld
  • 浏览: 168238 次
  • 性别: Icon_minigender_1
  • 来自: 南京
社区版块
存档分类
最新评论

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

阅读更多

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

 

  • 大小: 75.9 KB
分享到:

相关推荐

Global site tag (gtag.js) - Google Analytics