2.6.1 application对象

2.6.1 application对象

在介绍application对象之前,先简单介绍一些web服务器的实现原理。虽然绝大部分读者都不需要、甚至不曾想过自己开发Web服务器,但了解一些web服务器的运行原理,对于更好地掌握JSP知识将有很大的帮助
虽然常把基于Web应用称为B/S(Browser/Server)架构的应用,但其实Web应用一样是C/S(Client/Server)结构的应用,只是这种应用的服务器是Web服务器,而客户端是浏览器。

浏览器功能

现在抛开Web应用直接看Web服务器和浏览器,对于大部分浏览器而言,它通常负责完成三件事情.

  • 向远程服务器发送请求
  • 读取远程服务器返回的字符串数据。
  • 负责根据字符串数据渲染出一个丰富多彩的页面。

实际上,浏览器是一个非常复杂的网络通信程序,它除了可以向服务器发送请求、读取网络数据之外,最大的技术难点在于将HTML文本渲染成页面,建立HTML页面的DOM模型,支持JavaScript脚本程序等。
通常浏览器有InternetExplorer,FireFoxChromeOperaSafari等,至于其他如MyE、傲游等浏览器可能只是对它们进行了简单的包装。

Web服务器功能

Web服务器则负责接收客户端请求,每当接收到客户端连接请求之后,web服务器应该使用单独的线程为该客户端提供服务:接收请求数据、送回响应数据。
图2.16显示了Web服务器的运行机制。

如图2.16所示的应用架构总是先由客户端发送请求,服务器接收到请求,然后后送回响应的数据,所以也将这种架构称做“请求响应”架构。根

Web服务器处理请求步骤

据如图2.16所示的机制进行归纳,对于每次客户端请求而言,Web服务器大致需要完成如下几个步骤。

  1. 启动单独的线程。
  2. 使用I/O流读取用户请求的二进制流数据。
  3. 从请求数据中解析参数
  4. 处理用户请求。
  5. 生成响应数据。
  6. 使用I/O流向客户端发送请求数据。

最新版的Tomcat已经不需要对每个用户请求都启用单独的线程、使用普通I/O读取用户请求的数据,最新的Tomcat使用的是异步I/O,具有更高的性能
在上面6个步骤中,第1、2和6步是通用的,可以由Web服务器来完成,但第3、4和5步则存在差异:因为不同请求里包含的请求参数不同,处理用户请求的方式也不同,所生成的响应自然也不同。那么Web服务器到底如何执行第3、4和5步呢?

Servlet的_jspService方法

实际上,web服务器会调用Servlet_jspService()方法来完成第3、4和5步。编写JSP页面时,页面里的静态内容、JSP脚本都会转换成_jspService方法的执行代码,这些执行代码负责完成解析参数、处理请求、生成响应等业务功能,而web服务器则负责完成多线程、网络通信等底层功能。
Web服务器在执行了第3步解析到用户的请求参数之后,将需要通过这些请求参数来创建HttpServletRequestHttpServletResponse等对象,作为调用_jspService()方法的参数,实际上一个Web服务器必须为Servlet API中绝大部分接口提供实现类。

JSP和Servlet如何交换数据

从上面介绍可以看出,Web应用里的JSP页面、Servlet等程序都将由Web服务器来调用,JSPServlet之间通常不会相互调用,这就产生了一个问题:JSPServlet之间如何交换数据?

通过application,session,request,page交换数据

为了解决这个问题,几乎所有Web服务器(包括JavaASPPHPRuby等)都会提供4个类似Map的结构,分别是:
applicationsessionrequestpage,并允许JSPServlet将数据放入这4个类似Map的结构中,并允许从这4个Map结构中取出数据。这4个Map结构的区别是范围不同:

  • application:对于整个Web应用有效,一旦JSPServlet将数据放入application中,该数据将可以被该应用下其他所有的JSPServlet访问
  • session:仅对一次会话有效,一旦JSPServlet将数据放入session中,该数据将可以被本次会话的其他所有的JSPServlet访问。
  • request:仅对本次请求有效,一旦JSPServlet将数据放入request中,该数据将可以被该次请求的其他JSPServlet访问.
  • page:仅对当前页面有效,一旦JSPServlet将数据放入page中,该数据只可以被当前页面的JSP脚本、声明部分访问。

类似银行

就像现实生活中有两个人,他们的钱需要相互交换,但他们两个人又不能相互接触那么只能让A把钱存入银行,而B从银行去取钱。因此可以把applicationsessionrequestpage理解为类似银行的角色。
把数据放入applicationsessionrequestpage之后,就相当于扩大了该数据的作用范围,所以认为applicationsessionrequestpage中的数据分别处于applicationsessionrequestpage范围之内。

application,session,request,pageContext内置对象

JSP中的applicationsessionrequestpageContext4个内置对象分别用于操作applicationsessionrequestpage范围中的数据

application对象代表Web应用本身,因此使用application来操作Web应用相关数据。application对象通常有如下两个作用:

  • 在整个Web应用的多个JSPServlet之间共享数据。
  • 访问Web应用的配置参数。

1. 让多个JSP、Servlet共享数据

setAttribute getAttribute方法

application通过setAttribute(String attrName, Object value)方法将一个值设置成applicationattrName属性,该属性的值对整个Web应用有效,因此该Web应用的每个JsP页面或Servlet都可以访问该属性访问属性的方法为getAttribute(String attrName)

put-application.jsp

看下面的页面,该页面仅仅声明了一个整型变量,每次刷新该页面时,该变量值加1,然后将该变量的值放入application内。下面是页面的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<%@ page contentType="text/html; charset=GBK" language="java" errorPage="" %>
<!DOCTYPE html>
<html>
<head>
<title>application测试</title>
</head>
<body>
<!-- JSP声明 -->
<%!
int i;
%>
<!-- 将i值自加后放入application的变量内 -->
<%
application.setAttribute("counter",String.valueOf(++i));
%>
<!-- 输出i值 -->
<%=i%>
</body>
</html>

以上页面在每次刷新该页面时,变量i都先自加,并被设置为applicationcounter属性的值,即每次application中的counter属性值都会加1。

get-application.jsp

再看下面的JSP页面,该页面可以直接访问到applicationcounter属性值。

1
2
3
4
5
6
7
8
9
10
11
<%@ page contentType="text/html; charset=GBK" language="java" errorPage="" %>
<!DOCTYPE html>
<html>
<head>
<title>application测试</title>
</head>
<body>
<!-- 直接输出application 变量值 -->
<%=application.getAttribute("counter")%>
</body>
</html>

get-application.jsp页面中的代码:

1
<%=application.getAttribute("counter")%>

直接输出applicationcounter属性值,虽然这个页面和put-application.Jsp没有任何关系,但它一样可以访问到application的属性,因为application的属性对于整个Web应用的JSPServlet都是共享的。

测试1

在浏览器的地址栏中访问第一个put-application.jsp页面,经多次刷新后,看到如图2.17所示的页面。
访问get-application.Jsp页面,也可看到类似于图2.17所示的效果,因为get-application.Jsp页面可以访问applicationcounter属性值。

application不仅可以用于两个JSP页面之间共享数据,还可以用于ServletJSP之间共享数据。可以把application理解成一个Map对象,任何JSPServlet都可以把某个变量放入application中保存,并为之指定一个属性名;而该应用里的其他JSPServlet就可以根据该属性名来得到这个变量

GetApplication.java

下面的Servlet代码示范了如何在Servlet中访问application里的变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package lee;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;

import java.io.*;

@WebServlet(name = "get-application", urlPatterns = { "/get-application" })
public class GetApplication extends HttpServlet {
public void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.setContentType("text/html;charset=GBK");
PrintWriter out = response.getWriter();
out.println("<html><head><title>");
out.println("测试application");
out.println("</title></head><body>");
ServletContext sc = getServletConfig().getServletContext();
out.print("application中当前的counter值为:");
out.println(sc.getAttribute("counter"));
out.println("</body></html>");
}
}

由于在Servlet中并没有application内置对象,所以上面程序中的代码:

1
ServletContext sc = getServletConfig().getServletContext();

显式获取了该Web应用的ServletContext实例,每个Web应用只有一个ServletContext实例,在JSP页面中可通过application内置对象访问该实例,而Servlet中则必须通过代码获取。
程序代码:

1
out.println(sc.getAttribute("counter"));

访问、输出了application中的counter变量。
Servlet类同样需要编译成class文件才可使用,实际上该Servlet还使用了@WebServlet注解进行部署,关于Servlet的用法请参看2.7节。编译Servlet时可能由于没有添加环境出现异常,只要将Tomcat8.5lib路径下的jsp-api.jarservlet-api.jar两个文件添加到CLASSPATH环境变量中即可。

测试2

Servlet部署在Web应用中,在浏览器中访问Servlet(http://localhost:8080/jspObject/get-application),出现如图2.18所示的页面。

最后要指出的是:虽然使用application(即Servletcontext实例)可以方便多个JsPServlet共享数据,但不要仅为了JSPServlet共享数据就将数据放入application中!由于application代表整个Web应用,所以通常只应该把Web应用的状态数据放入application里。

2. 获得Web应用配置参数

application还有一个重要用处:可用于获得web应用的配置参数。看如下JSP页面,该页面访问数据库,但访问数据库所使用的驱动、URL、用户名及密码都在web.xml中给出

getWebParam.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<%@ page contentType="text/html; charset=GBK" language="java" errorPage="" %>
<%@ page import="java.sql.*" %>
<!DOCTYPE html>
<html>
<head>
<title>application测试</title>
</head>
<body>
<%
//从配置参数中获取驱动
String driver = application.getInitParameter("driver");
//从配置参数中获取数据库url
String url = application.getInitParameter("url");
//从配置参数中获取用户名
String user = application.getInitParameter("user");
//从配置参数中获取密码
String pass = application.getInitParameter("pass");
//注册驱动
Class.forName(driver);
//获取数据库连接
Connection conn = DriverManager.getConnection(url,user,pass);
//创建Statement对象
Statement stmt = conn.createStatement();
//执行查询
ResultSet rs = stmt.executeQuery("select * from news_inf");
%>
<table bgcolor="#9999dd" border="1" width="480">
<%
//遍历结果集
while(rs.next())
{
%>
<tr>
<td><%=rs.getString(1)%></td>
<td><%=rs.getString(2)%></td>
</tr>
<%
}
%>
<table>
</body>
</html>

获取Web应用配置参数 getInitParameter方法

上面的程序中使用applicationgetInitParameter(String paramName)来获取Web应用的配置参数,这些配置参数应该在web.xml文件中使用context-param元素配置.

context-param元素

每个<context-param>元素配置一个参数,该元素下有如下两个子元素

  • param-name:配置web参数名。
  • param-value:配置Web参数值。

web.xml文件中使用<context-param>元素配置的参数对整个Web应用有效,所以也被称为web应用的配置参数。与整个web应用有关的数据,应该通过application对象来操作。

web.xml

为了给Web应用配置参数,应在web.xml文件中增加如下片段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?xml version="1.0" encoding="GBK"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
......
<!-- 配置第一个参数:driver -->
<context-param>
<param-name>driver</param-name>
<param-value>com.mysql.jdbc.Driver</param-value>
</context-param>
<!-- 配置第二个参数:url -->
<context-param>
<param-name>url</param-name>
<param-value>jdbc:mysql://localhost:3306/javaee</param-value>
</context-param>
<!-- 配置第三个参数:user -->
<context-param>
<param-name>user</param-name>
<param-value>root</param-value>
</context-param>
<!-- 配置第四个参数:pass -->
<context-param>
<param-name>pass</param-name>
<param-value>32147</param-value>
</context-param>
......
</web-app>

在浏览器中浏览getWebParam.jsp页面时,可看到数据厍连接、数据査询完全成功。可见,使用application可以访问Web应用的配置参数。
通过这种方式,可以将一些配置信息放在web.xml文件中配置,避免使用硬编码方式写在代码中,从而更好地提高程序的移植性.