上一篇文章中,我们学习了有关java web应用的基础知识,比如web服务器与客户端,http协议,html语言,web容器,以及如何通过servlet和jsp创建web应用。
这篇文章的目的是介绍servlet技术的更多细节知识,servlet API的一些核心接口,servlet 3.0中的注解(annotation)的应用,servlet的生命周期,最后我们会创建一个简单的servlet登陆案例。
一.servlet概述:
servlet是j2ee中服务端编程技术的核心。javax.servlet和javax.servlet.http包提供了大量接口和类以便我们创建自己的serlvet。
所有的servlet必须实现javax.servlet.Servlet接口,这个接口定义了servlet的生命周期方法。当我们仅仅想写一个普通的服务,我们可以继承GenericServlet.而如果你的服务是基于http的,那你应该继承HttpServlet类。
但通常情况下,我们的web应用都是采用http协议的,所以我们总是继承HttpServlet类。
二.CGI概述(Common Gate Interface/公用网关接口)
在Servlet技术诞生之前,我们使用CGI技术创建动态web应用。CGI技术有很多缺陷比如为每个请求创建单独的线程,基于c/c++平台,占内存,性能不好等。
三.CGI vs Servlet
servlet技术克服了CGI的缺陷。
1.servlet在响应时间,内存使用上比CGI性能更好,因为servlet拥有多线程的优势,它为每个请求创建单独的线程。
2.servlet的平台与系统无关性。servlet开发的web应用可以运行在任何标准的web容器中比如tomcat,jboss,glassfish服务器,并且可以运行在各种系统之上,比如windows,linux,uniux,solaris,mac等等。
3.servlet更健壮。因为容器负责处理servlet的生命周期,我们并不用担心内存泄露、安全问题、垃圾回收等等。
4.servlet可维护性高,学习成本低。我们仅需关注web应用本身的业务逻辑。
四.Servlet API简介
javax.servlet.Servlet是Servlet API的基本接口。我们另需注意一些其他常用的接口和类。另外在servlet3.0规范中建议我们采用注解技术而不是将所有servlet的配置放在web.xml中。
在这个部分中,我们将关注几个重要的servlet接口,类,注解。下面的插图是servlet API的层次关系:
(1)servlet接口
javax.servlet.Servlet是java servlet API的核心接口,定义了一系列servlet生命周期方法。所有servlet类必须实现这个接口(直接/间接)。这个接口中定义的方法如下:
public abstract void init(ServletConfig
paramServletConfig) throws ServletException
这是一个非常重要的方法,它被web容器调用,用来初始化servlet和ServletConfig参数。除非init()方法执行完毕,否则servlet不会处理客户端的请求。这个方法在servlet生命周期中仅被调用一次,这使得它跟普通的java对象不太一样。我们可以在我们的servlet类中继承这个方法,用来初始化一些资源比如数据库连接,socket连接等。
public abstract ServletConfig
getServletConfig()
这个方法返回一个servletConfig对象,对象里包含了任何初始化的参数以及一些servlet的启动参数。我们可以使用这个方法获取在web.xml或者servlet3.0注解中定义的初始化参数(init parameter)。后面我们还会提到这个方法。
public abstract void service(ServletRequest
req, ServletResponse res) throws ServletException, IOException
这个方法负责处理客户端请求。只要接收到一个请求,servlet都会开辟一个线程,并执行service方法,servletRequest和servletResponse作为参数被传递进来。因为servlet是运行在多线程的环境之下的,所以开发者需要保证共享资源的安全性。
public abstract String getServletInfo()
这个方法返回一个字符串,字符串中包含了作者,版本,版权等信息。
public abstract void destroy()
这个方法在servlet生命周期中也仅调用一次,通常用来关闭资源,跟java中的finalize方法差不多。
(2)ServletConfig接口
javax.servlet.ServletConfig接口通常用来向servlet传递配置信息。每一个servlet都拥有自己的servletConfig对象,servlet容器负责实例化这个对象。我们可以在web.xml文件中配置初始化参数(servlet节点下增加init-param节点),或者使用webInitParam注解。我们可以在servlet中调用this.getServletConfig方法获取servletConfig对象。
这个对象的主要方法有:
public abstract ServletContext getServletContext()
这个方法返回servletContext对象(web应用的上下文)。
public abstract Enumeration getInitParameterNames()
这个方法返回的是servlet中配置的初始化参数(init patameters)的枚举,如果没有配置,则返回空集合。
public abstract String getInitParameter(String
paramString)
这个方法根据特定的初始化参数名查找对应的值并返回,如果没有找到则返回null。
(3)ServletContext接口
javax.servlet.ServletContext接口可以用来获取整个web应用的相关参数或一些配置等。这个接口很独特,它对一个web应用的所有servlet都是可用的。如果我们希望创建一些对部分或者所有servlet都可用的初始化参数的话,我们可以在web.xml文件中根节点(web-app)下增加一个context-param节点(注意,这个是web应用的共享参数,跟servlet中的init-param不一样),可以在servlet中通过this.getServletContext方法(或者通过servletConfig对象的getServletContext方法)获取该对象,
这个接口有如下重要方法:
public abstract ServletContext getContext(String
uripath)
这个方法返回的是绑定在特定url路径下的servletContext对象。
public abstract URL getResource(String path)
throws MalformedURLException
这个方法返回特定路径下的资源的URL对象,不管该资源在本地文件系统,远程文件系统,数据库,或者是远程的某个站点。
public abstract InputStream getResourceAsStream(String
path)
以inputStream流的形式返回给定路径下的资源。
public abstract RequestDispatcher getRequestDispatcher(String
urlpath)
返回一个RequestDispatcher对象,通常是用来将请求转发给另一个servlet或者jsp处理
(this.getServletContext().getRequestDispatcher(path).forward())。
public abstract void log(String msg)
这个方法通过参数打印特定的日志信息
public abstract Object getAttribute(String
name)
这个方法根据指定名查找绑定的值。
public abstract void setAttribute(String paramString,
Object paramObject)
这个方法可以向servletContext对象中设置一个键值对,这个键值对对整个web应用的其他所有的servlet都可用,可以通过removeAttribute方法移除该键值对。
String getInitParameter(String name)
获取初始化参数。可以同setInitParameter方法(或者是web.xml中配置)设置
(4)ServletRequest接口
这个接口用来向servlet传递请求的信息,servlet容器创建这个对象(根据客户端的请求)并传递给servlet,由servlet的service方法处理。
方法简介:
1.这也是一个域对象,提供setAttribute和getAttribute方法用来设置/获取键值对。
2.通过getPatameter方法获取请求参数。
3.getServrName和getServerPort方法返回服务器的主机名和端口号。
(5)ServletResponse接口
servlet通过这个接口将响应信息返回给客户端。servlet容器创建这个对象并传递给servlet,由servlet的service方法处理,这个方法还负责生成html页面返回给客户端。
方法简介:
void
addCookie(Cookie cookie)
添加一个Cookie。
void addHeader(String name, String value)
添加响应头信息。
String encodeURL(java.lang.String url)
将给定的url字符串编码,即加上一个sessionID,如果没有sessionId,返回原串。
String getHeader(String name)
获取响应头信息。
void sendRedirect(String location)
请求重定向。
void setStatus(int sc)
设置状态码
(6)RequestDispatcher接口
这个接口用来转发一个请求或者将另一资源的处理结果包含进来。
方法:
void forward(ServletRequest request, ServletResponse
response)
将请求转发给另一个servlet或者jsp处理。
void include(ServletRequest request, ServletResponse
response)
将另一个资源的处理结果包含进来。
通常在servlet中可以使用两种方式获取这个对象:
1.this.getServletContext().getRequestDispatcher(path);
2.request.getRequestDispatcher(path);
注:
1.path必须是以‘/’开头代表相对当前项目路径:
this.getServletContext().getRequestDispatcher("/index.jsp").forward(req, resp);
2.请求转发(forward)和请求重定向(sendRediect)的区别:
转发是服务器行为,重定向是客户端行为。两个动作的工作流程如下:
转发过程:客户浏览器发送http请求----》web服务器接受此请求--》调用内部的一个方法在容器内部完成请求处理和转发动作----》将目标资源发送给客户;在这里,转发的路径必须是同一个web容器下的url,其不能转向到其他的web路径上去,中间传递的是自己的容器内的request。在客户浏览器路径栏显示的仍然是其第一次访问的路径,也就是说客户是感觉不到服务器做了转发的。转发行为是浏览器只做了一次访问请求。
重定向过程:客户浏览器发送http请求----》web服务器接受后发送302状态码响应及对应新的location给客户浏览器--》客户浏览器发现是302响应,则自动再发送一个新的http请求,请求url是新的location地址----》服务器根据此请求寻找资源并发送给客户。在这里location可以重定向到任意URL,既然是浏览器重新发出了请求,则就没有什么request传递的概念了。在客户浏览器路径栏显示的是其重定向的路径,客户可以观察到地址的变化的。重定向行为是浏览器做了至少两次的访问请求的。
3.forward方法和include方法的区别:
forward方法是把请求的内容转发到另外的一个servlet进行处理.而include是把另一个servlet处理过后的内容拿过来,包括自身的处理结果都会返回给浏览器。下面来个测试:
forward:
this.getServletContext().getRequestDispatcher("/index.jsp").forward(req, resp);
显示:
sendRediect:
resp.getWriter().println("hey,I am Rowandjj");
this.getServletContext().getRequestDispatcher("/index.jsp").include(req, resp);
显示:
(7)GenericServlet类:
这是一个实现servlet,servletConfig,serializable接口的抽象类,这个方法给出servlet生命周期,servletConfig中的方法的默认实现。
这个类有一个重要的方法叫init,如果我们在执行请求之前需要初始化一些资源的话最好重写这个方法。
(8)HTTPServlet类:
这是一个继承GenericServlet的抽象类。专门用来处理Http协议的servlet类,这个类虽然是抽象类,不能被实例化。但是没有抽象方法,我们可以根据需要选择重写方法。一般情况下我们会重写doGet,doPost,doDelete等doxxx方法。
五.servlet属性域简介
servlet提供了一些域对象方便内部servlet之间的通信,我们可以通过set/get方法为web应用设置或取出属性值。servlet提供3个域(和jsp区分开来):
1.request scope
2.session scope
3.application scope
分别由ServletRequest,HttpSession,ServletContext对象提供对应的set/get方法去操作者三个域。
注:这跟web.xml中为servletConfig(针对单个servlet)和servletContext(针对web应用)定义的初始化参数不一样。
六.servlet3.0规范中注解技术的应用(重要!!)
在servlet3.0规范出来之前,所有的servlet映射,过滤器,监听器,初始化参数等都得在web.xml
中进行配置。这很不方便而且当servlet很多的情况下很容易出错。
servlet3.0建议我们使用注解配置servlet,filter,listener等内容。
一些比较重要的注解如下:
WebServlet:
我们可以使用这个注解配置servlet类,包括init-param,description,loadonstartup,url pattern等等,这个注解修饰的类必须继承HttpServlet抽象类。
WebInitParam:
这个方法可以为servlet或者filter添加初始化参数(相当于web.xml中filter/servlet节点下的init-param),可以和webFilter或者webServlet注解一起使用。
WebFilter:
顾名思义,这个类用来配置filter。这个注解修饰的类必须继承javax.servlet.Filter接口。
WebListener:
在给定的web应用上下文环境下,这个注解用于声明一个监听器(Listener)。
我将在后面的文章中介绍监听器和过滤器。
简单实例:
首先确保tomcat版本必须是7.0及以上,jdk版本在6.0以上。
以eclipse为例,首先配置服务器(参照上一篇文章),启动服务器。然后新建一个动态web工程(Dynamic Web Project),工程建好之后,创建一个servlet。如下所示:
package cn.edu.chd.web;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/servlet/Test")
public class Test extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
resp.setContentType("text/html;charset=utf-8");
resp.getWriter().println("tomcat 7测试");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
this.doGet(req, resp);
}
}
浏览器中输入http://localhost:8080/Tomcat7Test/servlet/Test即可访问(视不同项目名称而定)。
七.servlet使用示例
依然采用eclipse+tomcat作为开发、运行环境:
最终项目截图如下:
登陆页面,我们将这个页面配置到web.xml的welcome-file-list下,作为默认访问页面:
<!DOCTYPE html>
<html>
<head>
<meta charset="US-ASCII">
<title>Login Page</title>
</head>
<body>
<form action="LoginServlet" method="post">
Username: <input type="text" name="user">
<br>
Password: <input type="password" name="pwd">
<br>
<input type="submit" value="Login">
</form>
</body>
</html>
如果登陆成功,跳转到下面这个页面:
<%@ page language="java" contentType="text/html; charset=US-ASCII"
pageEncoding="US-ASCII"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=US-ASCII">
<title>Login Success Page</title>
</head>
<body>
<h3>Hi Rowandjj, Login successful.</h3>
<a href="login.html">Login Page</a>
</body>
</html>
下面是web.xml文件中配置的内容:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
<display-name>LoginExample</display-name>
<welcome-file-list>
<welcome-file>login.html</welcome-file>
</welcome-file-list>
<context-param>
<param-name>dbURL</param-name>
<param-value>jdbc:mysql://localhost/mysql_db</param-value>
</context-param>
<context-param>
<param-name>dbUser</param-name>
<param-value>mysql_user</param-value>
</context-param>
<context-param>
<param-name>dbUserPwd</param-name>
<param-value>mysql_pwd</param-value>
</context-param>
</web-app>
处理用户登陆请求的servlet如下(servlet采用注解进行配置):
package com.journaldev.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet implementation class LoginServlet
*/
@WebServlet(
description = "Login Servlet",
urlPatterns = { "/LoginServlet" },
initParams = {
@WebInitParam(name = "user", value = "Rowandjj"),
@WebInitParam(name = "password", value = "aa")
})
public class LoginServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public void init() throws ServletException {
//we can create DB connection resource here and set it to Servlet context
if(getServletContext().getInitParameter("dbURL").equals("jdbc:mysql://localhost/mysql_db") &&
getServletContext().getInitParameter("dbUser").equals("mysql_user") &&
getServletContext().getInitParameter("dbUserPwd").equals("mysql_pwd"))
getServletContext().setAttribute("DB_Success", "True");
else throw new ServletException("DB Connection error");
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//get request parameters for userID and password
String user = request.getParameter("user");
String pwd = request.getParameter("pwd");
//get servlet config init params
String userID = getServletConfig().getInitParameter("user");
String password = getServletConfig().getInitParameter("password");
//logging example
log("User="+user+"::password="+pwd);
if(userID.equals(user) && password.equals(pwd)){
response.sendRedirect("LoginSuccess.jsp");
}else{
RequestDispatcher rd = getServletContext().getRequestDispatcher("/login.html");
PrintWriter out= response.getWriter();
out.println("<font color=red>Either user name or password is wrong.</font>");
rd.include(request, response);//这里使用了include,自己体会为什么
}
}
}
好了,这篇文章到此就结束了,下一篇我们将介绍session管理,过滤器以及监听器。