JSP模板继承功能实现

 背景

最近刚入职新公司,浏览一下新公司项目,发现项目中大多数JSP页面都是独立的、完整的页面,所以许多页面都会有以下重复的代码:javascript

复制代码
<%@ 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隐患。
举两个“栗子”:css

  • 若是从此开发过程当中咱们须要全局引入、删除一些公共的脚本(例如在线客服图标、GA分析脚本等),变动一下jQuery的版本,更改DocType类型为Html5类型等等。要完成相似的需求咱们必须逐个修改JSP文件,工做量就会与项目中JSP文件数量成正比。
  • 更麻烦的问题是,对于上述这些全局操做咱们没法保证代码是不是在全部页面上都生效了,手工检查?呵呵...

解决方案

上面扯了那么多,其实核心问题就是全部的jsp页面都是各自为战,没有一个统一的公共的模板来维护一些全局的信息,因此这里就介绍一下咱们之前的实现方案:html

  1. 实现JSP文件的模板功能、让全部的页面都引入一个公共的模板。
  2. 公共部分信息直接在模板中维护,可变部分在模板中定义占位符,而后由页面进行重写来维护不一样页面的多样性。

有了模板之后就能够这样写页面了:java

      

这样的写法好处显而易见:jquery

  • 首先,页面结构一目了然,写页面时无须再关注内容之外的公共部分,减小了许多copy代码的工做量,同时也下降出错率
  • 其次,公共样式、脚本等都在模板中引入,便于统一调整

模板内容大概是这个样子的:web

      

实现原理

实现原理其实很简单,模板功能的实现主要是两个自定义标签(自定义标签的开发步骤这里就不讲了)架构

BlockTag

该标签主要用于在模板文件中定义相应的模块(能够看作一个占位符),在渲染JSP页面时会将标签订义的位置替换为页面重写的内容,替换时根据标签的name属性加上特定的前缀做为key值从request的attribute中读取内容。jsp

复制代码
/** * 自定义标签,用于在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; } }
复制代码

OverwriteTag

该标签主要用于在最终的页面上重写模板中的相应模块,在页面渲染时将标签内部的内容写入到当前request的attribute中,该标签有一个必填参数name属性做为该内容的key值,这个name属性必需要和模板中对应要重写的block的name值相同。ide

复制代码
/** * 自定义标签,用于在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; } }
复制代码

总结与拓展

  1. 全部页面都使用了模板之后,就能够很方便的控制项目全局的样式、脚本,因为屏蔽了许多页面公共信息,也使得平常页面开发更加高效并减小错误率。
  2. JSP原生是不支持模板机制的,可是仅仅稍加一些手段使用两个自定义标签就能够实现模板功能,减小了许多重复的工做量。所以,工做过程当中的痛点每每也是我的得到成长的机会。
  3. 我在上面Demo中只简单定义了一个base_template.jsp这一个模板,可是实际场景中一个网站可能有许多布局风格不一样类型的页面,那么一个模板显然不能知足多样性的布局要求,这时咱们就能够给模板进行分级将模板定义为base,common,channel三个级别,抽象程度从高到低,实现channel->common->base的继承关系,不一样风格的页面只须要引入对应的channel模板便可,具体如何抽象还需根据实际的场景区别对待。
相关文章
相关标签/搜索