JSP模板继承功能实现
背景
最近刚入职新公司,浏览一下新公司项目,发现项目中大多数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页面都是各自为战,没有一个统一的公共的模板来维护一些全局的信息,所以这里就介绍一下我们以前的实现方案:
- 实现JSP文件的模板功能、让所有的页面都引入一个公共的模板。
- 公共部分信息直接在模板中维护,可变部分在模板中定义占位符,然后由页面进行重写来维护不同页面的多样性。
有了模板以后就可以这样写页面了:

这样的写法好处显而易见:
- 首先,页面结构一目了然,写页面时无须再关注内容以外的公共部分,减少了许多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代码
总结与拓展
- 所有页面都使用了模板以后,就可以很方便的控制项目全局的样式、脚本,由于屏蔽了许多页面公共信息,也使得日常页面开发更加高效并减少错误率。
- JSP原生是不支持模板机制的,但是仅仅稍加一些手段使用两个自定义标签就可以实现模板功能,减少了许多重复的工作量。因此,工作过程中的痛点往往也是个人获得成长的机会。
- 我在上面Demo中只简单定义了一个base_template.jsp这一个模板,但是实际场景中一个网站可能有许多布局风格不同类型的页面,那么一个模板显然不能满足多样性的布局要求,这时我们就可以给模板进行分级将模板定义为base,common,channel三个级别,抽象程度从高到低,实现channel->common->base的继承关系,不同风格的页面只需要引入对应的channel模板即可,具体如何抽象还需根据实际的场景区别对待。
JSP模板继承功能实现的更多相关文章
- jsp模板继承
jsp通过自定义标签实现类似模板继承的效果 关于标签的定义.注册.使用在上面文章均以一个自定义时间的标签体现,如有不清楚自定义标签流程的话请参考这篇文章 http://www.cnblogs.com/ ...
- tp框架之模板继承
模板继承是一项更加灵活的模板布局方式,模板继承不同于模板布局,甚至来说,应该在模板布局的上层.模板继承其实并不难理解,就好比类的继承一样,模板也可以定义一个基础模板(或者是布局),并且其中定义相关的区 ...
- Thinkphp3.2中的模板继承
1:模板继承: 是3.1.2版本添加的一项更加灵活的模板布局方式,模板继承不同于模板布局,甚至来说,应该在模板布局的上层.模板继承其实并不难理解,就好比 类的继承一样,模板也可以定义一个基础模板( ...
- thinkphp中模板继承
模板继承是3.1.2版本添加的一项更加灵活的模板布局方式,模板继承不同于模板布局,甚至来说,应该在模板布局的上层.模板继承其实并不难理解,就好比类的继承一样,模板也可以定义一个基础模板(或者是布局), ...
- php随机10-thinkphp 3.1.3 模板继承 布局
8.25 模板继承 模 板继承是3.1.2版本添加的一项更加灵活的模板布局方式,模板继承不同于模板布局,甚至来说,应该在模板布局的上层.模板继承其实并不难理解,就好比类 的继承一样,模板也可以定义一个 ...
- Django模板继承和引用
一.模板继承 1.模板继承可以在创建一个基本“骨架”后,被其它子模板继承并覆盖,通过修改基础模板可以修改子模板中的所有框架 2.在模板teacher文件夹下创建基础模板 {% block xxx}与{ ...
- Django 模板继承
本质上来说,模板继承就是先构造一个基础框架模板,而后在其子模板中对它所包含站点公用部分和定义块进行重载. 让我们通过修改 current_datetime.html 文件,为 current_date ...
- python 全栈开发,Day70(模板自定义标签和过滤器,模板继承 (extend),Django的模型层-ORM简介)
昨日内容回顾 视图函数: request对象 request.path 请求路径 request.GET GET请求数据 QueryDict {} request.POST POST请求数据 Quer ...
- Django 模板 语法 变量 过滤器 模板继承 组件 自定义标签和过滤器 静态文件相关
本节目录 一 语法 二 变量 三 过滤器 四 标签Tags 五 模板继承 六 组件 七 自定义标签和过滤器 八 静态文件相关 一 语法 模板渲染的官方文档 关于模板渲染你只需要记两种特殊符号(语法 ...
随机推荐
- Winform窗体最大化的时候,如何指定窗体的位置、大小
一.重写窗体的SizeChanged事件不能改变窗体最大化的位置和大小. public partial class Form2 : Form { public Form2() { Initialize ...
- OpenCV特征点检测------ORB特征
OpenCV特征点检测------ORB特征 ORB是是ORiented Brief的简称.ORB的描述在下面文章中: Ethan Rublee and Vincent Rabaud and Kurt ...
- 大话css之display的Block未解之谜(一)
用了几年的css了,css中inline | block |inline-block|table|flex从来没有做过系统的整理和分析,网上的分析文章也很多,零散. 今天有空,就在这做一下整理分析 b ...
- for in 结构
in 运算符也是一个二元运算符,但是对运算符左右两个操作数的要求比较严格.in 运算符要求第 1 个(左边的)操作数必须是字符串类型或可以转换为字符串类型的其他类型,而第 2 个(右边的)操作数必须是 ...
- Linux内核笔记——进程管理之执行体
内核版本:linux-2.6.11 在Linux中,有多种执行体(指令流.执行单位),它们是CPU调度和分配资源的基本单位,它们是内核态可见的,即内核态下,每一种执行体都有对应的唯一数据结构task_ ...
- CODEVS1090 加分二叉树
codevs1090 加分二叉树 2003年NOIP全国联赛提高组 题目描述 Description 设一个n个节点的二叉树tree的中序遍历为(l,2,3,…,n),其中数字1,2,3,…,n为节点 ...
- js判断图片是否加载完成
var img = new Image(); //新建一个图片对象:img.src = ...; //图片地址是你准备要加载的地址:if(img.complete){ //表示图片已经加载完成}
- TDD测试驱动开发
TDD测试驱动开发 一.概念 TDD故名思意就是用测试的方法驱动开发,简单说就是先写测试代码,再写开发代码.传统的方式是先写代码,再测试,它的开发方式与之正好相反. TDD是极限编程的一个最重要的设计 ...
- touches 事件捕获不到
在UIView上加载了一个UIScrollView(全屏),touches 事件捕获不到了 原因:UIView的touch事件被UIScrollView捕获了,无法传递下去 解决方法:写一个UIScr ...
- yum 只下载不安装
以下载busybox为例 1.首先确定有yumdownloader 这个软件,这个软件在yum-utils 工具包里面. # rpm -qa |grep yum-utils # yum -y inst ...