您的位置 首页 >  博文

《项目架构那点儿事》——浅析web层struts2的构建

【前言】

所谓快速开发,实质上为了节省项目的开支成本,减少程序员的开发时 间,固然就形成了种种二次封装的框架,也就是造轮子,然后我们的程序就按照这个轮子去画瓢,这里我就把公司这几次开发系统的框架源码贴出来,做一下讲解以 及使用示范,并有附件提供参考,希望能给各位在基于后台管理系统提供帮助。

【目录】

11.struts2的配置
22.struts2的基类构成
33.具体实例应用

【内容】

一、struts2的配置

1 .struts2的过滤器拦截配置 :相信大家对struts2的配置一定不陌生,这里我就简单带过struts2过滤器在web.xml的配置:

1<filter>
2     <filter-name>struts2</filter-name>
3     <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
4</filter>
5<filter-mapping>
6     <filter-name>struts2</filter-name>
7     <url-pattern>*.action</url-pattern>
8</filter-mapping>

2 .struts2全局配置:这里我要做一点介绍了,按常理来讲,我们都是基于约定大于配置的原则,不做重复劳动的思想:

  • A.基于Annotation的配置(struts2有一种叫convention的插件,可以实现零配置,具体可以百度Struts2 convention,我就不做讲解,注:这里的零配置是针对struts2在xml中配置)。

  • B.采用通配符进行配置,也就是我推荐的,也是我们项目中用到的一种方法,先上代码struts.xml:  

 1<?xml version="1.0" encoding="UTF-8" ?>
2<!DOCTYPE struts PUBLIC
3    "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
4    "http://struts.apache.org/dtds/struts-2.0.dtd">

5<struts>
6    <package name="default" namespace="/" extends="struts-default">
7
8
9        <global-results>
10            <result name="error">/page/error.jsp</result>(1)
11            <result name="input"></result>(1)
12            <result name="login" type="redirect">/login.jsp</result>(1)
13            <result>${target}</result>(2)
14            <result type="redirectAction" name="redirectAction">${target}</result>(2)
15            <result type="redirect" name="redirect">${target}</result>(2)
16        </global-results>
17    </package>
18    <package name="app" namespace="/" extends="default">(4)
19        <action name="*/*/*" class="st.{1}.web.{2}Action" method="{3}">(3)
20        </action>
21    </package>
22</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内容如下:

 1#允许使用下划线#
2struts.enable.SlashesInActionNames=true //必须要的,因为我们配置Action时用了下划线
3#spring做为对象管理工厂#
4struts.objectFactory=spring 
5#开发模式#
6struts.devMode=false//开发阶段可以开启这个模式
7#静态缓存#
8struts.serve.static.browserCache=false //开发阶段可以开启
9#配置是否重新加载#
10struts.configuration.xml.reload=true //建议开启,方便调试,发布版本的时候可以关闭

当然还有很多属性的配置,详细可以参考struts2源码包中struts.properties

二、struts2的基类
  1package com.st.web.action;
 2import java.io.IOException;
 3import java.util.Map;
 4import javax.servlet.http.HttpServletRequest;
 5import javax.servlet.http.HttpServletResponse;
 6import javax.servlet.http.HttpSession;
 7import org.apache.commons.lang.StringUtils;
 8import org.apache.commons.logging.Log;
 9import org.apache.commons.logging.LogFactory;
10import org.apache.struts2.ServletActionContext;
11import org.apache.struts2.interceptor.ServletRequestAware;
12import org.apache.struts2.interceptor.ServletResponseAware;
13import org.apache.struts2.interceptor.SessionAware;
14import com.opensymphony.xwork2.ActionContext;
15import com.opensymphony.xwork2.ActionSupport;
16import com.opensymphony.xwork2.ModelDriven;
17import com.opensymphony.xwork2.Preparable;
18import com.st.utils.AjaxMsg;
19import com.st.utils.JsonUtil;
20/**
21@author fisher
22@description Action公共基类
23@param 所有对象类型
24*/

25public abstract class BaseAction extends ActionSupport implements
26                ModelDrivenPreparableSessionAwareServletRequestAware,
27                ServletResponseAware 
{
28
29    private static final long serialVersionUID = 1L;
30    protected Log log = LogFactory.getLog(getClass());
31    // -- header 常量定义 --//
32    private static final String HEADER_ENCODING = "encoding";
33    private static final String HEADER_NOCACHE = "no-cache";
34    private static final String DEFAULT_ENCODING = "UTF-8";
35    private static final boolean DEFAULT_NOCACHE = true;
36    // -- Content Type 定义 --//
37    public static final String TEXT_TYPE = "text/plain";
38    public static final String JSON_TYPE = "application/json";
39    public static final String XML_TYPE = "text/xml";
40    public static final String HTML_TYPE = "text/html";
41    public static final String JS_TYPE = "text/javascript";
42    public static final String EXCEL_TYPE = "application/vnd.ms-excel";
43    public static final String IMG_TYPE = "image/jpeg";
44    // -------------------- 作用域对象 --------------------//
45    /** sessionMap对象 */
46    private Map sessionMap;
47    /** request对象 */
48    private HttpServletRequest request;
49    /** response对象 */
50    private HttpServletResponse response;
51    /** ajax消息 */
52    public static final AjaxMsg ajaxMsg = new AjaxMsg();
53    // --------------------视图跳转路径------------------//
54    /**
55     * 例如:"index.jsp index.ftl"
56     */

57    private String target;
58    // -- Preparable函数 --//
59    /**
60     * 实现空的prepare()函数,屏蔽了所有Action函数都会执行的公共的二次绑定.
61     */

62    public void prepare() throws Exception {
63    }
64    // -------------绕过jsp/freemaker直接输出文本函数-------------//
65    // -- 绕过jsp/freemaker直接输出文本的函数 --//
66    /**
67     * 直接输出内容的简便函数. eg. render("text/plain", "hello", "encoding:GBK");
68     * render("text/plain",hello", "no-cache:false");
69     * render("text/plain","hello", "encoding:GBK","no-cache:false");
70     * 
71     * @param headers
72     *            可变的header数组,目前接受的值为"encoding:"或"no-cache:",默认值分别为UTF-8和true.
73     */

74    public static void render(final String contentType, final String content,
75                    final String... headers)
 
{
76            HttpServletResponse response = initResponseHeader(contentType, headers);
77            try {
78                    response.getWriter().write(content);
79                    response.getWriter().flush();
80            } catch (IOException e) {
81                    throw new RuntimeException(e.getMessage(), e);
82            }
83    }
84    public static void renderImg(final String img, final String... headers) {
85            render(IMG_TYPE, img, headers);
86    }
87    /**
88     * 直接输出文本.
89     * 
90     * @see #render(String, String, String...)
91     */

92    public static void renderText(final String text, final String... headers) {
93            render(TEXT_TYPE, text, headers);
94    }
95    /**
96     * 直接输出HTML.
97     * 
98     * @see #render(String, String, String...)
99     */

100    public static void renderHtml(final String html, final String... headers) {
101            render(HTML_TYPE, html, headers);
102    }
103    /**
104     * 直接输出XML.
105     * 
106     * @see #render(String, String, String...)
107     */

108    public static void renderXml(final String xml, final String... headers) {
109            render(XML_TYPE, xml, headers);
110    }
111    /**
112     * 直接输出JSON.
113     * 
114     * @param jsonString
115     *            json字符串.
116     * @see #render(String, String, String...)
117     */

118    public static void renderJson(final String jsonString,
119                    final String... headers)
 
{
120            render(JSON_TYPE, jsonString, headers);
121    }
122    /**
123     * 直接输出JSON,使用fastJson转换Java对象.
124     * 
125     * @param data
126     *            可以是List, POJO[], POJO, 也可以Map名值对.
127     * @see #render(String, String, String...)
128     */

129    public static void renderJson(final Object data, final String... headers) {
130            String jsonString = JsonUtil.serialize(data);
131            renderJson(jsonString, headers);
132    }
133    public static void renderJson(final Object data,
134                    final String[] propertyFilter, final boolean isInclude,
135                    final String... headers)
 
{
136            String jsonString = JsonUtil.serialize(data, propertyFilter, isInclude);
137            renderJson(jsonString, headers);
138    }
139    /**
140     * 分析并设置contentType与headers.
141     */

142    private static HttpServletResponse initResponseHeader(
143                    final String contentType, final String... headers)
 
{
144            // 分析headers参数
145            String encoding = DEFAULT_ENCODING;
146            boolean noCache = DEFAULT_NOCACHE;
147            for (String header : headers) {
148                    String headerName = StringUtils.substringBefore(header, ":");
149                    String headerValue = StringUtils.substringAfter(header, ":");
150                    if (StringUtils.equalsIgnoreCase(headerName, HEADER_ENCODING)) {
151                            encoding = headerValue;
152                    } else if (StringUtils.equalsIgnoreCase(headerName, HEADER_NOCACHE)) {
153                            noCache = Boolean.parseBoolean(headerValue);
154                    } else {
155                            throw new IllegalArgumentException(headerName
156                                            + "不是一个合法的header类型");
157                    }
158            }
159            HttpServletResponse response = ServletActionContext.getResponse();
160            // 设置headers参数
161            String fullContentType = contentType + ";charset=" + encoding;
162            response.setContentType(fullContentType);
163            if (noCache) {
164                    // Http 1.0 header
165                    response.setDateHeader("Expires"1L);
166                    response.addHeader("Pragma""no-cache");
167                    // Http 1.1 header
168                    response.setHeader("Cache-Control""no-cache, no-store, max-age=0");
169            }
170            return response;
171    }
172    /**
173     * @description 获取磁盘物理路径
174     * @return String
175     */

176    protected String getRealPath() {
177            return request.getSession().getServletContext().getRealPath("");
178    }
179    /**
180     * @description 得到session对象
181     * @return Session
182     */

183    protected HttpSession getSession() {
184            return this.request.getSession();
185    }
186    /**
187     * @Title: getParams
188     * @Description: 得到页面所有参数
189     * @return Map
190     * @throws
191     */

192    public Map getParameters() {
193            return ActionContext.getContext().getParameters();
194    }
195    /**
196     * @description 设置session对象Attribute属性
197     * @param key
198     * @param value
199     */

200    protected void setSessionAttribute(String key, Object value) {
201            sessionMap.put(key, value);
202    }
203    /**
204     * @description 返回session对象的Attribute属性
205     * @param key
206     * @return Object
207     */

208    protected Object getSessionAttribute(String key) {
209            return sessionMap.get(key);
210    }
211    /**
212     * @description 清除整个session
213     */

214    protected void clearSession() {
215            sessionMap.clear();
216    }
217    /**
218     * @description 清除session中指定的内容
219     * @param key
220     */

221    protected void remove(Object key) {
222            sessionMap.remove(key);
223    }
224    /**
225     * @description 通过request获取页面参数值
226     * @return string
227     */

228    protected String getParameters(String name) {
229            return (null != request.getParameter(name)) ? request
230                            .getParameter(name) : null;
231    }
232    /**
233     * @description 获取项目根目录
234     * @return String
235     */

236    protected String getRootPath() {
237            return request.getContextPath();
238    }
239    /**
240     * @description 获取request独享
241     * @return
242     */

243    public HttpServletRequest getRequest() {
244            return request;
245    }
246    /**
247     * @description 获取response对象
248     * @return
249     */

250    public HttpServletResponse getResponse() {
251            return response;
252    }
253    /**
254     * @description 获取sessionMap
255     * @return
256     */

257    public Map getSessionMap() {
258            return sessionMap;
259    }
260    public String getTarget() {
261            return target;
262    }
263    public void setTarget(String target) {
264            this.target = target;
265    }
266    public void setServletResponse(HttpServletResponse response) {
267            this.response = response;
268    }
269    public void setServletRequest(HttpServletRequest request) {
270            this.request = request;
271    }
272    public void setSession(Map sessionMap) {
273            this.sessionMap = sessionMap;
274    }
275}


解析:

  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个小时,由于文笔水品有限,有瑕疵的地方,请大家指正批评。


关于作者: 王俊南(Jonas)

昨夜寒蛩不住鸣。惊回千里梦,已三更。起来独自绕阶行。人悄悄,帘外月胧明。 白首为功名。旧山松竹老,阻归程。欲将心事付瑶琴。知音少,弦断有谁听。

热门文章