Struts1.1部分源代码分析 一:说明 本文针对Struts1.1b3做分析,主要希望通过对源代码的分析阐述Struts1.1的工作方式。 本文不适合初学者参考,适合具有一定基于Struts开发的程序员参考。 下面的描述;里面将会对ActionServlet,RequestProcessor,ModuleConfig等几个类做一些 说明。以注释源代码的方式,说明取工作流程。 特别申明:Struts1.1代码版权属于Apache遵循The Apache Software License, Version 1.1. 本文版权属于孤魂一笑个人所有,任何个人或组织希望转载,请与我联系。并获得我的授权 方可转载。
二:ActionServlet分析 我们先来看一下使用Struts的配置文件。 action org.apache.struts.action.ActionServlet definitions-config /WEB-INF/tiles-defs.xml,/WEB-INF/tiles-tests-defs.xml,/WEB-INF/tiles-tutorial-defs.xml, /WEB-INF/tiles-examples-defs.xml definitions-debug 0 definitions-parser-details 0 definitions-parser-validate true config /WEB-INF/struts-config.xml config/examples /WEB-INF/struts-examples-config.xml config/test /WEB-INF/struts-tests-config.xml config/tutorial /WEB-INF/struts-tutorial-config.xml validate true debug 2 detail 2 application org.apache.struts.webapp.tiles.dev1-1.ApplicationResources 2
action *.do
接下来我们来看一下ActionServlet的具体使用 javax.servlet.http.HttpServlet | |-->org.apache.struts.action.ActionServlet 所以本质上ActionServlet是一个普通的servlet,负责处理.do为后缀的Http请求. servlet在执行doGet(),doPost(),之前先调用init(), 以下我们先分析一下init()方法 /** * 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.
* servlet初始化操作,注意初始化顺序 * @exception ServletException if we cannot configure ourselves correctly */ public void init() throws ServletException { //注意初始化的顺序 //Initialize our internal MessageResources bundle initInternal(); //Initialize other global characteristics of the controller servlet //处理一些全局变量的设置如:debug,detail等 initOther(); //Initialize the servlet mapping under which our controller servlet //is being accessed. This will be used in the &html:form> //tag to generate correct destination URLs for form submissions //主要是注册DTD文件以及解析web.xml关于ActionServlet的配置。如后缀名等. // 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); //initServlet()的上面一段将把Struts默认的后缀名从web.xml中解析得到 //也就是web.xml中的如下配置: // //action //*.do //默认以.do结尾的请求都将由Struts来处理,你可以自己修改 // initServlet(); // Initialize modules as needed //在Attribute中保存类实例 getServletContext().setAttribute(Globals.ACTION_SERVLET_KEY, this); //根据配置文件生成ModuleConfig,这是很重要的一步.下面会专门分析 //在tiles的配置中先解析注释为"Mark 0"的一个配置文件:/WEB-INF/struts-config.xml //使用initModuleConfig方法解析XML文件. //参数为prefix:"",paths:"/WEB-INF/struts-config.xml" ModuleConfig moduleConfig = initModuleConfig("", config); //初始化Message initModuleMessageResources(moduleConfig); //初始化JDBC DataSource initModuleDataSources(moduleConfig); //初始化PlunIn initModulePlugIns(moduleConfig); moduleConfig.freeze(); //在Struts1.1以后可以使用多个配置文件,在解析完默认的配置文件也就是上面提到的 //注释为"Mark 0"的一个配置文件:/WEB-INF/struts-config.xml后解析其他的配置文件 Enumeration names = getServletConfig().getInitParameterNames(); //依次解析注释为"Mark 1"、"Mark 2"、"Mark 3"对应配置文件 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(); } destroyConfigDigester();
} /** * 此方法使用Digester解析XML,关于使用Digester的介绍参看我的另外一篇文章 * Initialize the application configuration information for the * specified module. * * @param prefix Module prefix for this module * @param paths Comma-separated list of context-relative resource path(s) * for this modules's configuration resource(s) * * @exception ServletException if initialization cannot be performed * @since Struts 1.1 */ protected ModuleConfig initModuleConfig (String prefix, String paths) throws ServletException {
if (log.isDebugEnabled()) { log.debug("Initializing module path '" + prefix + "' configuration from '" + paths + "'"); }
// Parse the configuration for this module ModuleConfig config = null; InputStream input = null; String mapping = null; try { //工厂方法创建ModuleConfig标记为:prefix ModuleConfigFactory factoryObject = ModuleConfigFactory.createFactory(); config = factoryObject.createModuleConfig(prefix);
// Support for module-wide ActionMapping type override mapping = getServletConfig().getInitParameter("mapping"); if (mapping != null) { config.setActionMappingClass(mapping); }
// Configure the Digester instance we will use //得到解析XML的digester Digester digester = initConfigDigester();
// Process each specified resource path 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 = paths.substring(comma + 1); } else { //当前只存在一个 path = paths.trim(); //没有剩余,下次循环根据上面一句将会在唯一的循环退出点"point break" //退出循环 paths = ""; } //全部解析完成跳出循环 //point break if (path.length() < 1) { break; } //根据文件名取文件 URL url = getServletContext().getResource(path); InputSource is = new InputSource(url.toExternalForm()); input = getServletContext().getResourceAsStream(path); is.setByteStream(input); digester.parse(is); //全局变量的形式把解析生成的ModuleConfig实例保存起来 //如"Mark 1"处标记的"config/examples", //key为:"org.apache.struts.action.MODULEexamples" //Globals.MODULE_KEY值为:org.apache.struts.action.MODULE getServletContext().setAttribute (Globals.MODULE_KEY + prefix, config); input.close(); }
} catch (Throwable t) { log.error(internal.getMessage("configParse", paths), t); throw new UnavailableException (internal.getMessage("configParse", paths)); } finally { if (input != null) { try { input.close(); } catch (IOException e) { ; } } }
// Force creation and registration of DynaActionFormClass instances // for all dynamic form beans we wil be using //根据ModuleConfig实例得到配置的FormBean的配置 //注意:因为在Struts整个运行当中FormBean的实例要在Action的实例创建之前先创建 //因为Action执行perform(1.1以前),execute(1.1)需要使用到FormBean FormBeanConfig fbs[] = config.findFormBeanConfigs(); for (int i = 0; i < fbs.length; i++) { if (fbs[i].getDynamic()) { DynaActionFormClass.createDynaActionFormClass(fbs[i]); } }
// Special handling for the default module (for // backwards compatibility only, will be removed later) //下面是生成一些实例 if (prefix.length() < 1) { defaultControllerConfig(config); defaultMessageResourcesConfig(config); defaultFormBeansConfig(config); defaultForwardsConfig(config); defaultMappingsConfig(config); }
// Return the completed configuration object //config.freeze(); // Now done after plugins init return (config);
}
到此初始化工作基本结束,下面将处理具体的Http请求。在Servlet协议中所有的post方法将调用 以下方法来处理 public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {} get方法也调用类似的方法来处理
在Struts中post,get方法都调用同一个方法 process(request, response);来处理具体的请求 如下: /** * 对每一个提交过来的Action进行处理 * Perform the standard request processing for this request, and create * the corresponding response. * * @param request The servlet request we are processing * @param response The servlet response we are creating * * @exception IOException if an input/output error occurs * @exception ServletException if a servlet exception is thrown */ protected void process(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { //设置或删除Attribute RequestUtils.selectModule(request, getServletContext()); //具体的处理交给RequestProcessor去处理HttpRequest,HttpResponse //这是一个很典型的设计模式 //下面我们将详细来分析RequestProcessor,很容易理解Struts的运行方式 getRequestProcessor(getModuleConfig(request)).process(request, response); }
三:RequestProcessor分析 通过前面的分析我们知道Struts中对HttpRequest,HttpResponse的处理都交给RequestProcessor 的process()方法来处理。下面我们来看看process方法 /** * Process an HttpServletRequest and create the * corresponding HttpServletResponse. * * @param request The servlet request we are processing * @param response The servlet response we are creating * * @exception IOException if an input/output error occurs * @exception ServletException if a processing exception occurs */ public void process(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
// Wrap multipart requests with a special wrapper //如果是upload,返回一个MultipartRequestWrapper() request = processMultipart(request); // Identify the path component we will use to select a mapping //对request进行分析得到提交过来的Form Action String path = processPath(request, response); if (path == null) { return; } if (log.isInfoEnabled()) { log.info("Processing a '" + request.getMethod() + "' for path '" + path + "'"); }
// Select a Locale for the current user if requested //本地化处理 processLocale(request, response);
// Set the content type and no-caching headers if requested processContent(request, response); //设置Cache不保存 processNoCache(request, response);
// General purpose preprocessing hook if (!processPreprocess(request, response)) { return; }
// Identify the mapping for this request //得到path以后,根据配置文件(struts-config.xml)的相关配置来得到一个 //ActionMapping的实例 //ActionMapping继承ActionConfig //仔细看一下ActionMapping的代码就能发现: //下面的一段将解析影射一个ActionMapping的实例 //在ActionServlet在初始化的时候实际上已经把所有的ActionMapping的实例 //都已经创建好了。processMapping方法实际上是从Attribute中去得到已经 //保存好的ActionMapping的实例,可以理解为在Tomcat启动的时候已经 //把所有的ActionMapping保存在Attribute里面。所以在Tomcat启动的时候 //比较慢,如果Struts-config.xml有问题启动就会出错。 /*** type="org.apache.struts.webapp.tiles.test.TestActionTileAction"> **/ //****注意得到创建实例的顺序 //ActionMapping实例已经创建好了,现在从Atribute中取到 ActionMapping mapping = processMapping(request, response, path); if (mapping == null) { return; }
// Check for any role required to perform this action if (!processRoles(request, response, mapping)) { return; }
// Process any ActionForm bean related to this request //根据mapping得到ActionForm的实例。 //同名ActionForm在系统中只会创建一次。 ActionForm form = processActionForm(request, response, mapping); //压栈 processPopulate(request, response, form, mapping); //处理校验,调用ActionForm的validate方法 //假如出错将会返回到前一页面 //也就是说在Action还没有创建之前就将做校验 if (!processValidate(request, response, form, mapping)) { return; }
// Process a forward or include specified by this mapping if (!processForward(request, response, mapping)) { return; } if (!processInclude(request, response, mapping)) { return; }
// Create or acquire the Action instance to process this request //在得到ActionMapping、ActionForm的实例后接下来得到Action实例 //实例如果已经创建从Map里面去取如果没有创建一个并保存在Map里面 //对保存Action实例的Map 实现线程同步 Action action = processActionCreate(request, response, mapping); if (action == null) { return; }
// Call the Action instance itself //在ActionMapping、ActionForm、Action实例创建好以后 //调用Action的execute()方法得到一个ActionForward //因为所有的可执行Action都必须有override Action的execute()/perform()方法 ActionForward forward = processActionPerform(request, response, action, form, mapping);
// Process the returned ActionForward instance //Action已经正常执行,执行结束后将返回到另外一个页面 processActionForward(request, response, forward);
}
仔细阅读上面的代码,要特别注意ActionMapping、ActionForm、Action的实例是依次创建的。 创建完以后才去执行Action的execute()方法。为什么要依次创建ActionMapping、ActionForm 、Action??????
四:ModuleConfig分析 现在我们很有必要了解一下ModuleConfig这个类,因为太多地方用到了。 实际上ModuleConfig是一个接口有一个实现。org.apache.struts.config.impl.ModuleConfigImpl 具体实现我就没有不要去分析了。我们来看看这个接口。 package org.apache.struts.config;
/** * The collection of static configuration information that describes a * Struts-based module. Multiple modules are identified by * a prefix at the beginning of the context * relative portion of the request URI. If no module prefix can be * matched, the default configuration (with a prefix equal to a zero-length * string) is selected, which is elegantly backwards compatible with the * previous Struts behavior that only supported one module. * * @author Rob Leland * @version $Revision: 1.2 $ $Date: 2002/12/22 05:31:14 $ * @since Struts 1.1 */ public interface ModuleConfig { /** * Has this module been completely configured yet. Once this flag * has been set, any attempt to modify the configuration will return an * IllegalStateException. */ boolean getConfigured();
/** * The controller configuration object for this module. */ ControllerConfig getControllerConfig(); /** * The controller configuration object for this module. * @param cc The controller configuration object for this module. */
void setControllerConfig(ControllerConfig cc);
/** * The prefix of the context-relative portion of the request URI, used to * select this configuration versus others supported by the controller * servlet. A configuration with a prefix of a zero-length String is the * default configuration for this web module. */ String getPrefix();
/** * The prefix of the context-relative portion of the request URI, used to * select this configuration versus others supported by the controller * servlet. A configuration with a prefix of a zero-length String is the * default configuration for this web module. */ public void setPrefix(String prefix); /** * The default class name to be used when creating action mapping * instances. */ String getActionMappingClass(); /** * The default class name to be used when creating action mapping * instances. * @param actionMappingClass default class name to be used when creating action mapping * instances. */
void setActionMappingClass(String actionMappingClass);
/** * Add a new ActionConfig instance to the set associated * with this module. * * @param config The new configuration instance to be added * * @exception java.lang.IllegalStateException if this module configuration * has been frozen */ void addActionConfig(ActionConfig config);
/** * Add a new DataSourceConfig instance to the set associated * with this module. * * @param config The new configuration instance to be added * * @exception java.lang.IllegalStateException if this module configuration * has been frozen */ void addDataSourceConfig(DataSourceConfig config);
/** * Add a new ExceptionConfig instance to the set associated * with this module. * * @param config The new configuration instance to be added * * @exception java.lang.IllegalStateException if this module configuration * has been frozen */ void addExceptionConfig(ExceptionConfig config);
/** * Add a new FormBeanConfig instance to the set associated * with this module. * * @param config The new configuration instance to be added * * @exception java.lang.IllegalStateException if this module configuration * has been frozen */ void addFormBeanConfig(FormBeanConfig config);
/** * Add a new ForwardConfig instance to the set of global * forwards associated with this module. * * @param config The new configuration instance to be added * * @exception java.lang.IllegalStateException if this module configuration * has been frozen */ void addForwardConfig(ForwardConfig config);
/** * Add a new MessageResourcesConfig instance to the set * associated with this module. * * @param config The new configuration instance to be added * * @exception IllegalStateException if this module configuration * has been frozen */ void addMessageResourcesConfig(MessageResourcesConfig config);
/** * Add a newly configured {@link org.apache.struts.config.PlugInConfig} instance to the set of * plug-in Actions for this module. * * @param plugInConfig The new configuration instance to be added */ void addPlugInConfig(PlugInConfig plugInConfig);
/** * Return the action configuration for the specified path, if any; * otherwise return null. * * @param path Path of the action configuration to return */ ActionConfig findActionConfig(String path);
/** * Return the action configurations for this module. If there are * none, a zero-length array is returned. */ ActionConfig[] findActionConfigs();
/** * Return the data source configuration for the specified key, if any; * otherwise return null. * * @param key Key of the data source configuration to return */ DataSourceConfig findDataSourceConfig(String key);
/** * Return the data source configurations for this module. If there * are none, a zero-length array is returned. */ DataSourceConfig[] findDataSourceConfigs();
/** * Return the exception configuration for the specified type, if any; * otherwise return null. * * @param type Exception class name to find a configuration for */ ExceptionConfig findExceptionConfig(String type);
/** * Return the exception configurations for this module. If there * are none, a zero-length array is returned. */ ExceptionConfig[] findExceptionConfigs();
/** * Return the form bean configuration for the specified key, if any; * otherwise return null. * * @param name Name of the form bean configuration to return */ FormBeanConfig findFormBeanConfig(String name);
/** * Return the form bean configurations for this module. If there * are none, a zero-length array is returned. */ FormBeanConfig[] findFormBeanConfigs();
/** * Return the forward configuration for the specified key, if any; * otherwise return null. * * @param name Name of the forward configuration to return */ ForwardConfig findForwardConfig(String name);
/** * Return the form bean configurations for this module. If there * are none, a zero-length array is returned. */ ForwardConfig[] findForwardConfigs();
/** * Return the message resources configuration for the specified key, * if any; otherwise return null. * * @param key Key of the data source configuration to return */ MessageResourcesConfig findMessageResourcesConfig(String key);
/** * Return the message resources configurations for this module. * If there are none, a zero-length array is returned. */ MessageResourcesConfig[] findMessageResourcesConfigs();
/** * Return the configured plug-in actions for this module. If there * are none, a zero-length array is returned. */ PlugInConfig[] findPlugInConfigs();
/** * Freeze the configuration of this module. After this method * returns, any attempt to modify the configuration will return * an IllegalStateException. */ void freeze();
/** * Remove the specified action configuration instance. * * @param config ActionConfig instance to be removed * * @exception java.lang.IllegalStateException if this module configuration * has been frozen */ void removeActionConfig(ActionConfig config);
/** * Remove the specified exception configuration instance. * * @param config ActionConfig instance to be removed * * @exception java.lang.IllegalStateException if this module configuration * has been frozen */ void removeExceptionConfig(ExceptionConfig config);
/** * Remove the specified data source configuration instance. * * @param config DataSourceConfig instance to be removed * * @exception java.lang.IllegalStateException if this module configuration * has been frozen */ void removeDataSourceConfig(DataSourceConfig config);
/** * Remove the specified form bean configuration instance. * * @param config FormBeanConfig instance to be removed * * @exception java.lang.IllegalStateException if this module configuration * has been frozen */ void removeFormBeanConfig(FormBeanConfig config);
/** * Remove the specified forward configuration instance. * * @param config ForwardConfig instance to be removed * * @exception java.lang.IllegalStateException if this module configuration * has been frozen */ void removeForwardConfig(ForwardConfig config);
/** * Remove the specified message resources configuration instance. * * @param config MessageResourcesConfig instance to be removed * * @exception java.lang.IllegalStateException if this module configuration * has been frozen */ void removeMessageResourcesConfig(MessageResourcesConfig config); }
上面的注释已经非常清晰了。我就不去浪费大家的时间了,再想仔细看就去看他的实现。 其实主要是对HashMap()的处理。
五:ActionMapping分析 最后我们实现很有必要看一下ActionMapping,因为你如果想清楚的了解Struts-config.xml 这个配置文件的作用,你应该要知道ActionMapping 前面已经说过ActionMapping继承ActionConfig 以下就是ActionMapping加的四个方法。 /** * Find and return the ExceptionConfig instance defining * how exceptions of the specified type should be handled. This is * performed by checking local and then global configurations for the * specified exception's class, and then looking up the superclass chain * (again checking local and then global configurations). If no handler * configuration can be found, return null. * * @param type Exception class for which to find a handler * @since Struts 1.1 */ public ExceptionConfig findException(Class type) {
}
/** * Find and return the ForwardConfig instance defining * how forwarding to the specified logical name should be handled. This is * performed by checking local and then global configurations for the * specified forwarding configuration. If no forwarding configuration * can be found, return null. * * @param name Logical name of the forwarding instance to be returned */ public ActionForward findForward(String name) {
}
/** * Return the logical names of all locally defined forwards for this * mapping. If there are no such forwards, a zero-length array * is returned. */ public String[] findForwards() { }
/** * Create (if necessary) and return an {@link ActionForward} that * corresponds to the input property of this Action. * * @since Struts 1.1b2 */ public ActionForward getInputForward() {
}
还是看以下他的基类ActionConfig吧 public class ActionConfig implements Serializable {
/** * Has configuration of this component been completed? */ protected boolean configured = false;
/** * The set of exception handling configurations for this * action, if any, keyed by the type property. */ protected HashMap exceptions = new HashMap();
/** * The set of local forward configurations for this action, if any, * keyed by the name property. */ protected HashMap forwards = new HashMap();
/** * The module configuration with which we are associated. */ protected ModuleConfig moduleConfig = null;
/** * The request-scope or session-scope attribute name under which our * form bean is accessed, if it is different from the form bean's * specified name. */ protected String attribute = null;
/** * Context-relative path of the web application resource that will process * this request via RequestDispatcher.forward(), instead of instantiating * and calling the Action class specified by "type". * Exactly one of forward, include, or * type must be specified. */ protected String forward = null;
/** * Context-relative path of the web application resource that will process * this request via RequestDispatcher.include(), instead of instantiating * and calling the Action class specified by "type". * Exactly one of forward, include, or * type must be specified. */ protected String include = null;
/** * Context-relative path of the input form to which control should be * returned if a validation error is encountered. Required if "name" * is specified and the input bean returns validation errors. */ protected String input = null;
/** * Fully qualified Java class name of the * MultipartRequestHandler implementation class used to * process multi-part request data for this Action. */ protected String multipartClass = null;
/** * Name of the form bean, if any, associated with this Action. */ protected String name = null;
/** * General purpose configuration parameter that can be used to pass * extra iunformation to the Action instance selected by this Action. * Struts does not itself use this value in any way. */ protected String parameter = null;
/** * Context-relative path of the submitted request, starting with a * slash ("/") character, and omitting any filename extension if * extension mapping is being used. */ protected String path = null;
/** * Prefix used to match request parameter names to form ben property * names, if any. */ protected String prefix = null;
/** * Comma-delimited list of security role names allowed to request * this Action. */ protected String roles = null;
/** * Identifier of the scope ("request" or "session") within which * our form bean is accessed, if any. */ protected String scope = "session";
/** * Suffix used to match request parameter names to form bean property * names, if any. */ protected String suffix = null;
/** * Fully qualified Java class name of the Action class * to be used to process requests for this mapping if the * forward and include properties are not set. * Exactly one of forward, include, or * type must be specified. */ protected String type = null;
/** * Should the validate() method of the form bean associated * with this action be called? */ protected boolean validate = true; }
其实ActionConfig是一个很典型的ValueObject.所以其他的get/set方法我就不写出来了。 看这个代码一定要和struts-config.xml一起来看,根据struts-config.xml去找找 每一段配置文件最终要生成一个ActionConfig,他们之间的对应关系。 如果你想扩展Struts,ActionMapping估计你一定要修改。还有ActionServlet你也要修改。
六:结束语 分析了一些代码下面做一些概述。先来整体的了解一下Struts的工作流程. 在实现一个基于Struts的运用之前我们首先是做环境设置,Struts正常工作需要至少两个 配置文件web.xml,struts-config.xml. web.xml告诉App Server所有以.do结尾的请求最终提交给ActionServlet去处理。 2就规定ActionServlet是在App Server启动的时候 创建的并且一直存在。 ActionServlet在创建的时候会做如下的工作: 保存一些后面需要使用的实例在Attribute(内存)里面。 根据web.xml的配置解析struts-config.xml文件。 根据struts-config.xml的配置生成ActionMapping实例并且保存。 ActionServlet在生命周期就一直等待Http 请求 每一个.do结尾的Http 请求都由ActionServlet先截获然后根据请求路径得到具体调用那 一个Action去处理,在这之前生成、处理ActionForm。具体知道那一个Action去处理请求 后调用Action的execute()/perform()处理完成,返回。
|