作者:zhyiwww zhyiwww@163.com |
分以下几步来理解: [1] Web 应用如何知道使用的是 Struts我们知道,在 Web 工程的 /WEB-INF/ 目录下面,我们有两个配置文件 Web.xml 和 Struts-config.xml. 一般情况下就只有一个 web.xml 文件,如果系统是用了 struts 技术的话,那么就会有 struts-config.xml 文件。 [2] 系统是如何启动和初始化 struts 的在 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 > < init-param > < param-name > debug </ param-name > < param-value > 3 </ param-value > </ init-param > < init-param > < param-name > detail </ param-name > < param-value > 3 </ param-value > </ init-param > < load-on-startup > 0 </ load-on-startup > </ servlet > < servlet > < servlet-name > startThread </ servlet-name > < servlet-class > com.cgogo.ypindex.StartThread </ servlet-class > < init-param > < param-name > startParam </ param-name > < param-value > 2 </ param-value > </ init-param > < load-on-startup > 1 </ load-on-startup > </ servlet > < servlet-mapping > < servlet-name > action </ servlet-name > < url-pattern > *.do </ url-pattern > </ servlet-mapping > 在 web.xml 中会配置 struts 的中央控制器类。 Web.xml 配置文件的初始化是由容器来实现载入和初始化的。如果你使用的是 tomcat 的话,那么就是由 tomcat 在启动的时候会载入此 web.xml 文件,也就为此 web 应用创建了一个 web Context, 此 context 也就是你 Web 应用的访问入口。 载入中央控制器 org.apache.struts.action.ActionServlet ,这是一个 Servlet, 那么,其就要进行 servlet 的初始化操作。此 action 是如何对 Struts 系统进行初始化的呢? [3] init()-- ActionServlet 如何初始化 Struts 系统?看一下 ActionServlet 的源代码: 我们知道,在一个 Servlet 中,在其启动的时候,首先要执行 init() 方法,那么我们先来看一下 actionServlet 的 init() 方法的源代码: /** * <p>Initialize this servlet. Most of the processing has been factored into * support methods so that you can override particular functionality at a * fairly granular level.</p> * * @exception ServletException if we cannot configure ourselves correctly */ public void init() throws ServletException { // Wraps the entire initialization in a try/catch to better handle // unexpected exceptions and errors to provide better feedback // to the developer try { (1) initInternal(); (2) initOther(); (3) initServlet(); getServletContext().setAttribute(Globals.ACTION_SERVLET_KEY, this); (4) initModuleConfigFactory(); // Initialize modules as needed ModuleConfig moduleConfig = initModuleConfig("", config); (5) initModuleMessageResources(moduleConfig); (6) initModuleDataSources(moduleConfig); (7) initModulePlugIns(moduleConfig); moduleConfig.freeze(); (8) 初始化配置参数 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(); } 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() 。 [4] initInternal()-- 内部资源文件的初始化
initInternal() 就是实现内部资源文件的初始化的,也就是转为 Struts 系统本身提供的以下错误信息提示等文字的国际化实现的。我们看一下源代码是如何实现的: /** * <p> Initialize our internal MessageResources bundle. </p> * * @exception ServletException if we cannot initialize these resources */ protected void initInternal() throws ServletException { // : FIXME : Document UnavailableException 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 根据配置文件的名字得到一个资源文件。 internalName 就是内部资源文件的名称,其在 actionServlet 中定义: /** * <p> The Java base name of our internal resources. </p> * @since Struts 1.1 */ protected String internalName = "org.apache.struts.action.ActionResources" ; 取得后的对象是一个 MessageResources 的对象,保存在 internal 中, /** * <p>The resources object for our internal resources.</p> */ protected MessageResources internal = null; 经过此初始化后, internal 就不在是 null 了。 至此就实现完成了内部资源文件的初始化。如果出现了异常的话,那么系统就捕捉到。 然后,系统就开始初始化其它的配置,即( 2 ) initOther(); [5] initOther()-- 如何初始化其他的配置的?/** * <p>Initialize other global characteristics of the controller servlet.</p> * * @exception ServletException if we cannot initialize these resources */ 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(); [6] initServlet()-- 如何初始化 servlet
这个初始化主要是初始化 servlet 的,哪些 servlet 呢?就是我们在 web.xml 中配置的那些需要在 web application 初始化时就栽入系统的 servlet 这是一个复杂的过程: 我的理解: 这部分代码就执行一次,仅在初始化的时候执行一次。 /** * <p>Initialize the servlet mapping under which our controller servlet * is being accessed. This will be used in the <code>&html:form></code> * tag to generate correct destination URLs for form submissions.</p> * * @throws ServletException if error happens while scanning web.xml */ protected void initServlet() throws ServletException { // Remember our servlet name // 这里保存当前的servlet名字,保存在actionServlet的servletName属性中 this.servletName = getServletConfig().getServletName(); // Prepare a Digester to scan the web application deployment descriptor
Digester digester = new Digester(); digester.push(this); digester.setNamespaceAware(true); digester.setValidating(false); // Register our local copy of the DTDs that we can find for (int i = 0; i < registrations.length; i += 2) { URL url = this.getClass().getResource(registrations[i+1]); if (url != null) { digester.register(registrations[i], url.toString()); } } // Configure the processing rules that we need digester.addCallMethod("web-app/servlet-mapping", "addServletMapping", 2); digester.addCallParam("web-app/servlet-mapping/servlet-name", 0); digester.addCallParam("web-app/servlet-mapping/url-pattern", 1); // Process the web application deployment descriptor if (log.isDebugEnabled()) { log.debug("Scanning web.xml for controller servlet mapping"); } // 取得当前的配置文件 InputStream input = getServletContext().getResourceAsStream("/WEB-INF/web.xml"); if (input == null) { log.error(internal.getMessage("configWebXml")); throw new ServletException(internal.getMessage("configWebXml")); } try { // 解析当前配置文件 digester.parse(input); } catch (IOException e) { log.error(internal.getMessage("configWebXml"), e); throw new ServletException(e); } catch (SAXException e) { log.error(internal.getMessage("configWebXml"), e); throw new ServletException(e); } finally { try { // 解析完毕,关闭输入 input.close(); } catch (IOException e) { log.error(internal.getMessage("configWebXml"), e); /** 如果有异常,当前部进行处理,而是留给他的调用者来处理。其实是当前的调用部分没有处理的能力。我们可以这样理解,假设你想在出现了这类异常异常的地方给用户一个提示,但是在我们封装功能实现的时候,我们并不知道谁会来调用,所以我们只有把异常抛出,让调用者自己去处理。 这一点也许不太好理解,不过,如果理解了,可能你就能够灵活的使用异常了。 */ throw new ServletException(e); } } // Record a servlet context attribute (if appropriate) if (log.isDebugEnabled()) { log.debug("Mapping for servlet ‘" + servletName + "‘ = ‘" + servletMapping + "‘"); } if (servletMapping != null) { getServletContext().setAttribute(Globals.SERVLET_KEY, servletMapping); } } [7] 初始化其他模块(1) 初始化工厂 getServletContext().setAttribute( Globals . ACTION_SERVLET_KEY , this ); initModuleConfigFactory(); (2) 初始化资源模块 // Initialize modules as needed ModuleConfig moduleConfig = initModuleConfig( "" , config ); initModuleMessageResources(moduleConfig); (3) 初始化数据源配置模块initModuleDataSources(moduleConfig); (4) 初始化 PlugIn 模块 initModulePlugIns(moduleConfig); moduleConfig.freeze(); [8] 初始化参数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(); } [9] 我也不知道做什么用的
this .initModulePrefixes( this .getServletContext()); this .destroyConfigDigester(); 以上就是Struts的初始化流程。 [10] 部分模块的详细实现:a. 工厂的初始化如何实现的: /** * <p>Initialize the factory used to create the module configuration.</p> * @since Struts 1.2 */ protected void initModuleConfigFactory(){ /** 这个部分的代码就是取得参数。 这个参数你可以自己扩展你的模块实现工厂。但是一般都没有自己去做。 所以一般都使用的默认的工厂初始化配置。 */ String configFactory = getServletConfig().getInitParameter("configFactory"); /** 下面的代码,只有你做了自己的配置才会有效。否则一般是不执行的。 */ if (configFactory != null) { /** 设置此工厂,并把其参数存入到 ModuleConfigFactory.factoryClass 属性中。 此部分可以看 ModuleConfigFactory 的代码。 ModuleConfigFactory 是一个 */ ModuleConfigFactory.setFactoryClass(configFactory); } } b. 资源模块式如何初始化的// 调用的部分 protected String config = "/WEB-INF/struts-config.xml"; ------------------------------------------------------------------ ModuleConfig moduleConfig = initModuleConfig("", config); initModuleMessageResources(moduleConfig); // 实现的部分: 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() + "‘"); } /** protected String factory = "org.apache.struts.util.PropertyMessageResourcesFactory"; 就是返回的这个值,如果你没有做其他的设置的话。 一般情况下,我们都用得是默认的 */ String factory = mrcs[i].getFactory(); /** 此处对每一个资源配置文件都回去创建一个工厂 */ MessageResourcesFactory.setFactoryClass(factory); MessageResourcesFactory factoryObject = MessageResourcesFactory.createFactory(); factoryObject.setConfig(mrcs[i]); MessageResources resources = factoryObject.createResources(mrcs[i].getParameter()); resources.setReturnNull(mrcs[i].getNull()); resources.setEscape(mrcs[i].isEscape()); /** 这一部分非常重要。 我们之所以能够直接调用,就是因为,初始化后,我们就把此 resources 放到了其对应的当前应用的属性值里面了。之后我们就可以直接调用了。 getServletContext().setAttribute( mrcs[i].getKey() + config.getPrefix(), resources); } } c. 数据模块是如何初始化的========================================================== // 调用部分 initModuleDataSources(moduleConfig); // 实现部分: /** * <p>Initialize the data sources for the specified module.</p> * * @param config ModuleConfig information for this module * * @exception ServletException if initialization cannot be performed * @since Struts 1.1 */ protected void initModuleDataSources(ModuleConfig config) throws ServletException { // :FIXME: Document UnavailableException? if (log.isDebugEnabled()) { log.debug("Initializing module path ‘" + config.getPrefix() + "‘ data sources"); } ServletContextWriter scw = new ServletContextWriter(getServletContext()); /** 因为你可能配置了多个数据源,所以此处返回的是一个数组 DataSourceConfig dscs[] = config.findDataSourceConfigs(); // 处理没有配置数据源的情况 if (dscs == null) { dscs = new DataSourceConfig[0]; } /** /** * <p>The JDBC data sources that has been configured for this module, * if any, keyed by the servlet context attribute under which they are * stored.</p> */ protected FastHashMap dataSources = new FastHashMap(); 这是一个加工过的 HashMap ,又不同的工作模式 */ 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())); } /** 这一个部分很重要。 把初始化后的数据源放入到 servlet 的属性中,所以我们才可以通过 struts 的对应属性直接访问。 protected String key = Globals.DATA_SOURCE_KEY; 所以,我们可以通过 Globals.DATA_SOURCE_KEY 属性的值来取得其配制后的数据源。 如果多个的话,可以通过数据源的参数 ID 来配置和调用。 */ getServletContext().setAttribute (dscs[i].getKey() + config.getPrefix(), ds); dataSources.put(dscs[i].getKey(), ds); } dataSources.setFast(true); } 至此,Struts系统初始化完毕。 |
|