Struts是一个基于MVC架构的框架(框架实现了某些领域通用完备功能的底层服务),它主要用于开发Web应用程序,帮助我们减少了用MVC开发Web应用的时间,简化了开发过程,使开发更具模块化、灵活性和重用性。可以说Struts把MVC的设计思想发挥到了极致,尤其在Controller层。
在用Struts开发项目直接接触的有这么几个类:ActionMapping、ActionServlet、ActionForm、Action和ActionForward,这五个类各司其职,使上有老下有小的Controller层接近完美。因Struts是面向对象设计,掌握了这几个类,也就基本掌握了Struts的用法,下面按照这几个类的执行流程举例说明他们在Struts中的具体用法。下图为Struts的一个大致流程图:
这里不考虑各个对象的创建细节,单从工作的流程分析各个类的作用。
ActionServlet
客户端向服务器(Tomcat)发起请求,通过在web.xml中的配置,请求直接进入ActionServlet,从命名就可以看出这是一个Servlet,此类是不需要我们显式去创建的,Struts框架已对它做了实现。请求继续调用ActionServlet的doGet/doPost方法,实际上这个两个方法共同调用了RequestProcess类的process方法,process方法才是真正的核心,在process方法中,通过调用processPtah方法截取Request中传递过来的URL,然后调用processMapping方法根据截取的URL取得相应的ActionMapping。
在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>2</param-value>
- </init-param>
- <init-param>
- <param-name>detail</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>
从上面配置不难看出,ActionServlet在Tomcat启动时,就已经创建好了,只要是以do结尾的请求都会被ActionServlet拦截下来,然后它将不同的请求转发给对应的Action对象,让Action进一步处理客户端的请求。因此ActionServlet肩负着中央控制器角色,是Struts的核心。下面介绍的各个类中,依旧会看到ActionServlet的影子。
ActionMapping
ActionServlet调用processMapping方法,此方法首先会调用moduleConfig.findActionConfig(path),获取对应的ActionMapping对象。Struts框架将核心配置文件struts-config.xml解析并放入了ActionMapping对象里。
等返回ActionMapping对象后,processMapping方法把ActionMapping设置到Request或Session中方便后面其他地方的使用,体现了面向对象封装的好处。struts-config.xml配置信息如下:
- <struts-config>
- <form-beans>
- <form-bean name="itemForm" type="com.snail.drp.web.forms.ItemActionForm"/>
- </form-beans>
- <global-exceptions>
- <exception key="errors.detail" type="com.snail.drp.AppException" path="/error.jsp"/>
- </global-exceptions>
- <action-mappings>
- <action path="/item"
- type="com.snail.drp.web.actions.ItemAction"
- name="itemForm"
- scope="request"
- parameter="command"
- >
- <forward name="list" path="/WEB-INF/jsp/item_maint.jsp"/>
- <forward name="show_add" path="/WEB-INF/jsp/item_add.jsp"/>
- <forward name="item_index" path="item.do" redirect="true"/>
- <forward name="show_modify" path="/WEB-INF/jsp/item_modify.jsp"/>
- <forward name="show_detail" path="/WEB-INF/jsp/item_detail.jsp"/>
- <forward name="show_upload" path="/WEB-INF/jsp/item_upload.jsp"/>
- </action>
- </action-mappings>
- <message-resources parameter="MessageResources"/>
- </struts-config>
配置信息里看出,其中中包含有与请求对应的ActionForm、Action、ActionForward、错误处理以及国际化等配置信息,这些都可以通过ActionMapping取出来。
ActionForm
processMapping方法生命周期结束后,ActionServlet继续调用processForm方法。ProcessForm方法根据ActionMapping中的name名称查找ActionForm,如果配置了ActionForm,那么就到request或session中查找,如果在request或session中存在已经创建的ActionForm,那么将返回。如果不存在那么会根据ActionForm的完成路径采用反射进行创建,再将创建好的ActionForm放到request或session中,方便后面的读取。
创建好ActionForm对象后,接下来就要该给ActionForm中赋值了,此时,ActionServlet会调用processPopulate方法,首先执行ActionForm中的reset方法进行重置,然后得到表单中所有输入域的name名称,再调用request.getParameterValues(),根据name名称得到相应的值,最后将表单中的数据全部放到一个map中,map的key为表单输入域的名称,map的value为表单输入域的值(字符串数组),接下来调用一个第三方组件BeanUtils,将Map中的值,根据ActionForm中的类型先转换好,再调用ActionForm中的setter方法设置到ActionForm上。
唯一要我们做的是创建一个类,然后让它继承ActionForm,根据表单输入域的name提供get和set方法,并在Struts-Config.xml中配置(参见上一篇)。ActionForm代码如下:
- package com.snail.drp.web.forms;
- import org.apache.struts.action.ActionForm;
- import org.apache.struts.upload.FormFile;
- public class ItemActionForm extends ActionForm {
- private String itemNo;
- private String itemName;
- public String getItemNo() {
- return itemNo;
- }
- public void setItemNo(String itemNo) {
- this.itemNo = itemNo;
- }
- public String getItemName() {
- return itemName;
- }
- public void setItemName(String itemName) {
- this.itemName = itemName;
- }
- }
那么一个完整的ActionForm就创建好了,类型转换等工作它也为我们完成了。接下来在Action中就可以任意调用了。其实,ActionForm非常类似于Domain模型中的JavaBean,他和表单中的输入域形成映射关系,将输入域中的name和值通过Set方法放入Map中,其他地方可以通过get方法来获取。
Action和DispatchAction
继续沿着ActionServlet前行,搞定ActionForm后,ActionServlet会调用processActionCreate方法,根据Action的完整类名称到ActionMapping中去查找,如果存在就返回Action对象,否则根据Action类的完整名称采用反射去创建,再将创建好的Action放到ActionMapping中,因此Struts1的Action是单实例的,存在线程安全问题。
然后ActionServlet调用processActionPerform方法,执行用户自定义的Action中的execute方法,将ActionMaping、ActionForm、request、response传递过去,将ActionForward对象返回。
Action是一个Java类,负责调用业务逻辑方法,检测处理异常,校验输入数据和根据逻辑进行转向,它一般按照职责划分,意味着职责的增多,Action类也会相应的增多,为了解决这种类过多的情况,由DispatchAction类代替Action,由它继承Action,把Action众多的类完成的功能放到了方法体中,因此可以在一个DispatchAction中处理多个Action所完成的事情,有效避免了类过多的弊病。代码如下:
- package com.snail.drp.web.actions;
- import java.io.FileOutputStream;
- import java.util.List;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import org.apache.commons.beanutils.BeanUtils;
- import org.apache.struts.action.ActionForm;
- import org.apache.struts.action.ActionForward;
- import org.apache.struts.action.ActionMapping;
- import org.apache.struts.actions.DispatchAction;
- import org.apache.struts.upload.FormFile;
- import com.snail.drp.BeanFactory;
- import com.snail.drp.PageModel;
- import com.snail.drp.domain.Item;
- import com.snail.drp.domain.ItemCategory;
- import com.snail.drp.domain.ItemUnit;
- import com.snail.drp.service.DataDictService;
- import com.snail.drp.service.ItemService;
- import com.snail.drp.web.forms.ItemActionForm;
- public class ItemAction extends DispatchAction {
- /**
- * 查找所有物料信息
- */
- @Override
- protected ActionForward unspecified(ActionMapping mapping, ActionForm form,
- HttpServletRequest request, HttpServletResponse response)
- throws Exception {
- ItemActionForm iaf = (ItemActionForm)form;
- int pageSize = Integer.parseInt(request.getSession().getServletContext().getInitParameter("pageSize"));
- String queryString = iaf.getClientIdOrName();
- int pageNo = iaf.getPageNo();
- ItemService itemService = (ItemService)BeanFactory.getInstance().getBean(ItemService.class);
- PageModel pageModel = itemService.findAllItem(queryString, pageNo, pageSize);
- request.setAttribute("pageModel", pageModel);
- return mapping.findForward("list");
- }
- /**
- * 添加物料
- * @param mapping
- * @param form
- * @param request
- * @param response
- * @return
- * @throws Exception
- */
- public ActionForward add(ActionMapping mapping, ActionForm form,
- HttpServletRequest request, HttpServletResponse response)
- throws Exception {
- ItemActionForm iaf = (ItemActionForm)form;
- Item item = new Item();
- BeanUtils.copyProperties(item, iaf);
- ItemCategory itemCategory = new ItemCategory();
- itemCategory.setId(iaf.getCategory());
- item.setItemCategory(itemCategory);
- ItemUnit itemUnit = new ItemUnit();
- itemUnit.setId(iaf.getUnit());
- item.setItemUnit(itemUnit);
- ItemService itemService = (ItemService)BeanFactory.getInstance().getBean(ItemService.class);
- itemService.addItem(item);
- return mapping.findForward("item_index");
- }
- }
如果将以上代码继承自Action,那么该类必须重写execute方法,执行需要的功能也必须在execute方法中,这样如果不利用多态创建多个Action类,那就得需要很多if...else...语句,这种编码方式是不可取的。
ActionForward
好了,终于能看到完整的图了。
其实从命名就可以看出ActionForward主要负责转向,执行完processActionPerform方法后,返回的就是ActionForward对象,ActionServlet紧接着调用processForwardConfig方法,根据ActionForward完成转向(转发或重定向)。ActionForward主要信息来自于ActionMapping对象,因此需要我们做的是配置好Struts-Config.xml文件即可。
这只是简单的分析了一下应用Struts的流程,要想真正的学习还需研究其源代码来的直接。