背景
最近刚入职新公司,浏览一下新公司项目,发现项目中大多数JSP页面都是独立的、完整的页面,因此许多页面都会有如下重复的代码:
<%@ page language="java" contentType="text/html; charset=UTF-8" import="java.util.Calendar" pageEncoding="UTF-8"%><%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%><%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %><%@ taglib uri="/common-tags" prefix="m"%><c:set var="ctx" value="${pageContext.request.contextPath}"></c:set><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="content-type" content="text/html; charset=UTF-8"/><title>${webModule.module.name} ---xxxx</title><meta name="keywords" content="xxxx"/><meta name="description" content="xxxx"/><link rel="stylesheet" href="${ctx}/css/web-bbs.css"/><link rel="stylesheet" href="${ctx}/css/page.css"/><script type="text/javascript" src="${ctx}/js/jquery-1.7.2.min.js"></script><script type="text/javascript" src="${ctx}/js/bbs.js"></script><script type="text/javascript" src="${ctx}/js/webUtil.js"></script><script type="text/javascript" src="${ctx}/js/index.js"></script><script type="text/javascript" src="${ctx}/js/faces.js"></script>
小伙伴们每新添加一个页面,就需要copy一份上面这坨代码,还需要在各自页面重复引入公共的头尾文件(如header.jsp,footer.jsp等)。。。
对于这种开发方式,重复的工作量就不多描述了,更重要的问题是这种架构方式未来会导致更多的维护工作量、甚至是bug隐患。
举两个“栗子”:
上面扯了那么多,其实核心问题就是所有的jsp页面都是各自为战,没有一个统一的公共的模板来维护一些全局的信息,所以这里就介绍一下我们以前的实现方案:
有了模板以后就可以这样写页面了:
这样的写法好处显而易见:
模板内容大概是这个样子的:
实现原理
实现原理其实很简单,模板功能的实现主要是两个自定义标签(自定义标签的开发步骤这里就不讲了)
该标签主要用于在模板文件中定义相应的模块(可以看做一个占位符),在渲染JSP页面时会将标签定义的位置替换为页面重写的内容,替换时根据标签的name属性加上特定的前缀作为key值从request的attribute中读取内容。
/** * 自定义标签,用于在Jsp模板中占位 * * @author 逆风之羽 * */public class BlockTag extends BodyTagSupport { /** * 占位模块名称 */ private String name; private static final long serialVersionUID = 1425068108614007667L; @Override public int doStartTag() throws JspException{ return super.doStartTag(); } @Override public int doEndTag() throws JspException { ServletRequest request = pageContext.getRequest(); //block标签中的默认值 String defaultContent = (getBodyContent() == null)?"":getBodyContent().getString(); String bodyContent = (String) request.getAttribute(OverwriteTag.PREFIX+ name); //如果页面没有重写该模块则显示默认内容 bodyContent = StringUtils.isEmpty(bodyContent)?defaultContent:bodyContent; try { pageContext.getOut().write(bodyContent); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } // TODO Auto-generated method stub return super.doEndTag(); } public String getName() { return name; } public void setName(String name) { this.name = name; }}
该标签主要用于在最终的页面上重写模板中的相应模块,在页面渲染时将标签内部的内容写入到当前request的attribute中,该标签有一个必填参数name属性作为该内容的key值,这个name属性必须要和模板中对应要重写的block的name值相同。
/** * 自定义标签,用于在jsp模板中重写指定的占位内容 * * 基本原理: * 将overwrite标签内容部分添加到ServletRequest的attribute属性中 * 在后续block标签中再通过属性名读取出来,将其渲染到最终的页面上即可 * * @author 逆风之羽 * */public class OverwriteTag extends BodyTagSupport { private static final long serialVersionUID = 5901780136314677968L; //模块名的前缀 public static final String PREFIX = "JspTemplateBlockName_"; //模块名 private String name; @Override public int doStartTag() throws JspException { // TODO Auto-generated method stub return super.doStartTag(); } @Override public int doEndTag() throws JspException { ServletRequest request = pageContext.getRequest(); //标签内容 BodyContent bodyContent = getBodyContent(); request.setAttribute(PREFIX+name, StringUtils.trim(bodyContent.getString())); // TODO Auto-generated method stub return super.doEndTag(); } public String getName() { return name; } public void setName(String name) { this.name = name; } }总结与拓展