《项目架构那点儿事》——浅析web层struts2的构建
【前言】所谓快速开发,实质上为了节省项目的开支成本,减少程序员的开发时 间,固然就形成了种种二次封装的框架,也就是造轮子,然后我们的程序就按照这个轮子去画瓢,这里我就把公司这几次开发系统的框架源码贴出来,做一下讲解以 及使用示范,并有附件提供参考,希望能给各位在基于后台管理系统提供帮助。
【目录】
1.struts2的配置
2.struts2的基类构成
3.具体实例应用
【内容】
一、struts2的配置
1 .struts2的过滤器拦截配置 :相信大家对struts2的配置一定不陌生,这里我就简单带过struts2过滤器在web.xml的配置:
<filter> <filter-name>struts2</filter-name> <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class> </filter> <filter-mapping> <filter-name>struts2</filter-name> <url-pattern>*.action</url-pattern> </filter-mapping>
A.基于Annotation的配置(struts2有一种叫convention的插件,可以实现零配置,具体可以百度Struts2 convention,我就不做讲解,注:这里的零配置是针对struts2在xml中配置)。
B.采用通配符进行配置,也就是我推荐的,也是我们项目中用到的一种方法,先上代码struts.xml:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN" "http://struts.apache.org/dtds/struts-2.0.dtd"> <struts> <package name="default" namespace="/" extends="struts-default"> <global-results> <result name="error">/page/error.jsp</result>(1) <result name="input"></result>(1) <result name="login" type="redirect">/login.jsp</result>(1) <result>${target}</result>(2) <result type="redirectAction" name="redirectAction">${target}</result>(2) <result type="redirect" name="redirect">${target}</result>(2) </global-results> </package> <package name="app" namespace="/" extends="default">(4) <action name="*/*/*" class="st.{1}.web.{2}Action" method="{3}">(3) </action> </package> </struts>
解 析:上面我对配置文件中加入(1)、(2)、(3)、(4),表示对此处的标注,下面我就对这些部分进行解析。 (1):如果有用过struts2的童鞋,可以清晰的看出它们名称都很有含义,对没错 struts2的Action类已经封装这4个静态常量,当我们在Action的方法中完成操作后,要对视图进行 跳转或者响应时,要返回一个字符串,比如:return SUCCESS;那么这个SUCCESS实际上就是对应我们在struts2配置的xxxx.jsp,简化一下也就是xxx.jsp,默认的result name就是"success" 默认的type是dispatch 也就是JSP视图,那么这样我们就可以对这些预定好的变量配置他们响应的跳转视图页面。
(2)
:很奇怪为什么有个${target},${target}只是作为struts2的配置的占位参数,而基类BaseAction中我们存放了一个
target变量,当解析struts.xml时,可以从我们实现类中读
取出来,然后进行替换,这样做的好处是什么,很显然,我们要返回的jsp页面有成千上万种,我不可能每次都去配置 a.jsp
b.jsp....,这样可以说是实现了统一设置路径,也就是通配,而且还有不同类型的返回,type="redirect"/
type="redirectAction",看上去好像是一样的,区别在于redirectAction可以指定重定向到某个action上
(struts2中配置的action),target就是Action的name,值得注意的是我这里只是罗列了重定向以及Jsp视图的类型,要知道还
有其他N多种类型的视图,怎么办?我先把关于模板类型的返回的告知出来以freemaker为例,其他见基类BaseAction:
struts.xml 中加入freemaker" name="freemaker">${target} ,其实道理是一样 只是type不一样而已,target="xxxx.ftl";
(3):这个是很重要的,我们所有的action都用了通用,原则是:{模块名}/{用例名}/{方法}.action
比如:st.sys.web.UserAction.java - sttbas_server/src/java这样一个类,我们访问它的查询方法:sys/User/view.action,很简单。
(4):我们这个名为app的package继承了 default包,也就意味可以重用default的配置。
好
像struts.xml少了点什么东西,对我们没有配置它的属性,这里我们将struts2的属性配置进行区分,也是官方推荐的设置将
struts.xml,与struts.properties分开,并放入classpath目录下,struts.properties内容如下:
#允许使用下划线#struts.enable.SlashesInActionNames=true //必须要的,因为我们配置Action时用了下划线#spring做为对象管理工厂#struts.objectFactory=spring#开发模式#struts.devMode=false//开发阶段可以开启这个模式#静态缓存#struts.serve.static.browserCache=false //开发阶段可以开启#配置是否重新加载#struts.configuration.xml.reload=true //建议开启,方便调试,发布版本的时候可以关闭
当然还有很多属性的配置,详细可以参考struts2源码包中struts.properties
二、struts2的基类
package com.st.web.action; import java.io.IOException; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.struts2.ServletActionContext; import org.apache.struts2.interceptor.ServletRequestAware; import org.apache.struts2.interceptor.ServletResponseAware; import org.apache.struts2.interceptor.SessionAware; import com.opensymphony.xwork2.ActionContext; import com.opensymphony.xwork2.ActionSupport; import com.opensymphony.xwork2.ModelDriven; import com.opensymphony.xwork2.Preparable; import com.st.utils.AjaxMsg; import com.st.utils.JsonUtil; /** * @author fisher * @description Action公共基类 * @param 所有对象类型 */ public abstract class BaseAction extends ActionSupport implements ModelDriven, Preparable, SessionAware, ServletRequestAware, ServletResponseAware { private static final long serialVersionUID = 1L; protected Log log = LogFactory.getLog(getClass()); // -- header 常量定义 --// private static final String HEADER_ENCODING = "encoding"; private static final String HEADER_NOCACHE = "no-cache"; private static final String DEFAULT_ENCODING = "UTF-8"; private static final boolean DEFAULT_NOCACHE = true; // -- Content Type 定义 --// public static final String TEXT_TYPE = "text/plain"; public static final String JSON_TYPE = "application/json"; public static final String XML_TYPE = "text/xml"; public static final String HTML_TYPE = "text/html"; public static final String JS_TYPE = "text/javascript"; public static final String EXCEL_TYPE = "application/vnd.ms-excel"; public static final String IMG_TYPE = "image/jpeg"; // -------------------- 作用域对象 --------------------// /** sessionMap对象 */ private Map sessionMap; /** request对象 */ private HttpServletRequest request; /** response对象 */ private HttpServletResponse response; /** ajax消息 */ public static final AjaxMsg ajaxMsg = new AjaxMsg(); // --------------------视图跳转路径------------------// /** * 例如:"index.jsp index.ftl" */ private String target; // -- Preparable函数 --// /** * 实现空的prepare()函数,屏蔽了所有Action函数都会执行的公共的二次绑定. */ public void prepare() throws Exception { } // -------------绕过jsp/freemaker直接输出文本函数-------------// // -- 绕过jsp/freemaker直接输出文本的函数 --// /** * 直接输出内容的简便函数. eg. render("text/plain", "hello", "encoding:GBK"); * render("text/plain",hello", "no-cache:false"); * render("text/plain","hello", "encoding:GBK","no-cache:false"); * * @param headers * 可变的header数组,目前接受的值为"encoding:"或"no-cache:",默认值分别为UTF-8和true. */ public static void render(final String contentType, final String content, final String... headers) { HttpServletResponse response = initResponseHeader(contentType, headers); try { response.getWriter().write(content); response.getWriter().flush(); } catch (IOException e) { throw new RuntimeException(e.getMessage(), e); } } public static void renderImg(final String img, final String... headers) { render(IMG_TYPE, img, headers); } /** * 直接输出文本. * * @see #render(String, String, String...) */ public static void renderText(final String text, final String... headers) { render(TEXT_TYPE, text, headers); } /** * 直接输出HTML. * * @see #render(String, String, String...) */ public static void renderHtml(final String html, final String... headers) { render(HTML_TYPE, html, headers); } /** * 直接输出XML. * * @see #render(String, String, String...) */ public static void renderXml(final String xml, final String... headers) { render(XML_TYPE, xml, headers); } /** * 直接输出JSON. * * @param jsonString * json字符串. * @see #render(String, String, String...) */ public static void renderJson(final String jsonString, final String... headers) { render(JSON_TYPE, jsonString, headers); } /** * 直接输出JSON,使用fastJson转换Java对象. * * @param data * 可以是List, POJO[], POJO, 也可以Map名值对. * @see #render(String, String, String...) */ public static void renderJson(final Object data, final String... headers) { String jsonString = JsonUtil.serialize(data); renderJson(jsonString, headers); } public static void renderJson(final Object data, final String[] propertyFilter, final boolean isInclude, final String... headers) { String jsonString = JsonUtil.serialize(data, propertyFilter, isInclude); renderJson(jsonString, headers); } /** * 分析并设置contentType与headers. */ private static HttpServletResponse initResponseHeader( final String contentType, final String... headers) { // 分析headers参数 String encoding = DEFAULT_ENCODING; boolean noCache = DEFAULT_NOCACHE; for (String header : headers) { String headerName = StringUtils.substringBefore(header, ":"); String headerValue = StringUtils.substringAfter(header, ":"); if (StringUtils.equalsIgnoreCase(headerName, HEADER_ENCODING)) { encoding = headerValue; } else if (StringUtils.equalsIgnoreCase(headerName, HEADER_NOCACHE)) { noCache = Boolean.parseBoolean(headerValue); } else { throw new IllegalArgumentException(headerName + "不是一个合法的header类型"); } } HttpServletResponse response = ServletActionContext.getResponse(); // 设置headers参数 String fullContentType = contentType + ";charset=" + encoding; response.setContentType(fullContentType); if (noCache) { // Http 1.0 header response.setDateHeader("Expires", 1L); response.addHeader("Pragma", "no-cache"); // Http 1.1 header response.setHeader("Cache-Control", "no-cache, no-store, max-age=0"); } return response; } /** * @description 获取磁盘物理路径 * @return String */ protected String getRealPath() { return request.getSession().getServletContext().getRealPath(""); } /** * @description 得到session对象 * @return Session */ protected HttpSession getSession() { return this.request.getSession(); } /** * @Title: getParams * @Description: 得到页面所有参数 * @return Map * @throws */ public Map getParameters() { return ActionContext.getContext().getParameters(); } /** * @description 设置session对象Attribute属性 * @param key * @param value */ protected void setSessionAttribute(String key, Object value) { sessionMap.put(key, value); } /** * @description 返回session对象的Attribute属性 * @param key * @return Object */ protected Object getSessionAttribute(String key) { return sessionMap.get(key); } /** * @description 清除整个session */ protected void clearSession() { sessionMap.clear(); } /** * @description 清除session中指定的内容 * @param key */ protected void remove(Object key) { sessionMap.remove(key); } /** * @description 通过request获取页面参数值 * @return string */ protected String getParameters(String name) { return (null != request.getParameter(name)) ? request .getParameter(name) : null; } /** * @description 获取项目根目录 * @return String */ protected String getRootPath() { return request.getContextPath(); } /** * @description 获取request独享 * @return */ public HttpServletRequest getRequest() { return request; } /** * @description 获取response对象 * @return */ public HttpServletResponse getResponse() { return response; } /** * @description 获取sessionMap * @return */ public Map getSessionMap() { return sessionMap; } public String getTarget() { return target; } public void setTarget(String target) { this.target = target; } public void setServletResponse(HttpServletResponse response) { this.response = response; } public void setServletRequest(HttpServletRequest request) { this.request = request; } public void setSession(Map sessionMap) { this.sessionMap = sessionMap; } }
解析:
1.
接到上面struts2的配置问题来说,可以看到我们在配置result的时候存在占位参数${target},那这个target是哪里来的?可以看
到,BaseAction中已经包含这个变量,并且提供get方法,有童鞋会问为什么这样子对target设置就能替换${target},我只能说这是
OGNL的一种机制。
2.可以看出baseAciton中定义了很多常量,怎么用?这些常量定义是为了处理response
的响应头,上面我有讲过,我没有在struts.xml中处理流返回类型,json返回类型,对吧?很显然是要在这里做操作了撒,可以看出有很多
render开头的重载方法,这些方法就是帮助我们直接去响应前台,后面例子中我会附带系统中struts2对json处理、对流文件(excel、
img)处理。
3.实现了很多接口,值得注意的是Preparable和ModelDriven。注意这个BaseAction是泛型,就是因为ModelDriven(模型驱动),有了它我们可以直接由ModelDriven的interceptor拦截前台参数,把参数分装成我们指定的泛型类型的对象,而Preparable则是提供给一个预处理接口,让在执行Action方法之前执行。其他几个几口是帮助我们获取作用域对象,session、request。
4.附件中会包含基类的引用类,JsonUtil(json工具类)以及AjaxMsg(ajax消息类,针对Json消息)
三、具体示例
示例我写得很简单,大概就是举例一下,如何进行前后台的交互,以及响应不同返回类型。由于帖子长度有限,所以基类的代码何示例代码大家只有下载了,写这篇文章耗费了我3个小时,由于文笔水品有限,有瑕疵的地方,请大家指正批评。
四、附件地址
http://pan.baidu.com/share/link?shareid=2838805807&uk=2701872319