什么是Servlet
Servlet(Server Applet),全称Java Servlet。一个Java Servlet就是一个小型Java应用程序,它可以继承HttpServlet实现,运行在Web服务器中。Servlet 会接收并响应来自浏览器的请求,通常是基于Http协议的请求。
如下图:
创建Servlet
实现Servlet或者继承Servlet的实现类:
方法一:实现Servlet接口
1> 实现接口Servlet并实现以下几个 生命周期 方法:
init(ServletConfig config)
:当Servlet被创建时,将会使用此方法对自己初始化
service(ServletRequest req,ServletResponse res)
:当浏览器对服务器发出请求后,servlet会使用该方法处理请求
destory()
:当Servlet处理完请求后,在销毁前会调用此方法,然后GC会将它回收掉
生命周期:
当client访问Servlet时,创建并初始化Servlet, 在服务器中只有一个Servlet实例,当服务器被关闭时,Servlet才会被销毁。
package com.feathers.servletdemo;
import java.io.IOException;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public class HelloServlet implements Servlet {
private ServletConfig config;
@Override
public void init(ServletConfig config) throws ServletException {
System.out.println("init(): servlet 初始化");
this.config = config;
}
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
System.out.println("service():servlet 收到请求了,开始取出请求的数据(request)并作出处理,\n 并将处理后的结果放置到响应对象(response)中");
// 响应对象向客户端写东西
response.getWriter().write("Hello Client, I'm Servlet");
}
@Override
public void destroy() {
System.out.println("destory(): servlet被销毁");
}
@Override // 获取配置信息,键值对的方式
public ServletConfig getServletConfig() {
return config;
}
@Override // 返回servlet信息,比如servlet的author version copyright等
public String getServletInfo() {
return null;
// return "Feather著作 Feathers 版权所有";
}
}
2> 在 web.xml
中注册Servlet,告诉服务器有这么一个Servlet
<!-- 注册servlet到服务器 -->
<servlet>
<servlet-name>HelloServlet</servlet-name><!-- 注册名称 -->
<servlet-class>com.feathers.servletdemo.HelloServlet</servlet-class><!-- java字节码路径 -->
</servlet>
<!-- 给注册过的Servlet配置路径 -->
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name><!-- 给哪一个Servlet配置路径 -->
<url-pattern>/HelloServlet</url-pattern><!-- 要配置的路径,servle访问路径 / 代表项目路径 -->
</servlet-mapping>
什么?太麻烦了?还好我们有注解 @WebServlet
使用java注解注册Servlet
@WebServlet("/FirstServlet") // 后面是Servlet的访问路径
@WebServlet(
name="Hello", // Servlet名称
urlPatterns={"/hello.view"}, // 访问路径
loadOnStartup=1 // 加载级别
)
public class HelloServlet extends HttpServlet {
启动服务器,访问此Servlet:
在tomcat中的项目结构:
我们可以看到,Java编译的class文件全部被eclipse放置到WEB-INF—> classes目录下了。
输出:
生命周期时序图:
服务器是如何查找Servlet的?
我们看到,通过 <url-pattern>
访问servlet,最后访问到他的class文件。
首先根据 <url-pattern>
找到它的name,然后根据name找到 <servlet> <name>
相同的servlet标签,根据name找到 <servlet-class>
class路径,使用找到的class生成servlet实例到服务器内存中。
ServletConfig接口:
servlet容器使用servlet配置对象,该对象在初始化期间将信息传递给ServletConfig。
String getInitParamter(String name)
:根据初始化参数名称获取初始化参数的值,如果没有此参数,返回null
Enumeration getInitParamterNames()
:获取所有初始化参数名称,以枚举方式返回,如果没有参数,返回一个空的枚举
在 web.xml
中初始化参数:
<servlet>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>com.feathers.servletdemo.HelloServlet</servlet-class>
<!-- 配置两个初始化参数 -->
<init-param>
<param-name>name</param-name><!-- key -->
<param-value>Feathers</param-value><!-- value -->
</init-param>
<init-param>
<param-name>age</param-name>
<param-value>100</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>/HelloServlet</url-pattern>
</servlet-mapping>
在Servlet service()中对初始化参数进行处理:
package com.feathers.servletdemo;
import java.io.IOException;
import java.util.Enumeration;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public class HelloServlet implements Servlet {
private ServletConfig config;
@Override
public void init(ServletConfig config) throws ServletException {
this.config = config;
}
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
// 响应对象向客户端写东西
response.setContentType("text/html;charset=utf-8");
response.getWriter().write("Hello Client, I'm Servlet");
// 获取初始化参数 作出处理
Enumeration<String> enums = getServletConfig().getInitParameterNames();
while (enums.hasMoreElements()){
String key = enums.nextElement();
String value = getServletConfig().getInitParameter(key);
response.getWriter().write(key+":"+value + "<br/>");
}
}
@Override
public void destroy() {
}
@Override
public ServletConfig getServletConfig() {
return config;
}
@Override
public String getServletInfo() {
return null;
}
}
请求结果如下:
getServletName()
:获取Servlet的名称 <servlet-name>
getServletContext()
:获取ServletContext上下文对象
点击form表单提交,跳转servlet
<!-- 注意action, 这个是Servlet的访问路径,但是不能加/ 不能加/ 不能加/ -->
<form action="FirstServlet" method="get">
<table>
<tr>
<td>用户名:</td>
<td><input type="text" name="username" value=""/></td>
</tr>
<tr>
<td>密码:</td>
<td><input type="password" name="password" value=""/></td>
</tr>
<tr>
<td><input type="checkbox" name="remember" value=“yes”/>记住用户名</td>
</tr>
<tr>
<td><input type="submit" value="提交"/></td>
</tr>
</table>
</form>
方法二、继承GenericServlet
所谓的GenericServlet只是一个Servlet的实现类,因为用户每次使用Servlet接口自定义一个Servlet都会进行许多不必要的操作,所以就封装了一个GenericServlet以方便程序员使用,
使用方法:在方法service()中进行请求的处理,具体请查看源码。
下面是它的源码:
package javax.servlet;
import java.io.IOException;
import java.util.Enumeration;
//GenericServlet实现了Servlet、ServletConfig、Serializable三个接口
public abstract class GenericServlet
implements Servlet, ServletConfig, java.io.Serializable
{
private transient ServletConfig config;
//无参的构造方法
public GenericServlet() { }
/*
实现接口Servlet接口生命周期初始化init(ServletConfig Config)方法,将ServletConfig对象保存到成员变量中,以扩展他的生命周期,让service方法也能访问
**/
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init(); // 用户的初始化方法
}
public void init() throws ServletException {
}
public void destroy() { }
//返回ServletConfig对象
public ServletConfig getServletConfig(){
return config;
}
//该方法实现接口<Servlet>中的ServletInfo,默认返回空字符串
public String getServletInfo() {
return "";
}
// service方法没有去实现,而是丢给使用者去实现
public abstract void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException;
// 为了方便用户调用,简化操作,实现了ServletConfig接口,这样就不用使用getServletConfig().getServletContext()获取对象了
// 只需使用getServletContext即可
// 即实现ServletConfig的目的
public ServletContext getServletContext() {
return getServletConfig().getServletContext();
}
public String getInitParameter(String name) {
return getServletConfig().getInitParameter(name);
}
public Enumeration getInitParameterNames() {
return getServletConfig().getInitParameterNames();
public String getServletName() {
return config.getServletName();
}
public void log(String msg) {
getServletContext().log(getServletName() + ": "+ msg);
}
public void log(String message, Throwable t) {
getServletContext().log(getServletName() + ": " + message, t);
}
}
方法三、继承HttpServlet
HttpServlet继承自GenericServlet,是针对Http协议的Servlet,是进一步的封装,
使用只需要继承HttpServlet,重写doGet或doPost方法即可,doGet用来处理get请求,而doPost方法用来进行post请求处理。
源码如下:
package javax.servlet.http;
.....
public abstract class HttpServlet extends GenericServlet
implements java.io.Serializable
{
private static final String METHOD_GET = "GET";
private static final String METHOD_POST = "POST";
......
public HttpServlet() { }
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException
{
// 1. 强转ServletRequest与ServletResponse
HttpServletRequest request;
HttpServletResponse response;
try {
request = (HttpServletRequest) req;
response = (HttpServletResponse) res;
} catch (ClassCastException e) {
throw new ServletException("non-HTTP request or response");
}
//调用重载的service()方法
service(request, response);
}
//重载的service方法
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
// 根据操作的不同,作出不同的处理
// 处理方法有 doGet,doPost,doHead,doDelete...
// 这些都是http协议中的方法,get请求,post修改,delete删除,put上传等。
// 获取请求方式
String method = req.getMethod();
if (method.equals(METHOD_GET)) { // get请求
long lastModified = getLastModified(req);
if (lastModified == -1) {
doGet(req, resp);
} else {
long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
if (ifModifiedSince < (lastModified / 1000 * 1000)){
maybeSetLastModified(resp, lastModified);
doGet(req, resp);
} else {
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
}
} else if (method.equals(METHOD_HEAD)) {
long lastModified = getLastModified(req);
maybeSetLastModified(resp, lastModified);
doHead(req, resp);
} else if (method.equals(METHOD_POST)) { // post请求
doPost(req, resp);
} else if (method.equals(METHOD_PUT)) { // put 增加操作
doPut(req, resp);
} else if (method.equals(METHOD_DELETE)) { // 删除操作
doDelete(req, resp);
} else if (method.equals(METHOD_OPTIONS)) {
doOptions(req,resp);
} else if (method.equals(METHOD_TRACE)) {
doTrace(req,resp);
} else {
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[1];
errArgs[0] = method;
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
}
}
......
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_get_not_supported");
if (protocol.endsWith("1.1")) {
resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
} else {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
}
}
protected void doHead(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
.......
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_post_not_supported");
if (protocol.endsWith("1.1")) {
resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
} else {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
}
}
protected void doPut(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
//todo
}
protected void doOptions(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
//todo
}
protected void doTrace(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
//todo
}
protected void doDelete(HttpServletRequest req,
HttpServletResponse resp)
throws ServletException, IOException {
//todo
}
}
Servlet线程安全问题:
Servlet存在这线程安全的问题,
假设在Servlet中放置一个成员变量name,用来接受用户的表单信息。当多个用户访问Servlet时,会对name进行多次赋值,那么用户得到的结果就很有可能出错,因为多个用户共用一块内存。
解决办法也很简单,让每个用户都有一块内存保存自己的name值,只需要将name值保存在方法中,建立一个局部变量即可。因为java方法在被调用时,每次被调用,都会开辟一个新的方法区。
修改Servlet创建时机
通常情况下,servlet在被用户第一次调用时创建,如果servlet要进行非常耗时的创建操作,用户就会等待很久,影响用户体验,所以,我们可以将servlet设置在服务器启动之后,解决这类问题:
在 web.xml
中,添加加载时间标签:
<servlet>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>com.feathers.servletdemo.HelloServlet</servlet-class>
<!-- 让servlet随项目的启动而启动 value: 0-5 数字越小,启动优先级越高 如果多个servlet优先级相同,则按照配置顺序启动-->
<load-on-startup>3</load-on-startup>
</servlet>
Servlet访问路径配置
相对路径:
/ 代表 项目路径
/AServlet => http://localhost:8080/ServletDemo/Aservlet
/File/* => /File/ => http://localhost:8080/ServletDemo/File/sfsdfdsfsdfe
匹配任意
匹配范围越大,优先级越低:
两个配置 /File/AServlet
与 /File/*
访问: http://localhost:8080/ServletDemo/File/AServlet
则会访问AServlet,而不会访问第二个路径,因为第一个优先级高。
后缀名匹配:
*.do
: http://localhost:8080/ServletDemo/sfdsfds.do
该方式不常用,Filter过滤器常用
注意:不能同时使用 后缀名 匹配和 相对路径。例如: /File/*.do
ServletContext
项目级别的对象,一个Web项目,有且只有一个ServletContext对象,在项目启动时创建,到项目关闭时销毁,可以理解为这个类集合了项目所有的功能方法,代表了项目,所以这个类非常强大。
我们使用 ServletConfig.getServletContext()
获得这个对象。
功能如下:
获取项目参数
配置项目初始化参数:
<webapp>
<context-param>
<param-name>name</param-name>
<param-value>tom</param-value>
</context-param>
</webapp>
获取项目初始化参数:
getServletContext().getInitParamter(String name)
getServletContext().getInitParamterNames()
ServletContext 域
域是服务器两个组件之间的通讯,比如两个Servlet之间通讯。
Application域
在ServletContext中,有一个 Map<String,Object>
集合,用来保存信息,这个Map集合就是ServletContext的域。
AServlet获取ServletContext对象,向域中添加信息,BServlet也可以获取Servlet,然后从ServletContext域中获取AServlet填入的信息。
这样,每个Servlet之间就可以共享信息了。
同样存在线程不安全的问题。
/*AServlet*/
// 1. 获取ServletContext
ServletContex scontext = getServletContext();
// 2. 向域中存放键值对
scontext.setAttribute(key,object);
/*BServlet*/
// 3. 在另一个Servlet中从域中取出键值对
getServletContext().getAttribute(key);
// 4. 删除key
getServletContext().removeAttribute(key);
// 5. 获取所有建
getServletContext().getAttributeNames();//返回枚举
ServletContext获取项目内的资源
获取WebRoot/WebContent下的 user.xml
文件:
InputStream is = getServletContext().getResourceAsStream("/user.xml"); // 获得资源流
String path = getServletContext().getRealPath("/user.xml");// 获取资源绝对路径(tomcat webapp下 项目的部署路径)
// 获取WEB-INFO下的`user.xml`文件
getServletContext().getResourceAsStream("/WEB-INFO/user.xml");
获取class资源:
// 方法1:使用上面的方法
getServletContext().getResourceAsStream("/WEB-INFO/classes/com/feathers/servlet/AServlet.class"); // 麻烦
// 方法2:
// “/” 代表src目录,或者是classes目录,详见getResourceAsStream方法
InputStrean is2 = AServlet.class.getResourceAsStream("/user.xml");