源码分析:Struts 2配置文件加载顺序

Struts 2可在多个配置文件设置属性值,而且可以多处配置同一常量,那么系统以什么方式加载,并以何处的值为准呢?

分析过程

这里从入口开始分析,逐渐按顺序找到各个配置文件。

1. 入口 Filter&Dispatcher

考虑到属性值应该是系统正常运行时就赋值好的,那么可能在系统初始化时就执行加载和赋值操作。Struts 2加载的入口类是:

org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter

这个 filter 的 init() 如下:

    public void init(FilterConfig filterConfig) throws ServletException {
        InitOperations init = new InitOperations();
        Dispatcher dispatcher = null;
        try {
            FilterHostConfig config = new FilterHostConfig(filterConfig);
            init.initLogging(config);
            dispatcher = init.initDispatcher(config);
            init.initStaticContentLoader(config, dispatcher);
            prepare = new PrepareOperations(filterConfig.getServletContext(), dispatcher);
            execute = new ExecuteOperations(filterConfig.getServletContext(), dispatcher);
            this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);
            postInit(dispatcher, filterConfig);
        } finally {
            if (dispatcher != null) {
                dispatcher.cleanUpAfterInit();
            }
            init.cleanup();
        }
    }

属性的初始化工作,通过代码字面的含义,可能是黑体部分 init.initDispatcher(config)

查看这个 InitOperations.initDispatcher(config) 方法,内容如下:

    /**
     * Creates and initializes the dispatcher
     */
    public Dispatcher initDispatcher( HostConfig filterConfig ) {
        Dispatcher dispatcher = createDispatcher(filterConfig);
        dispatcher.init();
        return dispatcher;
    }

具体的初始化操作,再查看黑体部分的方法:Dispatcher.init(),内容如下:

    /**
     * Load configurations, including both XML and zero-configuration strategies,
     * and update optional settings, including whether to reload configurations and resource files.
     */
    public void init() {
         if (configurationManager == null) {
              configurationManager = createConfigurationManager(DefaultBeanSelectionProvider.DEFAULT_BEAN_NAME);
         }
        try {
            init_FileManager();
            init_DefaultProperties(); // [1]
            init_TraditionalXmlConfigurations(); // [2]
            init_LegacyStrutsProperties(); // [3]
            init_CustomConfigurationProviders(); // [5]
            init_FilterInitParameters() ; // [6]
            init_AliasStandardObjects() ; // [7]
            Container container = init_PreloadConfiguration();
            container.inject(this);
            init_CheckWebLogicWorkaround(container);
            if (!dispatcherListeners.isEmpty()) {
                for (DispatcherListener l : dispatcherListeners) {
                    l.dispatcherInitialized(this);
                }
            }
        } catch (Exception ex) {
            if (LOG.isErrorEnabled())
                LOG.error("Dispatcher initialization failed", ex);
            throw new StrutsException(ex);
        }
    }

看上面方法的注释,确实找对地方了。

2. default.properties

先看 init_DefaultProperties() 方法:

    private void init_DefaultProperties() {
        configurationManager.addContainerProvider(new DefaultPropertiesProvider());
    }
/**
* Loads the default properties, separate from the usual struts.properties loading
*/
public class DefaultPropertiesProvider extends PropertiesConfigurationProvider {
    public void destroy() {
    }
    public void init(Configuration configuration) throws ConfigurationException {
    }
    public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException {
        try {
            PropertiesSettings defaultSettings = new PropertiesSettings("org/apache/struts2/default");
            loadSettings(props, defaultSettings);
        } catch (Exception e) {
            throw new ConfigurationException("Could not find or error in org/apache/struts2/default.properties", e);
        }
    }
}

可见首先加载的是 org/apache/struts2/default.properties

3. *.xml

再来看 init_TraditionalXmlConfigurations() 这个方法:

    private void init_TraditionalXmlConfigurations() {
        String configPaths = initParams.get("config");
        if (configPaths == null) {
            configPaths = DEFAULT_CONFIGURATION_PATHS;
        }
        String[] files = configPaths.split("\\s*[,]\\s*");
        for (String file : files) {
            if (file.endsWith(".xml")) {
                if ("xwork.xml".equals(file)) {
                    configurationManager.addContainerProvider(createXmlConfigurationProvider(file, false));
                } else {
                    configurationManager.addContainerProvider(createStrutsXmlConfigurationProvider(file, false, servletContext));
                }
            } else {
                throw new IllegalArgumentException("Invalid configuration file name");
            }
        }
    }
    /**
     * Provide list of default configuration files.
     */
    private static final String DEFAULT_CONFIGURATION_PATHS = "struts-default.xml,struts-plugin.xml,struts.xml";

上面的代码显示,将从 web.xml 中读取 config 参数的值,如果有 xwork.xml 就读取 xwork.xml 的值——这可能是历史遗留的兼容问题所致,如果 config 值未设置,则读取默认值:

struts-default.xml,struts-plugin.xml,struts.xml

这三个配置文件,顺序自然是 DEFAULT_CONFIGURATION_PATHS 定义的顺序。

具体内容还可以自定义。

4. Legacy Properties

再来看 init_LegacyStrutsProperties() 这个方法:

    private void init_LegacyStrutsProperties() {
        configurationManager.addContainerProvider(new PropertiesConfigurationProvider());
    }
    public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException {
        final DefaultSettings settings = new DefaultSettings();
        loadSettings(props, settings);
    }

    /**
     * Constructs an instance by loading the standard property files,
     * any custom property files (<code>struts.custom.properties</code>),
     * and any custom message resources ().
     * <p>
     * Since this constructor  combines Settings from multiple resources,
     * it utilizes a {@link DelegatingSettings} instance,
     * and all API calls are handled by that instance.
     */
    public DefaultSettings() {
        ArrayList<Settings> list = new ArrayList<Settings>();
        // stuts.properties, default.properties
        try {
            list.add(new PropertiesSettings("struts"));
        } catch (Exception e) {
            LOG.warn("DefaultSettings: Could not find or error in struts.properties", e);
        }
        delegate = new DelegatingSettings(list);
        // struts.custom.properties
        String files = delegate.get(StrutsConstants.STRUTS_CUSTOM_PROPERTIES);
        if (files != null) {
            StringTokenizer customProperties = new StringTokenizer(files, ",");
            while (customProperties.hasMoreTokens()) {
                String name = customProperties.nextToken();
                try {
                    list.add(new PropertiesSettings(name));
                } catch (Exception e) {
                    LOG.error("DefaultSettings: Could not find " + name + ".properties. Skipping.");
                }
            }
            delegate = new DelegatingSettings(list);
        }
    }

以上代码可以看出,这里加载的是 struts.properties, 而且可以通过 StrutsConstants.STRUTS_CUSTOM_PROPERTIES 设置自定义的配置文件。

5. 结论

通过上面的分析,可以发现 Struts 2 对配置文件的加载顺序是:

default.properties -> struts-default.xml -> struts-plugin.xml -> struts.xml -> struts.properties

後加载的文件中的赋值,会覆盖前面的文件中的赋值。

但不知道为什么很多地方都说是这个顺序:

struts-default.xml -> struts-plugin.xml -> struts.xml -> struts.properties -> web.xml

配置文件说明

下面详细说明相关配置文件

1. default.properties

/org/apache/struts2/default.properties

这个文件位于 struts2-core-2.3.16.1.jar 中,是 Struts 2默认的配置文件,含有大量 Struts 2属性参数的默认值以及说明。

这里的配置一般不需修改。

另,org.apache.struts2.StrutsConstants 中也有常量定义说明。

2. struts-default.xml

默认位置:struts2-core-2.3.16.1.jar/struts-default.xml

这里的配置一般不需修改。

3. struts-plugin.xml

据称位于一些 struts 2 插件的 jar 包中,如:

struts2-tiles3-plugin-2.3.16.1.jar/struts-plugin.xml

struts2-gxp-plugin-2.3.16.1.jar/struts-plugin.xml

struts2-embeddedjsp-plugin-2.3.16.1.jar/struts-plugin.xml

struts2-osgi-plugin-2.3.16.1.jar/struts-plugin.xml

这些 struts-plugin.xml 中的内容不会冲突。

4. struts.xml

默认位置:{web app home}/WEB-INF/classes/struts/struts.xml

这是 Web app 默认的配置文件,可以通过修改 Web app 的 web.xml 来修改它的路径:

<filter>
   <filter-name> struts2 </filter-name>
   <filter-class> org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter </filter-class>
    <init-param>
          <param-name> config </param-name>
          <param-value>struts-default.xml,struts-plugin.xml,META-INF/config/struts/struts.xml </param-value>
   </init-param>
</filter>

5. struts.properties

默认位置:{web app home}/WEB-INF/classes/struts.properties

按当前的 Struts 2版本,这个已经算是遗留方式了,不建议考虑修改它了。

6. web.xml

默认位置:{web app home}/web.xml

web.xml 是 web 应用的配置文件,一般不通过这种方式配置常量,在上面的源码分析中,也没发现最後以此为准的说法 TODO

参考:

《struts2源码分析之配置文件加载顺序》:http://my.oschina.net/gschen/blog/121433