从几个简单的需求来讲解一下如何自定义 JSTL 标签:
需求:
在控制台输出 hello world
在页面输出当前 web 应用的上下文路径
向四大域中设置属性值自定义一个单标签
,调用该标签的时候在控制台打印一句 hello。
创建一个类实现 SimpleTag 接口
因为这个接口已经有实现类 SimpleTagSupport ,所以我们只需要继承这个 SimpleTagSupport 即可
重写 doTag 方法
package com.lyu.tag;
import java.io.IOException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.SimpleTagSupport;
/**
* 类描述:用来输出Hello的tag标签处理类
* 类名称:com.lyu.tag.HelloTag
* @author 曲健磊
* 2018年5月14日.下午11:25:45
* @version V1.0
*/
public class HelloTag extends SimpleTagSupport {
@Override
public void doTag() throws JspException, IOException {
System.out.println("hello");
}
}
tld 文件的格式可以参考 jstl 的 jar 包中的 tld 文件格式。
QJL 1.2 core library
QJL core
1.2
qjl
http://java.sun.com/qjl/core
hello
com.lyu.tag.HelloTag
empty
注:这个 tld 文件中有几个重要的标签(short-name, uri, tag)
short-name 用来定义当前定义的一系列标签的前缀,在页面上引入的时候可以这样写:<%@ taglib uri="http://java.sun.com/qjl/core" prefix="qjl" %>
uri 其实是个虚的路径映射,并不是访问互联网上的某个网址(lz 一开始就以为引入 jstl 就要访问网页…),在 tld 文件中的 uri 标签上写什么,在页面上引入的时候就在 uri 属性上写什么,容器就会自动去 tld 文件中找该 uri 对应的标签了。
tag 标签就是用来配置我们上面写好的标签处理类 HelloTag ,name 是用来配置标签的名称,就拿上面的例子来说,页面上就可以这样定义该标签
,tag-class 对应的标签处理类的全限定性类名,body-content 表示的是这个标签体的一些信息,emtpy 表示这是一个单标签(就是不成对出现,只单个出现,像 input 标签一样)。
在 jsp 页面中引入 刚才定义的标签:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/qjl/core" prefix="qjl" %>
JSTL测试
<%-- 自定义一个标签 在控制台打印一句话 --%>
**Question:**为什么写个
标签就能在控制台打印了 hello 呢?
**Answer:**看一看刚才访问过的 jsp 页面被 jsp 引擎解析后的 java 源文件:
...
private boolean _jspx_meth_qjl_005fhello_005f0(javax.servlet.jsp.PageContext _jspx_page_context)
throws java.lang.Throwable {
javax.servlet.jsp.PageContext pageContext = _jspx_page_context;
javax.servlet.jsp.JspWriter out = _jspx_page_context.getOut();
// qjl:hello
// 这里是关键,创建了一个 HelloTag 对象
com.lyu.tag.HelloTag _jspx_th_qjl_005fhello_005f0 = new com.lyu.tag.HelloTag();
_jsp_getInstanceManager().newInstance(_jspx_th_qjl_005fhello_005f0);
try {
_jspx_th_qjl_005fhello_005f0.setJspContext(_jspx_page_context);
// 调用 doTag 方法
_jspx_th_qjl_005fhello_005f0.doTag();
} finally {
_jsp_getInstanceManager().destroyInstance(_jspx_th_qjl_005fhello_005f0);
}
return false;
}
...
相信看到这里,大家已经一目了然。是 servlet 容器(tomcat)创建了标签处理类的对象,并且调用了标签处理类的 doTag 方法。
自定义一个单标签
,调用该标签的时候把当前web应用的上下文路径在页面输出。
前几步与上一个需求类似(创建标签处理类,继承 SimpleTagSupport 类,重写 doTag 方法,在 hello.tld 中添加一个与上一个需求同样的 tag 标签),此处省略。
**因为我们的目标是向页面输出当前 web 应用的上下文路径,而要想获得当前 web 应用的上下文路径就需要调用 ServletContext 的 getContextPath 方法来获得,所以就需要获得 ServletContext ,而大家都知道我们可以通过 PageContext 的 getServletContext 方法来获得 ServletContext,那么现在的问题就变成了如何获得 PageContext 呢? **
过查阅 JEE 的 API 发现 SimpleTage 这个接口有一个方法: setJspContext
不难猜出这个方法是用来设置 JspContext。
那么它的实现类 SimpleTagSupport 就理所应当的继承了这个方法,看一下 API:
可以看出 SimpleTagSupport 不仅实现了 SimpleTag 的 setJspContext 方法来设置 JspContext 而且还定义了一个 getJspContext 方法来获取 JspContext。
这样我们就可以在标签处理类(继承了 SimpleTagSupport)中通过 getJspContext 方法获取 JspContext 了,像下面这样:
@Override
public void doTag() throws IOException {
JspContext ctx = this.getJspContext();
}
但是,我们不是要获取 PageContext 吗,获取到了 JspContext 又有什么用呢?
相信看到这里,大家都已经明白了:PageContext 是 JspContext 的一个子类,我们获取到了 JspContext 之后只需要把它强转为 PageContext 就可以了。
拿到了 PageContext 之后,就可以获取 ServletContext,获取了 ServletContext 就可以得到 contextPath(当前 web 应用的上下文路径),然后就可以把上下文路径发送到页面了…等会,怎么发送到页面?
@Override
public void doTag() throws IOException {
// 1.获取当前web应用的上下文路径
PageContext ctx = (PageContext) this.getJspContext();
String path = ctx.getServletContext().getContextPath();
// 2.向浏览器输出路径
ctx.getOut().write(path);
}
还是通过 PageContext 获取到输出流,向页面输出字符串即可。
下面是完整的标签处理类的代码:
package com.lyu.tag;
import java.io.IOException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.SimpleTagSupport;
/**
* 类描述:负责向页面输出当前web应用上下文路径的标签处理类
* 类名称:com.lyu.tag.PathTag
* @author 曲健磊
* 2018年5月16日.下午10:38:15
* @version V1.0
*/
public class PathTag extends SimpleTagSupport {
@Override
public void doTag() throws IOException {
// 1.获取当前web应用的上下文路径
PageContext ctx = (PageContext) this.getJspContext();
String path = ctx.getServletContext().getContextPath();
// 2.向浏览器输出路径
ctx.getOut().write(path);
}
}
下面是 tld 文件中的配置:
<taglib xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd"
version="2.1">
<description>QJL 1.2 core librarydescription>
<display-name>QJL coredisplay-name>
<tlib-version>1.2tlib-version>
<short-name>qjlshort-name>
<uri>http://java.sun.com/qjl/coreuri>
<tag>
<name>pathname>
<tag-class>com.lyu.tag.PathTagtag-class>
<body-content>emptybody-content>
tag>
taglib>
下面是请求的 jsp 页面的代码:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/qjl/core" prefix="qjl" %>
JSTL测试
<%-- 自定义一个标签 把当前web应用的上下文路径在页面输出 --%>
至此为止,向页面打印当前 web 应用上下文路径这个需求就完成了。但是大家有没有想过这个 JspContext 是什么时候注入到 SimpleTagSupport 里面的呢?(相信很多人已经猜出来了)
...
private boolean _jspx_meth_qjl_005fpath_005f0(javax.servlet.jsp.PageContext _jspx_page_context)
throws java.lang.Throwable {
javax.servlet.jsp.PageContext pageContext = _jspx_page_context;
javax.servlet.jsp.JspWriter out = _jspx_page_context.getOut();
// qjl:path
// new 了一个刚才定义的标签处理类对象
com.lyu.tag.PathTag _jspx_th_qjl_005fpath_005f0 = new com.lyu.tag.PathTag();
_jsp_getInstanceManager().newInstance(_jspx_th_qjl_005fpath_005f0);
try {
// 看这里,是容器调用了 setJspContext 方法来注入的一个 pageContext,注意是 PageContext,所以我们才可以进行强制类型转换
_jspx_th_qjl_005fpath_005f0.setJspContext(_jspx_page_context);
_jspx_th_qjl_005fpath_005f0.doTag();
} finally {
_jsp_getInstanceManager().destroyInstance(_jspx_th_qjl_005fpath_005f0);
}
return false;
}
...
没错,是 servlet 容器调用的标签处理类的 setJspContext 方法来注入的 PageContext,所以我们就可以将得到的 JspContext 强转成 PageContext,进而获取到 ServletContext,从而获取到当前 web 应用的上下文路径,当然,除此之外,还可以获得其他的 8 个对象。
实现一个
标签来向四大域中设置值。
我们从上面的小需求上已经知道了如何获取 PageContext 域,拿到了 PageContext 域之后其实就可以随心所欲的向四大域中设置属性值了,那么这个需求的问题就转换成了如何获取标签中的属性。
重新定义一个标签处理类,并在标签处理类中定义成员变量,定义其 setter 方法
package com.lyu.tag;
import java.io.IOException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.SimpleTagSupport;
/**
* 类描述:用来向作用域中设置属性值的标签处理类
* 类名称:com.lyu.tag.SetTag
* @author 曲健磊
* 2018年5月18日.下午10:58:41
* @version V1.0
*/
public class SetTag extends SimpleTagSupport {
private String var = "";
private String value = "";
// 因为这个属性在 tld 文件中设置为不是必须的所以要设置一个默认值,否则方法中调用的时候会报空指针
private String scope = "";
public void setVar(String var) {
this.var = var;
}
public void setValue(String value) {
this.value = value;
}
public void setScope(String scope) {
this.scope = scope;
}
@Override
public void doTag() throws JspException, IOException {
// 1.拿到PageContext
PageContext pageContext = (PageContext) this.getJspContext();
// 2.获取标签中设置的属性值
// get方法中已经获得
// 3.判断scope是哪个域,把属性值保存到对应的作用域里面
switch (scope) {
case "request":
pageContext.setAttribute(var, value, PageContext.REQUEST_SCOPE);
break;
case "session":
pageContext.setAttribute(var, value, PageContext.SESSION_SCOPE);
break;
case "application":
pageContext.setAttribute(var, value, PageContext.APPLICATION_SCOPE);
break;
default:
pageContext.setAttribute(var, value);
break;
}
}
}
在 tld 文件中添加相应的 tag 标签,并在 tag 标签中 添加 attribute 标签(每个 attribute 标签对应定义的标签中的一个属性)
QJL 1.2 core library
QJL core
1.2
qjl
http://java.sun.com/qjl/core
set
com.lyu.tag.SetTag
empty
var
true
false
value
true
true
scope
false
false
在页面中使用定义好的标签:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/qjl/core" prefix="qjl" %>
JSTL测试
<%-- 自定义一个标签 来向所用域中添加值 --%>
${sessionScope.name}
到目前为止,我相信大家已经学会了如何自定义一个简单的 JSTL 标签,无非就是四步:编写继承了 SimpleTagSupport 的标签处理类,配置 xxx.tld 文件,在页面中引用配置好的标签库,在页面上使用自定义的标签。一些比较简单的需求应该没啥问题,后续会整理一篇复杂的标签如何定义的博客,例如:forEach,显示标签体中的内容。敬请期待!