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,然后啟動服務器

  這里我們項目的結構為:

    

 

  ①、我們直接通過項目名來訪問,由於我們在 web.xml 文件中配置了 <welcome-file-list>,那么會依次找下面配置的文件,我們只創建了一個 index.jsp,那么就會訪問這個JSP 文件

  

  ②、通過在 web.xml 文件中配置的<url-pattern>/hello</url-pattern>  來訪問

  

  我們可以看控制台打印內容如下:

  

  如果我們不斷的刷新  http://localhost:8080/ServletImprove/hello 這個訪問鏈接,那么控制台如下:

  

 

3、Servlet 的生命周期

  我們通過上面的實例,可以看到也就是只有第一次才會執行 構造器和 init() 方法,后面每次點擊都只調用 service() 方法。那這是為什么呢?

  

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

  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的。

    

  而 GenericServlet 又是實現 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,然后一起訪問,不斷刷新,結果如下:

    

結果分析:顯然,我們用兩個瀏覽器訪問,便相當於兩個線程,第一個訪問,已經執行了 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);		
		}
	}
	
}

  結果:

    

 

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

 

 

   第二種辦法:實現接口 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);		
	}
	
}

 

  結果:

    

  分析: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);		
	}
	
}

  結果:

  

 

 

 

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中,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() 方法就已經開始運行了

  服務器啟動成功之后,我們輸入任意連接,比如

    

  每刷新一次,控制台都會打印 doFilter()...

  

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM