struts初始化起始于ActionServlet。正如其名,它是Servlet,按照Servlet的声明周期,struts的初始化放在了init方法之中。
public void init() throws ServletException {
// struts初始化流程放入try/catch中,这样可以更好的处理未捕获的异常或错误
try {
// 1. 初始化内部国际化信息
initInternal();
// 2. 判断convertNull,进行特殊类型转换器注册
initOther();
// 3. 获取当前serverlet的url-pattern
initServlet();
// 4. 将自身(ActionServlet)放入Servlet上下文中
getServletContext().setAttribute(Globals.ACTION_SERVLET_KEY, this);
// 5. 判断用户是否有自定义模块初始化功能,若存在注册到模块工厂
initModuleConfigFactory();
// 6. 初始化模块默认配置文件
ModuleConfig moduleConfig = initModuleConfig("", config);
// 7. 初始化自定义国际化信息
initModuleMessageResources(moduleConfig);
// 8. 初始化数据源
initModuleDataSources(moduleConfig);
// 9. 初始化struts插件
initModulePlugIns(moduleConfig);
// 10. 冻结配置信息
moduleConfig.freeze();
// 11. 初始化struts自定义模块配置信息
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();
}
// 12. 存储模块前缀,放入ServletContext
this.initModulePrefixes(this.getServletContext());
// 13. 销毁digester
this.destroyConfigDigester();
} catch (UnavailableException ex) {
throw ex;
} catch (Throwable t) {
// The follow error message is not retrieved from internal message
// resources as they may not have been able to have been
// initialized
log.error("Unable to initialize Struts ActionServlet due to an "
+ "unexpected exception or error thrown, so marking the "
+ "servlet as unavailable. Most likely, this is due to an "
+ "incorrect or missing library dependency.", t);
throw new UnavailableException(t.getMessage());
}
}
1. initInternal()
使用struts*.jar中的org/apache/struts/action/ActionResources*.properties文件初始化国际化信息。
1.1 ActionResources*仅包含默认英文和日文两类,因此初始化过程中发生的异常信息仅能使用以上两种语言进行提示;
1.2 方法初始化一个MessageResources对象,初始化流程和里面的内容稍后介绍,目前仅需要知道国际化信息存放到了internal中;
1.3 若国际化信息初始化失败(目前仅可能是国际化文件未找到),抛出UnavailableException
protected MessageResources internal = null;
protected void initInternal() throws ServletException {
try {
internal = MessageResources.getMessageResources(internalName);
} catch (MissingResourceException e) {
log.error("Cannot load internal resources from '" + internalName + "'",
e);
throw new UnavailableException
("Cannot load internal resources from '" + internalName + "'");
}
}
2. initOther()
struts的form bean使用beanUtil进行转换。initOther对null进行了指定处理,保证特殊类可以正确转换为form bean。
2.1 参数使用web.xml中actionServlet的参数convertNull;
2.2 struts为保证严格的兼容性,建议设置为true
protected void initOther() throws ServletException {
String value = null;
value = getServletConfig().getInitParameter("config");
if (value != null) {
config = value;
}
// Backwards compatibility for form beans of Java wrapper classes
// Set to true for strict Struts 1.0 compatibility
value = getServletConfig().getInitParameter("convertNull");
if ("true".equalsIgnoreCase(value)
|| "yes".equalsIgnoreCase(value)
|| "on".equalsIgnoreCase(value)
|| "y".equalsIgnoreCase(value)
|| "1".equalsIgnoreCase(value)) {
convertNull = true;
}
if (convertNull) {
ConvertUtils.deregister();
ConvertUtils.register(new BigDecimalConverter(null), BigDecimal.class);
ConvertUtils.register(new BigIntegerConverter(null), BigInteger.class);
ConvertUtils.register(new BooleanConverter(null), Boolean.class);
ConvertUtils.register(new ByteConverter(null), Byte.class);
ConvertUtils.register(new CharacterConverter(null), Character.class);
ConvertUtils.register(new DoubleConverter(null), Double.class);
ConvertUtils.register(new FloatConverter(null), Float.class);
ConvertUtils.register(new IntegerConverter(null), Integer.class);
ConvertUtils.register(new LongConverter(null), Long.class);
ConvertUtils.register(new ShortConverter(null), Short.class);
}
}
3. initServlet()
获取ActionServlet对应的url-pattern,即ActionServlet所能够拦截的请求类型,配置的值将存放在ServletContext中。
4. ActionServlet存储
将actionServlet存放到ServletContext中,需要注意的是ActionServlet是单例的。
5. initModuleConfigFactory()
struts的配置信息存放在ModuleConfig中(后文详细介绍),使用工厂模式进行创建ModuleConfigImpl对象,参数使用web.xml中ActionServlet的configFactory参数。这个配置不是必须的,因为Struts有默认的实现工厂类DefaultModuleConfigFactory。
6. initModuleConfig("", config)
initModuleConfig方法用于初始化struts配置文件,这块是struts初始化的核心内容。
protected ModuleConfig initModuleConfig(String prefix, String paths)
throws ServletException {
// :FIXME: Document UnavailableException? (Doesn't actually throw anything)
if (log.isDebugEnabled()) {
log.debug(
"Initializing module path '"
+ prefix
+ "' configuration from '"
+ paths
+ "'");
}
/*
* 获取工程类并创建ModuleConfig:
* 1. createFactory常见工厂类,默认是DefaultModuleConfigFactory,若用户在第5步中自定义了工厂类,将创建该工厂类的实例;
* 2. 使用抽象工厂模式,createModuleConfig是抽象方法,用于创建ModuleConfigImpl。DefaultModuleConfigFactory实现createModuleConfig方法并通过prefix区分并创建ModuleConfigImpl
*/
ModuleConfigFactory factoryObject = ModuleConfigFactory.createFactory();
ModuleConfig config = factoryObject.createModuleConfig(prefix);
// 初始化解析器
Digester digester = initConfigDigester();
// 若配置多个struts配置文件,遍历解析
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);
}
// 使用prefix进行区分,将对应的ModuleConfigImpl存放到ServletContext
getServletContext().setAttribute(
Globals.MODULE_KEY + config.getPrefix(),
config);
// 获取配置文件中的FormBean配置,如果是DynaActionForm进行初始化操作
FormBeanConfig fbs[] = config.findFormBeanConfigs();
for (int i = 0; i < fbs.length; i++) {
if (fbs[i].getDynamic()) {
fbs[i].getDynaActionFormClass();
}
}
return config;
}
7. initModuleMessageResources(moduleConfig)
moduleConfig是struts-config.xml配置信息存放的对象,也就是第5步中所说的ModuleConfigImpl,里面存放有国际化信息:
7.1 国际化信息可以是多个,每个国际化对应一个抽象类MessageResources;
7.2 struts对MessageResources的默认实现是PropertyMessageResources;
7.3 国际化信息内容在首次获取时写入HashMap;
protected void initModuleMessageResources(ModuleConfig config)
throws ServletException {
// 获取struts-config.xml配置的国际化信息
MessageResourcesConfig mrcs[] = config.findMessageResourcesConfigs();
for (int i = 0; i < mrcs.length; i++) {
if ((mrcs[i].getFactory() == null)
|| (mrcs[i].getParameter() == null)) {
continue;
}
if (log.isDebugEnabled()) {
log.debug(
"Initializing module path '"
+ config.getPrefix()
+ "' message resources from '"
+ mrcs[i].getParameter()
+ "'");
}
// 若配置了国际化工厂类,使用自定义工厂类,否则使用默认的PropertyMessageResourcesFactory
String factory = mrcs[i].getFactory();
MessageResourcesFactory.setFactoryClass(factory);
MessageResourcesFactory factoryObject =
MessageResourcesFactory.createFactory();
// 设置国际化配置信息
factoryObject.setConfig(mrcs[i]);
// 通过配置中的parameter参数创建国际化信息存储对象,parameter用于稳定国际化文件的配置
MessageResources resources =
factoryObject.createResources(mrcs[i].getParameter());
// 参数null用于设置国际化信息未找到时的显示内容,true-null false-返回包含key值、国际化等信息的字符串
resources.setReturnNull(mrcs[i].getNull());
// 设置转义参数:对单引号'做特殊处理
resources.setEscape(mrcs[i].isEscape());
// 将国际化信息以配置信息的key参数和prefix作为 key值存放到ServletContext
getServletContext().setAttribute(
mrcs[i].getKey() + config.getPrefix(),
resources);
}
}
8. initModuleDataSources(moduleConfig)
初始化数据源,初始化之后的数据源根据配置文件的prefix(配置文件前缀),放置到ServletContext。
protected void initModuleDataSources(ModuleConfig config) throws ServletException {
// :FIXME: Document UnavailableException?
if (log.isDebugEnabled()) {
log.debug("Initializing module path '" + config.getPrefix() +
"' data sources");
}
// PrintWriter的特殊实现类,将出现新行、flush()被调用、println被调用时将信息输出到ServletContext
ServletContextWriter scw =
new ServletContextWriter(getServletContext());
// 读取数据源配置信息
DataSourceConfig dscs[] = config.findDataSourceConfigs();
if (dscs == null) {
dscs = new DataSourceConfig[0];
}
// 使用FastHashMap存储数据源信息
dataSources.setFast(false);
for (int i = 0; i < dscs.length; i++) {
if (log.isDebugEnabled()) {
log.debug("Initializing module path '" + config.getPrefix() +
"' data source '" + dscs[i].getKey() + "'");
}
DataSource ds = null;
try {
ds = (DataSource)
RequestUtils.applicationInstance(dscs[i].getType());
BeanUtils.populate(ds, dscs[i].getProperties());
ds.setLogWriter(scw);
} catch (Exception e) {
log.error(internal.getMessage("dataSource.init", dscs[i].getKey()), e);
throw new UnavailableException
(internal.getMessage("dataSource.init", dscs[i].getKey()));
}
// 使用数据源key参数和prefix做key,存放到ServletContext中
getServletContext().setAttribute
(dscs[i].getKey() + config.getPrefix(), ds);
dataSources.put(dscs[i].getKey(), ds);
}
dataSources.setFast(true);
}
9. initModulePlugIns(ModuleConfig config)
初始化struts插件,在1.2.9版本中,struts提供4类插件:DigestingPlugIn、ModuleConfigVerifier、TilesPlugin、ValidatorPlugIn。
初始化后的插件信息将通过prefix进行区分后存放到ServletContext。
protected void initModulePlugIns
(ModuleConfig config) throws ServletException {
if (log.isDebugEnabled()) {
log.debug("Initializing module path '" + config.getPrefix() + "' plug ins");
}
// 获取配置文件中的插件信息并创建插件对象
PlugInConfig plugInConfigs[] = config.findPlugInConfigs();
PlugIn plugIns[] = new PlugIn[plugInConfigs.length];
// 使用prefix存放到ServletContext
getServletContext().setAttribute(Globals.PLUG_INS_KEY + config.getPrefix(), plugIns);
for (int i = 0; i < plugIns.length; i++) {
try {
plugIns[i] =
(PlugIn)RequestUtils.applicationInstance(plugInConfigs[i].getClassName());
BeanUtils.populate(plugIns[i], plugInConfigs[i].getProperties());
// Pass the current plugIn config object to the PlugIn.
// The property is set only if the plugin declares it.
// This plugin config object is needed by Tiles
try {
PropertyUtils.setProperty(
plugIns[i],
"currentPlugInConfigObject",
plugInConfigs[i]);
} catch (Exception e) {
// FIXME Whenever we fail silently, we must document a valid reason
// for doing so. Why should we fail silently if a property can't be set on
// the plugin?
/**
* Between version 1.138-1.140 cedric made these changes.
* The exceptions are caught to deal with containers applying strict security.
* This was in response to bug #15736
*
* Recommend that we make the currentPlugInConfigObject part of the PlugIn Interface if we can, Rob
*/
}
plugIns[i].init(this, config);
} catch (ServletException e) {
throw e;
} catch (Exception e) {
String errMsg =
internal.getMessage(
"plugIn.init",
plugInConfigs[i].getClassName());
log(errMsg, e);
throw new UnavailableException(errMsg);
}
}
}
10. moduleConfig.freeze()
将ModuleConfigImpl的configured属性设置为true。其他配置信息进行变更前都会判断configured属性,而ModuleConfigImpl没有提供将configured设置为false的方法,因为配置文件一旦冻结,不可修改。
11. 初始化struts自定义模块配置信息
根据web.xml中ActionServlet启动参数进行其他配置文件的初始化工作:
11.1 配置信息需要以config开头;
11.2 每一个配置文件对应一个MuduleConfigImpl,用过前缀进行区分,存放到ServletContext;
11.3 每个配置文件的信息都是独立的,作用于仅限当前配置文件;
11.4 每个配置文件在进行初始化操作后都会冻结;
12. this.initModulePrefixes(this.getServletContext());
将所有配置文件的前缀放入ServletContext;
13. this.destroyConfigDigester();
销毁解析器。