1. 程式人生 > >Java for Web學習筆記(四):Servlet(2)HelloServlet

Java for Web學習筆記(四):Servlet(2)HelloServlet

繼承關係: javax.servlet.GenericServlet –》javax.servlet.http.HttpServlet。

405返回

如果我們不重寫Servlet的doGet而採用HTTP GET的方式,將返回405 將返回405 Method Not Allowed。

如果我們重寫doGet()方法,直接return,則返回空頁面,需要區分這兩種方式。很多情況下,我們不太區分doGet()和doPost()這兩類請求,但是如果介面明確說明,則應當不重寫,拒絕服務。

第一個Servlet:HelloServlet

例子如下:

@WebServlet("/HelloServlet")
public class HelloServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void doGet(HttpServletRequest request, HttpServletResponse response) 
                throws ServletException, IOException {
       /* 我們這裡並不需要將reponse.getWriter()這個java.io.PrintWriter物件進行close。 一般而言,
        * 我們只需要關閉我們建立的物件, 這個PrintWriter不是我們建立的, web container建立這個
        * 資源並負責將之關閉。即使我們通過PrintWriter out = response.getWriter() 進行引用,也
        * 不屬於建立,即無需進行close()。*/
        response.getWriter().println("Hello, World!");
    }
}

init()和destroy()

我們先利用Maven加上Log4j2,目前最新的版本是2.4。我們在pom.xml中加入相關的關聯,如圖所示。Maven會下載log4j-web的2.4版本的jar包,並將關聯的jar包,log4j-api.2.4.jar和log4j-core-2.4.jar都下載到maven的倉庫目錄中,並在專案中加入。

如果我們不是web專案,而是普通的java專案,則在pom.xml中關聯log4j-api(scrope=compile)和log4j-core(scrope=runtime)即可,groupId仍是org.apache.logging.log4j。

我們將log4j2的配置檔案放入main/resources目錄中,這些配置檔案和src檔案分佈存放在不同的目錄。如果直接建立Dynamic web project,則這個xml檔案也位於src下面。這是Maven對專案的管理功能之一。

程式碼如下:

private static final Logger log = LogManager.getLogger();
@Override
public void destroy() {
	log.info("Servlet " + this.getServletName() + " has stopped.");
}

@Override
public void init() throws ServletException {
	log.info("Servlet " + this.getServletName() + " has started.");
}

要注意的是init()和destroy()與java類的構造和finalizer不同。

init()是在web container啟動servlet時呼叫,是在servlet建立之後,在第一次請求時呼叫。由於已經建立,可以通過getServetCofnig()訪問ServletConfig和ServletContext物件,可以讀取一個properties檔案或者建立JDBC的連線。

destroy()在servlet不能在接收請求時馬上呼叫,通常是在web app停止或者卸裝,或者web container關閉時。finalizer()則是在垃圾回收時觸發的,而垃圾回收的VM的事,可能會在幾分鐘,甚至一個小時之後才進行。我們應該在此清除資源,包括臨時檔案,關閉與資料庫的連線。

Web.xml

display-name

在maven中,我們重寫了web.xml。web.xml用來描述釋出servlet,filter,listener。在Servlet 3.0版本開始,我們無需為servelt在web.xml中加上定義,可以在程式碼中加上描述(Eclipse會自動加上),如本例的:@WebServlet("/HelloServlet")其相當於在web.xml中有以下的定義。注意是相當於,即不會寫到web.xml中

<servlet>
	<servlet-name> cn.wei.flowingflying.chapter03.HelloServlet </servlet-name>
	<servlet-class>cn.wei.flowingflying.chapter03.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
	<servlet-name>cn.wei.flowingflying.chapter03.HelloServlet</servlet-name>
	<url-pattern>/HelloServlet</url-pattern>
</servlet-mapping>

web.xml的例子如下,我們加上display-name,這樣如果通過tomcat管理頁面進行部署,介面上會顯示相關的資訊。

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                             http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">
	<display-name>Hello World Application</display-name>
</web-app>

通過專案右鍵->Export->war獲得war檔案,然後進行部署。

load-on-startup

現在,我們可以不在web.xml中定義servlet,但是web.xml提供了更多的控制。init()是在第一個請求時執行,如果init()的過程持續比較久,會導致第一個請求的響應時間慢,使用者體驗差。我們在web.xml的web-app下面加入以下內容,要求web container在啟動web app時啟動servlet(即觸發init()),如下:

<servlet>
	<servlet-name>HelloServlet</servlet-name>
	<servlet-class>cn.wei.flowingflying.chapter03.HelloServlet</servlet-class>
	<load-on-startup>1</load-on-startup>
</servlet>

如果有多個servlet都要求馬上啟動,數值越小表示越優先,本例是1,是最小的數值,即最優先執行。如果兩個servlet的load-on-startup的數值相同,則按它們在web.xml檔案中的順序。

但是我們需要注意,如果這裡定義的servlet-name與程式碼中定義的HelloServlet不一樣的。程式碼中的@WebServlet("/HelloServlet")定義的servelt的名字包含完整的包名。Tomcat會認為這有兩個獨立的servlet例項,名字分別是cn.wei.flowingflying.chapter03.HelloServlet和HelloServlet,只不過是同一個class的兩個例項。我們將會看到web app啟動時,馬上啟動了名為HelloServlet例項,即觸發了HelloServlet的init()。

此外由於HelloServlet尚未在web.xml中進行url的mapping,這個HelloServlet例項在是無法進行請求的。如果我們在瀏覽器中發起http://localhost:8080/chapter03/HelloServlet,則觸發了名字為cn.wei.flowingflying.chapter03.HelloServlet的另一個servlet例項的init()。

所以我們應該在預設的程式碼定義和web.xml的手工定義方式繼續二選一,以免引起混淆。如果選擇手工定義的方式,要登出掉程式碼的@WebServlet的定義,以免程式碼由他人處理時會在使用servlet例項上出現混亂。如果兩者同時使用,發生衝突時,以web.xml中的定義為準,但這和具體的web container有關。

這也給我們另外的啟示。我們可以為同一個servlet類給出兩個servlet名字,也就是web container基礎同樣的codebase建立兩個servlet例項。我們可以通過getServlet()來獲取servlet的名字,基於不同的servlet,我們可以有不同的初始化,例如引導到不同的資料庫來源。

mapping

url的對映不是必要的。預設按程式碼中@WebServlet("/HelloServlet")的描述。也可以將之對映到多個url中,如下所示。我們仍保留@WebServlet的描述,將url增加對映到/greeting和/hello,整個web.xml如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                             http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">
	<display-name>Hello World Application</display-name>

	<servlet-mapping>
		<servlet-name>cn.wei.flowingflying.chapter03.HelloServlet</servlet-name>
		<url-pattern>/greeting</url-pattern>
		<url-pattern>/hello</url-pattern>
	</servlet-mapping>

</web-app>

名字為cn.wei.flowingflying.chapter03.HelloServlet將可以被http://localhost:8080/chapter03/greeting和http://localhost:8080/chapter03/hello訪問到。這是不同的入口,實際上都是同一個servlet。注意,如果訪問http://localhost:8080/chapter03/HelloServlet,則得到404的響應,對程式碼中描述@WebServlet在mapping中進行覆蓋,因此再次建議@WebServlet和xml的描述應二選一。

使用@webServlet

如果我們要使用@WebServlet進行諸如load-on-startup的定義,如下所示。urlPatterns是一個數組,可以定義多個url mapping。

@WebServlet(
        name = "ticketServlet",
        urlPatterns = {"/tickets"},
        loadOnStartup = 1
)