背景

最近刚入职新公司,浏览一下新公司项目,发现项目中大多数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隐患。
举两个“栗子”:

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

解决方案

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

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

有了模板以后就可以这样写页面了:

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

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

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

实现原理

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

BlockTag

该标签主要用于在模板文件中定义相应的模块(可以看做一个占位符),在渲染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;
}
}

BlockTag代码

OverwriteTag

该标签主要用于在最终的页面上重写模板中的相应模块,在页面渲染时将标签内部的内容写入到当前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;
}
}

OverwriteTag代码

总结与拓展

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

JSP模板继承功能实现的更多相关文章

  1. jsp模板继承

    jsp通过自定义标签实现类似模板继承的效果 关于标签的定义.注册.使用在上面文章均以一个自定义时间的标签体现,如有不清楚自定义标签流程的话请参考这篇文章 http://www.cnblogs.com/ ...

  2. tp框架之模板继承

    模板继承是一项更加灵活的模板布局方式,模板继承不同于模板布局,甚至来说,应该在模板布局的上层.模板继承其实并不难理解,就好比类的继承一样,模板也可以定义一个基础模板(或者是布局),并且其中定义相关的区 ...

  3. Thinkphp3.2中的模板继承

    1:模板继承:   是3.1.2版本添加的一项更加灵活的模板布局方式,模板继承不同于模板布局,甚至来说,应该在模板布局的上层.模板继承其实并不难理解,就好比 类的继承一样,模板也可以定义一个基础模板( ...

  4. thinkphp中模板继承

    模板继承是3.1.2版本添加的一项更加灵活的模板布局方式,模板继承不同于模板布局,甚至来说,应该在模板布局的上层.模板继承其实并不难理解,就好比类的继承一样,模板也可以定义一个基础模板(或者是布局), ...

  5. php随机10-thinkphp 3.1.3 模板继承 布局

    8.25 模板继承 模 板继承是3.1.2版本添加的一项更加灵活的模板布局方式,模板继承不同于模板布局,甚至来说,应该在模板布局的上层.模板继承其实并不难理解,就好比类 的继承一样,模板也可以定义一个 ...

  6. Django模板继承和引用

    一.模板继承 1.模板继承可以在创建一个基本“骨架”后,被其它子模板继承并覆盖,通过修改基础模板可以修改子模板中的所有框架 2.在模板teacher文件夹下创建基础模板 {% block xxx}与{ ...

  7. Django 模板继承

    本质上来说,模板继承就是先构造一个基础框架模板,而后在其子模板中对它所包含站点公用部分和定义块进行重载. 让我们通过修改 current_datetime.html 文件,为 current_date ...

  8. python 全栈开发,Day70(模板自定义标签和过滤器,模板继承 (extend),Django的模型层-ORM简介)

    昨日内容回顾 视图函数: request对象 request.path 请求路径 request.GET GET请求数据 QueryDict {} request.POST POST请求数据 Quer ...

  9. Django 模板 语法 变量 过滤器 模板继承 组件 自定义标签和过滤器 静态文件相关

    本节目录 一 语法 二 变量 三 过滤器 四 标签Tags 五 模板继承 六 组件 七 自定义标签和过滤器 八 静态文件相关 一 语法   模板渲染的官方文档 关于模板渲染你只需要记两种特殊符号(语法 ...

随机推荐

  1. strtok源码 bitset 空间压缩

    源代码里有一段: unsigned char map[32]; /* Clear control map */ for (count = 0; count < 32; count++) map[ ...

  2. 服务升级中的zookeeper

    服务升级中zookeeper可以管理服务中的配置以及作为leader选举以及分布式事务等, 在这次主要用于配置管理,关于配置管理主要设计如下,通过zookeeper管理配置项,通过 管理界面来管理数据 ...

  3. 转:Delphi 6 实用函数

    来自: daocaoren0824, 时间: -- ::, ID: 再给你一份 程序员实用函数 {▎▎▎▎▎▎▎▎▎▎▎▎▎▎▎▎▎▎▎▎▎▎▎▎▎▎▎▎▎▎▎▎▎▎▎▎▎▎▎} {▎ ▎} {▎ 大 ...

  4. 转:Delphi2010新发现-类的构造和析构函数功能

    Delphi2010发布了. 虽然凭着对Delphi的热爱第一时间就安装了,但是现在可能是年纪大了,对新事物缺乏兴趣了.一直都没有仔细研究. 今天有点时间试了一下新功能. 本来C#和Delphi.NE ...

  5. 头显HTC Vive北美直降100美元,中国区降价活动今日公布

    如果你现在想要购买一台VR头显,591ARVR资讯网www.591arvr.com的小编提醒大家可以等一等,在即将到来的年末促销中各种VR设备都将迎来大力度降价.目前北美市场的HTC Vive已经直降 ...

  6. Javascript 面向对象编程(一):封装

    Javascript是一种基于对象(object-based)的语言,你遇到的所有东西几乎都是对象.但是,它又不是一种真正的面向对象编程(OOP)语言,因为它的语法中没有class(类). 那么,如果 ...

  7. React-native之持久化保存----AsyncStorage

    AsyncStorage AsyncStorage是一个简单的,未加密的,异步的,持久化,关键值存储系统,是全局的. iOS中存储类似于NSUserDefault,存储问plist文件存放在设备中. ...

  8. Eclipse 双击变量,其他相同变量有底色

    转载:http://blog.csdn.net/majian_1987/article/details/46691697 方便下次查找. 在Eclipse中,鼠标选中或者光标移动到Java类的变量名时 ...

  9. Cloud Engine:大杀器如何炼成

    郑昀(微博:http://weibo.com/yunzheng) 创建于2016/6/18 最后更新于2016/6/19 点击查看我的<如何从零搭建一个技术平台>,这是一个系列.转载时请注 ...

  10. HTML5新特性——HTML 5 Canvas vs. SVG

    Canvas 和 SVG 都允许您在浏览器中创建图形,但是它们在根本上是不同的. SVG SVG 是一种使用 XML 描述 2D 图形的语言. SVG 基于 XML,这意味着 SVG DOM 中的每个 ...