J2EE struts2MVC应用在线书签1
序:之前花了一天研究了一下filter,虽然是实现了MVC模式开发了 WebBookmark,但是代码过于冗长,集中在filter中使用if语句不易阅读,为了体现两份作业的不同点,我决定学习 JavaEE下的struts2框架,实现WebBookmark的MVC模式,这一次,代码将会更加清晰,代码文件分布也会更加容易被读懂,这一次敏捷学习,将会沿用上一代产品的部分代码技术,同时也会暴露很多愚蠢的使用方式,毕竟不是专业的JavaEE程序员,见谅。
科普:struts2与struts从框架构成来说,完全是两个不同产品,可能大部分新手会觉得这两个东西名儿都一样,struts2一定是struts的 升级版吧,不然,我们不要慵懒的认为struts1书写的项目可以轻松升级到struts2,这么说,要升级几乎就要重写。因为struts2根本不是 struts1升级来这么简单的,struts2基于WebWork2,是完全不同于struts1的框架,Struts2对应的有自己的标签,并且功能 强大。Webwork也有自己的标签。在2005年12月,WebWork与Struts Ti决定合并, 在此同时, Struts Ti 改名为 Struts Action Framework 2.0,成为Struts真正的下一代。官方这么做纯粹是为了炒作,因为struts1很火,但事实上很糟糕,需要被更加优秀的WebWork淘汰掉了, 于是为了拯救这个商标,他们直接升级了Webwork,命名为struts2,基于大名鼎鼎的struts使得struts2可以不费吹灰之力,一炮走 红!
技术点一:搭建struts框架环境
接下来我们看看struts2的搭建,还是基于javaweb的dynamic web project应用,创建详情请看我的博客《带你在javaee世界起飞——开发环境搭建》!
我们直接上struts2环境搭建(注意:基于javaee环境已经搭建好了),struts2将会把应用的代码质量带上一个新的高度,首先下载几个jar包作为类库:
第一步:下载并解压struts2:
1.百度“struts2”即可,上“http://struts.apache.org/”下载struts-2.3.24.1-all.zip包
2.打开压缩文件=》apps/struts2-blank.war这个应用
解压其中的/WEB-INF/lib下的jar包,安放到新建的WebBookmark对应的/WebContent/WEB-INF/lib下
解压/src/java/struts.xml到新建项目的java source/src下
第二步:修改web.xml文件(注意在新建项目最后一个next窗口finish前需要勾选)
xml文件在WebContent/WEB-INF/下,配置如下:
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1"> <display-name>WebBookmark</display-name> <filter> <filter-name>struts2</filter-name> <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>utf8</param-value> </init-param> </filter> <filter-mapping> <filter-name>struts2</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app>
其实这段代码可以直接在刚刚解压的struts2-blank这个项目相同位置找到并复制!
我们看看这个xml文件主要就配置了两项,一个是filter,另一个是filter-mapping,咦,不就是我们用的filter过滤器吗?这么配置只不过是把servlet的过滤器交给的struts2框架
另外welcome-file设置项目首页为index.jsp。
第三步,设置struts.xml:
既 然使用了struts作为filter,那么我们不用在像第一次开发那样自己去制作filter了,统统交给struts2配置,那么所有的配置,最麻烦 的地方是我们要不停往这个struts2.xml文件添加配置项了,且先看完整的WebBookmark项目所需的配置文件吧,上代码:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN" "http://struts.apache.org/dtds/struts-2.3.dtd"> <struts> <constant name="struts.enable.DynamicMethodInvocation" value="true"/> <constant name="struts.configuration.xml.reload" value="true"/> <constant name="struts.i18n.encoding" value="UTF-8"/> <!--配置Convention 插件自动重加载映射 --> <constant name="struts.convention.classes.reload" value="true"/> <!--user--> <package name="BookmarkUser" namespace="/user" extends="struts-default"> <!--列出bookmark--> <action name="list" class="cslg.cn.controller.BookmarkAction" method="list"> <result name="list">/WEB-INF/view/user/list.jsp</result> <result name="error" type="">/index.jsp</result> </action> <!--新增bookmark--> <action name="add" class="cslg.cn.controller.BookmarkAction" method="add"> <result name="add">/WEB-INF/view/user/add.jsp</result> <result name="error" type="">/index.jsp</result> </action> <!--保存--> <action name="save" class="cslg.cn.controller.BookmarkAction" method="save"> <result name="success" type="redirect">/user/list.action</result> <result name="error">/WEB-INF/view/user/error.jsp</result> <result name="login" type="">/index.jsp</result> </action> <!--删除--> <action name="del" class="cslg.cn.controller.BookmarkAction" method="del"> <result name="success" type="redirect">/user/list.action</result> <result name="error">/WEB-INF/view/user/error.jsp</result> <result name="login" type="">/index.jsp</result> </action> <!--登录操作--> <action name="login" class="cslg.cn.controller.UserAction" method="login"> <!--登录成功:重置到list页面--> <result name="success" type="redirect">/user/list.action</result> <!--登录错误:首页(非重置)--> <result name="error" type="">/index.jsp</result> </action> <!--注销操作--> <action name="logout" class="cslg.cn.controller.UserAction" method="logout"> <result name="logout" type="">/index.jsp</result> </action> </package> <!--admin操作--> <package name="BookmarkAdmin" namespace="/admin" extends="struts-default"> <!--登录操作--> <action name="login" class="cslg.cn.controller.UserAction" method="login"> <!--登录成功:重置到list页面--> <result name="success" type="redirect">/admin/list.action</result> <!--登录错误:首页(非重置)--> <result name="error" type="">/index.jsp</result> </action> <!--列出user--> <action name="list" class="cslg.cn.controller.UserAction" method="list"> <result name="list">/WEB-INF/view/admin/list.jsp</result> <result name="error" type="redirect">/index.jsp</result> </action> <!--新增user--> <action name="add" class="cslg.cn.controller.UserAction" method="add"> <result name="add">/WEB-INF/view/admin/add.jsp</result> <result name="error" type="">/index.jsp</result> </action> <!--保存--> <action name="save" class="cslg.cn.controller.UserAction" method="save"> <result name="success" type="redirect">/admin/list.action</result> <result name="error">/WEB-INF/view/adminr/error.jsp</result> <result name="login" type="">/index.jsp</result> </action> <!--删除--> <action name="del" class="cslg.cn.controller.UserAction" method="del"> <result name="success" type="redirect">/admin/list.action</result> <result name="error">/WEB-INF/view/admin/error.jsp</result> <result name="login" type="">/index.jsp</result> </action> <!--注销操作--> <action name="logout" class="cslg.cn.controller.UserAction" method="logout"> <result name="logout" type="">/index.jsp</result> </action> </package> </struts>
xml文件有点小复杂,但是稍作介绍,就能明白原理。
首先,所有的配置都写在struts这个大架子里面。开头是一些开发中用的初始化,不必多了解,为了方便开发的调试,统一文字编码而已。
然后,我们看到两个<package>,一个注释为user,意为普通用户的功能,另一个是admin,意为超级用户的功能,在 struts.xml下,package可以作为命名空间,相当于把他们安放在了不同的路径目录“/user”和“/admin”中,注意既然这么设置 了,那么访问的时候,所有关于普通用户操作bookmark的功能url都是“/WebBookmark/user/xxx.action”,同理admin用户管理用户的功能url都在“/WebBookmark/admin/xxx.action”。这个在前端开发中尤为重要,而且不同package的namespace中跳转的相对路径都在当前命名空间下,因为每个命名空间都被看成为一个目录,所以相对路径理所当然是当前空间目录下。
再后,看<action>,这个是调用java source下的功能的核心,一个action对应一个访问的方法,class映射到java source下package下的类,method映射到类下的方法名,那么name属性就是为用户提供的访问方法,就是url中 “xxx.action”的xxx啦!
最后,请看<result>,意为“结果”,就是执行完后的页面跳转,里面夹着一个页面,这个页面也可以是另外一个url访问,也可以是对应服务器的jsp文件的物理路径,有一个name是什么呢?一会儿解释!
至少我们已经配置完成了所有的struts配置,也已经把前(V)后(C,M)端分离了,MVC已经基本实现,来看看新目录结构:
1.java文件
2.前端文件
3.类库文件,其中mysql连接和jstl请参照《J2EE MVC应用在线书签——filter实现1》一文,并没有变
主要是多了struts的类库罢了
技术点二:struts2控制器下的action的getter,setter方法
在这儿,我发现struts2有个特别的功能,可以将controller和model结合使用,但我没有做深入研究,这里提一下,请看下面这段代码:
public class UserAction { /* * Model:User */ private Integer id; private String username; private String password; private String user; public String getUser() { return user; } public void setUser(String user) { this.user = user; } /*其他getter,setter方法略*/ public String login() { // 普通用户 if ("user".equals(this.getUser())) { String sql = "select * from user where username='" + this.getUsername() + "' and password='" + this.getPassword() + "'"; MyDB db = new MyDB(); try { db.openConnection(); ResultSet res = db.executeQuery(sql);
上面这段代码来自“UserAction.java”,被精简了,但是总结下来无非两个东西,一个是user类的 getter方法,一个是setter方法,我们在filter中,把这个类分离在了Model(模型)中了,但在这里我们直接放在action中,并且 发挥了作用,原来struts2可以把form表单提交的数据(无论post,get方式)利用actiong下的setter方法存储到私有变量中,然 后使用this.getXxx()方法直接在Action之后的方法中获得,这是及其方便的,但,我只用了一次,后面的方法中,我们使用另一种接收方法获取。
技术点三,借用servlet来获取form表单提交的数据
看下面这行代码:
String url = "http://"+ new String(ServletActionContext.getRequest().getParameter("url")).trim();
来自“BookmarkAction.java”,这样获取表单数据看上去很愚蠢,但是借用servlet的方法也是很机智的绕过了很多struts特性,对于敏捷学习的初学者来说相当的实用。
技术点四,借用servlet来操作session
看下面两段代码:
保存session:
ActionContext.getContext().getSession().put("loginuser", loginuser);
获取已有session:
User user = (User) ActionContext.getContext().getSession().get("loginuser");
也是借用了servlet的session
技术点五,借用servlet向jsp发送标签
后端代码:
ServletActionContext.getRequest().setAttribute("message", "错误:该用户已存在!");
前端代码:
${requestScope.message }
后端代码:
List<BookMark> bms = new ArrayList<BookMark>(); MyDB db = new MyDB(); String sql = "select * from bookmark where userid = '" + user.getId() + "';"; try { db.openConnection(); ResultSet res = db.executeQuery(sql); while (res.next()) { BookMark bm = new BookMark(res.getInt(1), res.getString(2), res.getString(3), res.getString(4), res.getInt(5)); bms.add(bm); } db.closeConnection(); } catch (SQLException e) { e.printStackTrace(); } ServletActionContext.getRequest().setAttribute("list", bms);
前端代码,由于后端set一个对象列表,前端使用JSTL标签foreach出来:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <c:forEach items="${list}" var="list" varStatus="num"> <tr> <td>${list.sitename}</td> <td>${list.url}</td> <td>${list.cate}</td> <td><a href="add.action?id=${list.id}">修改</a> <a href="del.action?id=${list.id}">删除</a></td> </tr> </c:forEach>
技术点六,方法的返回处理字符串
注意,在struts.xml对应的所有Action类下的method,即方法,最后返回值都是String类型,所以所有方法都是“public String xxx(){}”形式的;
且看下面的代码,是删除一条书签的方法:
public String del() throws UnsupportedEncodingException { User user = (User) ActionContext.getContext().getSession().get("loginuser"); //检查session if (user != null && user.getId() > 0) { // 获取id String getid = new String(ServletActionContext.getRequest().getParameter("id").getBytes("ISO-8859-1"), "UTF-8").trim(); Integer id = Integer.parseInt(getid); MyDB db = new MyDB(); String sql = "delete from bookmark where id='" + id + "'"; if (id > 0 && id != null) { try { db.openConnection(); db.executeUpdate(sql); db.closeConnection(); return "success"; } catch (SQLException e) { e.printStackTrace(); } } return "error"; } return "login"; }
有三种情况,返回success,返回error,返回login,分别对应删除成功,删除失败,未登录返回登录,然后看关于它的struts.xml配置:
<action name="del" class="cslg.cn.controller.UserAction" method="del"> <result name="success" type="redirect">/admin/list.action</result> <result name="error">/WEB-INF/view/admin/error.jsp</result> <result name="login" type="">/index.jsp</result> </action>
很明显,最终返回的字符串是被映射到了struts的result标签的name属性上,例如返回了success,最终以type="redirect"的形式跳转到list.action,重新列出所有的书签,另外两个显而易见,分别跳转到错误页,登录页(index.jsp被设定为登录页)!
技术点七,公用edit和add页面
从代码高可用性的角度说,edit和add页面其实是可以公用的,且看前端代码:
<form action="save.action" method="post" class="am-form am-form-horizontal"> <input type="hidden" name="id" value="${requestScope.bookmark.id}" /> <div class="am-form-group"> <label for="url" class="am-u-sm-2 am-form-label">网 址:http://</label> <div class="am-u-sm-10"> <input type="text" id="url" name="url" placeholder="输入书签地址" value="${requestScope.bookmark.url}"> </div> </div> <div class="am-form-group"> <label for="sitename" class="am-u-sm-2 am-form-label">网站名称:</label> <div class="am-u-sm-10"> <input type="text" id="sitename" name="sitename" placeholder="输入网站名称" value="${requestScope.bookmark.sitename}"> </div> </div> <div class="am-form-group"> <label for="cate" class="am-u-sm-2 am-form-label">分类:</label> <div class="am-u-sm-5"> <select name="cate" id="cate"> <option value="">选择分类</option> <c:forEach items="${list}" var="list" varStatus="num"> <option value="${list.catename}" <c:if test="${requestScope.bookmark.cate==list.catename}">selected</c:if>>${list.catename }</option> </c:forEach> </select> </div> <div class="am-u-sm-5"> <input type="text" name="newcate" placeholder="新增一个分类"/> </div> </div> <div class="am-form-group"> <div class="am-u-sm-10 am-u-sm-offset-2"> <button type="submit" class="am-btn am-btn-primary am-btn-block">提交</button> </div> </div> </form>
我们只需要一个hidden的input传递一个bookmark的id来标记,id存在即为编辑模式,不存在即为添加模式,在后端进行判断,且看代码:
//获取id String id = Se)vletActionContext.getRequest().getParameter("id").trim(); String sql = ""; if (!"".equals(id)) { bm.setId(Integer.parseInt(id)); sql = "update bookmark set url='" + bm.getUrl() + "', sitename='" + bm.getSitename() + "', cate='" + bm.getCate() + "' where userid=" + user.getId() + " and id=" + bm.getId() + ";"; } else { sql = "INSERT INTO bookmark VALUES (null,'" + bm.getUrl() + "','" + bm.getSitename() + "','" + bm.getCate() + "','" + bm.getUserid() + "');"; }
如果存在id就执行update语句,不存在就insert into。