目录
一、介绍
JSP(Java Server Page)建立在servlet规范上的动态网页开发技术。在服务器端动态生成网页,而静态网页服务器每次返回的源代码都一样。jsp主要由html页面内容和jsp元素组成,即html网页中可以插入java代码。
二、运行原理(重点)
jsp文件后缀为jsp,当有请求到来时,web服务器会在web.xml中查到成功匹配的映射<servlet-mapping/>,会找到名称为jsp的servlet。这个servlet是jsp引擎,由/conf/web.xml配置的,拦截.jsp后缀的jsp网页。jsp引擎收到请求后,找到jsp对应的编译好的servlet,交由该servlet执行,处理结果传入响应消息。如果是第一次请求该jsp,则没有存在对应的servlet,jsp引擎便会将jsp文件转化为.java文件。转化非常简单,所有的模板元素(html标签)直接转化为write语句,所有的jsp元素转化为java代码。再将.java文件转化为.class可执行文件,最后交由servlet容器执行,处理结果传入响应消息中。
生成的java、class文件都被放入/work目录下。因此打开其中一个生产的java文件,可以看到对应的servlet源码。生成的servlet源码会继承HttpJspBase类,该类的init方法中调用了jspInit()和_jspInit(),destroy方法调用了jspDestroy()和_jspDestroy(),service方法调用了_jspService()。生成的servlet源码中实现了_jspInit()和_jspDestroy(),因此可以在jsp的声明元素中定义jspInit()和jspDestroy()方法来实现jsp的初始化和销毁。
因此jsp的生命周期为:jspInit()(jsp第一次被执行时)--->_jspService()(可能被执行多次)---->jspDestroy()(jsp被销毁时)。
jsp文件由模板元素(html内容)、jsp表达式、jsp脚本文件、jsp声明和jsp注释组成。模板元素会被转化为_jspService的输出语句;jsp表达式和jsp脚本文件会被转化到_jspService()中的java代码;jsp声明被转化生成类的成员声明;而jsp注释被忽略。
在_jspService中声明了很多隐式变量(见下文),因此可以在jsp表达式、jsp脚本片段中使用这些变量,当然也可以使用jsp声明的变量和函数。
jsp指令,设置了一些jsp页面的信息,影响jsp引擎翻译的过程和翻译后servlet的总体结构。
三、jsp基本语法
3.1、模板元素
jsp和html相似,只不多jsp可以在html中加入了jsp元素。
3.2、jsp表达式
jsp表达式用于将表达式中的结果转化为字符串并输入到相应位置。由于是表达式,所有expression后面不能有分号。
<%= expression %>
3.3、jsp脚本片段
脚本片段里可以嵌套一条或多条java程序代码,如:
<% int x=3;
out.println(x);
%>
单个脚本片段的java语句可以不完整,但是多个脚本片段组成的是完整java语句就行,即jsp引擎生成的jsp代码没有语法错误,如:
<% for(int i=1;i<3;i++){ %>
<h<%=i%>>itcast</h<%=i%>>
<%}%>
3.4、jsp声明
用于成员声明,比如可以定义成员方法、成员变量、实例代码块、静态方法、静态变量、静态代码块等。
<%!
java代码
%>
3.5、jsp注释
jsp注释信息在jsp引擎翻译jsp时被忽略,格式如下:
<%--注释信息--%>
不同于html注释,html注释在jsp文件中依然存在于输出的html结果中。html注释格式如下:
<!--html注释-->
四、jsp指令
jsp指令,设置了一些jsp页面的信息,影响jsp引擎翻译的过程和翻译后servlet的总体结构。格式如下:
<%@ directive attribute = "value" %>
jsp有三种指令:page、include和taglib。page定义了和页面相关的属性,include指令用于翻译阶段静态包含其他文件内容,tablib声明了标签库、自定义动作。
4.1、page指令
<%@ page attribute = "value" %>
S.No. | Attribute & Purpose |
---|---|
1 | buffer Specifies a buffering model for the output stream.指定缓存的大小,默认设有缓存,大小可以通过JspWriter的getBufferSize方法获得。 |
2 | autoFlush Controls the behavior of the servlet output buffer.指定缓存是否自动刷新,buffer需要存在。如果为true,则缓存满时自动刷新,如果为false,缓存满时抛出异常。默认true |
3 | contentType Defines the character encoding scheme. |
4 | errorPage Defines the URL of another JSP that reports on Java unchecked runtime exceptions.可以通过在web.xml中声明<error-page>元素为不同状态码设置通用错误页面,但errorPage属性可以覆盖该设置。 |
5 | isErrorPage Indicates if this JSP page is a URL specified by another JSP page's errorPage attribute.默认false,但是为true时该页面可以使用隐式变量exception。当然错误页面也可以为false,只是不能使用exception罢了 |
6 | extends Specifies a superclass that the generated servlet must extend. |
7 | import Specifies a list of packages or classes for use in the JSP as the Java import statement does for Java classes.可以多次声明该属性 |
8 | info Defines a string that can be accessed with the servlet's getServletInfo() method. |
9 | isThreadSafe Defines the threading model for the generated servlet.表明该页面是否是线程安全的,如果线程安全,则可以多个线程同时访问该jsp程序,否则一次只能一个线程执行。默认true |
10 | language Defines the programming language used in the JSP page.默认java |
11 | session Specifies whether or not the JSP page participates in HTTP sessions.是否内置session隐式变量。默认true |
12 | isELIgnored Specifies whether or not the EL expression within the JSP page will be ignored.是否使用EL表达式默认true |
13 | isScriptingEnabled Determines if the scripting elements are allowed for use.是否使用jsp表达式、脚本片段、声明。true则可以使用,false则只能使用EL表达式。默认true |
从上面可以看出,page指令可以影响隐式对象的定义,比如session、isErrorPage属性,当然也影响其他和jsp页面相关的内容。
这里要注意的是,Buffer和autoFlush影响了JspWriter的功能,也就是隐式变量out的功能,请看下文对out的介绍(5.1小节)。
如果发现jsp乱码,请添加:
<%@page language="java" contentType="text/html; utf-8" pageEncoding="utf-8" %>
4.2、include指令
include指令被用于翻译阶段,来静态包含一个文件,且只能是jsp文件,而动态包含<jsp:include.../>标签可以动态包含任何文件。原理是,静态包含仅仅是在jsp引擎翻译时将其他jsp文件包含进来合并生成一个servlet文件,而动态包含是将其他文件写入到response中。基本语法:
<%@ include file = "relative url" >
file属性是相对路径,如果以“/”开头,表示相对于当前web应用的根目录;否则,表示相对于当前文件。注意,相对路径是相对于文件的,而不是页面,因为是在翻译时执行的。
tablib现在都没怎么用了,不讲了。
五、jsp隐式对象
在第二节中讲到了,_jspService()中声明了很多隐式对象供jsp表达式、脚本片段使用。下面给出了所有的隐式对象,但是一些对象的存在受page指令的影响。
S.No. | Object & Description |
---|---|
1 | request This is the HttpServletRequest object associated with the request. |
2 | response This is the HttpServletResponse object associated with the response to the client. |
3 | out This is the JspWriter object used to send output to the client. |
4 | session This is the HttpSession object associated with the request.默认存在 |
5 | application This is the ServletContext object associated with the application context. |
6 | config This is the ServletConfig object associated with the page. |
7 | pageContext 代表jsp页面的容器 |
8 | page This is simply a synonym for this, and is used to call the methods defined by the translated servlet class.类型为Object |
9 | Exception The Exception object allows the exception data to be accessed by designated JSP.默认没有,需要设置page指令的isErrorPage,且该页面为错误页面 |
5.1、out对象
out为JspWriter类型的对象,该类模仿了BufferedWriter和PrintWriter的功能,拥有格式化输出和缓存的功能。通过page指令的buffer属性可以设置缓存大小,autoFlush设置是否自动刷新缓存。
如果有缓存,向out写入数据时,out对象先写入该对象的缓存中,当缓存满了后,如果autoFlush为true,将自动刷新缓存,否则抛出异常;如果没有缓存,向out写入数据时会直接写入到response中,表现的和PrintWriter一样。
注意的是,也可以通过response.getWriter()获得PrintWriter类型的输出流对象,同样可以向response写入,但是是直接就写入到response中!看个例子:
<%@ page ...
<html>
...
<%
out.println("first line<br>");
response.getWriter().println("second line<br>");
%>
....
</html>
输出结果:
second line
first line
这是因为out输出的内容在缓存中,等到刷新时,PrintWriter对象已经写入到了response中。
上面是书上的解释,然而不幸的是,PrintWriter自带BufferWriter缓存。。而JspWriter我也没有找到源码,因此上面的解析有点说不通了,但是可以把PrintWriter的缓存理解成该缓存在response中存在,因此向PrintWriter写入就是向response写入,这样有点说得通了。。
5.2、pageContext对象
pageContext代表当前JSP页面的运行环境,提供了一系列用于获取其他隐式对象的方法,也提供了操作不同作用域属性的方法。通过一个pageContext就可以获得其他8个隐式对象,通过查看jsp生成的servlet源码,会发现,其他隐式对象就是通过pageContext获取的。其他隐式对象可以通过一些列getXXX获得。
通过pageContext也可以存取不同作用域的属性,作用域分为四种:页面范围、请求范围、会话范围和应用程序范围。除了页面范围,其他范围的操作都是通过存取对应的隐式对象实现的,比如request、session(如果有的话)、application。需要注意的是,使用findAttribute(String name)在所有域对象中查找名称为name的属性时,会按照page、request、session和application的顺序依次查找,如果找到则返回该属性,否则返回null。
六、Actions
这里讲的Action就是以前缀jsp开头的标签,可以sevlet引擎的行为。不展开讲,略微了解下其中几个,或者参考标题的链接。基本语法格式是:
<jsp:action_name attribute="value" />
6.1、jsp:include
<jsp:include page="relative URL" flush="true|false" />
动态包含其他资源,将其他资源的内容插入到当前jsp的响应response中。page指定相对路径,flush表示在包含其他资源前是否要刷新当前页面的内容,默认为false。
与include指令不同,jsp:include可以包含的不仅仅是jsp格式的文件。现在从内部原理理解:与RequestDispatcher.include方法类似,使用jsp:include时,被包含的资源先被web容器执行,然后去掉状态行和头字段,将消息体的内容插入到该jsp的响应中repsonse。因此被包含的必须能够被web容器独立执行。
jsp:include运行时才包含其他资源,而且只包含运行结果。而include指令编译时包含,包含的是源代码。
6.2、jsp:forward
<jsp:forward page="relativeURL | <%= expression %>" />
与RequestDispatcher.forward类似,用于将请求转发给另一个资源,转发前后对response的输出无效,但不能关闭流,否则会报错。
6.3、jsp:useBean
<jsp:useBean id="..." type="..." class="..."/>
该标签用于在某个指定的域范围(pageContext、request、session、application)中查找一个指定名称的javaBean对象,如果存在则直接返回该javaBean对象的引用,如果不存在则实例化一个新的JavaBean对象并将它按指定的名称存储在指定的域范围中。id为域中属性名和变量名,type为变量的引用类型(默认为class的值),class为变量的实例类型。还可以在jsp:useBean标签内嵌套其他元素,只有第一次实例化时才执行。
实际上,该标签是通过上面所说的PageContext实现的,调用他的getAttribute和setAttribute方法实现的。
与之配套的还有jsp:setProperty和jsp:getProperty标签,但是不常用并不给出了,这里仅仅只是为了指出useBean的原理。
七、EL表达式
EL表达式可以简化jsp的书写,用来获取servlet域对象(pageContext、request、session和application)中存储的数据,是一种简单的数据访问语言。当然,EL提供了隐式对象来访问一些对象,不同于jsp提供的隐式对象!!!EL是一种新的语言,不像jsp一样简单的插入java代码,因此学习EL语言时不要被java思想误导。
7.1、简单语法
EL表达式的基本格式如下:
${ expression }
expression表示表达式,而变量名也是表达式,因此${variableName}也是正确的,关于何谓表达式请参考:Expressions, Statements, and Blocks 如果变量不存在则返回空字符串,如果变量存在则将结果转化为字符串输出。
EL表达式的操作数可以是域对象中的属性和隐式对象,如下所示:
操作数为隐式对象
${pageContext.request.requestURL}
操作数为域对象中的属性,先在page域中存入属性
<%pageContext.setAtrribute("userName","itcast")%>
然后可以直接访问该属性
${userName}
当然也可以通过域对象来访问
${pageScope.userName}
由于EL是数据访问语言,所以它的操作符大致为获取属性的点运算符、应用更广泛的方括号运算符、算术运算符和逻辑运算符等等。上面的例子只使用了点运算符。
总结来说,就是使用操作符来操作操作数。
7.2、运算符
这里只介绍重要的一些运算符,全部操作符请参考链接。
7.2.1、点运算符
点操作符用于访问Bean的属性和部分Map。访问对象属性,实际上通过反射调用了对象的setter方法,即使没有对应属性,但是有相应形式的setter方法就行了。比如,${customer.name}访问customer的name方法,实际上就是简单的调用了getName()方法,即使没有name属性。
map的键值为字符串时便可以通过点运算符来调用值:
<%
HashMap<String,Integer> map=new HashMap<>();map.put("a", 1);map.put("b",2);
pageContext.setAttribute("map",map);
%>
然后访问
${map.b}
7.2.2、方括号运算符[]
实际上方括号运算符的功能包括且比点运算符多,比如访问对象属性:${customer["name"]}。可以说方括号运算符可以访问Bean对象、List对象、数组、map。貌似set不好访问。下面给出例子:
访问对象属性
${customer["name"]}
访问List数组或数组
${list[0]}
访问map比较复杂点
如果为Map<String,Integer> map,且存入map.put("abc",22);则
${map["abc"]
如果为Map<Integer,String> map,且存入map.put(1,"abc");则
${map[1]}读取失败!!!
因为EL中索引为Long类型的,因此要改成Map<Long,String> map,然后
${map[1]}成功
7.2.3、其他运算符
其他运算符和java类似,比如算术运算符+-*/%,不过要注意的是,EL中两整数除法,商为小数。
还有比较运算符,每个比较运算符都有一个等价的保留字代替,逻辑运算符也是。
等等等,请参考标题链接。
7.3、EL隐式对象
EL有11个隐式对象,如下:
S.No | Implicit object & Description |
---|---|
1 | pageScope Scoped variables from page scope |
2 | requestScope Scoped variables from request scope |
3 | sessionScope Scoped variables from session scope |
4 | applicationScope Scoped variables from application scope |
5 | param Request parameters as strings |
6 | paramValues Request parameters as collections of strings |
7 | header HTTP request headers as strings |
8 | headerValues HTTP request headers as collections of strings |
9 | initParam Context-initialization parameters |
10 | cookie Cookie values |
11 | pageContext The JSP PageContext object for the current page |
但强调的是!!!除了pageContext,其他10个都不同于jsp的隐式对象。但是由于pageContext是相同的,因此可以通过EL表达式访问到其他8个jsp的隐式对象。
这里要说明一下EL表达式查找对象的顺序:EL表达式中获取对象名,然后从这11个隐式对象中找,如果没有就从所有作用域对象中查找,作用域对象的查找顺序为pageContext、request、session、application。因此就算域中出现了和隐式对象重名的属性,也是先访问到隐式对象。
通过pageScope、requestScope、sessionScope、applicationScope可以访问各个作用域中的属性,但是可以不写出隐式对象,直接访问属性。
param和paramValues用来访问请求参数的值,如果一个请求参数对应多个值,那么可以通过paramValues获得请求参数组成的数组。如果通过param访问,那只能访问到数组的第一个值。
header和headerValues用来访问头字段的值,和上述类似。
参考:
《java web程序开发入门》 传智。。。
《java web程序开发进阶》 传智。。。
jsp教程:https://www.tutorialspoint.com/jsp/jsp_expression_language.htm
jsp API:https://javaee.github.io/javaee-spec/javadocs/javax/servlet/jsp/package-summary.html
https://blog.csdn.net/han_dongwei/article/details/7988386