详解如何自定义JSTL标签(一)?

从几个简单的需求来讲解一下如何自定义 JSTL 标签:

需求:

  1. 使用一个单标签 在控制台输出 hello world
  2. 通过一个单标签 在页面输出当前 web 应用的上下文路径
  3. 使用一个单标签 向四大域中设置属性值

一、在控制台打印hello:

自定义一个单标签 ,调用该标签的时候在控制台打印一句 hello。

  1. 新建一个 web 项目
    详解如何自定义JSTL标签(一)?_第1张图片

  2. 引入 jstl1.2 的 ja r包(注意这里是 1.2
    详解如何自定义JSTL标签(一)?_第2张图片

  3. 创建一个类实现 SimpleTag 接口

  4. 因为这个接口已经有实现类 SimpleTagSupport ,所以我们只需要继承这个 SimpleTagSupport 即可

  5. 重写 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");
    	}
    	
    }
    
  6. 在 WEB-INF 里面创建一个 hello.tld文件(名称随意)
    详解如何自定义JSTL标签(一)?_第3张图片

  7. 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)

  8. short-name 用来定义当前定义的一系列标签的前缀,在页面上引入的时候可以这样写:<%@ taglib uri="http://java.sun.com/qjl/core" prefix="qjl" %>

    1. uri 其实是个虚的路径映射,并不是访问互联网上的某个网址(lz 一开始就以为引入 jstl 就要访问网页…),在 tld 文件中的 uri 标签上写什么,在页面上引入的时候就在 uri 属性上写什么,容器就会自动去 tld 文件中找该 uri 对应的标签了。

    2. tag 标签就是用来配置我们上面写好的标签处理类 HelloTag ,name 是用来配置标签的名称,就拿上面的例子来说,页面上就可以这样定义该标签 ,tag-class 对应的标签处理类的全限定性类名,body-content 表示的是这个标签体的一些信息,emtpy 表示这是一个单标签(就是不成对出现,只单个出现,像 input 标签一样)。

  9. 在 jsp 页面中引入 刚才定义的标签:

    <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
    
    <%@ taglib uri="http://java.sun.com/qjl/core" prefix="qjl" %>
    
    
    
        
        JSTL测试
    
    
    
        <%-- 自定义一个标签  在控制台打印一句话 --%>
        
        
    
    
    
  10. 运行服务器,请求这个页面,控制台打印如下:
    详解如何自定义JSTL标签(一)?_第4张图片

**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应用的上下文路径:

自定义一个单标签 ,调用该标签的时候把当前web应用的上下文路径在页面输出。

前几步与上一个需求类似(创建标签处理类,继承 SimpleTagSupport 类,重写 doTag 方法,在 hello.tld 中添加一个与上一个需求同样的 tag 标签),此处省略。

**因为我们的目标是向页面输出当前 web 应用的上下文路径,而要想获得当前 web 应用的上下文路径就需要调用 ServletContext 的 getContextPath 方法来获得,所以就需要获得 ServletContext ,而大家都知道我们可以通过 PageContext 的 getServletContext 方法来获得 ServletContext,那么现在的问题就变成了如何获得 PageContext 呢? **

过查阅 JEE 的 API 发现 SimpleTage 这个接口有一个方法: setJspContext
详解如何自定义JSTL标签(一)?_第5张图片
详解如何自定义JSTL标签(一)?_第6张图片
不难猜出这个方法是用来设置 JspContext。

那么它的实现类 SimpleTagSupport 就理所应当的继承了这个方法,看一下 API:
详解如何自定义JSTL标签(一)?_第7张图片
可以看出 SimpleTagSupport 不仅实现了 SimpleTag 的 setJspContext 方法来设置 JspContext 而且还定义了一个 getJspContext 方法来获取 JspContext。

这样我们就可以在标签处理类(继承了 SimpleTagSupport)中通过 getJspContext 方法获取 JspContext 了,像下面这样:

@Override
public void doTag() throws IOException {
	JspContext ctx = this.getJspContext();
}

但是,我们不是要获取 PageContext 吗,获取到了 JspContext 又有什么用呢?
详解如何自定义JSTL标签(一)?_第8张图片
相信看到这里,大家都已经明白了: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应用的上下文路径在页面输出 --%>
    
    


运行结果如下:
详解如何自定义JSTL标签(一)?_第9张图片

至此为止,向页面打印当前 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 个对象。

三、实现一个 “set” 标签:

实现一个 标签来向四大域中设置值。

我们从上面的小需求上已经知道了如何获取 PageContext 域,拿到了 PageContext 域之后其实就可以随心所欲的向四大域中设置属性值了,那么这个需求的问题就转换成了如何获取标签中的属性。

  1. 重新定义一个标签处理类,并在标签处理类中定义成员变量,定义其 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;
    		}
    	}	
    }
    
  2. 在 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
            
        
    
    
    
  3. 在页面中使用定义好的标签:

    <%@ 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}
    
    
    
  4. 开启服务器,请求这个页面,运行结果如下:
    详解如何自定义JSTL标签(一)?_第10张图片

到目前为止,我相信大家已经学会了如何自定义一个简单的 JSTL 标签,无非就是四步:编写继承了 SimpleTagSupport 的标签处理类,配置 xxx.tld 文件,在页面中引用配置好的标签库,在页面上使用自定义的标签。一些比较简单的需求应该没啥问题,后续会整理一篇复杂的标签如何定义的博客,例如:forEach,显示标签体中的内容。敬请期待!

关注我的微信公众号(曲健磊的个人随笔),观看更多精彩内容:
详解如何自定义JSTL标签(一)?_第11张图片

你可能感兴趣的:(【javaEE】)