使用ssh框架做的一个小论坛是我的第一个项目,ssh框架在现在早已经过时,但是当初学习和解决问题的精神却是最重要的,因此我将相关的博文进行归档,记录并缅怀初学编程时的历程。这一篇是struts2框架相关内容。
winestory中struts2的使用 🔗
什么是Struts 🔗
Struts直译过来就是支柱,枝干的意思,它实现了基于javaweb应用的Model-View——Controller设计模式的应用框架
Struts的体系结构 🔗
一个请求在Struts2框架中的处理大概会经过以下结构步骤
- 客户端发出一个指向Servlet容器(例如Tomcat)的请求
- 这个请求会经过结构过滤器Filter(ActionContextCleanUP可选过滤器,其他web过滤器如SiteMesh等),最后到达FilterDispatcher过滤器
- 接着FilterDispatcher(过滤调度器)过滤器被调用,FilterDispatcher询问ActionMapper来决定这个请是否需要调用某个Action
- 如果ActionMapper决定需要调用某个Action,FilterDispatcher把请求的处理交给Action对象的代理(ActionProxy)
- ActionProxy通过配置管理器(Configuration Manager)读取框架的相关的配置文件(Struts.xml以及它包含的*.xml配置文件)找到需要调用的Action类
- 找到需要调用的Action类之后,ActionProxy会创建一个ActionInvocation(动作调用)的实例
- ActionInvocation在调用Action的过程之前,会先依次调用相关配置拦截器(Intercepter)执行结果返回结果字符串
- ActionInvocation负责查找结果字符串对应的Result。然后再执行这个Result,再返回对应的结果视图(如JSP)来呈现界面
- 再次调用所用的配置拦截器(调用顺序与第7步相反),然后响应(HttpServletResponse)被返回给浏览器
Struts2的优点 🔗
- Struts2是非侵入式设计,即不依赖与Servlet API和StrutsAPI
- Strtus2提供了强大的拦截器,利用拦截器可进行AOP编程(面向切面的编程),实现权限的拦截等功能
- Strtus2提供了类型转换器,可以很方便地进行类型转换,例如将特殊的的请求参数转换成需要的类型
- Struts2支持多种表现层技术,如JSP、FreeMarker、Vectocity
- Struts2的输入验证可以对指定的方法进行验证笔记来源: 实验楼
struts2核心配置文件struts.xml详解 🔗
struts.xml文件示例 🔗
<?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="false" />
<constant name="struts.devMode" value="true" />
<package name="default" namespace="/" extends="struts-default">
<default-action-ref name="index" />
<global-results>
<result name="error">/WEB-INF/jsp/error.jsp</result>
</global-results>
<global-exception-mappings>
<exception-mapping exception="java.lang.Exception" result="error"/>
</global-exception-mappings>
<action name="index">
<result type="redirectAction">
<param name="actionName">HelloWorld</param>
<param name="namespace">/example</param>
</result>
</action>
</package>
<include file="example.xml"/>
</struts>
constant 🔗
包含一些属性设置,他可以改变struts框架的一些行为。例如示例中的 struts.enable.DynamicMethodInvocation 设为true,表示设置动态方法调用为真,而 struts.devMode 表示是否启用开发者模式。
package 🔗
在struts中,package用来管理action、result、interceptor、interceptor-stack等配置信息
- name:必须唯一,这样其他package如果引用本package的话,才能找得到。
- extends:当本package继承其他package的时候,会继承父package的所有配置属性(例如action、result等等);由于package的信息获取是按照struts.xml文件中的先后顺序进行的,因此父package必须在子package之前先定义。通常情况下,继承一个“struts-default.xml”的package,这是 Struts2 默认的package。namespace:namespace的配置会改变项目的url访问地址,主要是针对比较大型的项目以方便管理action,因为不同namespace中的action可以同名,从而解决action重名的问题。如果没有指定namespace,则默认为“”。
action 🔗
- name:action的名称
- class:action对应的java类
- method:在该class中对应执行Action的函数方法,默认是execute()。
- converter:类型转换器
result 🔗
- name:具体来说,就是根据某个返回结果,指定响应逻辑,默认是success。
- type:返回结果的类型,默认为dispatcher。
default-action-ref 🔗
如果找不到项目请求的action,就会报出404错误,而且这种错误不可避免,所以我们可以使用 default-action-ref 来指定一个默认的action,如果系统出现找不到action的情况,就会来调用这个默认的action。
global-results 🔗
设置package范围内的全局响应结果。在多个action都返回同一个逻辑视图(通常为某个jsp页面)的情况下,可以通过该标签来统一配置。
global-exception-mapping 🔗
配置发生异常时的视图信息。exception-mapping是控制action范围内的,而global-exception-mapp是控制package范围内的。两个都配置时,exception-mapping的优先级更高。
include 🔗
使用include引入外部配置文件,直接给出url即可
<include file="**/**/***.xml" />
使用 include 的好处在于,例如当我们开发一个比较大型的项目的时候,配置文件肯定会写一大堆。如果写在一个配置文件里就不好查看和修改,不便于维护;所以使用 include 后可以根据模块、也可以根据功能来划分,这样就比较清晰,方便管理和维护。其他较为常用的还有 拦截器等等。来源:实验楼 https://www.shiyanlou.com/courses/32/labs/920/document
struts2中action接收参数的三种方法 🔗
struts2中action接收参数的方法主要有一下三种
1.使用action 中的属性接收参数 🔗
- 定义:在action类中定义属性,创建get和set方法
- 接收:通过属性接收参数,如userId;
- 发送:通过属性名传递参数,如:user_usershow?userId=1;
2.使用DomainModel接收参数: 🔗
- 定义:定义Model类,在action中定义Model类的对象(不需要new),创建该对象的get和set方法
- 接收:通过对象的属性接收参数,如user.getUserName();
- 发送:使用对象的属相传递参数,如user_usershow?user.user.userName=xxx;
3使用ModelDriven接收参数 🔗
- 定义:action实现ModelDriven泛型接口,定义Model类的对象(必须new),通过getModel方法返回该对象;
- 接收:通过对象的属性接收参数,如:user.getUserName();
- 发送:直接使用属性名传递参数,如:user_usershow?userName=xxx
Action获取表单提交数据 🔗
获取表单提交数据的三种主要方式 🔗
1.使用ActionContext 🔗
Map<String,Object>getParameters() 返回一个包含所有HttpServletRequest参数信息
- 因为方法不是静态的方法,需要创建ActionContext类的对象
- ActionContext对象不是new出来的 static ActionContext getContext() 获取当前线程的ActionContext对象
- 实例
public String execute() throws Exception {
//第一种方式:使用ActionContext类获取
//获取ActionContext对象
ActionContext context=ActionContext.getContext();
//调用方法得到表单数据
Map<String, Object> map = context.getParameters();
Set<String> keys=map.keySet();
//根据key得到vaule,使用数组形式是因为输入项可能有复选框
for(String key:keys){
Object[] obj=(Object[]) map.get(key);
System.out.println(Arrays.toString(obj));
}
return NONE;
}
2.使用ServletActionContext类 🔗
public String execute() throws Exception {
//使用ServletActionContext获取request对象
HttpServletRequest request=ServletActionContext.getRequest();
//调用request里面的方法得到结果
String username=request.getParameter("username");
String password=request.getParameter("password");
String address=request.getParameter("address");
System.out.println(username+" "+password+" "+address);
return NONE;
}
3.使用接口注入方式 🔗
在action操作域对象 🔗
//操作三个域对象
//1.request域
HttpServletRequest request=ServletActionContext.getRequest();
request.setAttribute("req","reqvalue" );
//2.session域
HttpSession session=request.getSession();
session.setAttribute("sess", "sessValue");
//3ServletContext域
ServletContext context=ServletActionContext.getServletContext();
context.setAttribute("contextname", "contextValue");
struts2提供获取表单数据方式 🔗
1.原始方式获取表单封装到实体类对象 🔗
public String execute() throws Exception {
HttpServletRequest request=ServletActionContext.getRequest();
String username=request.getParameter("username");
String password=request.getParameter("password");
String address=request.getParameter("address");
//封装到实体类
User user=new User();
user.setUsername(username);
user.setPassword(password);
user.setAddress(address);
System.out.println(user);
return NONE;
}
2.属性封装 🔗
- 直接把表单提交属性封装到action属性中
- 实现步骤
- 在action成员变量位置定义变量,变量名称和表单输入项的name属性值一样
- 生成变量的get和set方法
- 使用属性封装获取表单数据到属性里面,不能直接把数据封装到实体类对象里面
- 实例
public class sysAction extends ActionSupport{
private String username;
public String login() throws Exception {
System.out.println(username);
return SUCCESS;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username= username;
}
}
3.模型驱动封装,对于要传入多个model,第二种方式不方便 🔗
- 可以直接把表单数据封装到实体类对象
- 实现步骤
- action实现接口ModelDriven
- 实现接口里面的方法getModel方法
- 在action里面创建实体类对象
public class DataDemo2Action extends ActionSupport implements ModelDriven<User>{
//创建对象
private User user=new User();
public User getModel() {
// 返回创建的对象
return user;
}
public String execute() throws Exception {
return NONE;
}
}
4.可以使用多个module实体对象的封装 🔗
此种封装方法,变量名称和表单输入项的name属性值不一样,变单输入项样式为
<form action="sys/login.action" method="post">
<input type="text" name="user.username">
<input type="text" name="teacher.level">
<input type="submit" value="submit">
</form>
public class sysAction extends ActionSupport{
private User user;
private Teacher teacher;
public String login() throws Exception {
System.out.println(user.getUsername());
System.out.println(teacher.getLevel());
return SUCCESS;
}
public void setUser(User user) {
this.user = user;
}
public void setTeacher(Teacher teacher) {
this.teacher = teacher;
}
}
封装数据到集合 🔗
封装数据到list集合 🔗
- 在action声明List
- 生成list变量的get和set方法
- 在表单输入项写表达式
封装数据到map集合 🔗
- 在action声明map集合
- 生成list变量的get和set方法
- 在表单输入项写表达式
struts2结果页面配置 🔗
action的访问 🔗
- 使用method方法
- 使用通配符实现 在action标签里面name属性,name属性值写符号 * ,表示匹配任何内容
- 动态访问
结果页面配置 🔗
全局结果页面 🔗
- 如果多个action,方法里面返回相同值的时候,可以使用全局结果界面配置
局部结果页面配置 🔗
- 当存在全局结果页面配置时,依旧以局部结果页面配置为准
result便签的type属性 🔗
- type属性:如何到路径里
- type属性值:
- 默认值:转发操作,值是dispatcher
- 重定向:redirect
- chain:转发到action
- redirectAction:重定向到action
实例 🔗
<?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>
<package name="demo1" extends="struts-default" namespace="/">
<action name="user_*" class="userAction" method="{1}">
<!-- <result name="loginsuccess">/index.jsp</result> -->
<result name="loginsuccess" type="redirectAction">story_indexlist</result>
<result name="login">/login.jsp</result>
<result name="logout" type="redirectAction">story_indexlist</result>
<result name="registersuccess">/login.jsp</result>
<result name="usershow">/myinfo.jsp</result>
<result name="showUserinfo">/myinfoedit.jsp</result>
<result name="update" type="redirectAction">story_indexlist</result>
</action>
<action name="story_*" class="storyAction" method="{1}">
<result name="toAddPage">/newstory.jsp</result>
<result name="add" type="redirectAction">story_indexlist</result>
<result name="indexlist">/index.jsp</result>
<result name="storyshow">/storyshow.jsp</result>
<result name="getAllStory">/showbypage.jsp</result>
<result name="addComment">/showbypage.jsp</result>
</action>
<action name="comment_*" class="commentAction" method="{1}">
<result name="add" type="redirectAction">story_show?story_id=${comment.story.story_id}</result>
</action>
</package>
</struts>
struts2中的重定向问题 🔗
在处理ssh框架的小论坛项目的时候,遇到一个问题,在一个帖子下需要进行评论,但是在点击评论按钮的时候,后台做出这几个动作:
- 存储评论信息到数据库,也就是comment实体;
- 重新加载帖子详情和评论详情 要实现以上需求,可以通过struts的重定向功能来实现
struts重定向的几种方式 🔗
- dispatcher —— 请求转发到一个页面 (默认),不可以用这种方式转发到一个action
- chain —— 一个action请求转发至另一个 action
- redirect —— 响应重定向到一个页面,也可以实现响应重定向到action
- redirectAction —— 一个action响应重定向至另一个 action
struts重定向的实例 🔗
- redirectAction —— 一个action响应重定向至另一个 action,这种是不带参数的
<result name="loginsuccess" type="redirectAction">story_indexlist</result>
- 带参数的redirectAction
<result type="redirect">successAction?name=${name}</result>
我的问题解决 🔗
我的项目的重定向就需要参数,在重现加载帖子和评论的时候需要知道帖子的id,具体的解决方案如下
<result name="add" type="redirectAction">story_show?story_id=${comment.story.story_id}</result>
在comment的action中,重定向,并获取参数,这里可能有问题的就是参数的获取,这个参数的获取和action方法中的属性获取有关系,可能有以下的集中情况
- 根据你的action获取表单数据方式的不同,${}里面的参数书写方式也不同,如果是属性,并有getter和setter方法,那么可以直接写属性值,如果是其他的方式获取,则要根据你的情况来设置
- 如果这个参数是实体内的实体对象,比如我需要的storyId,则是comment实体内的一个story对象,在这个story对象里具有storyId这个值,那么就需要使用上面的方式
${comment.story.story_id}
如果使用${comment.story_id}
是会报错 我的comment Action如下
package cn.haigeek.action;
import cn.haigeek.entity.Comment;
import cn.haigeek.service.CommentService;
import com.opensymphony.xwork2.ActionSupport;
import java.util.Date;
/**
* Created by haigeek on 2017/7/22.
*/
public class CommentAction extends ActionSupport {
Date date = new Date();
private Comment comment;
private CommentService commentService;
//private Integer storyId;
public void setCommentService(CommentService commentService) {
this.commentService = commentService;
}
public Comment getComment() {
return comment;
}
public void setComment(Comment comment) {
this.comment = comment;
}
public String add(){
comment.setCommentDate(date);
commentService.add(comment);
return "add";
}
}
struts2文件上传(以上传头像为例) 🔗
在java的ssh项目中,需要为注册的用户设置一个头像,具体解决方案如下
jsp视图层 🔗
<form action="${pageContext.request.contextPath }/user_updateavatar.action?user.usid=${user.uid}"
method=post enctype="multipart/form-data">
<input type="hidden" name="user.uid" value="${user.uid }"/>
<div class="panel panel-default">
<div class="panel-body">
<br>
<div class="mdui-card-header-title">头像上传</div>
<div class="mdui-container">
<input type="file" name="avatar">
<button class="mdui-btn mdui-btn-dense mdui-color-blue mdui-float-right mdui-m-a-2">
保存
</button>
</div>
</div>
</div>
</form>
input类型要设置为type为File类型,name需要对应action中的命名
action层 🔗
private File avatar;//上传文件域
private String avatatFileType;//上传文件类型
private String avatarFileName;//上传文件名字
private String savePathAvatar;//文件存储位置,在struts2中配置
//对应的get和set方法
public String updateavatar(){
//文件在本地的具体存储位置
String path=ServletActionContext.getServletContext().getRealPath("/")+getSavePathAvatar()+"\\"+getAvatarFileName();
//文件存储到数据库的路径(根据实际情况来设置)
String path2=getSavePathAvatar()+"/"+getAvatarFileName();
try{
FileOutputStream fos=new FileOutputStream(path);
FileInputStream fis=new FileInputStream(getAvatar());
byte[]buffer=new byte[1024];
int len=0;
try {
while ((len=fis.read(buffer))>0){
fos.write(buffer,0,len);
}
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
//接受页面传过来的用户id
int uid=user.getUid();
User user4=userService.findOne(uid);
user4.setAvatar(path2);
//调用service进行路径的存储
userService.update(user4);
return "update";
}
Struts.xml的配置 🔗
<action name="user_*" class="userAction" method="{1}">
<!-- 动态设置存储位置的值 -->
<param name="savePathAvatar">/upload/avatar</param>
</action>
显示 🔗
<img src="${story.user.avatar}"/></div>
我的项目是显示这个story对应的用户的头像
ssh框架实现分页显示 🔗
目的 🔗
最近在写的一个java小论坛的首页想要实现首页帖子列表的分页显示
实现 🔗
分页类 🔗
package cn.haigeek.entity;
/**
* Created by haigeek on 2017/7/16.
*/
public class pageShow {
//此类用于分页
private int pageNow;//当前页
private int totalSize;//总条数
private int totalPage;//总页数
private int pageSize = 10;//每页显示条数
private boolean hasPre;//是否有上一页
private boolean hasNext;//是否有下一页
private boolean hasFirst;//是否有首页
private boolean hasLast;//是否有尾页
public pageShow(int pageNow, int totalSize) {
//构造方法
this.setPageNow(pageNow);
this.setTotalSize(totalSize);
}
public pageShow(int pageNow, int totalSize, int pageSize) {//可动态改变每页条数
//构造方法
this.setPageNow(pageNow);
this.setTotalSize(totalSize);
this.pageSize = pageSize;
}
public void setPageNow(int pageNow) {
this.pageNow = pageNow;
}
public int getPageNow() {
return pageNow;
}
public void setTotalSize(int totalSize) {
this.totalSize = totalSize;
}
public int getTotalSize() {
return totalSize;
}
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
public int getPageSize() {
return pageSize;
}
public int getTotalPage() {//总页数 = 总条数/每页显示条数
totalPage = this.getTotalSize() / this.getPageSize();
if (this.getTotalSize() % this.getPageSize() != 0) {
totalPage++; //若余数为不0 则要多加一页
}
return totalPage;
}
public void setTotalPage(int totalPage) {
this.totalPage = totalPage;
}
public boolean isHasPre() {//是否有上一页 除第一页以外都有上一页 说明有首页的就有上一页
if (this.isHasFirst()) {
return true;
} else return false;
}
public void setHasPre(boolean hasPre) {
this.hasPre = hasPre;
}
public boolean isHasNext() {//是否有下一页 除最后一页以外都有下一页 说明有尾页的就有下一页
if (this.isHasLast()) {
return true;
} else return false;
}
public void setHasNext(boolean hasNext) {
this.hasNext = hasNext;
}
public boolean isHasFirst() {//是否有首页 除第一页以外都有首页
if (this.pageNow == 1)//是第一页就没有首页
return false;
else
return true;
}
public void setHasFirst(boolean hasFirst) {
this.hasFirst = hasFirst;
}
public boolean isHasLast() {//是否有尾页 除最后一页以外都有尾页
if (pageNow == this.getTotalPage()) {//最后一页
return false;
} else return true;
}
public void setHasLast(boolean hasLast) {
this.hasLast = hasLast;
}
}
编写action 🔗
public String getAllStory(){
List <Story>StoryList=storyService.getAllStory(pageNow,pageSize);
//放进域对象
ServletActionContext.getRequest().setAttribute("StoryList", StoryList);
//放入request对象
Map request= (Map) ActionContext.getContext().get("request");
pageShow page=new pageShow(pageNow,storyService.findStorySize(),pageSize);
request.put("page",page);
return "getAllStory";
}
编写service层 🔗
//service层
public List <Story>getAllStory(int page,int pageSize){
return storyDao.getAllStory(page,pageSize);
}
//获取帖子数目
public int findStorySize(){
return storyDao.findStorySize();
}
Dao接口层 🔗
int findStorySize();
List <Story>getAllStory(int page, int pageSize);
Dao层的实现层 🔗
public class StoryDaoImpl extends HibernateDaoSupport implements StoryDao {
public int findStorySize() {
Session session=getSessionFactory().openSession();
String hql = "from Story ";
int size = session.createQuery(hql).list().size();
session.close();
return size;
}
public List<Story> getAllStory(int pageNow, int pageSize) {
//获取session的方式很重要,这里的dao层是继承了HibernateDaoSupport来实现dao接口
Session session=this.getHibernateTemplate().getSessionFactory().getCurrentSession();
String hql="from Story order by story_id desc ";
Query query = session.createQuery(hql);//执行查询操作
query.setFirstResult((pageNow - 1) * pageSize);
query.setMaxResults(pageSize);
List <Story> StoryList = query.list();
return StoryList;
}
}
视图层 🔗
<div class="panel-body">
<c:forEach items="${StoryList }" var="story">
<ul class="mdui-list">
<a href="${pageContext.request.contextPath }/story_show.action?story_id=${story.story_id}">
<li class="mdui-list-item mdui-ripple">
<div class="mdui-list-item-avatar"><img src="avatar1.jpg"/></div>
<div class="mdui-list-item-content">
<div class="mdui-list-item-title mdui-list-item-two-line">${story.story_title}</div>
<div class="mdui-list-item-text mdui-list-item-one-line"> ${story.user.username}发表在说天谈地
最后回复:haigeek 2017.06.07
</div>
</div>
</li>
</a>
</ul>
</c:forEach>
<s:set name="page" value="#request.page"></s:set>
<nav aria-label="Page navigation">
<ul class="pagination center-block">
<s:if test="#page.hasPre">
<li>
<a href=story_getAllStory?pageNow=<s:property value="#page.pageNow-1"/>
aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
</s:if>
<s:if test="#page.hasFirst">
<li>
<a href="story_getAllStory?pageNow=1"
aria-label="Previous">
<span aria-hidden="true">首页</span>
</a>
</li>
</s:if>
<li><a href="story_getAllStory?pageNow=1">1</a></li>
<li><a href="story_getAllStory?pageNow=2">2</a></li>
<li><a href="story_getAllStory?pageNow=3">3</a></li>
<li><a href="story_getAllStory?pageNow=4">4</a></li>
<li><a href="story_getAllStory?pageNow=5">5</a></li>
<s:if test="#page.hasLast">
<li>
<a href=story_getAllStory?pageNow=<s:property value="#page.totalPage"/>
aria-label="Previous">
<span aria-hidden="true">尾页</span>
</a>
</li>
</s:if>
<s:if test="#page.hasNext">
<li>
<a href="story_getAllStory?pageNow=<s:property value="#page.pageNow+1" /> "
aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
</s:if>
</ul>
</nav>
</div>
视图使用了一些样式(bootstrap等),主要是标签的使用,需要加入
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib uri="/struts-tags" prefix="s" %>
配置文件 🔗
配置struts.xml和spring配置文件即可,我的项目主要是添加功能,配置文件已经配置好,只贴了核心的代码,最后在struts.xml添加一项配置即可