Java Tutorial 第六堂(3)整合 Jersey/Spring/Hibernate




既然談到了 JAX-RS,也使用了其參考實作 Jersey,在課程的最後,就來將 Java Tutorial 第五堂(1) 練習 14 的成果中的 Spring MVC 換成 Jersey,作為本課程的最後一個練習。

練習 20:整合 Jersey/Spring/Hibernate

在 Lab 檔案的 exercises/exercise20 中有個 DVDLibrary 目錄,已經事先將練習 14 中一些可重用的程式碼(像是 Dvd.java、DvdDao.java、DvdLibraryService.java 等)與設定檔(像是 build.gradle 等)準備好。因為現在要使用 Jersey 取代 Spring MVC,所以請將 web.xml 中的 <servlet><servlet-mapping> 等設定刪除,並撰寫以下的內容:
...
<filter>
    <filter-name>jersey</filter-name>
    <filter-class>org.glassfish.jersey.servlet.ServletContainer</filter-class>
    <init-param>
        <param-name>jersey.config.server.provider.packages</param-name>
        <param-value>tw.codedata</param-value>
    </init-param>
    <init-param>
        <param-name>jersey.config.server.provider.classnames</param-name>
        <param-value>org.glassfish.jersey.server.mvc.jsp.JspMvcFeature</param-value>
    </init-param>
    <init-param>
        <param-name>jersey.config.server.mvc.templateBasePath.jsp</param-name>
        <param-value>/</param-value>
    </init-param>
    <init-param>
       <param-name>jersey.config.servlet.filter.staticContentRegex</param-name>
       <param-value>/.*jsp</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>jersey</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
...

相關設定選項之意義,可以參考 Java Tutorial 第六堂(2) 中的說明。因為不使用 Spring MVC 了,所以可以將 dispatcher-servlet.xml 中的 <mvc:annotation-driven /> 刪除,而且因為不再設定 DispatcherServlet 作為 Servlet,因此必須有另一個方式來讀取 Spring IoC 的 XML 設定檔,我們稍後會使用 Spring 的 XmlWebApplicationContext 來執行這項任務,它預設會讀取 WEB-INF 中的 applicationContext.xml,因此請將 dispatcher-servlet.xml 更名為 applicationContext.xml。

我們希望在 Web 應用程式啟始時,就讀取 applicationContext.xml,這可以在 Web 應用程式中定義 ServletContextListener 實例來達成,請在 src/main/java/tw/codedata 中新增一個 DVDLibraryContextListener.java 如下:
package tw.codedata;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import org.springframework.web.context.support.XmlWebApplicationContext;

@WebListener
public class DVDLibraryContextListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        XmlWebApplicationContext xmlWebAppCtx = new XmlWebApplicationContext();
        xmlWebAppCtx.setServletContext(sce.getServletContext());
        xmlWebAppCtx.refresh();
        sce.getServletContext().setAttribute("dvdLibraryService", xmlWebAppCtx.getBean("dvdLibraryService"));
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {}
}

由於標註了 @WebListener 且實作了 ServletContextListener 介面,應用程式初始後會呼叫 DVDLibraryContextListenercontextInitialized 方法,這個方法中簡單來說,執行了 applicationContext.xml 內容的讀取並進行各種物件 IoC 處理,接著取得 DvdLibraryService 實例,並設定給 ServletContext 作為屬性。

每個 Web 應用程式,都會有唯一的 ServletContext 實例,因此,你只要想辦法取得 ServletContext 實例,就可以取得已設定為 "dvdLibraryService" 屬性的 DvdLibraryService 實例,JAX-RS 支援依賴注入,你可以透過 @Context 標註想要注入的 ServletContext 參考名稱,這樣 JAX-RS 就會自動為你注入,因此,DvdController 可以改寫如下:
package tw.codedata;

import java.util.*;

import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.FormParam;
import javax.ws.rs.core.Context;

import org.glassfish.jersey.server.mvc.Viewable;

import javax.servlet.ServletContext;

@Path("/dvds")
public class DvdController {
    @Context ServletContext context;

    public DvdLibraryService getDvdLibraryService() {
        return (DvdLibraryService) context.getAttribute("dvdLibraryService");
    }

    @GET
    public Viewable list() {
        Map model = new HashMap();
        model.put("dvds", getDvdLibraryService().allDvds());               
        return new Viewable("/list", model);
    }

    @POST
    public Viewable add(
            @FormParam("title") String title, 
            @FormParam("year") Integer year,
            @FormParam("duration") Integer duration,
            @FormParam("director") String director) {

        Dvd dvd = getDvdLibraryService().addDvd(title, year, duration, director);
        Map model = new HashMap();
        model.put("dvd", dvd);
        return new Viewable("/success", model);

    }
}

根據 @PATH 設定,index.jsp 中 List Dvds 的路徑應該修改為 dvds
<a href="dvds">List Dvds</a>

而 add.jsp 中的 action 也要修改為 dvds
<form action="dvds" method="post">

由於 Jersey 的 Model 中設定的名稱,在 JSP 頁面中必須以 it 名稱為前綴,因此 list 中的 <c:forEach> 必須修改如下:
<c:forEach var="dvd" items="${it.dvds}">

而 success.jsp 中每個 EL 變數前都要加上 it
<li>Title: ${it.dvd.title}</li>
<li>Year: ${it.dvd.year}</li>
<li>Duration: ${it.dvd.duration}</li>
<li>Director: ${it.dvd.director.name}</li>

完成以上所有修改之後,試著執行 gradle tomcatRunWar,看看修改是否成功。

終於,本課程到了尾聲了 … 撰寫本文的這個時候,正值 JDK8 發表不久,算算 Java 也歷經了快二十年的變遷,這麼多年來 Java 累積的東西非常龐大,JDK8 又試著導入新的典範與不少的觀念 ... 這個課程只是個簡短的生態之旅,如果你正要進入 Java 的世界,別忘了,你的旅程才剛要開始!...