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 {
// ...
}
}
可以看到,这些内置对象分别为:
对象 | 类型 | 作用 |
---|---|---|
application | javax.servlet.ServletContext | 代表jsp所属的web应用,可用于在jsp页面或Servlet之间通信 |
session | javax.servlet.http.HttpSession | 代表一次对话,当客户端浏览器与服务器建立连接时,对话开始,当客户端浏览器关闭时,对话结束 |
page | java.lang.Object | 代表该jsp页面,也就是this |
pageContext | javax.servlet.jsp.PageContext | 代表该jsp页面上下文,可用于访问页面中的共享数据 |
config | javax.servlet.ServletConfig | 代表该jsp页面的配置信息,更多的在Servlet而不是jsp中使用 |
request | javax.servlet.http.HttpServletRequest | 代表一次用户请求,封装了客户端的请求参数等 |
response | javax.servlet.http.HttpServletResponse | 代表一次用户响应 |
out | javax.servlet.jsp.JspWriter | 代表该jsp页面的输出流 |
exception | java.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访问的效果如下:
获取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表中的用户信息,如下所示:
另外可以发现,在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+"';");
%>
<!-- ... -->
结果如下:
可以看到,ServletContext(application)并不能访问某个jsp或Servlet的初始配置参数,而必须通过ServletConfig对象获取。
另外还要注意,我们访问的地址是http://localhost:8080/WebDemo/Tom
,如果直接访问http://localhost:8080/WebDemo/db.jsp
,则将不能得到init-param
参数。如下:
session和HttpSession
session代表一次对话,即从客户端浏览器连接服务器开始,一直到客户端浏览器与服务器断开连接为止。session在Servlet中对应HttpSession对象,通常用于跟踪用户的会话信息,如判断用户是否登录,或跟踪用户购买的商品等。
下面我们编写一个登录页面、跳转页面和在线页面,用户通过登录页面登录,跳转页面根据用户名和密码判断跳转到哪个页面,如果匹配则跳转到在线页面,否则重新回到登录页面。
登录页面:
<!-- user-login.jsp -->
<!-- ... -->
<body>
<form method="post" action="user-login">
用户名:<input type="text" name="user"><br>
密 码:<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
,可以发现显示的页面是登录页面,因为我们还没有登录; - 然后我们在登录页面输入
admin
和aaaaa
,可以发现显示的页面还是登录页面(因为密码错误),但是浏览器的地址栏变成http://localhost:8080/WebDemo/user-login
,可见之前的一次登录由UserLoginServlet处理了; - 我们再在登录页面输入
admin
和admin
,可以发现显示的页面变成了在线页面,但浏览器的地址栏还是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>
密 码:<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中显示了请求头和请求参数,结果如下:
发送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
,结果如下:
我们上面传递的参数只是简单的英文字符,如果传递的参数有中文字符,则可能出现中文乱码,修改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>");
%>
<!-- ... -->
结果如下:
这里编码需要根据具体的浏览器来设置,有的浏览器甚至会出现http 400错误。
转发和包含
request还可以代替jsp:forward
和jsp: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
,回车后结果如下:
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变量,结果如下:
exception和Throwable
exception对象只存在于错误处理页中,它负责处理其他jsp页面发送的异常和错误。exception实际上是一个Throwable对象,因此可以调用Throwable类中的方法。
测试源码
上述所有测试源码已上传到github:
https://github.com/jzyhywxz/WebDemo.git