JSP就是一个Servlet, 只不过长的不像Servlet, 但是会被编译成一个Servlet在容器内运行. 知道了这一点, 就很方便了. 将JSP文件变成Servlet也是容器做的事情.
既然是一个Servlet, 其实JSP生成的servlet类也会有符合Servlet标准的那些方法, 之前的那些规范对于JSP文件也是一样的, 但是不能够直接编写.
这里还学到了一个新名词, 就是放在 <%...%>
之间的代码叫做scriptlet.
学JSP就像之前Django的模板一样, 需要换一种思路, 一起来看看知识点很多的JSP技术吧.
- JSP技术 - 指令
- JSP技术 - JS为P如何被解析成Servlet
- JSP技术 - 隐式对象
- JSP技术 - 生命周期
- JSP技术 - 给JSP配置初始参数
- JSP技术 - JSP的属性存放位置
- JSP技术 - include
JSP的代码与指令
在JSP文件中, 被<%...%>
包围的是Java代码, 还有一类特殊的Java 代码叫做指令, 指令有三种, page include 和 taglib, 这里先了解page 指令. 指令是放在<%@...%>
如果只是调用out隐式对象来输出内容, 可以将输出的内容简化为一个表达式, 表达式放在<%=...%>
中, 注意到这些细微的差别了吗, 看一个简单的例子:
<%@ page contentType="text/html;charset=UTF-8" language="java" import="com.example.util.Counter, java.util.List" %>
<%@ page import="java.util.ArrayList" %>
<html>
<head>
<title>测试程序</title>
</head>
<body>
<p>这是你第<%=Counter.getCount()%>次访问该页面.</p>
<a href="${pageContext.request.contextPath}/test">测试HttpServletResponse和Request对象</a>
<a href="${pageContext.request.contextPath}/form.html">啤酒表单</a>
<a href="${pageContext.request.contextPath}/attr">测试属性变化</a>
<p>以下是使用 out.println()输出的代码:</p>
<p>
<% List<Integer> lists = new ArrayList<>();
for (int i = 0; i < 10; i++) {
lists.add(i);
}
out.print(lists); %>
</p>
</body>
</html>
可以看到page指令的使用以及引入多个类. 还有表达式元素无需带分号, 相当于其中的内容是out.println()的参数. 而正常的scriptlet就是普通的java代码.
JSP中可以使用两种注释, HTTP注释和JSP注释, HTTP的注释客户端也会收到, JSP则在编译阶段被去掉.
指令一共有三个, taglib与标签库有关, include是页面包含, 这里先看一下page 指令后边可以跟哪些属性:
import
, 相当于java的 import 语句, 会将其转换成import语句放在生成的servlet类源代码中
method
, 指定这个JSP文件中的所有scriptlet放到哪个方法中, 默认就是service()方法, 一般无需改动
language
, jsp最初设想可以用于其他语言, 不过很遗憾目前为止仅仅只有java语言
contentType
, MIME类型, 对于HTML, 要设置成text/html;charset=UTF-8, 不然中文会乱码
session
, 指定当前页面是否使用session, 默认是true
errorPage
, 指定当前页面出现未捕获的异常的时候, 转到哪个页面.
isErrorPage
, 设置当前页面是不是处理异常的页面.
isElIgnored
, 设置当前页面是不是忽略EL表达式, 默认是不忽略也就是解析EL表达式.
这其中与El表达式和错误处理相关的后边才会看到, 不过其实已经略知一二了.
深入JSP - JS为P如何被解析成Servlet
JSP中的所有Java代码, 都会被放置在一个通用的方法里, 所以不能够像一个类一样定义实例变量. 其中定义的变量, 都可以将其看成一个方法的局部变量.
然而如果用 <! int count=0; %>
声明的变量, 就会被当成实例变量, 声明静态变量和方法也都可以.
某个容器具体处理JSP并编译, 取决于容器, 不过都会遵循规范. 整体的处理方式如下:
- 先会创建一个HttpServlet子类
- 然后解析JSP文件, 开始填充这个HttpServlet子类
- 查看指令, 如果有import, 将其写在类文件的最上边, 就和我们自己的源代码类似
- 查看声明, 将声明写到类文件内部, 方法前边.
- 创建服务方法, tomcat生成的服务方法是_jspService(), 这个方法会覆盖Servlet超类的service()方法, 此时容器会声明并初始化全部隐式对象, 然后把JSP中的普通代码部分都放到服务方法中.
- 将jsp文件中属于HTML的部分放到服务方法中, 与动态部分一起完成格式化工作, 输出到响应中.
深入JSP - 隐式对象
既然是一个servlet, 就想想我们自己的servlet对象, 需要ServletContext, 需要请求对象和响应对象, 需要Session和ServletConfig等等, JSP生成的Servlet类会初始化这些对象并分配固定的名字作为实例变量.
所以对象的名称可以直接在我们编写的jsp文件中使用(准确的说是在scriptlet和表达式中使用, 指令和声明中无法使用).对应关系如下:
- JspWriter - out
- HttpServletRequest - request
- HttpServletResponse - response
- HttpSession - session
- ServletContext - application
- ServletConfig - config
- JspException - exception, 只有指定的错误页面才能使用该对象
- PageContext - pageContext, 其中封装了其他隐式对象, 一般用于将隐式对象传递给其他对象
- Object - page
深入JSP - 生命周期
当一个请求用到JSP文件的时候, 下列事情发生:
- 容器将JSP文件转换成java源代码, 捕获JSP语法错误
- 容器把java源代码编译成class文件, 捕获Java语法错误
- 加载class文件作为Servlet
- 实例化这个Servlet, 调用生命周期方法
- 多线程调用Servlet的方法 _jspService()
JSP有和普通Servlet类似的生命周期方法, 由于JSP是一个Servlet, 因此有Servlet标准的生命周期方法, 标准生命周期方法里调用的是自己的三个方法:
- jspInit(), 在init()方法中调用
- jspDestroy(), 在destroy()方法中调用
- _jspService(), 在service()方法中调用
相当于JSP生成的Servlet把生命周期方法委托给了自己的三个特殊方法.
深入JSP - 给JSP配置初始参数
既然是一个Servlet, 应该能够配置初始化参数才对.
确实能够配置, 需要在Servlet标签内, 使用 jsp-file 标签, 通过路径指定jsp文件就可以了.
在取出的时候, 就可以使用隐式对象来取得了. web.xml中配置如下:
<servlet>
<servlet-name>gugu</servlet-name>
<jsp-file>/index.jsp</jsp-file>
<init-param>
<param-name>email</param-name>
<param-value>lee0709@vip.sina.com</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>gugu</servlet-name>
<url-pattern>/index.jsp</url-pattern>
</servlet-mapping>
在配置JSP的时候, 依然要一个servlet对应一个servlet-mapping, 但是servlet标签内不再用servlet-class标签来指定类, 而是换成了 jsp-file 标签来指定具体的jsp文件.
这里要注意, 也可以为jsp文件配置URL路径, 如果将上边的<url-pattern>/index.jsp</url-pattern>中的/index.jsp改成/index, 通过/index就可以访问到这个JSP.
按照上述配置好之后, 就可以在jsp文件中获取了:
<p><%= config.getInitParameter("email")%></p>
这里是使用了隐式对象config来获取. 想玩点花样的话, 也可以在jsp文件中声明 jspInit() 方法来覆盖默认的方法, 自己编写获取方法.在jspInit()执行的时候, 隐式对象都已经创建完毕了.
深入JSP - JSP的属性存放位置
在之前servlet中, 我们知道属性可以存在三个地方, 应用上下文, 请求对象, session对象中.
JSP还有第四个存放属性的作用域, 叫做PageContext, 也是前边的隐式对象之一. 既然说到存放属性, 自然这个对象也有那一套方法.
不过这个对象还有若干特殊之处:
- 封装了所有对其他作用域和隐式对象的引用, 可以获取出来
- 可以直接设置自己的, 以及其他作用域的属性
- 可以查找属性, 从自己开始查找, 如果找不到, 依次找 请求作用域 -> 会话作用域 ->应用作用域
来具体看看PageContext对象的API和应用:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>测试PageContext对象</title>
</head>
<body>
<h1>测试pageContext对象</h1>
<p>通过request隐式对象获取: <%= request.getAttribute("penguin") %></p>
<p>通过PageContext findAttribute获取对象: <%= pageContext.findAttribute("penguin") %></p>
<p>直接指定作用域设置: <% pageContext.setAttribute("foo", "bar", PageContext.SESSION_SCOPE);%></p>
</body>
</html>
这里特殊的是PageContext对象的setAttribute方法,带有四个作用域可供选择, 可以直接将指定的属性设置到指定的作用域上, 所以比较方便.
JSP技术 - include
顾名思义, 就是在页面中包含另外一个jsp对象, 不过有两种写法:
//使用指令的是静态导入
<%@ include file="index.jsp"%>
//使用动作的是动态导入
<jsp:include page="index.jsp">
<jsp:
这种叫做动作标签, 在之后的标记中会来仔细的看, 静态导入的意思是先把源代码放入到这里, 再编译成一个Servlet, 所以会共享当前jsp的变量.
动态导入就不是这样, 导入的动作相当于用当前的请求和响应对象到index.jsp里走了一圈, 然后把返回的结果放在这里. 通过为页面增加一个计数器, 可以很明显的看出两者的区别.
JSP的技术看上去确实很有意思, 已经可以想象到能在HTML里编写Java语言, 能做的事情就非常多了.
不过其实我们已经知道, 将代码写在HTML里可不是什么好主意, 一定会有标记语言来进行解耦.