第1章 JSP基础

1.1 什么是JSP

JSP,即Java Server Pages,Java服务器页面,即运行在服务器端的页面。它是由Sun公司倡导,许多国际性大公司参与,一起建立的一种动态网页技术。JSP技术是在传统的静态网页HTML文件中插入Java代码片断和JSP标签后形成的一种文件,其后缀名为.jsp。使用JSP开发的Web应用是跨平台的,既能在Linux上运行,也能在其他操作系统上运行。

在MyEclipse下的Web工程的WebRoot目录中新建一个JSP文件,例如新建一个index.jsp文件。

JSP基础​_内置对象


在jsp文件中写入如下内容:

JSP基础​_内置对象_02


然后运行这个Web项目,在浏览器中即可看到“Hello JSP World!”字样。

在Tomcat中可看到两个文件:

JSP基础​_内置对象_03


源码文件index_jsp.java就是由刚才的Web项目中的index.jsp文件转变成的java文件。而index_jsp.class文件为index_jsp.java文件编译后的字节码文件。

打开这个index_jsp.java文件,发现其继承自HttpJspBase类。

JSP基础​_Java_04


打开Tomcat的源码目录中的java子目录,在其中查看HttpJspBase的源码,发现其继承自HttpServlet。

JSP基础​_Java_05


也就是说,JSP文件的本质是Servlet。只不过,JSP与Servlet不同的是,JSP是专门用于进行数据展示的Servlet,其有特殊的写法。而普通的Servlet是用于完成业务逻辑处理的。由于Servlet是运行在单例多线程环境下的,所以JSP同样也是运行在单例多线程环境下的。

1.2 JSP规范

将JSP页面翻译为Servlet的过程,是由Tomcat完成。在Tomcat中内置了一个JSP翻译引擎,当第一次访问该JSP页面时,翻译引擎会将JSP页面翻译为Servlet 的.java文件,再将其编译为.class文件进行运行。

SUN公司制定的JavaEE规范中包含两个很重要的子规范:Servlet规范,及JSP规范。其中JSP规范中就包含了如何将JSP页面翻译为Servlet。例如,JSP页面中的HTML、CSS、JavaScript,及普通文本部分,均会被翻译到out. write()中。

Tomcat中的JSP翻译引擎,就遵循了这个JSP规范。

1.3 JSP注释

在JSP页面中使用注释,可以将要注释的内容使用括起来。

当然,在JSP页面中也可以使用HTML的注释。但它们的使用效果是不同的。

(1)HTML注释会被JSP翻译引擎翻译到Servlet的out.write()中;而JSP注释则会被JSP翻译引擎忽略,在Servlet中是看不到的。

(2)在客户端浏览器查看源码时,HTML注释是可以查看到的;但JSP注释在客户端是查看不到的。

例如,在JSP页面中写入如下注释:

JSP基础​_代码块_06


查看

JSP基础​_内置对象_07


查看客户端浏览器的源码文件,可以看到:

JSP基础​_内置对象_08


1.4 JSP的Java代码块

JSP的Java代码块也称为JSP小脚本,是以括起来的部分,其中可以编写Java代码。编写在Java代码块中的代码,会被JSP翻译引擎将其置入到Servlet的_jspService()方法中作为方法中的Java语句出现。所以,其中存放的必须是以分号结尾的Java语句。

需要注意的是,JSP代码块可以放置在JSP页面的任意位置,可以放置任意数量。但它们都会被按照顺序放置到Servlet的_jspService()方法中。也就是说,其执行顺序是与其在JSP页面中的出现顺序是一致的。例如,

JSP基础​_内置对象_09


被翻译到Servlet的_jspService()方法中为:

JSP基础​_内置对象_10


由于JSP小脚本是被翻译到了Servlet的_jspService()方法中,所以JSP小脚本中是不能出现如下内容的:

  1. 声明的变量是不能添加权限访问控制符的。

(2)不能定义方法。

(3)不能定义静态语句块。

1.5 JSP的声明语句块

在JSP页面中使用括起来的部分,称为声明语句块。声明语句块中的内容,将被JSP引擎翻译到Servlet的类体中,没有包含到哪个方法体中。

这样的话,在JSP的声明语句块中就可声明实例变量、实例方法、静态方法、静态代码块等内容。并且,这些内容均可被JSP的Java代码块中代码访问。因为它们原本就是一个Servlet类中的代码。不过,还是不建议在JSP的声明语句块中声明实例变量。因为JSP是运行在单例多线程环境下的,实例变量将会引起线程安全问题。

需要注意的是,在JSP的声明语句块中,是不能编写普通的Java语句的。否则的话,这些语句将会直接出现在Servlet的类体中。

声明语句块也可以定义在JSP页面的任意位置,且可以定义任意多个。

JSP基础​_内置对象_11


在JSP的Java代码块与声明语句块中,均可使用Java的单行注释与多行注释。该注释将会出现在Servlet中的相应位置。

JSP基础​_Java_12


JSP基础​_代码块_13


1.6 JSP的表达式块

在JSP页面中使用括起来的部分,称为表达式块。其可在JSP页面中输出变量、常量,及它们组成的各种表达式的值。注意,是表达式,而不是语句,是没有分号的。该表达式将被JSP引擎翻译到_jspService()方法的out.write()方法中输出。例如,

JSP基础​_代码块_14


被JSP引擎翻译为Servlet后为:

JSP基础​_Java_15


第2章 JSP核心

2.1内置对象

在JSP的Java代码块、表达式块等中可以直接使用的引用,称为JSP的内置对象。常用的内置对象有九个,分别是:

JSP基础​_内置对象_16


这九个对象在JSP的Java代码块、表达式块中可以直接使用。之所以可以直接使用,是因为Java代码块与表达式块被JSP引擎翻译后均出现在了Servlet的_jspService()方法中。而这九个对象,就是_jspService()方法中的局部变量。在JSP的Java代码块、表达式块中的代码就是_jspService()方法中的代码,所以在其中可以直接使用。

JSP基础​_Java_17


2.1.1 pageContext

pageContext,页面上下文,其具有一个只在当前页面范围的域属性空间,即其具有setAttribute()方法与getAttribute()方法。但,由于在当前页面范围,数据不存放到域属性空间也可直接使用,将数据存放到域属性空间反而感觉“多此一举”,所以这些方法并不常用。

不过,在同一页面中,为了使用EL表达式(后面学习)来访问某变量的值,此时一般使用pageContext。

pageContext具有一些get方法,可以获取到Request、Response、Session、ServletContext、ServletConfig、page(即当前Servlet)、exception、out等另外八个内置对象。

JSP基础​_内置对象_18


但由于这些对象本身就是内置对象,在JSP页面中可以直接使用,所以这些方法也并不常用。不过,在后面将要学习的EL表达式中,将会经常使用pageContext的这些方法。

2.1.2 application

application,即ServletContext。所以ServletContext所具有的方法,application都具有。常用的方法例如:

● String getInitParameter():获取web.xml文件的中指定名称的上下文参数值。例如:getInitParameter(“myDBDriver”);会返回字符串“com.mysql.jdbc.Driver”。

● Enumeration getInitParameterNames():获取web.xml文件的中的所有的上下文参数名称。其返回值为枚举类型Enumeration。

● void setAttribute(String name, Object object):在ServletContext的公共数据空间中,也称为域属性空间,放入数据。这些数据对于Web应用来说,是全局性的,与整个应用的生命周期相同。当然,放入其中的数据是有名称的,通过名称来访问该数据。

● Object getAttribute(String name):从ServletContext的域属性空间中获取指定名称的数据。

● void removeAttribute(String name):从ServletContext的域属性空间中删除指定名称的数据。

● String getRealPath(String path):获取当前Web应用中指定文件或目录在本地文件系统中的路径,是基于盘符的路径。

● String getContextPath():获取当前应用在Web容器中的名称。

2.1.3 out

out,类型为javax.servlet.jsp.JspWriter。查看JavaEE文档,发现JspWriter类继承自IO流的Writer类。即out就是一个输出流对象。

JSP基础​_内置对象_19


2.1.4 page

查看JSP翻译为的Servlet,可以看到page对象即Servlet对象本身。这个对象在实际应用中并不常用。

JSP基础​_内置对象_20


2.1.5 exception

在普通的JSP页面中是不能使用exception内置对象的。因为打开JSP翻译为的Servlet,发现其中并没有exception对象。若要在页面中直接使用exception对象,则需要配合着page指令使用。page指令后面讲解。

2.1.6 其它对象

其它对象,还有request、response、session,及config。它们的用法与之前Servlet学习时的用法相同。只不过是直接使用在了JSP页面中了。

2.2 JSP指令(directive)

JSP指令的作用是为当前页面做一些基本的属性设置,为当前的页面的运行提供基本的环境。

根据功能的不同,JSP中包含三类指令:page指令,即页面指令include指令,即包含指令;及taglib指令,即标签库指令。无论哪种指令,其使用的语法格式均为如下形式:

JSP基础​_代码块_21


taglib指令在后面学习JSTL时再进行讲解。

2.2.1 page指令

page指令用于设置当前JSP页面的相关信息。一个JSP文件中可以包含多个page指令。常用的page指令的属性意义及用法如下:

(1)pageEncoding属性

pageEncoding属性用于设置当前JSP页面所使用的字符编码格式。即,用户在浏览器中通过右击查看编码所看到的编码格式。


JSP基础​_Java_22


其被JSP翻译引擎翻译到Servlet中的语句,是_jspService()方法中的setContentType()。

JSP基础​_Java_23


(2)contentType属性

contentType属性用于设置当前JSP页面呈现于用户浏览器中的内容类型,通常为text/html类型,即html格式的文本。若在JSP页面中设置如下:

JSP基础​_代码块_24


对应JSP翻译引擎翻译过的Servlet中的语句,则为_jspService()方法中的如下语句:

JSP基础​_Java_25


但,若直接通过contentType属性指定内容类型与字符编码:

JSP基础​_Java_26


则生成的Servlet语句仍为:

JSP基础​_Java_27


所以,当内容类型为”text/html”时,使用pageEncoding属性与contentType属性效果是相同的。只有当内容类型不为”text/html”时,才专门使用contentType 属性指定。在指定字符编码时,这两个属性一般不同时使用。

(3)import属性

import属性,用于完成在JSP页面中导入指定的类。其被JSP引擎翻译为Servlet中的import语句。例如:

JSP基础​_内置对象_28


在Servlet中被翻译为了:

JSP基础​_Java_29


若要导入多个类,则在import属性值中可使用逗号将这些类分隔。

JSP基础​_内置对象_30


在Servlet中将被翻译为多个import语句:

JSP基础​_Java_31


(4)errorPage属性

errorPage属性用于指定,当前页面运行过程中发生异常时所要跳转到的页面。

JSP基础​_Java_32


该属性会被翻译到Servlet的_jspService()方法中。

JSP基础​_内置对象_33


(5)isErrorPage属性

若一个页面中指定了发生异常后所要跳转的页面,将会出现一个问题:异常信息被隐藏了。在控制台看不到异常信息,在所跳转的页面中也看不到异常信息。这对于程序员来说,不是件好事,没有足够的信息提示。

此时,一般是希望当异常发生后,在所要跳转的页面中能够给出相应的异常信息。而异常信息是封装在异常对象exception中的。也就是说,我们需要在所要跳转的页面中能够获取到异常对象。此时,就需要将这个所要跳转的页面指定为“错误处理页面”。

当一个页面的page指令中设置isErrorPage的值为true时,表明当前页面为一个“错误处理页面”。默认isErrorPage的值为false。

JSP基础​_代码块_34


一旦一个页面page指令的isErrorPage属性被指定为了true,查看JSP页面所翻译为的Servlet 则会发现,在_jspService()方法中,多出了一个变量exception。

这就是内置对象exception,可以在JSP的Java代码块、表达式块中直接使用的内置对象。

JSP基础​_代码块_35


(6)session属性

session属性用于指定当前页面中是否可以直接使用内置对象session。默认为true,可以使用。查看JSP翻译为的Servlet,可以看到session的创建,使用的是无参方法getSession()。该方法的功能是,若当前具有session,则使用当前的session;若当前没有session,则会新建一个session。即session对象肯定不为null。

JSP基础​_Java_36


但,有些情况下,我们希望的仅仅是获取到之前已经存在的session,若之前没有session,则并不需要创建session,即让session为null。显示使用默认的session 属性为true已经无法满足需求了。此时,就需要将session属性设置为false,即不使用内置对象session,而是在JSP页面的代码块中使用request的带参getSession()方法。 HttpSession session = request.getSession(false);

若设置session属性的值为false,查看生成的Servlet代码,会发现根本就没有出现内置对象session。

JSP基础​_代码块_37


JSP基础​_Java_38


2.2.2 include指令

include指令,即包含指令,用于将指定的文件包含到当前的JSP文件中。该指令只有一个属性file,用于指定要包含的文件。

(1)用法

被include指定包含的文件,可以是JSP动态页面文件,也可以是HTML静态页面文件。这里定义一个名为left.jsp的动态文件。其中定义了一个变量sum。

JSP基础​_代码块_39


再定义一个index.jsp,不仅将left.jsp文件包含了进来,还访问了变量sum。

JSP基础​_Java_40


运行结果为:

JSP基础​_Java_41


(2)静态联编

查看Tomcat的work目录中相关子目录,发现只生成了一个index_jsp.java的Servlet源文件,并没有生成left_jsp.java文件。那是因为JSP翻译引擎在翻译时,会将include指令所指定的文件内容直接翻译到当前JSP对应的Servlet中,形成一个.java文件。这就说明一个问题:这个包含操作是在编译之前完成的,是在编译之前由JSP翻译引擎完成的,不是在程序运行期完成的。这种包含是一种静态包含,称为静态联编。

由于在编译期就将这些文件合并为了一个Servlet文件,所以,整个过程就一个_jspService()方法。也就是说,这些文件之间是可以相互访问局部变量的。只要满足变量声明与使用的先后顺序即可。

JSP基础​_Java_42


(3)为什么使用include指令

对于一个包含很多页面的系统或站点,很多页面的头部、底部,或者左侧部分都是相同的。为了减少页面定义的重复性工作,为了便于对这些相同部分的修改,我们将这些相同的部分,分别定义为了多个页面。然后,让其它需要使用这些部分的页面,使用include指令将这些部分包含进来。这样的话,不仅大大减少了工作量,还做到了对于页面修改的“一改全改”效果。

2.3 JSP动作(Action)

在JSP页面中大量使用Java代码块、表达式块等内容,会使JSP页面看起来“杂乱无章”。为了使JSP页面看得简洁明了,为了简化Java代码,一般情况下,我们会尽量少的使用Java代码块与表达式块。取而代之的则是使用EL表达式、JSTL标签(后面学习),及JSP动作。

JSP动作是指,使用系统定义好的标签来完成本应由Java代码来完成的功能。

JSP动作的语法格式为:

JSP基础​_Java_43


JSP基础​_Java_44


JSP动作很多,但在实际开发时常用的就两个:转发动作与包含动作。

这两份个动作的完成,底层使用的是RequestDispatcher的forward()与include()方法实现的。而这两份种请求转发方式的本质区别是,标准输出流的开启时间不同。forward()方式的标准输出流是在目标资源中开启的标准输出流,而include()方式的标准输出流则是在当前发出包含运作的页面中开启的。所以,forward()动作的发起页面中是无法向标准输出流中写入数据的;而include()动作的发起页面及目标页面中均可向标准输出流中写入数据。

这两份个动作都具有一个page属性,用于指定要转向的页面。

2.3.1 forward动作

页面中一旦具有了forward动作,那么当前页面中的所有要显示的内容都将无法显示。因为页面直接转发到了下一个页面。

JSP基础​_Java_45


定义index.jsp页面

JSP基础​_代码块_46


定义next.jsp页面


JSP基础​_内置对象_47


打开JSP页面翻译为的Servlet,可以看到要跳转的路径出现在了Servlet代码中,即可以使用后台路径。

JSP基础​_代码块_48


注意,在JSP动作中,没有用于完成重定向的动作

2.3.2 include动作

include动作用于完成将指定页面包含到当前页面中的功能。

JSP基础​_代码块_49


(1)用法

定义index.jsp页面

JSP基础​_内置对象_50


定义left.jsp页面

JSP基础​_代码块_51


运行结果:

JSP基础​_Java_52


(2)动态联编

打开Tomcat的work目录的相关子目录,可以看到有两个.java文件:index_jsp.java与left_jsp.java。也就是说,包含动作的包含,是在运行期完成的,而非在编译期。这个包含动作,是在程序运行过程中,由index_jsp文件中的_jspService()方法通过JspRuntimeLibrary类的include()方法调用了left_jsp文件中的_jspService()方法。在运行期所执行的这种包含,称为动态联编。

JSP基础​_内置对象_53


(3)静态联编与动态联编的应用场景

在静态联编与动态联编均可使用时,一般使用静态联编。因为在程序运行时只存在一个Servlet,对资源的消耗较少,且不存在调用问题,执行效率较高。

若在两个文件间需要共享同一变量,此时只能使用静态联编。

若在两个文件间存在同名变量,且不能混淆,此时只能使用动态联编。

2.4 EL表达式

EL,Expression Language,表达式语言,是一种在JSP页面中获取数据的简单方式。EL表达式是从JSP2.0版本开始才引入的概念。

EL表达式的基本语法形式很简单:在JSP页面的任何静态部分均可通过${expression}的形式获取到指定表达式的值。

2.4.1 获取数据

(1)从四大域中依次查找数据

EL只能从pageConext、request、session、application四大域属性空间中获取数据。以下方式是无法获取到指定数据的。因为这个数据没有存放在四大域属性空间中。

JSP基础​_代码块_54


此时,可以将这个值存放到四大域属性空间中的任意一个中。然后访问其存放时的key即可。

JSP基础​_代码块_55


我们发现,无论是将变量存放到了哪一个域属性空间,通过${key}的方式,均可访问到该数据的值。EL到底是从哪个域空间中查找指定的key的呢?其底层实际是从最小范围依次查找,直到查找到最大范围application。这期间,只要查找到了,则直接获取,后面的域空间将不再查找。若最终没有查找到,则什么也不输出。

(2)从指定域中获取数据

从pageContext依次查找到application域空间,会降低执行效率。若某属性确定存放在某个域属性空间,则可指定直接从该空间中查找。此时需要借助EL的四个域属性空间相关的内置对象。

JSP基础​_内置对象_56


JSP基础​_内置对象_57


(3)访问Bean的属性

EL可以通过${key.属性} 的方式获取到指定对象的指定属性值。其底层实际调用的是该对象的相应属性的get方法。

当然,也可以使用${key[属性]或${key[“属性”]的方式获取。该方式不常用。

JSP基础​_Java_58


当然,若要访问一个对象的域属性的值,则可多次使用点号运算符,依次取出相应的属性值。

定义School类和Student类:

JSP基础​_内置对象_59


JSP基础​_内置对象_60


在JSP页面中访问:

JSP基础​_代码块_61


(4)获取数组中的元素

EL可以通过${key[索引]}的方式获取到指定索引的元素。不过,需要注意的是,若数组中不存在该指定索引的元素,系统并不会抛出数组越界异常。

JSP基础​_代码块_62


(5)获取List中的元素

与获取数组中的元素相同,通过${key[索引]}的方式可以获取List中指定索引的元素。若List中不存在该指定索引的元素,系统并不会抛出越界异常。

JSP基础​_代码块_63


不过,需要注意的是,EL无法输出Set集合中的元素。因为Set集合中的元素具有无序性,即没有索引的概念。无法通过索引获取元素。

(6)获取Map中的元素

EL通过${attributeName.mapKey}的方式可以获取指定Map的指定key的值。

JSP基础​_内置对象_64


2.4.2 运算符

EL表达式可以进行各种运算,其中常用的运算符有:

JSP基础​_代码块_65


除了上述运算符外,还有一个非常有用的运算符empty,其用法为${empty 变量},结果为布尔值。

● 若变量未定义,则返回值为true。

● 若变量为String类型,且其值为空串,则返回值为true。

● 若变量为引用类型,且其值为null,则返回值为true。

● 若变量为集合类型,且其不包含任何元素,则返回值为true。

JSP基础​_代码块_66


2.4.3 EL内置对象

就像JSP的Java代码块及表达式块中可以使用九个内置对象一样,EL表达式中,同样也存在有内置对象,并且存在11个内置对象。常用的内置对象,除了前面使用过的四个域属性空间相关的内置对象外,还有如下几个。

(1)pageContext

该pageContext与JSP内置对象中的pageContext是同一个对象。通过该对象,可以获取到request、response、session、servletContext、servletConfig等对象。注意,这些对象在EL中不是内置对象。这些对象只能通过pageContext获取。

在EL中直接${pageContext.request}即可获取request对象。当然,其底层实际调用的是pageContext.getRequest()方法。同理,也可以通过类似方式获取到其它对象。

在这些获取的对象中,有一个是实际工程中最常用的:${pageContext.request.contextPath},用于获取当前项目的发布到服务器的名称。一般会用在JSP页面的路径前。


JSP基础​_内置对象_67


在EL的11个内置对象中,除了pageContext外,其它10个内置对象,其类型均为java.util.Map类型。

(2)param

在EL中通过${param.参数名}可获取到请求中指定参数名的值。例如,提交的请求为:

JSP基础​_Java_68


在JSP页面中通过如下方式,可获取到phone参数的值为:15388994236。

JSP基础​_内置对象_69


(3)paramValues

若提交的请求中同一参数具有多个值,则可通过${paramValues.参数名[索引]}获取到指定索引号的该参数值。例如,提交的请求为:

JSP基础​_内置对象_70


在JSP页面中获取方式如下:

JSP基础​_内置对象_71


在浏览器中显示内容如下:

JSP基础​_代码块_72


(4)initParam

在EL中通过使用${initParam.初始化参数名}可以获取到指定的初始化参数的值。例如,在web.xml中定义了初始化参数。

JSP基础​_内置对象_73


在JSP的EL中可访问该初始化参数:

JSP基础​_Java_74


2.4.4 自定义EL函数

EL中存在一个问题:不支持字符串的操作。就连最简单的字符串连接,例如${“ab”+“cd”}都不支持,运行会报错。更不要提将字符串中的字符转换为全大写、判断一个字符串中是否包含另一个子串等操作,EL本身更是无法完成。但EL支持自定义函数,可以在EL中调用自定义的函数来完成这些功能。

下面以实现“将一个字符串转换为全大写”功能为例,来讲解自定义EL函数的过程。

(1)定义函数

定义一个POJO类,在该类中定义一个静态方法。该方法可以实现将字符串转换为全大写字母的功能。

JSP基础​_代码块_75


(2)注册函数

在Web项目的/WEB-INF目录下,新建一个扩展名为.tld的XML文件,例如myfn.tld。tld,即Tag Library Definition,标签库定义。将定义好的函数,在这个文件中进行注册。

A、添加约束

XML文件是需要在文件中添加相应约束的,即添加配置文件头部信息。这个头部信息可以从Tomcat的webapps中的/examples/WEB-INF/jsp2/jsp2-example-taglib.tld文件中复制。

JSP基础​_Java_76


B、添加其它子标签注册函数

JSP基础​_内置对象_77


(3)使用函数

在JSP页面中若要使用自定义的EL函数,则需要如下几步:

A、引入自定义的函数库

使用taglib指令将自定义的函数库引入。

JSP基础​_内置对象_78


注意:taglib指令的prefix属性也可以不指定,但是,必须要在上面的标签库描述文件myfn.tld中使用<short-name>标签指定;若在上面的标签库描述文件myfn.tld中没有使用<short-name>标签指定,则必须在JSP页面中指定taglib指令的prefix属性和值。

B、在EL中使用函数

需要注意的是,EL是不能处理域属性空间以外的变量的。所以,EL函数也是无法处理域属性空间以外的变量的。

JSP基础​_Java_79


2.4.5 JSTL中的EL函数

(1)JSTL

Apache已经定义好了一套标准的标签库规范,称为JSTL,JSP Standard Tag Library,即JSP标准标签库。该规范已通过JCP审核认定。

在JSTL中,已经定义好了一套对于字符串进行处理的函数标签库,这个函数标签库中定义了16个对于字符串进行处理的函数。我们在JSP页面中可以直接使用。

当然,需要使用JSTL,首先需要将其Jar包导入。

JSP基础​_Java_80


注意:MyEclipse中自带JSTL的jar包,分别为:javax.servlet.jsp.jstl.jar和jstl-impl.jar

(2)JSTL的EL函数标签库

JSTL函数标签库的.tld文件存放于standard的jar包的META-INF目录中,文件名为fn.tld。

JSP基础​_Java_81


JSP基础​_内置对象_82


打开fn.tld文件,可以看到其为fn,uri为http://java.sun.com/jsp/jstl/functions。然后,其中定义了16个对于字符串进行处理的函数。

JSP基础​_内置对象_83


(3)16个EL函数

JSTL的函数标签库中定义的这16个EL函数如下表所示:

序号

函数名

功能说明

1

fn:contains(String,String)

判断在源字符串中是否包含目标字符串

2

fn:containsIgnoreCase(String,String)

判断在源字符串中是否包含目标字符串,并且 在判断时忽略大小写

3

fn: endsWith(String,String)

判断源字符串是否以指定的目标字符串结尾

4

fn:startsWith(String,String)

判断源字符串是否以指定的目标字符串开头

5

fn:indexOf(String,String)

在源字符串中查找目标字符串,并返回源字符串中最先与目标字符串匹配的第一个字符的索引

6

fn:replace(String,String,String)

把源字符串中的一部分替换为另外的字符串,并返回替换后的字符串

7

fn:substring(String,int,int)

获取源字符串中的从第二个参数指定下标开始,到第三个参数减1后的下标结束的子串

8

fn:substringBefore(String,String)

获取源字符串中指定子字符串之前的子字符串

9

fn: substringAfter(String,String)

获取源字符串中指定子字符串之后的子字符串

10

fn:split(String,String)

将源字符串拆分为一个字符串数组

11

fn:join(String[],String)

将源字符串数组中的所有字符串连接为一个字符串

12

fn:toLowerCase(String)

将源字符串中的所有字符改为小写

13

fn: toUpperCase(String)

将源字符串中的所有字符改为大写

14

fn:trim(String)

将源字符串中的开头和末尾的空格删除

15

fn:escapeXml(String)

将源字符串中的字符“<”、“>”、“””和“&”等转换为转义字符

16

fn:length(Object)

返回字符串中的字符的个数,或者集合和数组 的元素的个数

(4)EL 函数用法

在JSP页面中使用JSTL的EL函数,首先需要使用taglib指令将JSTL的函数标签库引入。需要注意的是,EL函数是无法操作非域属性中的变量的。

JSP基础​_Java_84


2.4.6 EL总结

● EL不能出现在Java代码块、表达式块等JSP的动态代码部分。

● EL只能从pageConext、request、session、application四大域属性空间中获取数据。

● EL不会抛出空指针异常。若访问一个null对象的属性,则什么也不显示。

● EL不会抛出数组访问越界异常。若访问一个数组中的不存在的元素,则什么也不显示。

● EL不具有对字符串进行处理的能力,就连简单的字符串拼接都不行。

2.5自定义标签

JSP中支持自定义标签。自定义标签是为了简化代码,使用自定义标签替换一个Java代码片断,完成相同的功能。

2.5.1 基本用法

下面以“在JSP页面中获取并输出客户端IP”为例,来讲解自定义标签的实现步骤。

(1)需求

若使用Java代码块来完成这个功能,则JSP页面定义如下:

JSP基础​_内置对象_85


下面要定义一个标签,替换掉这段代码并实现相同功能。

(2)定义标签处理器

一个标签实际上对应着一个类中的一段代码。若要定义一个类来完成自定义标签的功能,则需要该类实现一个接口:javax.servlet.jsp.tagext.SimpleTag。这个类称为标签处理器类。该接口中具有五个需要实现的方法:

JSP基础​_Java_86


● doTag():当JSP页面中执行到标签时,由服务器自动调用执行的方法。自定义标签功能的实现都在这个方法中。

● getParent():获取到当前标签的父标签的引用。

● setJspBody():由服务器自动调用。服务器会将JSP代码片断传入给当前的Java类。

● setJspContext():由服务器自动调用。服务器会将PageContext对象传入给当前的Java类。注意,PageContext是JspContext的子类。

● setParent():由服务器自动调用。服务器会将当前标签的父标签引用传入给当前的Java类。

不过,javax.servlet.jsp.tagext.SimpleTagSupport类实现了SimpleTag接口,并且,该类还提供了获取JSP片断、获取PageContext的方法。

JSP基础​_代码块_87


所以,我们就无需再实现SimpleTag接口了,只需要继承SimpleTagSupport类即可。

JSP基础​_内置对象_88


(3)注册标签处理器

在项目的WEB-INF目录下,新建一个扩展名为.tld的XML文件。该文件的约束,同样来自于Tomcat的webapps中的/examples/WEB-INF/jsp2/jsp2-example-taglib.tld 文件。在其中添加相应的子标签。

JSP基础​_Java_89


以上配置就定义了一个前辍为mytag,标签为clientIp的标签,该标签没有标签体。

(4)使用自定义标签

在JSP页面中,只需通过taglib指令,将相应的标签库导入即可。

JSP基础​_内置对象_90


(5)标签处理器实例的生命周期

标签处理器是多例的。每执行一次自定义标签,都会创建一个标签处理器的实例。该标签执行完毕,则标签处理器实例被销毁。

可以在标签处理器中添加一个无参构造器。会发现,每刷新一次页面,均会创建一个标签处理器实例。

2.5.2 定义带标签体的标签

若要定义带标签体的标签,则需要注意以下几点:

● 使用getJspBody()方法可以获取到标签的文本对象,此文本对象以JspFragment对象形式出现。

● JspFragment对象具有一个invoke(Writer)方法,该方法可以将标签文本对象写入到输出流中。

● 若要获取字符串类型的标签文本数据,则需要将JspFragment对象写入到一个具有缓存功能的输出流中,且该流数据还可以被截取到。一般这个输出流使用StringWriter。

(1)需求

JSP基础​_Java_91


定义一个标签,实现的功能是,将标签体文本中小写字母转换为大写字母。若标签体文本来自于表达式的计算结果,则先计算表达式,再将结果转换为大写。

(2)定义标签处理器

JSP基础​_代码块_92


(3)注册标签处理器

JSP基础​_Java_93


对于标签,其值有四个选项:

● empty:表示自定义的标签没有标签体

● scriptless:表示自定义的标签具有标签体,且标签体文本中不能包含Java脚本,即不能包含Java代码段与Java表达式。但可以包含EL表达式,且会对EL表达式进行计算。

● JSP:在JSP2.0之前,定义标签处理器类需要实现Tag接口,或继承自TagSupport类。那时候该属性值有用,表示会将标签体的文本内容原样显示在浏览器。该属性值对于继承自SimpleTagSupport类的标签处理器是不能使用的。使用会报错。

● tagdependent:表示会将标签体的文本内容原样显示在浏览器。若其中包含EL表达式,也会将其作为普通字符串,而不对其进行计算。

(4)运行结果

JSP基础​_代码块_94


(5)其它情况

若将customTagLib.tld中该<tag/>中的<body-content>值设置为tagdependent。

JSP基础​_Java_95


则以上JSP页面的运算结果会是,将所有内容全部作为普通字符串,即使EL表达式,也不会进行计算。

JSP基础​_内置对象_96


2.5.3 定义带属性的标签

若要定义带属性的标签,则需要注意以下几点:

● 标签中的属性,反映到标签处理器中,就是一个具有set方法的属性。

● 对于JspFragment的invoke(Writer)方法需要注意,若指定了输出流,则会将标签文本对象写入到指定的输出流中。若没有指定输出流,即指定输出流为null,则默认将标签文本对象写入到JSP标准的输出流JspWriter中。

(1)需求

JSP基础​_代码块_97


定义一个标签,实现的功能是:若test属性的值为true,则显示标签体的文本。否则不显示。其中test的值可以来自于表达式的计算结果。

(2)定义标签处理器

标签处理器中声明的具有set方法的属性,其属性名即为标签的属性名。

JSP基础​_内置对象_98


以上代码完全等价于下面的代码:

JSP基础​_内置对象_99


(3)注册标签处理器

JSP基础​_Java_100


若自定义标签具有属性,则需要在中添加子标签。

● 指定属性名。要保证与标签处理器中的属性(IfSimpleTag类中有一个名为test的属性)名相同。

● 指定该属性在标签中是否是必须的,为true则为必须的,为false则可有可无。

● 指定该属性值是否可以来自于运行时表达式的值,为true表示属性值可以来自表达式,为false则表示该值只能为常量。这里的表达式指的是EL表达式与JSP中的表达式块。rtexprvalue,是Runtime Expression Value的简写。

2.5.4 定义forEachList标签

(1)需求

JSP基础​_Java_101


定义一个标签,实现的功能是:循环遍历属性items所指定的list,当前遍历的元素名称为var属性所指定的名称,使用EL在标签体中输出当前元素的值。

(2)定义标签处理器

JSP基础​_Java_102


需要注意以下几点:

● var定义为String,而非Object。因为我们需要的是将var作为域属性名称放入到域属性空间,而具体的元素,是作为域属性空间中的值出现的。

● 在循环遍历时,需要将当前循环元素存放到域属性空间中。因为JSP页面中是通过EL读取每一个List的元素的。所以每一个List的元素,均需要存放到域属性空间中。

● 每循环遍历一次,都需要将当前的标签文本对象放入到JSP的标准输出流中,以显示于JSP页面中。

(3)注册标签处理器

JSP基础​_代码块_103


注册标签处理器时需要注意:

● 属性items的值允许来自表达式,所以的值为true。

● 属性var的值不允许是动态的,所以的值为false。

2.5.5 定义forEach标签

(1)版本一

A、需求

前面定义的forEachList标签,只能循环遍历List集合。但对 Set、Map集合,及数组均不能进行遍历。这里要定义一个标签forEach,可以用来遍历Set、Map集合,及 Object数组。

JSP基础​_代码块_104


JSP基础​_Java_105


B、定义标签处理器

JSP基础​_Java_106


对于标签处理器类的定义,需要注意以下几点:

l ● 为了兼容所有集合类型及数组,items属性定义为了Object类型。

在处理器中声明一个Collection类型的成员变量,并让其指向一个ArrayList。目的是为了将所有集合类型及数组元素的值存放到这个ArrayList中。

根据集合及数组类型的不同,采用不同的方式为Collection赋值。

对于Map,是将Map.entry作为元素的 Set 集合赋予给了Collection。

对于Object[],是借助Arrays.asList()方法,将数组转换为了List。

在doTag()方法中,循环编历的是Collection对象,而非items。

C、注册标签处理器

JSP基础​_内置对象_107


(2)版本二

A、问题

以上的标签处理器对于Object[]可以进行正确处理,但对于基本数据类型的数组,例如int[]进行处理,将会出错。

JSP基础​_内置对象_108


B、改进标签处理器

对于int[]类型的数组,需要专门进行判断,且还不能使用Arrays工具进行转换,需要逐一将数组元素赋值给集合元素。只需要修改setItems()方法即可。

JSP基础​_Java_109


(3)版本三

A、问题

上面的版本二的标签处理器的确将遍历int[]数组的问题解决了。但对于基本数据类型的数组,还有7种,例如,short[]、long[]等。逐个类型的判断就显得很麻烦了。此时可以通过反射数组的方式实现。

使用反射数组方式,无需判断数组是引用类型还是基本类型,只要是数组即可。这里需要用到Class中的方法isArray()来判断当前对象是否为数组。

JSP基础​_代码块_110


另外,还需要用到反射类java.lang.reflect.Array的两个方法getLength()与 get()获取数组长度及数组元素。

JSP基础​_内置对象_111


JSP基础​_内置对象_112


B、进一步改进标签处理器

JSP基础​_Java_113


2.5.6 将自定义标签库打包发行

将前面的自定义标签库打为Jar包后,就可以在今后的项目中使用了。打开步骤如下:

(1)在自定义标签库项目上右击,选择Export…

(2)在弹出的Export窗口中选择 Java/JAR file 后,Next

JSP基础​_代码块_114


(3)在JAR Export窗口中选择要打包的资源。

首先将如下图所标识的三个文件前的对勾去掉,即不进行打包。

然后,单击Browse…按钮,选择打包后的文件的存放位置及文件名,例如存放到桌面,文件名为myjstl.jar。

然后,在右侧窗口中打开包含资源的项目,默认src与WebRoot前打有对勾。将WebRoot前的对勾去掉。直接Finish。

JSP基础​_代码块_115


(4)结束后,在桌面就可看到一个名称为myjstl.jar的文件。但,需要注意的是,该打包的文件中是没有.tld文件的,需要将其放进去。

双击该jar文件,WinRar软件会打开它,看到其中有一个名称为META-INF的目录。将.tld文件,本例为myCustomTagLib.tld,直接拖放到META-INF目录中即可。

JSP基础​_代码块_116


JSP基础​_Java_117


(5)这样,一个自定义的标签库就打包好了。在今后的项目中,只需将该Jar包导入,在页面中将通过taglib指令将标签库导入即可使用了。

2.6 JSTL

前面我们自定义的标签库,已经由JCP的成员Apache定义好了,并且也已经打包。我们只需要导入该Jar包后,在页面中使用taglib指令将其导入即可使用。这套标签库称为JSTL,JSP Standard Tag Library,即JSP标准标签库。JSTL中定义了五个标签库:

● 核心标签库:主要用于完成基本的逻辑运算。重点。

● 格式化标签库:主要用于完成日期、数字的格式化显示。

● EL函数标签库:定义了若干EL函数。

● SQL操作标签库:完成SQL操作。不使用了。对于SQL操作,已经完全由Java代码完成。

● XML操作标签库:完成XML操作。不使用了。对于XML操作,已经完全由Java代码完成。

2.6.1 核心标签库

使用JSTL的核心标签库,需要在页面中通过taglib指令首先将标签库导入。

JSP基础​_内置对象_118


(1)c:set

用于进行变量定义,并将变量存放到指定域属性空间;为Bean的属性赋值;设置Map的key与value等。该标签在实际开发中并不常用。

JSP基础​_Java_119


value:变量的值。

var:变量名。

scope:将变量存放的域属性空间,取值为page、request、session、application。默认为page范围。

JSP基础​_内置对象_120


target:指定的Bean对象。

property:指定对象的指定属性。

value:为指定对象的指定属性所赋的值。

JSP基础​_内置对象_121


target:指定的map

property:指定map的指定的key

value:为指定map的指定的key所赋的值

(2)c:remove

从域属性空间中删除指定变量。该标签不常用。

JSP基础​_Java_122


var:指定要删除的变量名

scope:指定要删除哪个域属性空间中的指定变量。不指定该属性,则删除所有域属性空间中的该变量。

(3)c:out

用于在页面上输出EL表达式的值。该标签不常用。

EL表达式不用标签,就可以直接在页面上输出,那么使用这个标签有什么更强的作用呢?

JSP基础​_代码块_123


value:指定要输出的EL表达式

escapeXml:是否忽略HTML标签。若为true,则不解析HTML标签,将按原样输出,为默认值。若为false,则解析HTML标签。EL表达式本身也是解析HTML标签的,无法忽略,不能将HTML标签按照原样输出。

JSP基础​_Java_124


default:指定默认值。若要输出的变量不存在,或其值为Null,则输出默认值。而对于EL表达式,对于不存在或值为Null的变量,其不进行输出。

(4)c:catch

当有异常发生时,用于捕获异常。相当于try-catch中的catch代码块。该标签不常用。该标签只有一个属性var,是捕获到的异常对象。

JSP基础​_Java_125


(5)c:if

用于实现对于条件的判断。

JSP基础​_代码块_126


test:判断的条件。若为true,则执行标签体,否则不执行。

var:存储test的判断结果。不常用。

scope:存储test判断结果的变量的存放范围。不常用。

(6)c:choose

实现多分支判断。类似于switch-case语句。一个标签中可以包含多个与一个。

下面的例子实现对于页面上“首页”“上一页”“下一页”“末页”导航按钮的显示功能:

总共一页的情况:四外按钮都是普通文本,没有超链接。

当前页是第一页的情况:首页、上一页是普通文本;下一页、末页是超链接。

当前页是最后一页的情况:首页、上一页是超链接;下一页、末页是普通文本。

其它情况,即当前页为中间页的情况:四个按钮均是超链接。

JSP基础​_Java_127


(7)c:forEach

用于循环遍历数组、List、Set、Map集合。在实际应用中使用非常频繁。

A、常规用法

JSP基础​_代码块_128


JSP基础​_代码块_129


JSP基础​_Java_130


JSP基础​_Java_131


B、指定遍历的起始索引及步长

对于数组及List,可以指定遍历的起始索引及步长。

JSP基础​_内置对象_132


● begin:指定遍历开始索引。从0开始计数。

● end:指定遍历结束索引,遍历结果包含这个索引。

运行结果为:

JSP基础​_Java_133


JSP基础​_内置对象_134


● step:指定遍历时的步长

运行结果为:

JSP基础​_代码块_135


C、获取当前遍历对象的相关信息

标签中有一个属性

● int getIndex():获取当前对象的索引。从 0 开始计数。

● int getCount():获取当前对象的序号。从 1 开始计数。

● boolean isFirst():判断当前对象是否是第一个对象。

● boolean isLast():判断当前对象是否是最后一个对象。

a、添加序号

JSP基础​_代码块_136


运行结果为:

JSP基础​_内置对象_137


b、隔行着色

在前面页面中添加一段CSS代码:使用类选择器,名称为odd的类使用一种背景颜色;名称为even的类使用另一种背景颜色。

JSP基础​_内置对象_138


再修改中的,为每一行添加一个类名:奇数行添加odd类名,偶数行添加even类名。

JSP基础​_代码块_139


运行结果为:

JSP基础​_Java_140


2.6.2 格式化标签库

使用JSTL的核心标签库,需要在页面中通过taglib指令首先将标签库导入。

JSP基础​_Java_141


(1)fmt:formatDate

该标签用于使用不同的模式格式化日期。其常用属性有:

● value:将要被格式化的数据。

● pattern:格式化的模式。其与SimpleDateFormat的参数设置方式相同。

● var:格式化后的字符串所要存放的变量。若不指定var,则会将格式化过的结果直接显示在页面。

● scope:变量存放的域属性空间,取值为page、request、session、application。默认为page范围。

● type:其取值为date、time,或 both,表示给出的value是日期、时间,还是两者都包含。默认为date。

JSP基础​_代码块_142


(2)fmt:parseDate

该标签用于将指定字符串转换为日期类型。常用的属性有:

● value:将要被转换的数据。

● pattern:将要被转换的数据的模式。其与SimpleDateFormat的参数设置方式相同。

● var:转换后的日期类型数据所要存放的变量。若不指定var,则会将转换过的结果直接显示在页面。

● scope:变量存放的域属性空间,取值为page、request、session、application。默认为page范围。

JSP基础​_代码块_143


(3)fmt:formatNumber

该标签用于按照指定格式对数字进行格式化。常用的属性有:

JSP基础​_内置对象_144


JSP基础​_Java_145


若要使用pattern模式,则其中常用的符号的意义为:

JSP基础​_代码块_146


(4)fmt:parseNumber

该标签用于将指定字符串转换为数值类型。常用的属性有:

JSP基础​_Java_147


JSP基础​_代码块_148






阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6