在開發 S2SH(Struts2 + Spring + Hibernate)專案時,分頁功能幾乎是每個列表頁面的標配。為了避免在每個 JSP 中重複撰寫分頁邏輯,將其封裝為自訂標籤(Custom Tag)不僅能提升開發效率,還能確保介面風格一致。本文將完整記錄如何從零開始建立一個功能完整、參數靈活的分頁標籤,供團隊內部複用。
在 JSP 頁面引入標籤庫:
<%@ taglib uri="/htdz-tag" prefix="htdz-tag" %>
在需要顯示分頁控件的位置插入標籤:
<htdz-tag:PagerTag
pagesize="${pagesize}"
rowcount="${rowcount}"
currpagenum="${currpagenum}"
action="${action}"
className="button_small" />
參數說明:
button_small)在 WEB-INF/tags/htdz-tag.tld 定義標籤元資料:
<?xml version="1.0" encoding="UTF-8"?>
<taglib xmlns="https://java.sun.com/xml/ns/j2ee"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://java.sun.com/xml/ns/j2ee https://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
version="2.0">
<description>htdz 分頁標籤庫</description>
<display-name>htdz-tag</display-name>
<tlib-version>1.0</tlib-version>
<short-name>htdz-tag</short-name>
<uri>/htdz-tag</uri>
<tag>
<description>分頁控件,支援首頁、上一頁、下一頁、尾頁、頁碼跳轉</description>
<name>PagerTag</name>
<tag-class>com.htdz.util.tag.PagerTag</tag-class>
<body-content>JSP</body-content>
<attribute>
<name>pagesize</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>rowcount</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>currpagenum</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>action</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>className</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
</taglib>
PagerTag.java核心邏輯繼承 TagSupport,在 doStartTag() 中完成參數驗證、總頁數計算、按鈕狀態判斷與 HTML 生成:
public class PagerTag extends TagSupport {
private static final String DEFAULT_BUTTON_CLASS = "button_small";
private static final String DISABLE_BUTTON_CLASS = "button_small_disable";
private int pagesize;
private int rowcount;
private int currpagenum;
private String action;
private String className;
// Setter 方法省略...
@Override
public int doStartTag() throws JspException {
// 1. 參數驗證
validateAttributes();
// 2. 套用預設樣式
if (className == null || className.isEmpty()) {
className = DEFAULT_BUTTON_CLASS;
}
// 3. 計算總頁數
int totalPages = getTotalPages(rowcount);
if (rowcount == 0) {
currpagenum = 1;
totalPages = 1;
}
// 4. 判斷按鈕啟用/停用狀態
boolean noUp = (currpagenum == 1);
boolean noDown = (currpagenum == totalPages);
String btnClass1 = noUp ? DISABLE_BUTTON_CLASS : className;
String btnClass2 = noDown ? DISABLE_BUTTON_CLASS : className;
// 5. 組裝 HTML
StringBuffer html = new StringBuffer();
html.append(currpagenum).append("/").append(totalPages).append("頁");
appendButton(html, "首頁", "first", btnClass1, noUp);
appendButton(html, "上一頁", "up", btnClass1, noUp);
appendButton(html, "下一頁", "down", btnClass2, noDown);
appendButton(html, "尾頁", "last", btnClass2, noDown);
html.append(currpagenum).append("/").append(totalPages).append("頁 ");
html.append("<input type=\"text\" maxlength=\"3\" id=\"text\" size=\"3\" onkeypress=\"return checkInput(event);\" />頁");
html.append("<input class=\"").append(className).append("\" type=\"button\" value=\"GO\" onclick=\"turnPage('to','")
.append(currpagenum).append("','").append(totalPages).append("','").append(action).append("')\" />");
try {
pageContext.getOut().println(html.toString());
} catch (IOException e) {
throw new JspException("分頁標籤輸出失敗: " + e.getMessage());
}
return SKIP_BODY;
}
private void appendButton(StringBuffer html, String label, String direction, String cssClass, boolean disabled) {
html.append("<input class=\"").append(cssClass)
.append("\" type=\"button\" value=\"").append(label)
.append("\" onclick=\"turnPage('").append(direction).append("','")
.append(currpagenum).append("','").append(totalPages).append("','")
.append(action).append("')\" ");
if (disabled) html.append("disabled=\"true\" ");
html.append("/>");
}
private int getTotalPages(int rowcount) {
return (rowcount % pagesize == 0) ? rowcount / pagesize : rowcount / pagesize + 1;
}
private void validateAttributes() throws JspException {
if (pagesize <= 0) throw new JspException("pagesize 必須大於 0");
if (rowcount < 0) throw new JspException("rowcount 不能為負數");
if (currpagenum <= 0) throw new JspException("currpagenum 必須大於 0");
if (action == null || action.isEmpty()) throw new JspException("action 不能為空");
}
}
以 Struts2 Action 為例,在查詢方法中設定分頁所需屬性:
public class UserAction extends ActionSupport {
private UserService userService;
private List<User> users;
private int pagesize = PagerTag.USER_PAGESIZE; // 靜態常數 5
private int rowcount;
private int currpagenum = 1;
private String action = "findUser.action";
public String findUser() {
HttpServletRequest request = ServletActionContext.getRequest();
String pagenum = request.getParameter("pagenum");
if (pagenum != null && !pagenum.isEmpty()) {
currpagenum = Integer.parseInt(pagenum);
}
users = userService.findUser(currpagenum, pagesize);
if (!users.isEmpty()) {
rowcount = userService.getCount();
request.setAttribute("users", users);
request.setAttribute("rowcount", rowcount);
request.setAttribute("currpagenum", currpagenum);
request.setAttribute("pagesize", pagesize);
request.setAttribute("action", action);
return SUCCESS;
} else {
addActionMessage("無記錄!");
return INPUT;
}
}
// Getter/Setter 省略
}
UserService 只負責轉呼叫 DAO:
public List<User> findUser(int pageNum, int pageSize) {
return userDao.findUser(pageNum, pageSize);
}
public int getCount() { return userDao.getCount(); }
UserDao 使用 Hibernate 的 setFirstResult / setMaxResults 實現實體分頁:
public List<User> findUser(int pageNum, int pageSize) {
Session session = getSession();
try {
Query query = session.createQuery("from User");
query.setFirstResult(pageSize * (pageNum - 1));
query.setMaxResults(pageSize);
return query.list();
} finally {
session.close();
}
}
public int getCount() {
Session session = getSession();
try {
Long count = (Long) session.createQuery("select count(id) from User").uniqueResult();
return count.intValue();
} finally {
session.close();
}
}
頁面需引入 turnPage(direction, curr, total, action) 與 checkInput(event) 兩個函式,分別處理翻頁導向與輸入框數字驗證,這裡不再贅述。
透過自訂標籤將分頁邏輯集中化,後續新增列表頁面時,只需在 Action 傳入四個必要屬性,JSP 一行標籤即可完成專業級分頁控件渲染。若專案後期需調整樣式或增加「每頁筆數下拉選單」等功能,僅需修改 PagerTag.java 與 TLD,即可全站生效,極大降低維護成本。