天天看點

Servlet 詳解

Servlet 詳解

1、什麼是 Servlet?

  Java Servlet 是運作在 Web 伺服器或應用伺服器上的程式,它是作為來自 Web 浏覽器或其他 HTTP 用戶端的請求和 HTTP 伺服器上的資料庫或應用程式之間的中間層。使用 Servlet,可以收集來自網頁表單的使用者輸入,呈現來自資料庫或者其他源的記錄,還可以動态建立網頁。

2、Servlet 入門執行個體

  第一步:建立一個JavaWeb項目,并建立一個servlet類-----HelloServlet,實作接口 Servlet

package com.ys.servlet;

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{
	//隻被調用一次,第一次請求Servlet時,建立Servlet的執行個體,調用構造器
	public HelloServlet() {
		System.out.println("構造器 HelloServelt()...");
	}
	
	//該方法用于初始化Servlet,就是把該Servlet裝載入記憶體
	//隻被調用一次,在建立好執行個體後立即被調用
	@Override
	public void init(ServletConfig config) throws ServletException {
		System.out.println("初始化方法 init()...");
	}
	
	//被多次調用,每次請求都會調用service方法。實際用于響應請求的
	@Override
	public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
		System.out.println("執行方法主體 service()...");
	}
	//隻被調用一次,在目前Servlet所在的WEB應用被解除安裝前調用,用于釋放目前Servlet所占用的資源
	@Override
	public void destroy() {
		System.out.println("servlet 銷毀時調用方法 destroy()...");
	}

	@Override
	public ServletConfig getServletConfig() {
		return null;
	}

	@Override
	public String getServletInfo() {
		return null;
	}


}
      

  第二步:在 web.xml 檔案中配置上面建立的 HelloServlet 映射關系

<?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">
  <!--在tomcat 伺服器中運作時,如果不指名通路檔案名,預設的根據項目名通路檔案順序如下配置  -->
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>
  
  <!--給建立的 Servlet 配置映射關系  -->
  <servlet>
  	<servlet-name>helloServlet</servlet-name>
  	<servlet-class>com.ys.servlet.HelloServlet</servlet-class>
                	<!--servlet的完整名稱-->  
  </servlet>
  
  <servlet-mapping>
  	<servlet-name>helloServlet</servlet-name>
  				<!-- 與上面配置的 servlet-name 名字要對應,一個servlet可以有多個 servlet-mapping  -->
  	<url-pattern>/hello</url-pattern>  
                <!--通路路徑--> 
  </servlet-mapping>
</web-app>
      

  

  第三步:将項目部署在 tomcat 伺服器,如何部署請看這篇文章:http://www.cnblogs.com/ysocean/p/6893446.html,然後啟動伺服器

  這裡我們項目的結構為:

    

Servlet 詳解

  ①、我們直接通過項目名來通路,由于我們在 web.xml 檔案中配置了 <welcome-file-list>,那麼會依次找下面配置的檔案,我們隻建立了一個 index.jsp,那麼就會通路這個JSP 檔案

Servlet 詳解

  ②、通過在 web.xml 檔案中配置的<url-pattern>/hello</url-pattern>  來通路

Servlet 詳解

  我們可以看控制台列印内容如下:

Servlet 詳解

  如果我們不斷的重新整理  http://localhost:8080/ServletImprove/hello 這個通路連結,那麼控制台如下:

Servlet 詳解

3、Servlet 的生命周期

  我們通過上面的執行個體,可以看到也就是隻有第一次才會執行 構造器和 init() 方法,後面每次點選都隻調用 service() 方法。那這是為什麼呢?

Servlet 詳解

上面這幅圖可以這樣了解:

  1、用戶端向 Web 伺服器發送請求,伺服器查詢 web.xml 檔案配置。根據請求資訊找到對應的 Servlet。

  2、Servlet 引擎檢查是否已經裝載并建立了該 Servlet 的執行個體對象,如果有,則直接執行第4步,否則執行第3步,

  3、Web 伺服器加載 Servlet,并調用 Servlet 構造器(隻會調用一次),建立 Servlet 的執行個體對象。并調用 init() 方法,完成 Servlet 執行個體對象的初始化(隻會調用一次)。

  4、Web 伺服器把接收到的 http 請求封裝成 ServletRequest 對象,并建立一個 響應消息的 ServletResponse 對象,作為 service() 方法的參數傳入。(每一次通路都會調用一次該方法)

  5、執行 service() 方法,并将處理資訊封裝到 ServletResponse 對象中傳回

  6、浏覽器拆除 ServletResponse 對象,形成 http 響應格式,傳回給用戶端。

  7、Web 應用程式停止或者重新啟動之前,Servlet 引擎将解除安裝 Servlet執行個體,并在解除安裝之前調用 destory() 方法

4、建立 Servlet 的三種方法

  第一種:就是我們上面寫的 實作接口 Servlet

  第二種:由于實作接口我們需要實作裡面所有的方法,裡面有一些方法我們可能并不想實作,那麼我們就繼承 GenericServlet 類

package com.ys.servlet;

import java.io.IOException;

import javax.servlet.GenericServlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class HelloServlet extends GenericServlet{
	//隻被調用一次,第一次請求Servlet時,建立Servlet的執行個體,調用構造器
	public HelloServlet() {
		System.out.println("構造器 HelloServelt()...");
	}
	//該方法用于初始化Servlet,就是把該Servlet裝載入記憶體
	//隻被調用一次,在建立好執行個體後立即被調用
	@Override
	public void init(ServletConfig config) throws ServletException {
		System.out.println("初始化方法 init()...");
	}
	
	//被多次調用,每次請求都會調用service方法。實際用于響應請求的
	@Override
	public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
		System.out.println("執行方法主體 service()...");
	}
	//隻被調用一次,在目前Servlet所在的WEB應用被解除安裝前調用,用于釋放目前Servlet所占用的資源
	@Override
	public void destroy() {
		System.out.println("servlet 銷毀時調用方法 destroy()...");
	}

}
      

  第三種:通常我們浏覽器發出的請求都是 http 請求,那麼請求方式可能有多種,比如 get,post,而我們在處理請求的時候都是在 service() 方法中,這種方式顯然不夠明确。那麼我們通常是 繼承 HttpServlet 類

package com.ys.servlet;

import java.io.IOException;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class HelloServlet extends HttpServlet{
	//隻被調用一次,第一次請求Servlet時,建立Servlet的執行個體,調用構造器
	public HelloServlet() {
		System.out.println("構造器 HelloServelt()...");
	}
	//該方法用于初始化Servlet,就是把該Servlet裝載入記憶體
	//隻被調用一次,在建立好執行個體後立即被調用
	@Override
	public void init(ServletConfig config) throws ServletException {
		System.out.println("初始化方法 init()...");
	}
	//處理 post 請求
	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
	
	}
	//處理get請求
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
	
	}
	
	//隻被調用一次,在目前Servlet所在的WEB應用被解除安裝前調用,用于釋放目前Servlet所占用的資源
	@Override
	public void destroy() {
		System.out.println("servlet 銷毀時調用方法 destroy()...");
	}

	
}
      

  其實上面三種方法,後面兩種都是對 Servlet 類的封裝,我們可以看 API,其實 HttpServlet 是繼承 GenericServlet的。

Servlet 詳解

  而 GenericServlet 又是實作 Servlet 接口的

Servlet 詳解
Servlet 詳解

5、Servlet 的多線程問題

  我們通過 Servlet 的生命周期可以知道,Servlet 類的構造器隻會在第一次通路的時候調用,後面的請求都不會再重新建立 Servlet 執行個體。即 Servlet 是單例,那麼既然是單例的,那就要注意多線程通路所造成的安全問題。如下:

package com.ys.servlet;

import java.io.IOException;
import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class HelloServlet extends GenericServlet{
	//多線程共享資源
	private int i = 0;
	
	public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
		i++;
		//為了使多線程通路安全問題更加突出,我們增加一個延時程式
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(i);		
	}
	
}
      

  我們用兩個浏覽器,輸入 http://localhost:8080/ServletImprove/hello,然後一起通路,不斷重新整理,結果如下:

Servlet 詳解

結果分析:顯然,我們用兩個浏覽器通路,便相當于兩個線程,第一個通路,已經執行了 i++,但是還沒來得及列印 i 的值,就馬上就睡眠了;接着第二個浏覽也來通路,執行 i++,那麼i的值相當于增加加了兩次1,然後這兩個浏覽器輸出最終結果。這便造成了多線程通路共享資源造成沖突。那麼如何解決多線程沖突呢?

  可以參考這篇文章:如何解決多線程同步問題 http://www.cnblogs.com/ysocean/p/6883729.html

那麼在 Servlet 中如何處理呢? 

  第一種方法:使用同步代碼塊  

package com.ys.servlet;

import java.io.IOException;
import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class HelloServlet extends GenericServlet{
	//多線程共享資源
	private int i = 0;
	
	public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
		synchronized (this) {
			i++;
			//為了使多線程通路安全問題更加突出,我們增加一個延時程式
			try {
				Thread.sleep(5000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(i);		
		}
	}
	
}
      

  結果:

Servlet 詳解

  分析:這種辦法雖然能解決多線程同步問題,但是如果 延時程式特别長,那麼會造成通路假死的現象。即第一個線程通路結果沒有出來,第二個線程就會一直卡死,出不來結果

   第二種辦法:實作接口 SingleThreadModel

package com.ys.servlet;

import java.io.IOException;
import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.SingleThreadModel;

public class HelloServlet extends GenericServlet implements SingleThreadModel{
	//多線程共享資源
	private int i = 0;
	
	public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
		i++;
		//為了使多線程通路安全問題更加突出,我們增加一個延時程式
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(i);		
	}
	
}
      
Servlet 詳解

  分析:SingleThreadModel 接口指定了系統如何處理對同一個Servlet的調用。如果一個Servlet被這個接口指定,那麼在這個Servlet中的service方法将不會有兩個線程被同時執行,當然也就不存線上程安全的問題。但是,如果一個Servlet實作了SingleThreadModel接口,Servlet引擎将為每個新的請求建立一個單獨的Servlet執行個體,這将引起大量的系統開銷,在現在的Servlet開發中基本看不到SingleThreadModel的使用,這種方式了解即可,盡量避免使用。

   第三種辦法:避免使用執行個體變量

  線程安全問題很大一部分是由于執行個體變量造成的,那麼我們隻要在 Servlet 裡面不定義任何的執行個體變量,那麼就不會有線程安全的問題。因為在 Java 記憶體模型中,方法中的臨時變量是在棧上配置設定空間,而且每個線程都有自己的私有棧空間,不會造成線程安全問題。

package com.ys.servlet;

import java.io.IOException;
import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class HelloServlet extends GenericServlet{
	
	public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
		int i = 0;
		i++;
		//為了使多線程通路安全問題更加突出,我們增加一個延時程式
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(i);		
	}
	
}
      
Servlet 詳解

6、Servlet 和 JSP 的差別

  ①、JSP 的本質就是 Servlet,JSP 經過編譯後就會變為一個類似 Servlet 的Java檔案

  ②、Servlet 基本是JAVA程式代碼構成,擅長于流程控制和事務處理,當然也可以用來生成html代碼,但是通過Servlet來生成動态網頁很不直覺.

  ③、JSP由HTML代碼和JSP标簽構成,可以友善地編寫動态網頁,當然裡面也可以編寫 Java代碼,但是整體看上去不夠優雅。而且比較麻煩

  是以:JSP側重于視圖,Servlet主要用于控制邏輯。

我們可以看一個 JSP 檔案,index.jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="utf-8"%>
<!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=ISO-8859-1">
<title>Insert title here</title>
</head>
<body>
	index.jsp
</body>
</html>
      

經過編譯後:很顯然下面的代碼結構和 Servlet 是差不多的

package org.apache.jsp;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
public final class index_jsp extends org.apache.jasper.runtime.HttpJspBase
    implements org.apache.jasper.runtime.JspSourceDependent {
  private static final javax.servlet.jsp.JspFactory _jspxFactory =
          javax.servlet.jsp.JspFactory.getDefaultFactory();
  private static java.util.Map<java.lang.String,java.lang.Long> _jspx_dependants;
  private javax.el.ExpressionFactory _el_expressionfactory;
  private org.apache.tomcat.InstanceManager _jsp_instancemanager;
  public java.util.Map<java.lang.String,java.lang.Long> getDependants() {
    return _jspx_dependants;
  }

  public void _jspInit() {
    _el_expressionfactory = _jspxFactory.getJspApplicationContext(getServletConfig().getServletContext()).getExpressionFactory();
    _jsp_instancemanager = org.apache.jasper.runtime.InstanceManagerFactory.getInstanceManager(getServletConfig());
  }

  public void _jspDestroy() {
  }

  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;
    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;

      out.write("\r\n");
      out.write("<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\r\n");
      out.write("<html>\r\n");
      out.write("<head>\r\n");
      out.write("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=ISO-8859-1\">\r\n");
      out.write("<title>Insert title here</title>\r\n");
      out.write("</head>\r\n");
      out.write("<body>\r\n");
      out.write("\tindex.jsp\r\n");
      out.write("</body>\r\n");
      out.write("</html>");
    } catch (java.lang.Throwable t) {
      if (!(t instanceof javax.servlet.jsp.SkipPageException)){
        out = _jspx_out;
        if (out != null && out.getBufferSize() != 0)
          try { out.clearBuffer(); } catch (java.io.IOException e) {}
        if (_jspx_page_context != null) _jspx_page_context.handlePageException(t);
        else throw new ServletException(t);
      }
    } finally {
      _jspxFactory.releasePageContext(_jspx_page_context);
    }
  }
}
      

  JSP 頁面的九個隐含對象:

  ①、request:HttpServletRequest的一個對象,封裝請求資訊

      ②、pageContext:頁面的上下文,是PageContext的一個對象,可以從該對象中擷取其它8個隐含對象。

      ③、session:代表浏覽器和伺服器的一次會話,是HttpSession 的一個對象

      ④、application:代表目前WEB應用,是ServletContext對象

      ⑤、config:目前JSP對應Servlet的ServletConfig對象

      ⑥、out:JspWriter對象,調用out.prinln()可以直接把字元串列印到浏覽器上

      ⑦、page:指向目前JSP對應的Servlet對象的應用,但為Object類型,隻能調用 Object 類的方法

      ⑧、exception:在聲明了page指令的isErrorPage="true"時,才可以使用

 

7、Servlet 的轉發和重定向

  重定向:

public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
		
		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) res;
		response.sendRedirect("index.jsp");//重定向
	}
      

  轉發:

HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) res;
		//response.sendRedirect("index.jsp");
		request.getRequestDispatcher("/index.jsp").forward(request, response);//轉發
	
      

我們再看看浏覽器通路:同時輸入 http://localhost:8080/ServletImprove/hello

  重定向變為:

     

Servlet 詳解

  轉發為:

Servlet 詳解

本質差別:轉發隻發出了一次請求,而重定向發出了兩次請求

  ①.轉發:位址欄是初次送出請求的位址

         重定向:位址欄不再是初次發出的請求位址,位址欄為最後響應的那個位址

   ②.轉發:在最終的Servlet中,request對象和中轉的那個request是同一個對象

         重定向:在最終的Servlet中,request對象和中轉的那個request不是同一個對象

  ③.轉發:隻能轉發給目前WEB應用的資源

         重定向:可以重定向到任何資源

                response.sendRedirect("http://www.baidu.com");是可以的

                轉發就不行

   ④.轉發:/  代表的是目前WEB應用的根目錄(http://localhost:8080/項目名稱/)

         重定向: / 代表的是目前WEB站點的根目錄(http://localhost:8080/)

注意:這兩條跳轉語句不能同時出現在一個頁面中,否則會報IllegalStateException - if the response was already committed

8、Servlet 的過濾器

  ①、什麼是 過濾器?

     JavaWEB 的一個重要元件,可以對發送到 Servlet 的請求進行攔截,并對響應也進行攔截

  ②、如何實作一個過濾器?

    第一步:建立一個過濾器類,實作 Filter 接口

package com.ys.filter;

import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class HelloFilter implements Filter{
	public HelloFilter() {
		System.out.println("構造器 HelloFilter()...");
	}
	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
		System.out.println("init()...");
	}

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		System.out.println("doFilter()...");
	}

	@Override
	public void destroy() {
		System.out.println("destroy()...");
	}

}
      

    第二步:在 web.xml 檔案中配置過濾器

<!--給建立的過濾器配置關系  -->
  <filter>
  	<filter-name>helloFilter</filter-name>
  	<filter-class>com.ys.filter.HelloFilter</filter-class>
  </filter>
  <filter-mapping>
  	<filter-name>helloFilter</filter-name>
  	<url-pattern>/*</url-pattern><!-- 這表示可以攔截任何請求 -->
  </filter-mapping>
      

  啟動伺服器:我們發現還沒發送請求,過濾器的 構造方法和 init() 方法就已經開始運作了

Servlet 詳解

  伺服器啟動成功之後,我們輸入任意連接配接,比如

Servlet 詳解

  每重新整理一次,控制台都會列印 doFilter()...

Servlet 詳解

總結:生命周期和 Servlet 的類似。隻不過其構造方法和初始化方法是在容器啟動時就調用了,而其 doFilter() 方法則是在每次請求的時候調用。故過濾器可以對請求進行攔截過濾。可以用來進行權限設定,對傳輸資料進行加密等等操作。

作者:IT可樂

出處:http://www.cnblogs.com/ysocean/

資源:微信搜【IT可樂】關注我,回複 【電子書】有我特别篩選的免費電子書。

本文版權歸作者所有,歡迎轉載,但未經作者同意不能轉載,否則保留追究法律責任的權利。