JavaEE jsp的内置对象及其在Servlet中的表示

jsp页面经过jsp引擎编译后,实际上会生成一个Servlet类,也就是说,jsp的本质就是一个Servlet对象。

jsp的内置对象

jsp页面实际会被jsp引擎编译成一个Servlet类,这个Servlet类有8个内置对象(当jsp页面是错误处理页时有9个),Servlet类的_jspService方法会创建这些对象的实例(request和response作为参数),下面是_jspService方法:

public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response)
        throws java.io.IOException, javax.servlet.ServletException {
    // ...

    final javax.servlet.jsp.PageContext pageContext;
    javax.servlet.http.HttpSession session = null;
    java.lang.Throwable exception = org.apache.jasper.runtime.JspRuntimeLibrary.getThrowable(request);
    if (exception != null) {
      response.setStatus(javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
    }
    final javax.servlet.ServletContext application;
    final javax.servlet.ServletConfig config;
    javax.servlet.jsp.JspWriter out = null;
    final java.lang.Object page = this;
    javax.servlet.jsp.JspWriter _jspx_out = null;
    javax.servlet.jsp.PageContext _jspx_page_context = null;

    try {
        response.setContentType("text/html; charset=ISO-8859-1");
        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;
        // ...
    } catch (java.lang.Throwable t) {
        // ...
        if (_jspx_page_context != null) _jspx_page_context.handlePageException(t);
        else throw new ServletException(t);
    } finally {
        // ...
    }
}

可以看到,这些内置对象分别为:

对象类型作用
applicationjavax.servlet.ServletContext代表jsp所属的web应用,可用于在jsp页面或Servlet之间通信
sessionjavax.servlet.http.HttpSession代表一次对话,当客户端浏览器与服务器建立连接时,对话开始,当客户端浏览器关闭时,对话结束
pagejava.lang.Object代表该jsp页面,也就是this
pageContextjavax.servlet.jsp.PageContext代表该jsp页面上下文,可用于访问页面中的共享数据
configjavax.servlet.ServletConfig代表该jsp页面的配置信息,更多的在Servlet而不是jsp中使用
requestjavax.servlet.http.HttpServletRequest代表一次用户请求,封装了客户端的请求参数等
responsejavax.servlet.http.HttpServletResponse代表一次用户响应
outjavax.servlet.jsp.JspWriter代表该jsp页面的输出流
exceptionjava.lang.Throwable代表其他页面中的异常和错误,只有当该页面为错误处理页时才有此对象

web应用中的jsp页面、Servlet等都将由web服务器来调用,而jsp、Servlet之间通常不会相互调用。为了让jsp、Servlet之间相互通信,Tomcat服务器提供了4个类似Map的结构——application、session、request和pageContext,jsp和Servlet可以将数据放入这4个结构中,也可以从其中取出数据。它们最大的区别是生存期不同:
- application:在整个web应用中存在,应用中的所有jsp和Servlet都可以访问它;
- session:在一次对话中存在,本次会话中的所有jsp和Servlet都可以访问它;
- request:在一次请求中存在,本次请求中的所有jsp和Servlet都可以访问它;
- pageContext:在当前页面中存在,只有当前页面可以访问它。

application和ServletContext

jsp页面中的application对象存在于应用生存期间,它在Servlet中对应着ServletContext对象,通常由PageContext对象获得。

多个jsp、Servlet间共享数据

下面我们编写一个程序,在应用生存期中维护一个计数器,不论是访问jsp页面还是Servlet,每访问一次,计数器就+1:

<!-- application.jsp -->

<!-- ... -->
<body>
<%
    Integer count=(Integer) application.getAttribute("count");
    if(count==null)
        count=new Integer(0);
    out.println("第"+(++count)+"次访问");
    application.setAttribute("count", count);
%>
</body>
<!-- ... -->
// ApplicationServlet.java

@WebServlet(name="ApplicationServlet", urlPatterns="/application")
public class ApplicationServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=GBK");
        PrintWriter out=response.getWriter();
        out.println("<html><head><title>application servlet</title></head><body>");
        Integer count=(Integer) getServletContext().getAttribute("count");
        if(count==null)
            count=new Integer(0);
        out.println("第"+(++count)+"次访问<br></body></html>");
        getServletContext().setAttribute("count", count);
    }

}

可以看到,在jsp页面中可以直接通过application在web应用生存期中访问/添加数据,而在Servlet中需要通过ServletContext来访问/添加数据。

通过jsp页面和Servlet访问的效果如下:
application_jsp
application_servlet

获取web应用的配置参数

通过application还可以设置/获取web应用的配置参数,比如配置数据库。下面我们在Servlet中获取数据库配置,并读取用户信息:

// DBServlet.java

@WebServlet(
        urlPatterns = { "/user" }, 
        initParams = { 
                @WebInitParam(name = "driver", value = "com.mysql.jdbc.Driver"), 
                @WebInitParam(name = "url", value = "jdbc:mysql://localhost:3306/XXX"), 
                @WebInitParam(name = "user", value = "XXX"), 
                @WebInitParam(name = "password", value = "******"), 
                @WebInitParam(name = "useUnicode", value = "true"), 
                @WebInitParam(name = "characterEncoding", value = "UTF-8"), 
                @WebInitParam(name = "useSSL", value = "true")
        })
public class DBServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    private Properties prop;

    public void init(ServletConfig config) throws ServletException {
        prop=new Properties();
        Enumeration<String> names= config.getInitParameterNames();
        while(names.hasMoreElements()) {
            String name=names.nextElement();
            prop.put(name, config.getInitParameter(name));
        }

        try {
            Class.forName(prop.getProperty("driver"));
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=GBK");
        PrintWriter out=response.getWriter();
        out.println("<html><head><title>user info</title></head><body>");
        out.println("<table border=\"1\" width=\"500\">");

        String url=prop.getProperty("url");
        try(Connection conn=DriverManager.getConnection(url, prop)) {
            Statement state=conn.createStatement();
            ResultSet result=state.executeQuery("select * from user order by id;");
            while(result.next()) {
                out.println("<tr>");
                out.println("<td>"+result.getInt(1)+"</td>");
                out.println("<td>"+result.getString(2)+"</td>");
                out.println("<td>"+result.getString(3)+"</td>");
                out.println("<td>"+result.getBoolean(4)+"</td>");
                out.println("<td>"+result.getString(5)+"</td>");
                out.println("</tr>");
            }
            out.println("</table></body></html>");
        } catch (SQLException e) {
            e.printStackTrace();
        }

    }

}

可以看到,我们在init方法中通过ServletConfig类型的参数得到了web应用的配置参数,这些参数是通过@WebServlet注解配置的,下面我们在web.xml文件中配置web应用参数并在jsp页面中获取:

<!-- web.mxl -->

<!-- ... -->
<context-param>
    <param-name>driver</param-name>
    <param-value>com.mysql.jdbc.Driver</param-value>
</context-param>
<context-param>
    <param-name>url</param-name>
    <param-value>jdbc:mysql://localhost:3306/cool_dictionary</param-value>
</context-param>
<context-param>
    <param-name>user</param-name>
    <param-value>root</param-value>
</context-param>
<context-param>
    <param-name>password</param-name>
    <param-value>262623</param-value>
</context-param>
<context-param>
    <param-name>useUnicode</param-name>
    <param-value>true</param-value>
</context-param>
<context-param>
    <param-name>characterEncoding</param-name>
    <param-value>UTF-8</param-value>
</context-param>
<context-param>
    <param-name>useSSL</param-name>
    <param-value>true</param-value>
</context-param>
<!-- ... -->
<!-- db.jsp -->

<!-- ... -->
<body>
<%
    Properties prop=new Properties();
    Enumeration<String> names=application.getInitParameterNames();
    while(names.hasMoreElements()) {
        String name=names.nextElement();
        prop.put(name, application.getInitParameter(name));
    }
%>
<!-- ... -->

在web.xml中通过context-param标签配置了数据库连接参数,并在jsp页面中通过application获得了这些配置参数。

上述Servlet和jsp都显示了user表中的用户信息,如下所示:
application_db

另外可以发现,在Servlet中我们通过ServletConfig得到了配置参数,而在jsp页面中通过ServletContext(application)得到了配置参数。其实我们可以通过调用ServletConfig.getServletContext方法来得到ServletContext。

config和ServletConfig

config对象代表当前jsp页面的配置信息,在Servlet中对应ServletConfig对象。ServletConfig除了可以对web应用进行配置外,还可以对某个jsp或Servlet进行配置。我们修改上面的web.xml和db.jsp,让其显示某个用户的信息:

<!-- web.xml -->

<!-- ... -->
<servlet>
    <servlet-name>db</servlet-name>
    <jsp-file>/db.jsp</jsp-file>
    <init-param>
        <param-name>name</param-name>
        <param-value>Tom</param-value>
    </init-param>
</servlet>
<servlet-mapping>
    <servlet-name>db</servlet-name>
    <url-pattern>/Tom</url-pattern>
</servlet-mapping>
<!-- ... -->
<!-- db.jsp -->

<!-- ... -->
<%
    if(prop.getProperty("name")!=null)
        out.println("application can obtain name<br>");
    else
        out.println("application cannot obtain name<br>");

    String name=config.getInitParameter("name");
    ResultSet result=state.executeQuery("select * from user where name='"+name+"';");
%>
<!-- ... -->

结果如下:
config_db

可以看到,ServletContext(application)并不能访问某个jsp或Servlet的初始配置参数,而必须通过ServletConfig对象获取。

另外还要注意,我们访问的地址是http://localhost:8080/WebDemo/Tom,如果直接访问http://localhost:8080/WebDemo/db.jsp,则将不能得到init-param参数。如下:
config_db_jsp

session和HttpSession

session代表一次对话,即从客户端浏览器连接服务器开始,一直到客户端浏览器与服务器断开连接为止。session在Servlet中对应HttpSession对象,通常用于跟踪用户的会话信息,如判断用户是否登录,或跟踪用户购买的商品等。

下面我们编写一个登录页面、跳转页面和在线页面,用户通过登录页面登录,跳转页面根据用户名和密码判断跳转到哪个页面,如果匹配则跳转到在线页面,否则重新回到登录页面。

登录页面:

<!-- user-login.jsp -->

<!-- ... -->
<body>
    <form method="post" action="user-login">
        用户名:<input type="text" name="user"><br>
        密&nbsp;&nbsp;码:<input type="text" name="password"><br>
        <input type="submit" value="提交">
    </form>
</body>
<!-- ... -->

跳转页面:

// UserLoginServlet.java

@WebServlet(urlPatterns="/user-login")
public class UserLoginServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String user=request.getParameter("user");
        String password=request.getParameter("password");

        if(user!=null && password!=null && user.equals("admin") && password.equals("admin")) {
            request.getSession(true).setAttribute("user", user);
            RequestDispatcher dispatcher=request.getRequestDispatcher("/online");
            dispatcher.forward(request, response);
        }
        else {
            RequestDispatcher dispatcher=request.getRequestDispatcher("/user-login.jsp");
            dispatcher.forward(request, response);
        }
    }

}

在线页面:

// OnlineServlet.java

@WebServlet(urlPatterns="/online")
public class OnlineServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        PageContext pageContext=JspFactory.getDefaultFactory().getPageContext(
                this, request, response, null, true, 8192, true);
        HttpSession session=pageContext.getSession();
        // session=request.getSession(true);
        String user=(String) session.getAttribute("user");
        if(user==null || user.equals("")) {
            RequestDispatcher dispatcher=request.getRequestDispatcher("/user-login.jsp");
            dispatcher.forward(request, response);
        }
        else {
            response.setContentType("text/html;charset=GBK");
            PrintWriter out=response.getWriter();
            out.println("<html><head><title>online</title></head><body>");
            out.println("亲爱的"+user+",欢迎您!");
            out.println("</body></html>");
        }
    }

}
  • 我们先访问http://localhost:8080/WebDemo/online,可以发现显示的页面是登录页面,因为我们还没有登录;
  • 然后我们在登录页面输入adminaaaaa,可以发现显示的页面还是登录页面(因为密码错误),但是浏览器的地址栏变成http://localhost:8080/WebDemo/user-login,可见之前的一次登录由UserLoginServlet处理了;
  • 我们再在登录页面输入adminadmin,可以发现显示的页面变成了在线页面,但浏览器的地址栏还是http://localhost:8080/WebDemo/user-login,可见登录请求是由UserLoginServlet处理的;
  • 现在我们再访问http://localhost:8080/WebDemo/online,可以发现显示的页面依然是在线页面,证明我们之前已经登录过(在线)了。

request和HttpServletRequest

request对象在Servlet中对应HttpServletRequest对象,它封装着一次用户请求。用户请求通常包含请求头和请求参数,用户主要通过两种方式发送请求:
- GET:直接在访问地址后添加请求参数,或者form表单没有设置method属性或method属性为get,这几种方式都是GET请求。GET请求会将请求参数附加在URL地址之后,因此可以看见请求参数,且GET请求传送的数据量较小;
- POST:通常由form表单提交请求,只需将method属性设置为post。POST请求传送的数据量较大,且请求参数放在header中传输,用户不能看到请求参数,安全性较高。

发送POST请求

jsp或Servlet可以通过request对象获取请求头和请求参数,我们先发送POST请求:

<!-- header.jsp -->

<!-- ... -->
<body>
    <form method="post" action="header">
        用户名:<input type="text" name="user"><br>
        密&nbsp;&nbsp;码:<input type="text" name="password"><br>
        <input type="submit" value="提交">
    </form>
</body>
<!-- ... -->
// HeaderServlet.java

@WebServlet(urlPatterns="/header")
public class HeaderServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=GBK");
        PrintWriter out=response.getWriter();
        out.println("<html><head><title>header and its parameters</title></head><body>");

        out.println("<h2>request header</h2>");
        Enumeration<String> names=request.getHeaderNames();
        while(names.hasMoreElements()) {
            String name=names.nextElement();
            out.println(name+"="+request.getHeader(name)+"<br>");
        }

        out.println("<h2>request parameters</h2>");
        request.setCharacterEncoding("gb2312");
        Enumeration<String> params=request.getParameterNames();
        while(params.hasMoreElements()) {
            String param=params.nextElement();
            out.println(param+"="+request.getParameter(param)+"<br>");
        }

        out.print("</body></html>");
    }

}

我们提交了用户名和密码,然后在Servlet中显示了请求头和请求参数,结果如下:
request_header

发送GET请求

接着我们再使用GET方式来传递参数:

<!-- get.jsp -->

<!-- ... -->
<%
    Enumeration<String> names=request.getParameterNames();
    while(names.hasMoreElements()) {
        String name=names.nextElement();
        out.println(name+"="+request.getParameter(name)+"<br>");
    }
%>
<!-- ... -->

在浏览器地址栏输入http://localhost:8080/WebDemo/get.jsp?user=Tom&password=123456,结果如下:
request_get_1

我们上面传递的参数只是简单的英文字符,如果传递的参数有中文字符,则可能出现中文乱码,修改get.jsp:

<!-- get.jsp -->

<!-- ... -->
<%
    String rawQuery=request.getQueryString();
    out.println("raw query string="+rawQuery+"<br>");
    String decodeQuery=java.net.URLDecoder.decode(rawQuery, "UTF-8");
    out.println("decoder query string="+decodeQuery+"<br>");
%>
<!-- ... -->

结果如下:
request_get_2

这里编码需要根据具体的浏览器来设置,有的浏览器甚至会出现http 400错误。

转发和包含

request还可以代替jsp:forwardjsp:include动作:

// 如果getRequestDispatcher方法的参数以"/"开头,则必须是相对于当前Servlet的相对路径
RequestDispatcher dispatcher=request.getRequestDispatcher("/another.jsp");
// 将请求转发到another.jsp页面
dispatcher.forward(request, response);
// 将another.jsp页面包含到当前页面
dispatcher.include(request, response);

response和HttpServletResponse

response代表服务器对客户端的响应,在Servlet中对应HttpServletResponse对象。大部分情况下我们只需要使用out对象(PrintWriter)输出响应页面而无需使用response。

显示图片

当需要生成一张图片或一个PDF文档时,out对象(PrintWriter是一个字符流)无法满足需求,此时需要借助response。我们下面编写一个显示图片的页面:

// ImageServlet.java

@WebServlet(urlPatterns="/image")
public class ImageServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String imagePath="F:\\workspace\\javaEE\\WebDemo\\res\\image\\butterfly.jpg";
        FileInputStream in=new FileInputStream(imagePath);
        byte[] buffer=new byte[in.available()];
        in.read(buffer);
        in.close();

        response.setContentType("image/jpg");
        OutputStream out=response.getOutputStream();
        out.write(buffer);
        out.flush();
        out.close();
    }

}
<!-- image.jsp -->

<html>
<%
    String basePath=request.getScheme()+"://"+
            request.getServerName()+":"+
            request.getServerPort()+
            request.getContextPath()+"/";
%>
<head>
<%@ page language="java" contentType="text/html; charset=GBK"
    pageEncoding="UTF-8"%>
<meta http-equiv="Content-Type" content="text/html; charset=GBK">
<base href="<%=basePath%>">
<title>image</title>
</head>
<body>
    <img alt="butterfly" src="image">
</body>
</html>

我们在image.jsp页面中只添加了一个img标签,它将显示链接为<%=basePath%>image的图片。注意这里在head标签中指定了基地址(<base href="<%=basePath%>">),所以在img标签的src属性中只需要指定相对地址。

另外注意到basePath是由一系列request方法的返回值合成的,这些方法的作用如下:
- getScheme:返回当前页面使用的协议;
- getServerName:返回服务器的名字;
- getServerPort:返回服务器的端口;
- getServletPath:返回当前页面所在的应用路径;
- getRequestURI:返回请求的URI;
- request.getSession().getServletContext().getRealPath(String path):返回path在服务器主机的文件系统上的绝对路径。

scheme=<%=request.getScheme() %><br>
server name=<%=request.getServerName() %><br>
server port=<%=request.getServerPort() %><br>
context path=<%=request.getContextPath() %><br>
servlet path=<%=request.getServletPath() %><br>
request URI=<%=request.getRequestURI() %><br>
real path=<%=request.getSession().getServletContext().getRealPath("/image.jsp") %>

<!--
scheme=http
server name=localhost
server port=8080
context path=/WebDemo
servlet path=/image.jsp
request URI=/WebDemo/image.jsp
real path=F:\workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp1\wtpwebapps\WebDemo\image.jsp
-->

我们访问http://localhost:8080/WebDemo/image.jsp,即可看到图片。

重定向

response也提供了重定向的功能:response.sendRedirect(String location)。这个功能看起来和request的forword方法类似,都是跳转到另一个页面,其实它们两者有很大区别:
- 执行forward后仍然是上一次请求,跳转后的页面仍然可以访问原来请求的参数,并且浏览器地址栏的URL地址不变;
- 执行redirect后将生成第二次请求,原来请求的参数将不能被访问,并且浏览器地址栏的URL地址将变为重定向后的地址。

增加Cookie

response还可以向客户端增加Cookie,Cookie通常用于网站记录用户的某些信息,一旦用户下次登录,网站就能获取用户的相关信息,从而根据这些信息提供更友好的服务。Cookie和session的不同之处在于,session会随浏览器的关闭而结束,而Cookie会在客户端浏览器一直存在直到超出Cookie的生命周期。

我们下面提供一个Cookie来记录用户名:

<!-- redirect.jsp -->

<!-- ... -->
<%
    String user=request.getParameter("user");
    Cookie cookie=new Cookie("user", java.net.URLEncoder.encode(user, "GBK"));
    cookie.setMaxAge(10*60);
    response.addCookie(cookie);
    response.sendRedirect("cookie");
%>
<!-- ... -->
// CookieServlet.java

@WebServlet(urlPatterns="/cookie")
public class CookieServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        Cookie[] cookies=request.getCookies();
        if(cookies!=null && cookies.length>0) {
            response.setContentType("text/html;charset=GBK");
            PrintWriter out=response.getWriter();
            out.println("<html><head><title>cookie</title></head><body>");

            for(Cookie cookie : cookies)
                out.println(cookie.getName()+"="+URLDecoder.decode(cookie.getValue(), "GBK")+"<br>");

            out.println("</body></html>");
        }
    }

}

我们先在redirect.jsp页面中设置了一个Cookie,然后重定向到CookieServlet,CookieServlet会显示所有的Cookie。在地址栏输入http://localhost:8080/WebDemo/redirect.jsp?user=Tom,回车后结果如下:
response_cookie

pageContext和PageContext

通过pageContext对象可以访问/设置应用域、会话域、请求域和页面域的数据:

// PageServlet.java

@WebServlet(urlPatterns="/page")
public class PageServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        PageContext pageContext=JspFactory.getDefaultFactory().getPageContext(
                this, request, response, null, true, 8192, true);
        pageContext.setAttribute("application", "application", PageContext.APPLICATION_SCOPE);
        pageContext.setAttribute("session", "session", PageContext.SESSION_SCOPE);
        pageContext.setAttribute("request", "request", PageContext.REQUEST_SCOPE);
        pageContext.setAttribute("page", "page");

        response.setContentType("text/html;charset=GBK");
        PrintWriter out=response.getWriter();
        out.println("<html><head><title>scope</title></head><body>");

        out.println("scope of application="+pageContext.getAttributesScope("application")+"<br>");
        out.println("scope of session="+pageContext.getAttributesScope("session")+"<br>");
        out.println("scope of request="+pageContext.getAttributesScope("request")+"<br>");
        out.println("scope of page="+pageContext.getAttributesScope("page")+"<br>");

        out.println("</body></html>");
    }

}

在Servlet中需要自己创建PageContext对象,而在jsp页面中可以直接使用pageContext变量,结果如下:
page

exception和Throwable

exception对象只存在于错误处理页中,它负责处理其他jsp页面发送的异常和错误。exception实际上是一个Throwable对象,因此可以调用Throwable类中的方法。

测试源码

上述所有测试源码已上传到github:
https://github.com/jzyhywxz/WebDemo.git

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值