什麼是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)
:根據初始化參數名稱獲取初始化參數的值,如果沒有此參數,返回nullEnumeration 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");