Struct1
推荐好文:
struts1工作原理:
https://blog.csdn.net/cs_fei/article/details/9765989
以"事件驱动"的方式处理xml:
https://www.cnblogs.com/chenpi/p/6930730.html
struts1插件:
https://blog.csdn.net/qq_22498277/article/details/51155998
struts1使用FormBean获取请求参数:
https://zhengbocong.iteye.com/blog/1933064
前言
源自Apache Struts1结束生命新闻稿:
Struts 1始于2000年,旨在创建比纯Java Server Pages(JSP)利用率更高的开发体验,很快成为基于Java的Web应用程序开发的事实标准。 许多公司采用Struts 1作为战略平台,即使在JSF作为Web应用程序开发的标准化Java EE框架引入之后仍然保留了它。 在21世纪初期,基于Java的Web技术领域的大多数工作产品都带有Struts 1作为必备技能。 即便如今,许多重要的网站和基于Web的用户界面都依赖于Struts 1技术。 此外值得注意的是,许多后来引入的Web框架,如Spring MVC或WebWork,都受到Struts 1的启发。
Struts 1在2008年12月发布了它的最后一个版本 - 版本1.3.10。与此同时,Struts社区专注于推动Struts 2框架向前发展,截至本文撰写时发布了多达23个版本。 考虑到这一点,宣布Struts 1 EOL只是官方声明,我们一段时间以来一直缺乏志愿者支持,用户在项目中使用Struts 1时不应该依赖于正确维护的框架状态。
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd"> <web-app> <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>chainConfig</param-name> <param-value>/WEB-INF/custom-chain.xml,org/apache/struts/chain/chain-config.xml</param-value> </init-param> <init-param> <param-name>debug</param-name> <param-value>2</param-value> </init-param> <load-on-startup>2</load-on-startup> </servlet> <servlet-mapping> <servlet-name>action</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping> </web-app>
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration1.1//EN" "http://jakarta.apache.org/struts/dtds/struts-config_1_3.dtd"> <struts-config> <global-exceptions /> <!-- global-forwards元素用于定义在整个应用程序内的全局转发。在该元素内可定义多个forward子元素 --> <global-forwards> <forward name="notFound" path="/404.jsp"></forward> <forward name="serverError" path="/505.jsp"></forward> </global-forwards> <!-- 配置bean的集合 --> <form-beans> <!-- 配置form-bean user:formBean的名字,需要和action中的name一致 type:该javaBean的类路径 --> <form-bean name="user" type="com.cong.bean.User"></form-bean> </form-beans> <action-mappings> <!-- 在action中使用name来指定使用该bean来存放请求参数 --> <action name="user" path="/todo" type="com.cong.action.LoginAction"> <forward name="success" path="/WEB-INF/jsp/login/somepage2.jsp" /> </action> </action-mappings> <message-resources parameter="com.custom.ApplicationResources" /> <plug-in className="org.apache.struts.validator.ValidatorPlugIn"> <set-property property="pathnames" value="/org/apache/struts/validator/validator-rules.xml, /WEB-INF/validation-message.xml" /> </plug-in> </struts-config>
/** * <p>Commons Logging instance.</p> * * @since Struts 1.1 */ protected static Log log = LogFactory.getLog(ActionServlet.class); // ----------------------------------------------------- Instance Variables /** * 默认模块的配置资源,多个则以逗号分隔 */ protected String config = "/WEB-INF/struts-config.xml"; /** * 默认公共链的配置,多个则以逗号分隔 */ protected String chainConfig = "org/apache/struts/chain/chain-config.xml"; /** * 从struts配置文件中生成moduleconfig对象(模块配置器,它能解析xml文件, * 并以“事件驱动”方式来处理xml, * 你必须先阅读一下上面推荐的第二篇好文) */ protected Digester configDigester = null; /** * 该标志表示请求Java包装类类型的表单bean属性是否向后兼容转换。 */ protected boolean convertNull = false; /** * 内部资源的资源对象。(封装了一些提示信息的对象) */ protected MessageResources internal = null; /** * 我们内部资源对象所有读取的文件(该文件内部定义了一系列的提示信息) */ protected String internalName = "org.apache.struts.action.ActionResources"; /** * 配置文件DTD版本的公共标识符集和相应的资源名 * 列表中必须有偶数个字符串,两个为一对 * * 使用Digester的register方法,让Digester在遇到DOCTYPE声明时,使用本地的dtd * 而不是从网上获取。所以在这里定义了本地的dtd */ protected String[] registrations = { "-//Apache Software Foundation//DTD Struts Configuration 1.0//EN", "/org/apache/struts/resources/struts-config_1_0.dtd", "-//Apache Software Foundation//DTD Struts Configuration 1.1//EN", "/org/apache/struts/resources/struts-config_1_1.dtd", "-//Apache Software Foundation//DTD Struts Configuration 1.2//EN", "/org/apache/struts/resources/struts-config_1_2.dtd", "-//Apache Software Foundation//DTD Struts Configuration 1.3//EN", "/org/apache/struts/resources/struts-config_1_3.dtd", "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN", "/org/apache/struts/resources/web-app_2_3.dtd" }; /** * Web应用程序部署描述符即:web.xml中映射到的URL模式 * */ protected String servletMapping = null; // :FIXME: - multiples? /** * 在Web应用程序部署描述符中注册时使用的servlet名称。 */ protected String servletName = null;
/** * 初始化这个servlet。大多数处理都被分解为支持方法, * 这样您就可以在相当精细的级别上覆盖特定的功能。 * @throws ServletException if we cannot configure ourselves correctly */ public void init() throws ServletException { final String configPrefix = "config/"; final int configPrefixLength = configPrefix.length() - 1; try { /** * 读取内部资源包,也就是一些常用的提示信息,然后封装到 * 上面全局的internal对象中。在initServlet中使用较为频繁,可参见。 */ initInternal(); /** * 初始化控制器servlet的其他全局特性。 * 主要内容: * 1、初始化全局config参数。先读取web.xml中配置在ActionServlet * 里的config参数值,如果没配置则默认使用全局变量中的config * 2、请求Java包装类类型的表单bean属性是否向后兼容转换?如果 * ActionServlet里的convertNull配置了true,则使用向后兼容 * 策略包装表单bean,否则不使用。 */ initOther(); /** * 初始化我们的ActionServlet,主要是初始化ActionServlet的servletMapping * 主要内容: * 1、创建一个Digerster对象,注意不是全局的那个。digester是用来 * 干什么的,简单点说就是可以像“事件驱动”那样在读取xml时注册一 * 些行为,详细看见推荐好文中的第二篇 * 2、 digester将当前的ActionServlet放入栈中,然后根据全 * 局registrations去读本地的dtd文件,根据dtd进行xml合法性校验 * 3、digester对象给栈中对象注册一些“事件”,在这里它给ActionServlet * 注册了一个方法即addServletMapping和一个 * 标签web-app/servlet-mapping(这是一个xpath) * 4、读取web.xml获取流对象传递给digester对象,digester对象解析 * web.xml,当其读取到web-app下的servlet-mapping标签时,会 * 触发给栈中ActionServlet对象注册的方法即addServletMapping。 * 虽然每读取一个Servlet就会触发这个方法,但是你看一下 * ActionServlet的addServletMapping方法即可发现,它只将自己 * 赋值给全局的servletMapping,其它的都忽略了。 * */ initServlet(); /** * 分析chainconfig init参数指定的配置文档,以配置在该应用程序 * 的catalogFactory实例中注册的默认org.apache.commons.chain.catalog。 * * 主要内容: * 1、首先你要知道chain主要是干什么的?chain表示着拦截链路(strut称之 * 为责任链),也就是说在这个方法里初始化一些你定义的拦截器, * 比如说:字符编码拦截。 * 2、还是像之前那样,先读取web.xml中ActionServlet下的chainConfig属 * 性,如果不存在,就默认读取全局chainConfig里的配置,初始化其中的责 * 任链,如果存在就初始化你配置的chainconfig文件。你可以看一下全局 * 责任链配置文件,它里面配置了异常处理责任链、国际化责任链、缓 * 存、contenType、formBeans、action、mapping等等 */ initChain(); getServletContext().setAttribute(Globals.ACTION_SERVLET_KEY, this); /** * 初始化用于创建模块配置的工厂。在init上面的注释中就已经说明“大多数处理都 * 被分解为支持的方法”,也就是被分解为了一个个模块。你可以在相当精细的级别 * 上覆盖特定的功能。 * 主要内容: * 1、读取web.xml中ActionServlet下的configFactory属性,如果配置了就 * 使用该值初始化工厂,否则不创建该工厂。 * 2、该工厂是用来创建ModuleConfig的,在initModuleConfig方法中有说明。 */ initModuleConfigFactory(); // Initialize modules as needed /** * 初始化需要的模块。 * initModuleConfig方法,用来初始化指定模块配置信息 * 主要内容: * 1、拿到上面initModuleConfigFaction创建的工厂,如果不存在则重新 * 创建一个默认的工厂。 * 2、根据config参数使用工厂去创建一个ModuleConfig对象,这个 * ModuleConfig参见org.apache.struts.config.impl.ModuleConfigImpl * 简单点说这个对象就是:Struts模块的静态配置信息的集合。 * 接下来就是初始化这个ModuleConfig对象封装的模块了 */ ModuleConfig moduleConfig = initModuleConfig("", config); /** * 初始化指定模块的应用程序消息资源。 * 虽然在initInternal方法中有全局的消息资源,但是如果用户想封装一些 * 自己模块的提示性信息,就可能需要自定义一些消息资源去初 * 始化了,这个方法就是用来初始化用户自己的消息资源的。它将用户自己 * 的消息资源封装在上下文环境的属性中,即在上下文环境都可使用。资源 * 写法可参见initInternal方法初始化的那个配置文件。 */ initModuleMessageResources(moduleConfig); /** * 初始化指定模块的插件。比如表单验证插件... * 关于struts1使用插件请阅读推荐好文中的struts1插件 */ initModulePlugIns(moduleConfig); /** * 初始化指定模块的表单bean。 * 可参见在struts-config.xml文件中配置的那个form-bean一样 * formbean的作用: * 1、在jsp页面中,当提交多个请求参数的时候,可以定义一个bean来获取这些 * 参数,struts会将这些请求参数自动添加到bean中。 * 更多可阅读推荐好文中:struts1使用FormBean获取请求参数。 * */ initModuleFormBeans(moduleConfig); /** * 初始化指定模块的转发。 * 初始化struts-config.xml里的global-forwards配置 */ initModuleForwards(moduleConfig); /** * 初始化指定模块的异常处理程序 * 初始化struts-config.xml里的global-exceptions配置 */ initModuleExceptionConfigs(moduleConfig); /** * 初始化指定模块的操作配置。 * 初始化struts-config.xml里的action配置 */ initModuleActions(moduleConfig); // 冻结模块的配置,不允许再更改了。 moduleConfig.freeze(); /** * 下面这些和上面基本都是一致的,只是它读取的是config目录下 * 的struts-confix.xml */ Enumeration names = getServletConfig().getInitParameterNames(); while (names.hasMoreElements()) { String name = (String) names.nextElement(); if (!name.startsWith(configPrefix)) { continue; } String prefix = name.substring(configPrefixLength); moduleConfig = initModuleConfig(prefix, getServletConfig().getInitParameter(name)); initModuleMessageResources(moduleConfig); initModulePlugIns(moduleConfig); initModuleFormBeans(moduleConfig); initModuleForwards(moduleConfig); initModuleExceptionConfigs(moduleConfig); initModuleActions(moduleConfig); moduleConfig.freeze(); } // 保存模块前缀的字符串 this.initModulePrefixes(this.getServletContext()); // 销毁全局configDigester 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()); } }
根据配置文件初始化一些全局变量信息(配置文件名,提示信息等等)
初始化控制器ActionServlet(“事件驱动”方式读取servlet节点,注册ActionServlet的mapping)
初始化一些用户自定义的责任链(拦截器)或全局配置文件里的责任链
模块化管理,创建模块对象ModuleConfig,将struts-config配置信息封装其内,模块化的好处就是你可以细粒度的扩展或更改一些模块以满足你的需求。
初始化模块内容:用户自定义提示消息或全局、一些插件(表单验证啊等等)、FormBean、异常处理(比如:全局异常处理模块)、Action
在init方法里做的事情都在上面的注释里了。当ActionServlet启动后,调用init方法,接下来就是处理客户端发来的请求了,在ActionServlet中有两个方法涵盖了基本请求:doGet,doPost,这两个方法都是调用了process方法,下面来看一下这个方法。
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { process(request, response); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { process(request, response); } /** * 为此请求执行标准请求处理,并创建相应的响应。 */ protected void process(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { /** * 选择指定请求所属的模块,并为此请求添加相应的请求属性。 * 主要内容: * 1、获取request指定URI所属的模块名 * 2、根据模块名找到请求所属的模块 * 3、将该模块以及模块配置的信息添加到request的属性中 */ ModuleUtils.getInstance().selectModule(request, getServletContext()); // 拿到刚才设置在request中的ModuleConfig ModuleConfig config = getModuleConfig(request); /** * 返回给定模块的请求处理器,如果不存在,则返回空值。 * 此方法不会创建RequestProcessor。 * RequestProcessor的作用:RequestProcessor包含ActionServlet从容器 * 接收每个servlet请求时执行的处理逻辑。 您可以通过继承此类并覆盖您 * 有兴趣更改其行为的方法来自定义请求处理行为。 * RequestProcessor:请求处理器,它里面封装了许多处理模块的方法。主要看 * 一下下面的process方法 */ RequestProcessor processor = getProcessorForModule(config); if (processor == null) { processor = getRequestProcessor(config); } processor.process(request, response); }
/** * 处理HttpServletRequest请求并且创建相应的HttpServletResponse或转发给 * 其他资源。 */ public void process(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { /** * 如果请求是上传文件(contentType="multipart/form-data",method="post") * ,则将request包装成MultipartRequestWrapper,否则直接返回request */ request = processMultipart(request); // 确定我们将要映射的路径组件 String path = processPath(request, response); if (path == null) { return; } if (log.isDebugEnabled()) { log.debug("Processing a '" + request.getMethod() + "' for path '" + path + "'"); } // 如果需要,设置当前用户的时区 processLocale(request, response); // 如果需要,设置内容类型和no-caching标头 processContent(request, response); // 如果需要,设置内容类型和no-caching标头 processNoCache(request, response); // !(return true)? if (!processPreprocess(request, response)) { return; } /** * 如果消息的isaccessed方法返回true,则删除会话中存储在 * globals.message_key和globals.error_key下的任何 * actionMessages对象。这允许消息存储在会话中,显示一次,并在此处释放。 * * ActionMessages用于存储一些消息的类,消息可以是全局消息,也可以特定于 * 特定的bean属性。每个消息都由ActionMessage对象描述,该对象包含消息密 * 钥(在相应的消息资源数据库中查找),最多使用四个占位符参数在结果消息 * 中进行参数替换。 */ this.processCachedMessages(request, response); /** * 根据request请求的path确定映射对象,如果没有映射对象创建一个 * 错误的response对象并且返回的mapping = null */ ActionMapping mapping = processMapping(request, response, path); if (mapping == null) { return; } /** * 如果某个action配置了roleNames即:可操作的用户集合,那么访问该action * 时至少要是其中定义的用户才行 */ if (!processRoles(request, response, mapping)) { return; } /** * 根据actionMapping(提供映射的对象) * 、moduleConfig(提供已读取配置文件后的真实from-bean对象),创 * 建出一个表单的bean对象(如果已经存在,就回收利用,不新建), * 这个bean对象存储在request或session中(取决于你配置时的 * scope参数) */ ActionForm form = processActionForm(request, response, mapping); /** * 从此请求包含的请求参数中填充指定的ActionForm实例的属性。 * 此外,如果请求是使用CancelTag创建的按钮提交的,则将 * 设置请求属性Globals.CANCEL_KEY。 * 上面一步时是创建一个ActionForm实例,这一步是填充FormBean属性 */ processPopulate(request, response, form, mapping); // 验证ActionForm任何字段是否合格 try { if (!processValidate(request, response, form, mapping)) { return; } } catch (InvalidCancelException e) { ActionForward forward = processException(request, response, e, form, mapping); processForwardConfig(request, response, forward); return; } catch (IOException e) { throw e; } catch (ServletException e) { throw e; } // 处理此映射指定的forward或include if (!processForward(request, response, mapping)) { return; } if (!processInclude(request, response, mapping)) { return; } /** * 使用ClassLoader创建或获取action实例,以处理此请求 */ Action action = processActionCreate(request, response, mapping); if (action == null) { return; } /** * 调用action实例的execute方法。 * * ActionForward表示控制器RequestProcessor可能被指向执 * 行RequestDispatcher.forward或HttpServletResponse.sendRedirect的 * 目标,作为Action类处理活动的结果。 可以根据需要动态创建此类的实例, * 或者与ActionMapping实例关联配置该实例,以便为特定映射实例的潜在 * 多个目标进行命名查找。 */ ActionForward forward = processActionPerform(request, response, action, form, mapping); // 处理返回的目标实例(ActionForward),进行请求转发到视图或其他 processForwardConfig(request, response, forward); }
总结: 在处理HTTP请求,doPost和doGet调用的都是process方法。
根据request获取请求的模块,创建一个被请求模块的处理器RequestProcessor,使用该请求处理器处理请求:
针对文件类上传将request包装为MultipartRequestWrapper
根据mapping,moduleConfig创建对应的ActionForm对象
调用action对象的execute方法,传入actionForm,返回一个指向转发的对象
public void destroy() { if (log.isDebugEnabled()) { log.debug(internal.getMessage("finalizing")); } destroyModules(); destroyInternal(); getServletContext().removeAttribute(Globals.ACTION_SERVLET_KEY); CatalogFactory.clear(); PropertyUtils.clearDescriptors(); // Release our LogFactory and Log instances (if any) ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); if (classLoader == null) { classLoader = ActionServlet.class.getClassLoader(); } try { LogFactory.release(classLoader); } catch (Throwable t) { ; // Servlet container doesn't have the latest version // of commons-logging-api.jar installed // :FIXME: Why is this dependent on the container's version of // commons-logging? Shouldn't this depend on the version packaged // with Struts? /* Reason: LogFactory.release(classLoader); was added as an attempt to investigate the OutOfMemory error reported on Bugzilla #14042. It was committed for version 1.136 by craigmcc */ } }
本文附带了一个简单的实例案例,详细可参见码云 struts。