Fork me on GitHub

Programming Design Notes

Guice + Jersey 打造 RESTful 應用程式

| Comments

以前我喜歡使用 Spring + Spring Web MVC 來打造 RESTful 應用程式,但 Spring 的起動有點慢,如果放上 Google App Engine 上經常令用戶等待很久才載入到頁面。

今次選用了 Guice + Jersey 的組合。Guice 是一個輕量級的容器,設定上比起 Spring 更簡單,起動或注入速度亦比 Spring 快。而 Jersey 則是一個為 RESTful web service 而設的一個 Framework

Gucie 官方網址: http://code.google.com/p/google-guice/
Jersey 官方網址: http://jersey.java.net/

因為手上的電腦沒有安裝 Application Server,只好拿 GAEDevelopment Server 來測試。

首先下載 Guice : http://code.google.com/p/google-guice/downloads/detail?name=guice-3.0.zip&can=2&q= (現時為止最新版本為 3.0)

打開後將 aopalliance.jar, guice-3.0.jar, guice-servlet-3.0.jarjavax.inject.jar 加到 WEB-INF/lib 中並加到 Class path

然後下載 Jersey 所需的 Jar (現時為止最新版本為 1.9):
  1. jersey-server.jar
  2. jersey-core.jar
  3. asm.jar
  4. jersey-guice-1.9-SNAPSHOT.jar

將以上的檔案一樣是放到 WEB-INF/lib 並加到 Class path

首先建立一個 class,每一次連接 http://www.xyzdomainname.com 都會顯示 I am Index page:
package com.ctlok.pro.controller;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("/")
public class CommonController {

@GET
@Produces(MediaType.TEXT_PLAIN)
public String index(){
return "I am Index page";
}

}

@Path 是設定這個 class 會處理的 URL,可以放在 ClassMethod。而 Jersey 會搜尋出在 Class 上有 @Path 的類別然後處理。
@GET 是設定這個 Method 處理那一種請求,一共有 5 種方式: GET, POST, HEAD, PUT, DELETE
@Produces 則是回傳資料的類型,可以是 Text, XML, JSON, HTML 等等。

然後再去建立一個 GuiceModule class 並設定 Jersey:
package com.ctlok.pro;

import java.util.HashMap;
import java.util.Map;

import com.ctlok.pro.controller.CommonController;
import com.google.inject.servlet.RequestScoped;
import com.google.inject.servlet.ServletModule;
import com.sun.jersey.guice.spi.container.servlet.GuiceContainer;

public class WebModule extends ServletModule {

@Override
protected void configureServlets(){
bind(CommonController.class).in(RequestScoped.class);

Map<String, String> parameters = new HashMap<String, String>();
parameters.put("com.sun.jersey.config.property.packages", "com.ctlok.pro.controller");
serve("/*").with(GuiceContainer.class, parameters);
}

}

然後設定 Guice 起動時的注入器:
package com.ctlok.pro;

import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.servlet.GuiceServletContextListener;

public class AppConfig extends GuiceServletContextListener{

@Override
protected Injector getInjector() {
return Guice.createInjector(new WebModule());
}

}

在 web.xml 設定好 Guice FilterListener:
<?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"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
<listener>
<listener-class>com.ctlok.pro.AppConfig</listener-class>
</listener>
<filter>
<filter-name>guiceFilter</filter-name>
<filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>guiceFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>

打開 http://localhost:8888 後即可看到 “I am Index page”.

如要增加 URL,可以在 CommonController 加入:
@GET
@Path("user/{userId}/{userName}")
@Produces(MediaType.TEXT_PLAIN)
public String getUser(@PathParam("userId") String userId, @PathParam("userName") String userName){
return "User ID: " + userId + ", user name: " + userName;
}

你可能有點疑問,@Path 在 Class 已經設家了,現在又有一個 @Pathmethod 上,那 Jersey 會怎麼決定。
其實 Jersey 會將 class@Path value 加上 method 的 @Path value,即是 “/” + “user/{userId}/{userName}”。

打開 http://localhost:8888/user/123/lawrence 顯示:User ID: 123, user name: lawrence

其實 JerseyMethod 不一定返回 String 可以是一個 ObjectJersey 定義 的 Response。以下例字示範如何顯示一個 JSP 頁面,並由 ControllerModel 傳給 View (JSP):

先在 WEB-INF 新增一個 views 資料夾並新增一個 example.jspJSP 內容如下:
<?xml version="1.0" encoding="utf-8"?>
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Guice + Jersey + JSP</title>
</head>
<body>
<c:out value="${it.msg}" />
</body>
</html>

Controller 內也增加一個 Method:
@GET
@Path("jsp")
@Produces(MediaType.TEXT_HTML)
public Response getJsp(){
Map<String, Object> model = new HashMap<String, Object>();
model.put("msg", "Hello World!");
return Response.ok(new Viewable("/WEB-INF/views/example.jsp", model)).build();
}

打開 http://localhost:8888/jsp 顯示: Hello World!

完整的 CommonController:
package com.ctlok.pro.controller;

import java.util.HashMap;
import java.util.Map;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import com.sun.jersey.api.view.Viewable;

@Path("/")
public class CommonController {

@GET
@Produces(MediaType.TEXT_PLAIN)
public String index() {
return "I am Index page";
}

@GET
@Path("user/{userId}/{userName}")
@Produces(MediaType.TEXT_PLAIN)
public String getUser(@PathParam("userId") String userId,
@PathParam("userName") String userName) {
return "User ID: " + userId + ", user name: " + userName;
}

@GET
@Path("jsp")
@Produces(MediaType.TEXT_HTML)
public Response getJsp() {
Map<String, Object> model = new HashMap<String, Object>();
model.put("msg", "Hello World!");
return Response.ok(new Viewable("/WEB-INF/views/example.jsp", model))
.build();
}

}

範例: Guice-Jersey.zip
密碼: pro.ctlok.com 相關書籍: Dependency InjectionGoogle Guice: Agile Lightweight Dependency Injection FrameworkRESTful Java with Jax-RS (Animal Guide)