背景
最近刚入职新公司,浏览一下新公司项目,发现项目中大多数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"%>${webModule.module.name} ---xxxx
小伙伴们每新添加一个页面,就需要copy一份上面这坨代码,还需要在各自页面重复引入公共的头尾文件(如header.jsp,footer.jsp等)。。。
对于这种开发方式,重复的工作量就不多描述了,更重要的问题是这种架构方式未来会导致更多的维护工作量、甚至是bug隐患。
举两个“栗子”:
上面扯了那么多,其实核心问题就是所有的jsp页面都是各自为战,没有一个统一的公共的模板来维护一些全局的信息,所以这里就介绍一下我们以前的实现方案:
- 实现JSP文件的模板功能、让所有的页面都引入一个公共的模板。
- 公共部分信息直接在模板中维护,可变部分在模板中定义占位符,然后由页面进行重写来维护不同页面的多样性。
有了模板以后就可以这样写页面了:
这样的写法好处显而易见:
模板内容大概是这个样子的:
实现原理
实现原理其实很简单,模板功能的实现主要是两个自定义标签(自定义标签的开发步骤这里就不讲了)
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; }}
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; } }总结与拓展
- 所有页面都使用了模板以后,就可以很方便的控制项目全局的样式、脚本,由于屏蔽了许多页面公共信息,也使得日常页面开发更加高效并减少错误率。
- JSP原生是不支持模板机制的,但是仅仅稍加一些手段使用两个自定义标签就可以实现模板功能,减少了许多重复的工作量。因此,工作过程中的痛点往往也是个人获得成长的机会。
- 我在上面Demo中只简单定义了一个base_template.jsp这一个模板,但是实际场景中一个网站可能有许多布局风格不同类型的页面,那么一个模板显然不能满足多样性的布局要求,这时我们就可以给模板进行分级将模板定义为base,common,channel三个级别,抽象程度从高到低,实现channel->common->base的继承关系,不同风格的页面只需要引入对应的channel模板即可,具体如何抽象还需根据实际的场景区别对待。