Java Web开发中的jsp入门

Java Web开发中,经常会用到jsp,这里需要知道,容器在处理jsp代码时,会将其转换为Java源代码,然后再编译成完整的Java Servlet类。下面将对这个过程进行介绍。

1、容器如何将jsp转换为Java Servlet

1.1 转换步骤

容器处理jsp的机制,可以理解为如下几个步骤:

  • 查看指令(包括page、include和taglib等),得到转换时可能需要的信息。
  • 创建一个HTTPServlet子类。对于Tomcat 5,所生成的Servlet会扩展org.apache.jasper.runtime.HttpJspBase。
  • 如果一个page指令有import属性,它会在类文件的最上面(package语句下面)写import语句。对于Tomcat 5,package语句(我们在开发时不需要关心)是package org.apache.jsp;
  • 如果有声明(就是放在<%! %>中的变量声明或者方法声明),容器将这些声明写到类文件中,通常放在类声明的下面,并在服务方法前面。Tomcat 5声明了自己的一个静态变量和一个实例方法。
  • 建立服务方法。服务方法具体的方法名是_jspService()。_jspService()由servlet父类被覆盖的service()方法调用,接收HttpServletRequest和HttpServletResponse参数,在该方法中,容器会声明并初始化所有的隐式对象。
  • 将普通的HTML(也就是模板文本),scriptlet和表达式放到服务方法中,完成格式化,并写至PrintWriter响应输出。

1.2 例子

假如有jsp代码如下:


<%! int count = 0; %>
The page count is now:
<%= ++count %>

下面是一个Tomcat 5转换jsp生成的servlet类代码:

//这是一个Tomcat 5转化jsp生成的servlet类代码示例
/*
原jsp代码如下:


<%! int count = 0; %>
The page count is now:
<%= ++count %>


*/
package org.apache.jsp;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;

//如果该jsp的page指令有import属性,这里就会有显式的import语句。该jsp的page指令没有import。

public final class BasicCounter_jsp
        extends org.apache.jasper.runtime.HttpJspBase
        implements org.apache.jasper.runtime.JspSourceDependent {
    //一开始是jsp代码中的声明语句
    int count = 0;

    //接下来是Tomcat 5自己声明的变量和方法
    private static java.util.Vector _jspx_dependants;

    public java.util.List getDependants(){
        return _jspx_dependants;
    }

    //这里是服务方法的定义
    public void _jspService(HttpServletRequest request, HttpServletResponse response)
        throws java.io.IOException, ServletException {
        //在服务方法的一开始,容器声明了一堆局部变量,包括表示隐式对象的变量,jsp代码中常会用到这些隐式对象,比如out和request
        JspFactory _jspxFactory = null;
        PageContext pageContext = null;
        HttpSession session = null;
        ServletContext application = null;
        ServletConfig config = null;
        JspWriter out = null;
        Object page = this;
        JspWriter _jspx_out = null;
        PageContext _jspx_page_context = null;

        try {
            _jspxFactory = JspFactory.getDefaultFactory();
            response.setContentType("text/html");

            //初始化各种隐式对象
            pageContext = _jspxFactory.getPageContext(this, request,response,
                    null,true,8192,true);
            _jspx_page_context = pageContext;
            application = pageContext.getServletContext();
            config = pageContext.getServletConfig();
            session = pageContext.getSession();
            out = pageContext.getOut();
            _jspx_out = out;

            //运行并输出jsp中的HTML、scriptlet和表达式代码
            out.write("\r\r\r");
            out.write("\rThe page count is now: \r");
            out.print(++count);
            out.write("\r\r");
        } catch (Throwable t){
            if(!(t instanceof SkipPageException)){
                out = _jspx_out;
                if(out != null && out.getBufferSize() != 0)
                    out.clearBuffer();
                if(_jspx_page_context != null)
                    _jspx_page_context.handlePageException(t);
            }
        }finally {
            if(_jspxFactory != null)
                _jspxFactory.releasePageContext(_jspx_page_context);
        }

    }
}

1.3 jsp转换为servlet之后源文件的存放位置

Tomcat启动之后,当jsp文件第一次被访问之后,jsp就完成了到servlet文件的转换。这样,在tomcat的目录中,我们就可以看到tomcat转换后的servlet文件,包括
.java.class
一般来说,该这些Tomcat编译后的JSP文件(_jsp.class_jsp.java)可能出现在如下目录:

  • 一般存放在你安装的Tomcat目录下的work目录下
    C:\Program Files\Apache Software Foundation\Tomcat 8.0\apache-tomcat-8.5.32\work\Catalina\localhost
  • 可能没有存放在Tomcat中,那么就是存放在你部署的编译器的workspace中,例如使用IntelliJ IDEA部署
    C:\Users\Administrator\.IntelliJIdea2018.2\system\tomcat\_Hello-World-JSP\work\Catalina\localhost\
  • 使用Eclipse部署的Tomcat存放的JSP编译后文件
    \.metadata\.plugins\com.genuitec.eclipse.easie.tomcat.myeclipse\ tomcat\work\Catalina\localhost\

我在MacOS上,用IDEA 2017进行开发,Tomcat 8,在我的机器上,这些文件出现在如下目录:
/Users/chengxia/Library/Caches/IntelliJIdea2017.1/tomcat/Unnamed_HelloWorld/work/Catalina/localhost/ROOT/org/apache/jsp/

Java Web开发中的jsp入门_第1张图片
jsp转化为Servlet之后的存放路径

2、通过实例来理解Java Web开发中jsp转换为servlet的过程

为了充分理解jsp到servlet的转化,这里举一个实例。假如要在一个jsp页面中,实现对该jsp访问次数的统计,应该如何实现呢?通过对上面jsp转换为servlet过程的理解,这里我们提供两种思路:通过一个新建类的静态变量实现;通过jsp页面中的变量声明实现。

2.1 通过类的静态变量实现jsp访问次数统计

新建一个Counter类,其中,有一个静态的count成员变量,用于记录访问次数,有一个静态的方法getCount()用于获得count的值并每次自增1。在jsp页面中,只需要调用这个方法就行。代码如下:
com/web/comp/Counter.java:

package com.web.comp;

/**
 * Created by chengxia on 2019/2/17.
 */
public class Counter {
    /**
     * 静态的count变量,属于整个类共享能够实现计数功能。
     * */
    private static int count;
    /**
     * getCount()方法,加同步的意思是为了确保并发访问时,能够正常统计访问次数。
     * */
    public static synchronized int getCount(){
        count++;
        return count;
    }
}

CountViaClass.jsp:

<%--
  Created by IntelliJ IDEA.
  User: chengxia
  Date: 2019/2/17
  Time: 10:55 AM
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%--
    这里必须要导入这个包,不然页面找不到Counter类。
    该page import指令会在jsp转化为servlet的过程中,被处理成import语句。
--%>
<%@ page import="com.web.comp.Counter" %>


    Counter via Class static member


The page count is:
<%
    out.println(Counter.getCount());
%>


启动服务器之后的效果就是,当访问该jsp页面(http://localhost:8080/CountViaClass.jsp)时,页面能显示当前的访问次数,如下。

Java Web开发中的jsp入门_第2张图片
jsp显示访问次数via Class

2.2 通过jsp声明的方式实现访问次数统计

通过jsp转化为servlet的过程,可以看到jsp中的声明变量(<%! %>中的变量)会被转化为servlet类的成员变量。从servlet的生命周期,我们知道,servlet在第一次被访问时被实例化,之后,所有对该servlet的访问都是在一个新的线程中执行该实例的服务方法来完成的。
这样,我们如果在jsp页面中通过声明标签定义了一个声明变量,然后,在页面中通过scriptlet实现对该变量的自加和访问,就能够实现访问次数统计功能。代码如下:
CountViaDeclareTag.jsp:

<%--
  Created by IntelliJ IDEA.
  User: chengxia
  Date: 2019/2/17
  Time: 10:55 AM
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>


    Counter via Class static member


<%! int count = 0; %>
The page count is:
<%--
    这里是一个表达式标签,标签中的内容会被放入out.println()中,所以最后不能有分号。
--%>
<%= ++count %>


启动服务器之后的效果就是,当访问该jsp页面(http://localhost:8080/CountViaDeclareTag.jsp)时,页面能显示当前的访问次数,如下。

Java Web开发中的jsp入门_第3张图片
jsp访问次数统计 via JSP声明

3、jsp其它

3.1 注释

3.1.1 jsp中的注释

一般来说jsp中有几种注释方式:html注释、jsp注释和java注释。
(1) html注释



这里由于是html注释,如果其中包含scriptlet代码,最后仍然会被执行,这样注释中会看到最后scriptlet执行的结果。这些注释会传递给客户,客户在浏览器中通过查看源码可以看到这些注释。

3.1.2 jsp注释

<%--
  这是jsp注释,可以单行也可以多行。
--%>

jsp注释是共jsp开发人员看的,最后不会传递给客户。即时客户在浏览器中查看html源码,也看不到这部分注释内容。这些注释甚至不会出现在jsp代码翻译成的servlet类文件的java代码中(可以在下面找到翻译后类文件的存放位置,然后,打开文件做下验证)。

3.1.3 java注释

由于jsp中可以嵌入java代码,这样,在嵌入的java代码块儿中,可以使用///* */等java格式的注释。当然,这部分注释也不会传递给客户。

3.2 jsp生成servlet的API

3.2.1 认识jsp相关的三个关键API

尽管在上面提到jsp转化为servlet时,会扩展org.apache.jasper.runtime.HttpJspBase。但实际中,不需要了解这些,只需要知道如下三个方法关键方法即可:

  • jspInt()
    这个方法由servlet的init()方法调用,可以覆盖这个方法。
  • jspDestroy()
    这个方法由servlet的destroy()方法调用,也可以覆盖这个方法。
  • _jspService()
    这个方法由servlet的service()方法调用。对于每一个请求,它都会在一个单独的线程中运行,容器将Request和Response对象传递给这个方法。不能覆盖这个方法。对于这个方法,开发人员什么都做不了。不过,在jsp中编写的代码会被放在里面,要由容器开发商来取得你的jsp代码,并生成使用这些jsp代码的_jspService()方法。(这里有一个常识,下划线开头的方法都不能够被覆盖。)

这三个方法声明在HttpJspPage接口中,简单的接口示意图如下:


Java Web开发中的jsp入门_第4张图片
接口示意图

前面提到的jsp在转化成servlet时用到的HttpJspBase类,和这几个接口什么关系呢,如下:

org.apache.jasper.runtime Class HttpJspBase

java.lang.Object
  javax.servlet.GenericServlet
      javax.servlet.http.HttpServlet
          org.apache.jasper.runtime.HttpJspBase

All Implemented Interfaces:
javax.servlet.jsp.HttpJspPage, javax.servlet.jsp.JspPage, java.io.Serializable, javax.servlet.Servlet, javax.servlet.ServletConfig

也就是说,org.apache.jasper.runtime.HttpJspBase类实现了javax.servlet.jsp.HttpJspPage接口。

3.2.2 通过重写jspInit方法读取jsp初始化参数

和Servlet一样,jsp也可以配置初始化参数。同样是在web.xml文件中,方法和常规的Servlet初始化参数配置基本一样,只是把servlet-class子标签换成了jsp-file。如下是一个例子:
web.xml部分:

    
        TestJspInitServlet
        /TestJspInit.jsp
        
            name
            PaopaoXia
        
        
            email
            [email protected]
        
    
    
        TestJspInitServlet
        /TestJspInit.jsp
    

然后,在jsp页面中通过用声明的方式,重写jspInit()方法,可以读取这些参数。
由于jspInit()方法是由Servlet的init()方法调用,所以运行jspInit()方法时,已经有了一个ServletConfig和ServletContext可以使用,所以,可以在jspInit()方法中,调用getServletConfig()和getServletContext()方法。下下面是一个例子,在其中,不但读取了Servlet初始化参数,也使用参数值设置了应用作用域的属性。
TestJspInit.jsp:

<%--
  Created by IntelliJ IDEA.
  User: chengxia
  Date: 2019/2/17
  Time: 10:55 AM
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>


    Test Jsp Init


<%!
    String name = "";
    String email = "";
    public void jspInit(){
        ServletConfig sConfig = getServletConfig();
        String name = sConfig.getInitParameter("name");
        String email = sConfig.getInitParameter("email");
        ServletContext ctx = getServletContext();
        ctx.setAttribute("name",name);
        ctx.setAttribute("email",email);
        this.name = name;
        this.email = email;
    }
%>
name:<%=name%>
email:<%=email%>

最后,启动tomcat,访问http://localhost:8080/TestJspInit2.jsp,效果如下:

Java Web开发中的jsp入门_第5张图片
jsp配置初始化参数例子

3.3 jsp中的属性

尽管可以像上面例子中那样,使用覆盖jspInit()方法的声明来在jsp页面中设置一个应用作用域属性。但是,大多数情况下,我们会使用四个隐式对象之一来得到和设置对应jsp中4个属性作用域的属性。
除了Servlet中常见的请求、会话和应用作用域外,jsp还增加了第四个作用域,页面作用域,可以从pageContext对象得到。
如下表格中介绍了这几种常见作用域的使用。

Servlet中 Jsp中
应用 getServletContext().setAttribute("foo", barObj) application.setAttribute("foo", barObj)
请求 request.setAttribute("foo", barObj) request.setAttribute("foo", barObj)
会话 request.getSession().setAttribte("foo", barObj) session.setAttribute("foo", barObj)
页面 不适用 pageContext.setAttribute("foo", barObj)

同时,pageContext隐式对象有方法可以接受一个作用域标识参数,从而可以获得和设置任意作用域中的属性值。也有findAttribute()方法,可以在各个作用域中依次查找指定属性名的属性。

3.4 jsp指令

jsp中有三种常用的指令:page、taglib和include。

3.4.1 page指令

<%@ page import="foo.*" session="false" %>
定义页面特定的属性,如字符编码、页面响应的内容类型,以及这个页面是否要有隐式的会话对象。page指令有如下13个属性:

属性 描述
buffer 指定out对象使用缓冲区的大小
autoFlush 控制out对象的 缓存区
contentType 指定当前JSP页面的MIME类型和字符编码
errorPage 指定当JSP页面发生异常时需要转向的错误处理页面
isErrorPage 指定当前页面是否可以作为另一个JSP页面的错误处理页面
extends 指定servlet从哪一个类继承
import 导入要使用的Java类
info 定义JSP页面的描述信息
isThreadSafe 指定对JSP页面的访问是否为线程安全
language 定义JSP页面所用的脚本语言,默认是Java
session 指定JSP页面是否使用session
isELIgnored 指定是否执行EL表达式
isScriptingEnabled 确定脚本元素能否被使用

3.4.2 taglib指令

定义可以使用的标记库。JSP API允许用户自定义标签,一个自定义标签库就是自定义标签的集合。
Taglib指令引入一个自定义标签集合的定义,包括库路径、自定义标签。
Taglib指令的语法:
<%@ taglib uri="uri" prefix="prefixOfTag" %>
uri属性确定标签库的位置,prefix属性指定标签库的前缀。
等价的XML语法:

3.4.3 include指令

<%@ include file="subfile.html" %>
定义在转换时增加到当前页面的文本和代码。从而允许用户建立可重用的块儿,如标准页面标题或导航栏,这些可以重用的块儿能增加到各个页面上,而不必在每个页面重复写。

参考资料

  • IDEA中部署tomcat,运行JSP文件,编译后的JSP文件存放地点总结
  • JSP 指令

你可能感兴趣的:(Java Web开发中的jsp入门)