haigeek blog


winestory中struts2的使用

使用ssh框架做的一个小论坛是我的第一个项目,ssh框架在现在早已经过时,但是当初学习和解决问题的精神却是最重要的,因此我将相关的博文进行归档,记录并缅怀初学编程时的历程。这一篇是struts2框架相关内容。

winestory中struts2的使用

什么是Struts

Struts直译过来就是支柱,枝干的意思,它实现了基于javaweb应用的Model-View——Controller设计模式的应用框架

Struts的体系结构

一个请求在Struts2框架中的处理大概会经过以下结构步骤

  1. 客户端发出一个指向Servlet容器(例如Tomcat)的请求
  2. 这个请求会经过结构过滤器Filter(ActionContextCleanUP可选过滤器,其他web过滤器如SiteMesh等),最后到达FilterDispatcher过滤器
  3. 接着FilterDispatcher(过滤调度器)过滤器被调用,FilterDispatcher询问ActionMapper来决定这个请是否需要调用某个Action
  4. 如果ActionMapper决定需要调用某个Action,FilterDispatcher把请求的处理交给Action对象的代理(ActionProxy)
  5. ActionProxy通过配置管理器(Configuration Manager)读取框架的相关的配置文件(Struts.xml以及它包含的*.xml配置文件)找到需要调用的Action类
  6. 找到需要调用的Action类之后,ActionProxy会创建一个ActionInvocation(动作调用)的实例
  7. ActionInvocation在调用Action的过程之前,会先依次调用相关配置拦截器(Intercepter)执行结果返回结果字符串
  8. ActionInvocation负责查找结果字符串对应的Result。然后再执行这个Result,再返回对应的结果视图(如JSP)来呈现界面
  9. 再次调用所用的配置拦截器(调用顺序与第7步相反),然后响应(HttpServletResponse)被返回给浏览器

Struts2的优点

  • Struts2是非侵入式设计,即不依赖与Servlet API和StrutsAPI
  • Strtus2提供了强大的拦截器,利用拦截器可进行AOP编程(面向切面的编程),实现权限的拦截等功能
  • Strtus2提供了类型转换器,可以很方便地进行类型转换,例如将特殊的的请求参数转换成需要的类型
  • Struts2支持多种表现层技术,如JSP、FreeMarker、Vectocity
  • Struts2的输入验证可以对指定的方法进行验证笔记来源:实验楼

struts2核心配置文件struts.xml详解

struts.xml文件示例

 1<?xml version="1.0" encoding="UTF-8" ?>
 2<!DOCTYPE struts PUBLIC
 3    "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
 4    "http://struts.apache.org/dtds/struts-2.3.dtd">
 5<struts>
 6    <constant name="struts.enable.DynamicMethodInvocation" value="false" />
 7    <constant name="struts.devMode" value="true" />
 8    <package name="default" namespace="/" extends="struts-default">
 9        <default-action-ref name="index" />
10        <global-results>
11            <result name="error">/WEB-INF/jsp/error.jsp</result>
12        </global-results>
13        <global-exception-mappings>
14            <exception-mapping exception="java.lang.Exception" result="error"/>
15        </global-exception-mappings>
16        <action name="index">
17            <result type="redirectAction">
18                <param name="actionName">HelloWorld</param>
19                <param name="namespace">/example</param>
20            </result>
21        </action>
22    </package>
23    <include file="example.xml"/>
24</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参数信息

  1. 因为方法不是静态的方法,需要创建ActionContext类的对象
  2. ActionContext对象不是new出来的 static ActionContext getContext() 获取当前线程的ActionContext对象
  3. 实例
 1public String execute() throws Exception {
 2        //第一种方式:使用ActionContext类获取
 3        //获取ActionContext对象
 4        ActionContext context=ActionContext.getContext();
 5        //调用方法得到表单数据
 6        Map<String, Object> map = context.getParameters();
 7        Set<String> keys=map.keySet();
 8        //根据key得到vaule,使用数组形式是因为输入项可能有复选框
 9        for(String key:keys){
10            Object[] obj=(Object[]) map.get(key);
11            System.out.println(Arrays.toString(obj));
12        }
13        return NONE;
14    }

2.使用ServletActionContext类

 1public String execute() throws Exception {
 2        //使用ServletActionContext获取request对象
 3        HttpServletRequest request=ServletActionContext.getRequest();
 4        //调用request里面的方法得到结果
 5        String username=request.getParameter("username");
 6        String password=request.getParameter("password");
 7        String address=request.getParameter("address");
 8        System.out.println(username+" "+password+" "+address);
 9        return NONE;
10    }

3.使用接口注入方式

在action操作域对象

 1//操作三个域对象
 2        //1.request域
 3        HttpServletRequest request=ServletActionContext.getRequest();
 4        request.setAttribute("req","reqvalue" );
 5        //2.session域
 6        HttpSession session=request.getSession();
 7        session.setAttribute("sess", "sessValue");
 8        //3ServletContext域
 9        ServletContext context=ServletActionContext.getServletContext();
10        context.setAttribute("contextname", "contextValue");

struts2提供获取表单数据方式

1.原始方式获取表单封装到实体类对象

 1public String execute() throws Exception {
 2        HttpServletRequest request=ServletActionContext.getRequest();
 3        String username=request.getParameter("username");
 4        String password=request.getParameter("password");
 5        String address=request.getParameter("address");
 6        //封装到实体类
 7        User user=new User();
 8        user.setUsername(username);
 9        user.setPassword(password);
10        user.setAddress(address);
11        System.out.println(user);
12        return NONE;
13    }

2.属性封装

  1. 直接把表单提交属性封装到action属性中
  2. 实现步骤
  3. 在action成员变量位置定义变量,变量名称和表单输入项的name属性值一样
  4. 生成变量的get和set方法
  5. 使用属性封装获取表单数据到属性里面,不能直接把数据封装到实体类对象里面
  6. 实例
 1public class sysAction extends ActionSupport{    
 2    private String username;    
 3   
 4    public String login() throws Exception {    
 5        System.out.println(username);    
 6        return SUCCESS;    
 7    }    
 8   
 9    public String getUsername() {    
10        return username;    
11    }    
12    public void setUsername(String username) {    
13        this.username= username;    
14    }    
15}

3.模型驱动封装,对于要传入多个model,第二种方式不方便

  1. 可以直接把表单数据封装到实体类对象
  2. 实现步骤
  3. action实现接口ModelDriven
  4. 实现接口里面的方法getModel方法
  5. 在action里面创建实体类对象
 1public class DataDemo2Action extends ActionSupport implements ModelDriven<User>{
 2    //创建对象
 3    private User user=new User();
 4    public User getModel() {
 5        // 返回创建的对象
 6        return user;
 7    }
 8    public String execute() throws Exception {
 9        
10        return NONE;
11    }
12}

4.可以使用多个module实体对象的封装

此种封装方法,变量名称和表单输入项的name属性值不一样,变单输入项样式为

1<form action="sys/login.action" method="post">    
2    <input type="text" name="user.username">    
3    <input type="text" name="teacher.level">    
4    <input type="submit" value="submit">    
5</form>
 1public class sysAction extends ActionSupport{    
 2    private User user;    
 3    private Teacher teacher;    
 4   
 5    public String login() throws Exception {    
 6        System.out.println(user.getUsername());    
 7        System.out.println(teacher.getLevel());    
 8        return SUCCESS;    
 9    }    
10   
11    public void setUser(User user) {    
12        this.user = user;    
13    }    
14    public void setTeacher(Teacher teacher) {    
15        this.teacher = teacher;    
16    }    
17}

封装数据到集合

封装数据到list集合

  1. 在action声明List
  2. 生成list变量的get和set方法
  3. 在表单输入项写表达式

封装数据到map集合

  1. 在action声明map集合
  2. 生成list变量的get和set方法
  3. 在表单输入项写表达式

struts2结果页面配置

action的访问

  • 使用method方法
  • 使用通配符实现 在action标签里面name属性,name属性值写符号 * ,表示匹配任何内容
  • 动态访问

结果页面配置

全局结果页面

  1. 如果多个action,方法里面返回相同值的时候,可以使用全局结果界面配置

局部结果页面配置

  1. 当存在全局结果页面配置时,依旧以局部结果页面配置为准

result便签的type属性

  1. type属性:如何到路径里
  2. type属性值:
  • 默认值:转发操作,值是dispatcher
  • 重定向:redirect
  • chain:转发到action
  • redirectAction:重定向到action

实例

 1<?xml version="1.0" encoding="UTF-8"?>
 2<!DOCTYPE struts PUBLIC
 3        "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
 4        "http://struts.apache.org/dtds/struts-2.3.dtd">
 5<struts>
 6    <package name="demo1" extends="struts-default" namespace="/">
 7    <action name="user_*" class="userAction" method="{1}">
 8        <!-- <result name="loginsuccess">/index.jsp</result> -->
 9        <result name="loginsuccess" type="redirectAction">story_indexlist</result>
10        <result name="login">/login.jsp</result>
11         <result name="logout" type="redirectAction">story_indexlist</result>
12        <result name="registersuccess">/login.jsp</result>
13        <result name="usershow">/myinfo.jsp</result>
14        <result name="showUserinfo">/myinfoedit.jsp</result>
15        <result name="update" type="redirectAction">story_indexlist</result>
16    </action>
17    <action name="story_*" class="storyAction" method="{1}">
18        <result name="toAddPage">/newstory.jsp</result>
19        <result name="add" type="redirectAction">story_indexlist</result>
20        <result name="indexlist">/index.jsp</result>
21        <result name="storyshow">/storyshow.jsp</result>
22        <result name="getAllStory">/showbypage.jsp</result>
23        <result name="addComment">/showbypage.jsp</result>
24    </action>
25        <action name="comment_*" class="commentAction" method="{1}">
26            <result name="add" type="redirectAction">story_show?story_id=${comment.story.story_id}</result>
27        </action>
28    </package>
29</struts>

struts2中的重定向问题

在处理ssh框架的小论坛项目的时候,遇到一个问题,在一个帖子下需要进行评论,但是在点击评论按钮的时候,后台做出这几个动作:

  1. 存储评论信息到数据库,也就是comment实体;
  2. 重新加载帖子详情和评论详情 要实现以上需求,可以通过struts的重定向功能来实现

struts重定向的几种方式

  • dispatcher —— 请求转发到一个页面 (默认),不可以用这种方式转发到一个action
  • chain —— 一个action请求转发至另一个 action
  • redirect —— 响应重定向到一个页面,也可以实现响应重定向到action
  • redirectAction —— 一个action响应重定向至另一个 action

struts重定向的实例

  • redirectAction —— 一个action响应重定向至另一个 action,这种是不带参数的
1<result name="loginsuccess" type="redirectAction">story_indexlist</result>
  • 带参数的redirectAction
1<result type="redirect">successAction?name=${name}</result>

我的问题解决

我的项目的重定向就需要参数,在重现加载帖子和评论的时候需要知道帖子的id,具体的解决方案如下

1<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如下
 1package cn.haigeek.action;
 2import cn.haigeek.entity.Comment;
 3import cn.haigeek.service.CommentService;
 4import com.opensymphony.xwork2.ActionSupport;
 5import java.util.Date;
 6/**
 7 * Created by haigeek on 2017/7/22.
 8 */
 9public class CommentAction extends ActionSupport  {
10    Date date = new Date();
11    private Comment comment;
12    private CommentService commentService;
13    //private Integer storyId;
14    public void setCommentService(CommentService commentService) {
15        this.commentService = commentService;
16    }
17    public Comment getComment() {
18        return comment;
19    }
20    public void setComment(Comment comment) {
21        this.comment = comment;
22    }
23    public String add(){
24        comment.setCommentDate(date);
25        commentService.add(comment);
26        return "add";
27    }
28}

struts2文件上传(以上传头像为例)

在java的ssh项目中,需要为注册的用户设置一个头像,具体解决方案如下

jsp视图层

 1<form action="${pageContext.request.contextPath }/user_updateavatar.action?user.usid=${user.uid}"
 2        method=post enctype="multipart/form-data">
 3    <input type="hidden" name="user.uid" value="${user.uid }"/>
 4    <div class="panel panel-default">
 5        <div class="panel-body">
 6            <br>
 7            <div class="mdui-card-header-title">头像上传</div>
 8            <div class="mdui-container">
 9                <input type="file" name="avatar">
10                <button class="mdui-btn mdui-btn-dense mdui-color-blue mdui-float-right mdui-m-a-2">
11                    保存
12                </button>
13            </div>
14        </div>
15    </div>
16</form>

input类型要设置为type为File类型,name需要对应action中的命名

action层

 1private File avatar;//上传文件域
 2private String avatatFileType;//上传文件类型
 3private String avatarFileName;//上传文件名字
 4private String savePathAvatar;//文件存储位置,在struts2中配置
 5//对应的get和set方法
 6public String updateavatar(){
 7  //文件在本地的具体存储位置
 8        String path=ServletActionContext.getServletContext().getRealPath("/")+getSavePathAvatar()+"\\"+getAvatarFileName();
 9  //文件存储到数据库的路径(根据实际情况来设置)
10        String path2=getSavePathAvatar()+"/"+getAvatarFileName();
11        try{
12            FileOutputStream fos=new FileOutputStream(path);
13            FileInputStream fis=new FileInputStream(getAvatar());
14            byte[]buffer=new byte[1024];
15            int len=0;
16            try {
17                while ((len=fis.read(buffer))>0){
18                    fos.write(buffer,0,len);
19                }
20            } catch (IOException e) {
21                e.printStackTrace();
22            }
23        } catch (FileNotFoundException e) {
24            e.printStackTrace();
25        }
26  //接受页面传过来的用户id
27        int uid=user.getUid();
28        User user4=userService.findOne(uid);
29        user4.setAvatar(path2);
30  //调用service进行路径的存储
31        userService.update(user4);
32        return "update";
33    }

Struts.xml的配置

1<action name="user_*" class="userAction" method="{1}">
2  <!-- 动态设置存储位置的值 -->  
3<param name="savePathAvatar">/upload/avatar</param>
4</action>

显示

1<img src="${story.user.avatar}"/></div>

我的项目是显示这个story对应的用户的头像

ssh框架实现分页显示

目的

最近在写的一个java小论坛的首页想要实现首页帖子列表的分页显示

实现

分页类

  1package cn.haigeek.entity;
  2
  3/**
  4 * Created by haigeek on 2017/7/16.
  5 */
  6public class pageShow {
  7    //此类用于分页
  8    private int pageNow;//当前页
  9    private int totalSize;//总条数
 10    private int totalPage;//总页数
 11    private int pageSize = 10;//每页显示条数
 12    private boolean hasPre;//是否有上一页
 13    private boolean hasNext;//是否有下一页
 14    private boolean hasFirst;//是否有首页
 15    private boolean hasLast;//是否有尾页
 16
 17    public pageShow(int pageNow, int totalSize) {
 18        //构造方法
 19        this.setPageNow(pageNow);
 20        this.setTotalSize(totalSize);
 21    }
 22
 23    public pageShow(int pageNow, int totalSize, int pageSize) {//可动态改变每页条数
 24        //构造方法
 25        this.setPageNow(pageNow);
 26        this.setTotalSize(totalSize);
 27        this.pageSize = pageSize;
 28    }
 29
 30    public void setPageNow(int pageNow) {
 31        this.pageNow = pageNow;
 32    }
 33
 34    public int getPageNow() {
 35        return pageNow;
 36    }
 37
 38    public void setTotalSize(int totalSize) {
 39        this.totalSize = totalSize;
 40    }
 41
 42    public int getTotalSize() {
 43        return totalSize;
 44    }
 45
 46    public void setPageSize(int pageSize) {
 47        this.pageSize = pageSize;
 48    }
 49
 50    public int getPageSize() {
 51        return pageSize;
 52    }
 53
 54    public int getTotalPage() {//总页数 = 总条数/每页显示条数
 55        totalPage = this.getTotalSize() / this.getPageSize();
 56        if (this.getTotalSize() % this.getPageSize() != 0) {
 57            totalPage++; //若余数为不0 则要多加一页
 58        }
 59        return totalPage;
 60    }
 61
 62    public void setTotalPage(int totalPage) {
 63        this.totalPage = totalPage;
 64    }
 65
 66    public boolean isHasPre() {//是否有上一页   除第一页以外都有上一页  说明有首页的就有上一页
 67        if (this.isHasFirst()) {
 68            return true;
 69        } else return false;
 70    }
 71
 72    public void setHasPre(boolean hasPre) {
 73        this.hasPre = hasPre;
 74    }
 75
 76    public boolean isHasNext() {//是否有下一页   除最后一页以外都有下一页  说明有尾页的就有下一页
 77        if (this.isHasLast()) {
 78            return true;
 79        } else return false;
 80    }
 81
 82    public void setHasNext(boolean hasNext) {
 83        this.hasNext = hasNext;
 84    }
 85
 86    public boolean isHasFirst() {//是否有首页 除第一页以外都有首页
 87        if (this.pageNow == 1)//是第一页就没有首页
 88            return false;
 89        else
 90            return true;
 91    }
 92
 93    public void setHasFirst(boolean hasFirst) {
 94        this.hasFirst = hasFirst;
 95    }
 96
 97    public boolean isHasLast() {//是否有尾页  除最后一页以外都有尾页
 98        if (pageNow == this.getTotalPage()) {//最后一页
 99            return false;
100        } else return true;
101    }
102
103    public void setHasLast(boolean hasLast) {
104        this.hasLast = hasLast;
105    }
106}

编写action

 1public String getAllStory(){
 2		List <Story>StoryList=storyService.getAllStory(pageNow,pageSize);
 3  			//放进域对象
 4			ServletActionContext.getRequest().setAttribute("StoryList", StoryList);
 5  			//放入request对象
 6			Map request= (Map) ActionContext.getContext().get("request");
 7			pageShow page=new pageShow(pageNow,storyService.findStorySize(),pageSize);
 8			request.put("page",page);
 9			return "getAllStory";
10		}

编写service层

1	//service层
2public List <Story>getAllStory(int page,int pageSize){
3        return storyDao.getAllStory(page,pageSize);
4    }
5    //获取帖子数目
6 public int findStorySize(){
7        return storyDao.findStorySize();
8  	}

Dao接口层

1    int findStorySize();
2
3    List <Story>getAllStory(int page, int pageSize);

Dao层的实现层

 1public class StoryDaoImpl extends HibernateDaoSupport implements StoryDao {
 2public int findStorySize() {
 3        Session session=getSessionFactory().openSession();
 4        String hql = "from Story ";
 5        int size = session.createQuery(hql).list().size();
 6        session.close();
 7        return size;
 8    }
 9    public List<Story> getAllStory(int pageNow, int pageSize) {
10    //获取session的方式很重要,这里的dao层是继承了HibernateDaoSupport来实现dao接口
11        Session session=this.getHibernateTemplate().getSessionFactory().getCurrentSession();
12        String hql="from Story order by story_id desc ";
13        Query query = session.createQuery(hql);//执行查询操作
14        query.setFirstResult((pageNow - 1) * pageSize);
15        query.setMaxResults(pageSize);
16        List <Story> StoryList = query.list();
17        return StoryList;
18
19    }
20 }

视图层

<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">&laquo;</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">&raquo;</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添加一项配置即可