2014-12-22

Java Servlet 3.0 裡的 @WebServlet

Java Servlet 3.0 終於引入了 Annotation,在學 Vaadin 時第一次看到 @WebServlet,今天就來好好認識一下,雖然最終幫助可能不大


Java Servlet Spec 寫得還真是精簡(陽春?),得配合 Java Docs 才免強奏齊拼圖。

web.xml

以前寫 Servlet,得先寫一隻 Servlet class,然後再到 web.xml 加上定義與設定 URL Pattern。

現在 web.xml 這段省了,不用寫任何 Servlet 資訊到 web.xml。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0">
  <display-name>Servlet 3.0</display-name>
</web-app>
只要直接寫 Servlet class 就可以了>

兩個原則:
  • 第一、繼承 HttpServlet。
  • 第二,加上 @WebServlet,並設定 value 或 urlPatterns。
@WebServlet("/servlet1")
public class Servlet1 extends HttpServlet {

  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    resp.getWriter().write("Hello Servlet 1!");
  }

}
這樣就可以用 /servlet1 連到了。

value or urlPatterns

前面第一種 @WebServlet 用法為捷徑,設定的是 value 屬性,標準用法如下。
@WebServlet(name = "Servlet2", urlPatterns = "/servlet2")
public class Servlet2 extends HttpServlet {

  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    resp.getWriter().write("Hello Servlet 2!");
  }

}
@WebServlet 至少要有 urlPatterns 或者 value 兩者之一,作用相同,但不能同時有,其他屬性都是非必要。

Spec 建議只設定 url 屬性,不需要設定其他屬性時,用 value;需要設定 url 以外的屬性時,用 urlPatterns。

urlPatterns 可以同時指定多個值,如以下作法,/servlet3a 與 /servlet3b 都可以連到 Servlet3。
@WebServlet(name = "Servlet3", urlPatterns = {
    "/servlet3a", "/servlet3b"
})
public class Servlet3 extends HttpServlet {

  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    resp.getWriter().write("Hello Servlet 3!");
  }

}

loadOnStartup

@WebServlet 也有 loadOnStartup 屬性,預設為 -1。

設定 loadOnStartup 為 20。
@WebServlet(urlPatterns = "/servlet4a", loadOnStartup = 20)
public class Servlet4a extends HttpServlet {

  public Servlet4a() {
    super();
    System.out.println("loadOnStartup at 20 with servlet4a");
  }

  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    resp.getWriter().write("Hello Servlet 4a!");
  }

}
設定 loadOnStartup 為 10。
@WebServlet(urlPatterns = "/servlet4b", loadOnStartup = 10)
public class Servlet4b extends HttpServlet {

  public Servlet4b() {
    super();
    System.out.println("loadOnStartup at 10 with servlet4b");
  }

  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    resp.getWriter().write("Hello Servlet 4b!");
  }

}
不設定 loadOnStartup,使用預設值 -1。
@WebServlet(urlPatterns = "/servlet4c")
public class Servlet4c extends HttpServlet {

  public Servlet4c() {
    super();
    System.out.println("loadOnStartup at -1 with servlet4c");
  }

  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    resp.getWriter().write("Hello Servlet 4c!");
  }

}
啟動 Tomcat 後,尚未開啟任何連結前,可以在 console 看到
loadOnStartup at 10 with servlet4b
loadOnStartup at 20 with servlet4a
開啟連結 /servlet4c,才會看到
loadOnStartup at -1 with servlet4c
所以當 loadOnStartup 不設定或者設為 -1 時,啟動 Tomcat 時是不會初始化該 Servlet 的,只有在設定 loadOnStartup 為正值時,才會在 Tomcat 啟動時初始化,不然就得等到第一次連結發生時。

initParams

@WebServlet 也可以設定 initParameter,是透過另一個 Annotation @WebInitParam 設定的。
@WebServlet(urlPatterns = "/servlet5", initParams = @WebInitParam(name = "name", value = "neil"))
public class Servlet5 extends HttpServlet {

  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    resp.getWriter().write("Hello Servlet 5 - " + this.getServletConfig().getInitParameter("name") + "!");
  }

}
特別要注意的是取用 initParameter 的方法。

疑?Tomcat 怎麼知道要去哪找 Servlet 呢?

按照 Spec 說法,WEB-INF/classes 與 WEB-INF/lib/*.jar 兩個地方都會找(怪?還有其他地方可以放嗎)。

但是,若是在 web.xml 裡的 web-app 加上 metadata-complete=true 的屬性,那 Tomcat 就不會去找 Annotation 了,完全只看 web.xml 裡的設定。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0" metadata-complete="true">
  <display-name>Servlet 3.0</display-name>
</web-app>
metadata-complete 預設為 false。

web.xml 與 @WebServlet 同時使用

如果同一個 Servlet class 的 name 屬性不相同,那就會同時存在兩個不同 name 的 instance。

但是如果同一個 Servlet class 的 name 屬性相同,那麼 web.xml 的設定會蓋過 @WebServlet,也就是 web.xml 的 url-pattern 會蓋掉 @WebServlet 的 urlPatterns。

Spec 上提到,除了 web.xml 與  @WebServlet  可以建立 Servlet,還可以動態建立 Servlet,就是寫程式立刻產生,在這種情況下,同名的話當然是程式建立的 Servlet 倖存。

Server push

Spec 裡還提到 @WebServlet 還有一個神奇的屬性,asyncSupported,據說可以讓 Server 一直一直一直灌資料給 client,這太艱深了,改天再研究。



最後,解釋一下一開始說得,為何 @WebServlet 幫助不大,因為現在沒有人會在一個 Web Application 裡寫一堆 Servlet。

多半搭配 Web Framework,這樣只要一隻 Servlet 就將所有 request 轉到 Web Framwork 裡處理了。

而且這一隻 Servlet 還是 Web Framework 提供的,根本沒機會用 @WebServlet。
---
---
---

沒有留言:

張貼留言