Java Web Reinforcement 05 JSP技术

Java Web Reinforcement 05 JSP技术

JSP就是一个Servlet, 只不过长的不像Servlet, 但是会被编译成一个Servlet在容器内运行. 知道了这一点, 就很方便了. 将JSP文件变成Servlet也是容器做的事情. 既然是一个Servlet, 其实JSP生成的servlet类也会有符合Servlet标准的那些方法, 之前的

JSP就是一个Servlet, 只不过长的不像Servlet, 但是会被编译成一个Servlet在容器内运行. 知道了这一点, 就很方便了. 将JSP文件变成Servlet也是容器做的事情. 既然是一个Servlet, 其实JSP生成的servlet类也会有符合Servlet标准的那些方法, 之前的那些规范对于JSP文件也是一样的, 但是不能够直接编写. 这里还学到了一个新名词, 就是放在 <%...%> 之间的代码叫做scriptlet. 学JSP就像之前Django的模板一样, 需要换一种思路, 一起来看看知识点很多的JSP技术吧.
  1. JSP技术 - 指令
  2. JSP技术 - JS为P如何被解析成Servlet
  3. JSP技术 - 隐式对象
  4. JSP技术 - 生命周期
  5. JSP技术 - 给JSP配置初始参数
  6. JSP技术 - JSP的属性存放位置
  7. 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 指令后边可以跟哪些属性:
  1. import, 相当于java的 import 语句, 会将其转换成import语句放在生成的servlet类源代码中
  2. method, 指定这个JSP文件中的所有scriptlet放到哪个方法中, 默认就是service()方法, 一般无需改动
  3. language, jsp最初设想可以用于其他语言, 不过很遗憾目前为止仅仅只有java语言
  4. contentType, MIME类型, 对于HTML, 要设置成text/html;charset=UTF-8, 不然中文会乱码
  5. session, 指定当前页面是否使用session, 默认是true
  6. errorPage, 指定当前页面出现未捕获的异常的时候, 转到哪个页面.
  7. isErrorPage, 设置当前页面是不是处理异常的页面.
  8. isElIgnored, 设置当前页面是不是忽略EL表达式, 默认是不忽略也就是解析EL表达式.
这其中与El表达式和错误处理相关的后边才会看到, 不过其实已经略知一二了.

深入JSP - JS为P如何被解析成Servlet

JSP中的所有Java代码, 都会被放置在一个通用的方法里, 所以不能够像一个类一样定义实例变量. 其中定义的变量, 都可以将其看成一个方法的局部变量. 然而如果用 <! int count=0; %> 声明的变量, 就会被当成实例变量, 声明静态变量和方法也都可以. 某个容器具体处理JSP并编译, 取决于容器, 不过都会遵循规范. 整体的处理方式如下:
  1. 先会创建一个HttpServlet子类
  2. 然后解析JSP文件, 开始填充这个HttpServlet子类
  3. 查看指令, 如果有import, 将其写在类文件的最上边, 就和我们自己的源代码类似
  4. 查看声明, 将声明写到类文件内部, 方法前边.
  5. 创建服务方法, tomcat生成的服务方法是_jspService(), 这个方法会覆盖Servlet超类的service()方法, 此时容器会声明并初始化全部隐式对象, 然后把JSP中的普通代码部分都放到服务方法中.
  6. 将jsp文件中属于HTML的部分放到服务方法中, 与动态部分一起完成格式化工作, 输出到响应中.

深入JSP - 隐式对象

既然是一个servlet, 就想想我们自己的servlet对象, 需要ServletContext, 需要请求对象和响应对象, 需要Session和ServletConfig等等, JSP生成的Servlet类会初始化这些对象并分配固定的名字作为实例变量. 所以对象的名称可以直接在我们编写的jsp文件中使用(准确的说是在scriptlet和表达式中使用, 指令和声明中无法使用).对应关系如下:
  1. JspWriter - out
  2. HttpServletRequest - request
  3. HttpServletResponse - response
  4. HttpSession - session
  5. ServletContext - application
  6. ServletConfig - config
  7. JspException - exception, 只有指定的错误页面才能使用该对象
  8. PageContext - pageContext, 其中封装了其他隐式对象, 一般用于将隐式对象传递给其他对象
  9. Object - page

深入JSP - 生命周期

当一个请求用到JSP文件的时候, 下列事情发生:
  1. 容器将JSP文件转换成java源代码, 捕获JSP语法错误
  2. 容器把java源代码编译成class文件, 捕获Java语法错误
  3. 加载class文件作为Servlet
  4. 实例化这个Servlet, 调用生命周期方法
  5. 多线程调用Servlet的方法 _jspService()
JSP有和普通Servlet类似的生命周期方法, 由于JSP是一个Servlet, 因此有Servlet标准的生命周期方法, 标准生命周期方法里调用的是自己的三个方法:
  1. jspInit(), 在init()方法中调用
  2. jspDestroy(), 在destroy()方法中调用
  3. _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, 也是前边的隐式对象之一. 既然说到存放属性, 自然这个对象也有那一套方法. 不过这个对象还有若干特殊之处:
  1. 封装了所有对其他作用域和隐式对象的引用, 可以获取出来
  2. 可以直接设置自己的, 以及其他作用域的属性
  3. 可以查找属性, 从自己开始查找, 如果找不到, 依次找 请求作用域 -> 会话作用域 ->应用作用域
来具体看看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里可不是什么好主意, 一定会有标记语言来进行解耦.
LICENSED UNDER CC BY-NC-SA 4.0
Comment