JavaWeb(六)EL表达式 && JSTL标签库 && 自定义标签

上一章链接;JavaBean

前面几章基本上将基础都讲完了,现在这一部分开始慢慢深入到JSP的高级运用上。

目录

EL表达式

    EL表达式概述

    EL表达式的初步运用

        全域查找

        指定域查找

        JavaBean导航

    EL内置对象

        param¶mValues

        header&headerValues

        initParam

        cookie

        pageContext

    EL函数库

    自定义EL函数库

JSP标准标签库

    JSTL概述

    core核心标签库常用标签

        out && set

        remove

        url

        if && choose

        foreach

    fmt标签库常用标签

        formatDate

        formatNumber

    标签源码分析

自定义标签

    自定义标签概述

    标签处理类

    标签库描述文件

    自定义标签案例

        空标签体

        有标签体

        中断执行

        附有属性标签

后话


EL表达式

    EL表达式概述

EL表达式,全称Expression Language,其使得访问存储在JavaBean中的数据变得非常简单。从JSP2.0开始,使用EL表达式和动态标签来替代Java脚本,而EL表达式替代的正是<%=...%>的Java脚本。

    EL表达式的初步运用

EL表达式的语法很简单,${expr},其中,expr指的是表达式。

        全域查找

    EL表达式可以全域查找某个属性,当查找的属性不存在时,则自动输出空字符串而不是null。用例代码如下:

	<%
		// 域优先级从上至下
		pageContext.setAttribute("aaa", "pageContext_AAA");
		request.setAttribute("aaa", "request_AAA");
		session.setAttribute("aaa", "session_AAA");
		application.setAttribute("aaa", "application_AAA");
	%>
	
	${aaa}

    结果如下所示:

    分别对每一个设置属性的代码进行注释,发现在获取application的域的属性的值的时候,仍然输出的是SESSION域的属性的值,这是由于SESSION没有被销毁的缘故。因此更换一个浏览器访问同页面时,结果显示如下:

        指定域查找

    EL表达式可以根据以下四种方式来查找指定域中的属性:

	<%
		// 域优先级从上至下
		pageContext.setAttribute("aaa", "pageContext_AAA");
		request.setAttribute("aaa", "request_AAA");
		session.setAttribute("aaa", "session_AAA");
		application.setAttribute("aaa", "application_AAA");
	%>
	
	${pageScope.aaa } 
${requestScope.aaa}
${sessionScope.aaa }
${applicationScope.aaa }

 

    在这里必须要注意的一点是,表达式中的Scope不能忘记。

        JavaBean导航

    EL表达式可以对Bean中的属性进行输出显示,用例代码如下:

    需注意的是,只要是满足JavaBean规范的属性都可以这样书写。

	<%
		Student stu = new Student();
		stu.setName("张三");
		stu.setAge(18);
		stu.setSex("男");
		request.setAttribute("stu", stu);
	%>
	
	${requestScope.stu.name } 
${requestScope.stu.age }
${requestScope.stu.sex }
/* requestScope.stu.name == request.getAttribute("stu").getName(); */

    找到JSP页面生成的.java文件,查看源码如下所示(只以其中一条为例):

    查看PageContextImpl源码发现其实现PageContext抽象类,其中,返回值为Object类型的proprietaryEvaluate()是专门用于输出EL表达式的方法,其源码在org.apache.jasper.runtime.PageContextImpl。由于源码过于复杂化,在这里不过多的讲解源码。

    EL内置对象

EL内置对象有11个,内置对象中有10个是Map类型,剩下一个是pageContext类型。在前面已经了解到了前四个,即pageScope、requestScope、SessionScope、applicationScope。剩下的七个内置对象分别为:param、paramValues、header、headerValues、initParam、cookie、pageContext。接下来分别一一演示其用法。

        param¶mValues

    paramparamValues这两个内置对象是用来获取请求参数的。

    param:Map类型,等同于request.getParameter()方法。其中key为参数名,value为参数值,适用于单值的参数。

    paramValues:Map类型,一个参数名对应多个参数值时可以使用它。其中key为参数名,value为多个参数值,适用于多值的参数。

    用例代码如下所示:

        ${param.username }
        // 等价于 request.getParameter("username");
        ${paramValues.ways[0] }
        // 等价于 request.getParameterValues("ways")[0];    

    通过如下地址访问:demo03.jsp?username=zhangsan&ways=phone&ways=telephone,结果如下所示:

        header&headerValues

    headerheaderValues这两个内置对象用于获取请求头中的内容。

    header:Map类型,key代表头名称,value是单个头值,适用于单值请求头。

    headerValues:Map类型,key代表头名称,value是多个头值,适用于多值请求头。

    用例代码如下:

        ${header['User-Agent'] }
        // 如果属性名中有带有如斜杠或者横杠等特殊字符,则需要用另一种方式去访问
        // 即map['key']
        // 等价于 request.getHeader("User-Agent");

    结果如下所示:

        initParam

    initParam这个内置对象用于获取web.xml中的内的参数。

    web.xml配置如下:

        
            context-param
            context-value
        

    用例代码如下所示;

        ${initParam['context-param'] }

    结果如下所示:

        cookie

    cookie:Map类型,其中key是Cookie名,而value是Cookie对象本身。

    用例代码如下所示:

        ${cookie.JSESSIONID.value }
        /*  等价于
        *
        *  Cookie[] cookies = request.getCookies();
        *  String name = cookie.getName();
        *  if(“JSESSIONID”.equals(name)) {
        *   String value = cookie.getValue();
        *  }
        *
        */

    结果如下所示:

        pageContext

    pageContext内置对象是EL表达式最为独特的一个,依旧是“一个顶九个”的作用。

    用例代码如下所示:

        click here

        

    结果如下所示:

JavaWeb(六)EL表达式 && JSTL标签库 && 自定义标签_第1张图片

    从结果来看,不难发现,以后用这样的格式去代替固定项目名路径即可达到便利性。即复制源文件至另一个项目不需要更改所有源文件内中的项目路径。

    EL函数库

EL函数库是由第三方对EL的扩展,其实际上就是定义一些有返回值静态方法通过EL语言来调用它们。

首先得说明一下,如果是MyEclipse的话会自带一个名为jstl-imp的jar包,而标签库就在jar包里。

打开这个jar包,在META-INF里会有一个fn.tld文件,用记事本打开,可以看到其内部构造。

这一块是对EL函数库的描述。需要注意的地方就是这个标签,其中的值将会在taglib中用到。

JavaWeb(六)EL表达式 && JSTL标签库 && 自定义标签_第2张图片

这一大块的function即是EL函数库中定义的众多函数的其中之一。其中,

  • 子标签用来描述函数的作用;
  • 子标签为函数名称;
  • 子标签为函数所在的类文件编译的class;
  • 子标签为函数具体的声明;
  • 子标签为函数的使用示范。

在之前有说过JSP指令中有一个taglib指令,其用于导入标签库的,现在就可以用上了,即

        <%@taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>

其中,prefix代表了标签库的前缀名,uri指定标签库的位置。

EL函数库的函数如下所示:

函数

说明

String toUpperCase(String input)

参数转换成大写

String toLowerCase(String input)

参数转换成小写

int indexOf(String input, String substring)

大串输出小串的位置

boolean contains(String input, String substring)

查看大串中是否包含小串

boolean containsIgnoreCase(String input, String substring)

忽略大小写的是否包含

boolean startsWith(String input, String substring)

是否以小串为前缀

boolean endsWith(String input, String substring)

是否以小串为后缀

String subString(String input, int beginIndex, int endIndex)

截取子串

String subStringAfter(String input, String substring)

获取大串中小串所在位置的后面的字符串

String subStringBefore(String input, String substring)

获取大串中小串所在位置的前面的字符串

String escapeXml(String input)

把字符串中"<", ">", "&"," ' "," " "的部分进行转义

String trim(String input)

去除前后空格

String replace(String input, String substringBefore, String subStringAfter)

替换

String[] split(String input, String delimiters)

分割字符串,得到字符串数组

int length(Object obj)

可获取字符串、数组、集合的长度

String join(String array[], String separator)

联合字符串数组

用例代码如下所示:

	<%
		String str = "hello world!";
		String[] strs = {"h", "e", "l", "l", "o"};
		pageContext.setAttribute("str", str);
		pageContext.setAttribute("strs", strs);
	%>
	
	${fn:length(str) } 
${fn:length(strs) }
${fn:toLowerCase(str) }
${fn:toUpperCase(str) }
${fn:indexOf(str, "o") }
${fn:contains(str, "Wor") }
${fn:containsIgnoreCase(str, "Wor") }
${fn:startsWith(str, "hell") }
${fn:endsWith(str, "ld") }
${fn:join(strs, "") }
${fn:join(fn:split(str, " "), "-") }
${fn:replace(str, "!", "!!!") }
${fn:substring(str, 1, 7) }
${fn:substring(str, 1, -1) }
${fn:substringAfter(str, " ") }
${fn:substringBefore(str, " ") }
${fn:trim(" x X x ") }
${fn:escapeXml("") }

其中需要注意的几个地方:

pageContext.setAttribute()这个方法是关键,对于EL表达式中的数据访问,其等价于getAttribute(),并且根据优先级:page域、request域、session域、application域来查找数据。

substring的范围是[beginIndex, endIndex),endIndex为-1时,即从beginIndex索引开始到最后的串都获取。

escapeXml根据其作用,可以防止JavaScript攻击。

    自定义EL函数库

如何自定义EL函数库呢?在上面就说过,EL函数库实际上就是定义一些有返回值静态方法。那么只要新建一个类,在这个类中写有返回值的静态方法即可。并且参照fn.tld文件中的格式来编写自定义EL函数库。

那么以下为用例代码:

MyFunction.java

        package it.test

        public class MyFunction {
            pulbic static String test() {
                return "自定义EL函数库";
            }
        }

test.tld(/WEB-INF/tlds/)




    
  testFunction
  My Function
  1.0
  myfn
  http://www.xxxxxx.com/jsp/functions

  
    
      description.
    
    test
    it.test.MyFunction
    java.lang.String test()
  


index.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="myfn" uri="/WEB-INF/tlds/test.tld" %>





Insert title here



	${myfn:test() }
	

如果需要在支持表达式语言的页面中正常输出“$”符号,则在“$”符号前加转义字符“\”即可。


JSP标准标签库

    JSTL概述

JSP标准标签库(JSTL)是apache对EL表达式的扩展,即一个JSP标签集合。

  • JSTL共包含了以下四大标签库:
    • core:核心标签库
    • fmt:格式化标签库
    • sql:数据库标签库
    • xml:xml标签库

其中,sql标签库和xml标签库已经过时,只需要学习前面两种即可。

    core核心标签库常用标签

        out && set

    out标签用于输出数据,其中有三个属性:value、default、escapeXml。分别解释下上述三个属性,

        value:输出的值,可以是字符串常量或者EL表达式

        default:默认值,若输出的内容为空时,就会输出default的值

        escapeXml:和EL函数库中的escapeXml作用一样,表示转义,默认值为true。

    以下为用例代码:

        // value为字符串常量
         
// value为EL表达式
// JavaScript攻击 <% request.setAttribute("code", ""); %>

    显示结果如下:

    set标签用于创建域的属性,其中附有三个属性:var、value、scope,分别代表着属性名、属性值、所属域

    用例代码如下所示:

        // scope默认为page域,在pageContext中添加值为zhangsan的name属性
        
        // 指定scope是session域
        

    显示结果如下所示:

        remove

    remove标签用于删除域变量。既然是删除域变量,就得指定要删除的变量名var和所属的域scope。

    需要注意的是,其是删除所有域中var的数据。

    用例代码如下所示:

        <%
            pageContext.setAttribute("loc", "pageContext");
            request.setAttribute("loc", "request");
            session.setAttribute("loc", "session");
            application.setAttribute("loc", "application");
        %>

        // 通过以下两段代码可以得知remove是删除所有域中的loc属性
        
        

        url

    url标签由三种属性value、var、scope和一个子标签param构成。其中,

        value:指定一个路径,会在路径前自动添加项目名

        var:指定变量名,若添加了这个属性,则url标签不会输出至页面而会将生成的url保存到域中。

        scope:和var同时存在,用来指定url保存在哪个域中。

    子标签param有两种属性:name、value,分别代表参数名和参数值。在这里必须提及的一点是:param标签会对参数自动进行url编码。

    用例代码如下所示:

        
            // 输出 /项目名/index.jsp

        
        

        
        
            
        

    结果如图所示:

        if && choose

    if标签对应java中的if语句,其只有一个属性test,而且必须是boolean类型的值,如果test的值为true,则执行if标签内的内容。反之,则不执行。

    用例代码如下所示:

        
        
        
            
        

    choose标签对应java中的if-else if...else语句。在choose标签内还有一个子标签when和子标签otherwise。子标签的when中也只有一个属性test,用于判断条件是true或者false。当所有when标签的test都为false时,才会执行otherwise的内容。

    用例代码如下所示:

        
        
            
                无username参数
            
            
                存在参数username,值为:${param.username}
            
        

    以demo01.jsp?username="zhangsan"访问,结果如下所示:

        foreach

    foreach为循环标签,用于循环遍历数组、集合,其有两种使用方式。

    第一种是计数方式,何为计数方式呢?这里先说一下其包含的属性:var、begin、end、step

        var:循环变量

        begin:设置循环变量从几开始

        end:设置循环变量到几结束

        step:设置循环变量递增的幅度,默认值为1

    看到上面这四种属性的解释,已经可以知道其格式,以下用代码演示:

        
            ${i}
        
        

    第二种是循环遍历集合,而属性只有两个:items、var

        items:指定被循环的对象,可以是个数组或是个集合

        var:用于数组或者集合的每个元素依次赋值

    用例代码如下所示:

        <%
            String[] strs = {"hello", "world"};
            request.setAttribute("strs", strs);
        %>

        
            ${str } 

    forEach标签还有一个独特的属性:varStatus,这个属性用于指定接收“循环状态”的变量名,其可以获取以下五种状态:

        count:int类型,当前已遍历的元素个数

        index:int类型,当前元素的索引值

        first:boolean类型,判断是否为第一个元素

        last:boolean类型,判断是否为最后一个元素

        current:Object类型,表示当前元素

    用例代码如下所示:

        <%
            ArrayList list = new ArrayList();
            list.add("one");
            list.add("two");
            list.add("three");
            pageContext.setAttribute("list", list);
        %>

        
            ${vs.index } ${vs.count } ${vs.first } ${vs.last } ${vs.current}
        

    结果如下所示:

    fmt标签库常用标签

fmt标签库最常用的两种就是对日期的格式化和对数字的格式化,用法简单不过多叙述,记得导入对应的标签库即可。

        formatDate

    formatDate标签只有两个属性,一个是需要格式化的数据属性value,另一个则是用于给定格式的属性pattern

    格式化时间代码如下所示:

        <%@ taglib prefix = "fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
        
        <%
            Date date = new Date();
            pageContext.setAttribute("date", date);
        %>

        

        formatNumber

    直接上代码:

        <%
            double d = 3.1415926;
            pageContext.setAttribute("d", d);
        %>

         

代码中两种不同格式结果是不一样的,两者一样都能四舍五入。

第一个格式的处理是:保留小数点后两位,如果不足两位,以0补位

而第二个格式的处理则是:保留小数点后两位,如果不足两位,不补位

    标签源码分析

以下列代码为例:

        // value为字符串常量
         

打开tomcat生成的jsp的java文件,不难发现,使用标签就是在调用方法:

        if (_jspx_meth_c_005fout_005f0(_jspx_page_context))
            return;
        out.write(" 
\r\n");

这里的_jspx_page_context和pageContext都是同一个对象的引用。在这个奇怪名字的函数里面,做了以下事情:

        javax.servlet.jsp.PageContext pageContext = _jspx_page_context;
        javax.servlet.jsp.JspWriter out = _jspx_page_context.getOut();
        // 这里有一个_jspx_th_c_005fout_005f0对象的定义
        try {
            _jspx_th_c_005fout_005f0.setPageContext(_jspx_page_context);
            _jspx_th_c_005fout_005f0.setParent(null);
            _jspx_th_c_005fout_005f0.setValue("hello");
            int _jspx_eval_c_005fout_005f0 = _jspx_th_c_005fout_005f0.doStartTag();
        ...
        }

其中setParent方法为设置父标签,setValue是指标签的属性值,而Value是这个标签类的成员变量。至于doStartTag是标签执行方法。


自定义标签

    自定义标签概述

仅仅有JSTL标签库并不能满足过多的要求,那么自定义标签就产生了。在JSP中,所谓的标签使用其实就等同于调用某个对象的某个方法。因此自定义标签只需要做两件事:定义一个标签处理类编写标签库描述文件(即TLD文件)。标签对应的类称之为“标签处理类”。

    标签处理类

这里稍微提及一下历史:早期Java提供了一个Tag接口,然后后来又提供了一个SimpleTag接口,而此时Tag和SimpleTag变成一个体系却是两个互不相关的个体。后来在Jsp2.0又创建了一个JspTag接口,为这两个接口的父接口。这些变迁都是为了简化自定义标签。

那么现在来看一下SimpleTag接口:

        void doTag() // 标签执行方法
        JspTag getParent() // 获取父标签 (非生命周期方法)
        void setJspBody(JspFragment jspBody) // 设置标签体内容 
        void setJspContext(JspContext pc) // 设置pageContext
        void setParent(JspTag parent) // 设置父标签

其实除了getParent()方法以外其他方法是Tomcat调用的,无需编程人员调用,只需要知道标签的生命周期。标签的生命周期是会先调用其余三个方法,最后调用doTag()方法。

这里需要注意的是:父标签是动态标签,HTML标签并不属于这一类

那么自定义标签只需要实现SimpleTag接口即可。但是实际上,只有doTag()方法需要重写,因此Java又提供了一个SimpleTagSupport类。

SimpleTagSupport类实现了SimpleTag接口,也就是说,只需要继承该类并且重写doTag()方法即可创建新的标签。

    标签库描述文件

在前面的时候就已经介绍过tld,现在介绍一下其中的子标签tag:

    
        
        
        
    

name:指定当前标签的名称

tag-class:指定当前标签的标签处理类

body-content:指定标签体的类型

  • body-content元素的可选值:
    • empty:无标签体
    • scriptless:标签体内容不能是java脚本,但可以是EL、JSTL等
    • JSP:标签体内可以是任何东西,但是SimpleTag已不再支持使用这个参数
    • tagdependent:意思就是自行处理标签,几乎不会用

    自定义标签案例

        空标签体

    即类似于HTML中
,非成对标签。用例代码如下所示:

        标签处理类:

    public class MyTag1 extends SimpleTagSupport {
        @Override
        public void doTag() throws JspException, IOException {
            this.getJspContext().getOut().print("Hello Tag");
        }
    }

        tld文件:




	
    1.0
    ittest
    http://www.baidu.com/tags/it-1.0
	
    
        mytag1
        it.test.tag.MyTag1
        empty
    

        demo.jsp:

        

    结果如图所示:

        有标签体

    有标签体即有内容的标签。那么用例代码如下所示:

        标签处理类:

        public class MyTag2 extends SimpleTagSupport {
            @Override
            public void doTag() throws JspException, IOException {
                Writer out = this.getJspContext().getOut();
                out.write("***************
"); // 执行标签体内容,把结果写到指定的流中 this.getJspBody().invoke(out); out.write("
***************"); } }

        tld文件:

    
        mytag2
        it.test.tag.MyTag2
        scriptless
    

        demo.jsp:

	
		Hello ${param.name }
	

    结果如图所示:

        中断执行

    在Java中有一个异常专门用于跳过页面显示内容,即SkipPageException。Tomcat当得到这个异常时,将会跳过页面其他的内容。用例代码如下所示:

        标签处理类:

        public class MyTag3 extends SimpleTagSupport {
            @Override
            public void doTag() throws JspException, IOException {
                this.getJspContext().getOut().print("阻断显示");
                throw new SkipPageException();
            }
        }

        tld文件:

    
        mytag3
        it.test.tag.MyTag3
        empty
    

        demo.jsp:

        
        
Hello ${param.name }

    结果如图所示:

JavaWeb(六)EL表达式 && JSTL标签库 && 自定义标签_第3张图片

        附有属性标签

    类似于c标签中的choose的子标签有一个boolean类型的test属性一样,在标签中的属性其实就是标签处理类的成员变量。

    用例代码如下所示:

        标签处理类:

        public class MyTag4 extends SimpleTagSupport {
            private boolean flag;
            public boolean isFlag() {
                return flag;
            }
            public void setFlag(boolean flag) {
                this.flag = flag;
            }
            @Override
            public void doTag() throws JspException, IOException {
                if(flag) {
                    //传递null,则表示使用的就是当前页面的out
                    this.getJspBody().invoke(null);
                }
            }
        }

        tld文件:

    
        mytag4
        it.test.tag.MyTag4
        scriptless
        
            flag
            true
            true
        
    

其中,required指定属性是否必须。rtexprvalue,指定属性是否能接受请求时表达式的值,默认为false,表示不能接受请求时表达式的值。

        demo.jsp:

        
            没有name参数,执行阻断显示

    结果如图所示:

后话

        '''
            EL表达式还有一些运算符的东西并没有拿出来讲,这些是可以自行测试的也过于简单
            这一章最主要还是学会使用EL表达式和JSTL以及理解JSP中所谓的标签本质即可
        '''

 

你可能感兴趣的:(JavaWeb,EL表达式,JavaWeb,JSP)