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()); } }
使用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 + "'"); } }
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); } }
获取ActionServlet对应的url-pattern,即ActionServlet所能够拦截的请求类型,配置的值将存放在ServletContext中。
将actionServlet存放到ServletContext中,需要注意的是ActionServlet是单例的。
struts的配置信息存放在ModuleConfig中(后文详细介绍),使用工厂模式进行创建ModuleConfigImpl对象,参数使用web.xml中ActionServlet的configFactory参数。这个配置不是必须的,因为Struts有默认的实现工厂类DefaultModuleConfigFactory。
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; }
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); } }
初始化数据源,初始化之后的数据源根据配置文件的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); }
初始化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); } } }
将ModuleConfigImpl的configured属性设置为true。其他配置信息进行变更前都会判断configured属性,而ModuleConfigImpl没有提供将configured设置为false的方法,因为配置文件一旦冻结,不可修改。
根据web.xml中ActionServlet启动参数进行其他配置文件的初始化工作:
11.1 配置信息需要以config开头;
11.2 每一个配置文件对应一个MuduleConfigImpl,用过前缀进行区分,存放到ServletContext;
11.3 每个配置文件的信息都是独立的,作用于仅限当前配置文件;
11.4 每个配置文件在进行初始化操作后都会冻结;
将所有配置文件的前缀放入ServletContext;
销毁解析器。