Servlet 詳解

一、什麼是 Servlet?html

  Java Servlet 是運行在 Web 服務器或應用服務器上的程序,它是做爲來自 Web 瀏覽器或其餘 HTTP 客戶端的請求和 HTTP 服務器上的數據庫或應用程序之間的中間層。使用 Servlet,能夠收集來自網頁表單的用戶輸入,呈現來自數據庫或者其餘源的記錄,還能夠動態建立網頁。java

 

二、Servlet 入門實例web

  第一步:建立一個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 映射關係express

 

<?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,而後啓動服務器apache

  這裏咱們項目的結構爲:瀏覽器

    

 

  ①、咱們直接經過項目名來訪問,因爲咱們在 web.xml 文件中配置了 <welcome-file-list>,那麼會依次找下面配置的文件,咱們只建立了一個 index.jsp,那麼就會訪問這個JSP 文件tomcat

  

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

  

  咱們能夠看控制檯打印內容以下:服務器

  

  若是咱們不斷的刷新  http://localhost:8080/ServletImprove/hello 這個訪問連接,那麼控制檯以下:

  

 

三、Servlet 的生命週期

  咱們經過上面的實例,能夠看到也就是隻有第一次纔會執行 構造器和 init() 方法,後面每次點擊都只調用 service() 方法。那這是爲何呢?

  

上面這幅圖能夠這樣理解:

  一、客戶端向 Web 服務器發送請求,服務器查詢 web.xml 文件配置。根據請求信息找到對應的 Servlet。

  二、Servlet 引擎檢查是否已經裝載並建立了該 Servlet 的實例對象,若是有,則直接執行第4步,不然執行第3步,

  三、Web 服務器加載 Servlet,並調用 Servlet 構造器(只會調用一次),建立 Servlet 的實例對象。並調用 init() 方法,完成 Servlet 實例對象的初始化(只會調用一次)。

  四、Web 服務器把接收到的 http 請求封裝成 ServletRequest 對象,並建立一個 響應消息的 ServletResponse 對象,做爲 service() 方法的參數傳入。(每一次訪問都會調用一次該方法)

  五、執行 service() 方法,並將處理信息封裝到 ServletResponse 對象中返回

  六、瀏覽器拆除 ServletResponse 對象,造成 http 響應格式,返回給客戶端。

  七、Web 應用程序中止或者從新啓動以前,Servlet 引擎將卸載 Servlet實例,並在卸載以前調用 destory() 方法

 

 

四、建立 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 接口的

    

  

 

五、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);		
	}
	
}

  結果:

  

 

 

 

六、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"時,纔可使用

 

 

七、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

 

 

八、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() 方法則是在每次請求的時候調用。故過濾器能夠對請求進行攔截過濾。能夠用來進行權限設置,對傳輸數據進行加密等等操做。

相關文章
相關標籤/搜索