Java 開發 RESTful API 入門教學 Jersey 需要了解的六個地方

me
林彥成
2018-04-06 | 4 min.
文章目錄
  1. 1. 什麼是 Jersey?
  2. 2. Jersey RESTful API 簡單範例
  3. 3. Jersey 常用的 Annotation
    1. 3.1. Jersey 資料傳輸格式 (@Consumes、@Produces)
  4. 4. Jersey API 權限管理
  5. 5. Jersey Filters & Interceptors
    1. 5.1. @Pre-matching 範例
  6. 6. NameBinding 和 Dynamic Binding
    1. 6.1. NameBinding 範例
    2. 6.2. Dynamic Binding 範例
  7. 7. JAX-RS Response & Error Message
  8. 8. Maven 設定
  9. 9. Log4J

幫大家複習開發一個 Web API 提供給前端使用主要流程如下:

  1. 建立 Java Web Appication 並提供 RESTful Web Service
  2. 透過 War 檔發佈至容器: 將程式發佈至伺服器供前端存取
  3. 讓撰寫的 RESTful Web Service 存取實體資料庫

在前一篇文章整理了網路上教學範例實作心得 透過 Java 開發 Web API (RESTful API),這篇文章將進一步整理 Jersey 在使用上需要了解的六個地方:

  1. 常用的 Annotation
  2. Filters & Interceptors
  3. API 權限管理
  4. NameBinding 和 Dynamic Binding
  5. JAX-RS Response & Error Message
  6. Maven 設定

什麼是 Jersey?

Jersey 是基於 JAX-RS 來實現 RESTful Web Service 的一個 Framework

  • 使用大量的 Annotation 來簡化撰寫
  • 支援 XML (jaxb) 及 JSON (jackson)

使用 Jersey 開發 RESTful API 完成後,將應用程式放到任何一種像是 jettyt、Tomcat、JBoss 就能完成 http server 的架設。

Jersey RESTful API 簡單範例

假設今天想要開的 API 路徑是 http://localhost:{PORT}/{專案名稱}/rest/test

  • @ApplicationPath: 設定應用程式層級的路徑,這個例子中為 rest,透過 Annotation 來幫你分配底層的 Servelt
  • @Path(“/test”): 定義功能路徑 test
  • @GET: 定義 HTTP Method 方法為 Get
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@ApplicationPath("/rest")
public class TomcatApplication extends ResourceConfig{
public TomcatApplication(){
property(ServerProperties.WADL_FEATURE_DISABLE, "true");
property(ServerProperties.PROVIDER_PACKAGES, "com.web.tomcat.jersey,com.web.tomcat.filter,com.web.tomcat.interceptor");
}
}

// test 則是用 @Path 來設置:
@Path("/test")
public class TestRestPractice {
@GET
public String hello() {
return "hello >////<";
}
}

Jersey 常用的 Annotation

底下是常用的 Annotation:

  • @ApplicationPath: 應用程式層級路徑
  • @PATH: 定義路徑
  • @GET, @POST, @PUT, @DELETE: http 方法
  • @Consumes, @Produces: 定義 MIME
  • @PathParam: http://localhost:你開的洞 XD/專案名稱/rest/test/我是 PathParam
  • @FormParam: 可以用 REST client 製造一個 Form 的 POST
  • @QueryParam: http://localhost:你開的洞 XD/專案名稱/rest/test?我是 QueryParam = 我的值
  • @DefaultValue: 也可以修飾 POJO 裡的變數
  • @CookieParam
  • @HeaderParam

進階款:

  • @Provider
  • @Priority: 利用 @Priority 來標註優先權
  • @PreMatching
  • @NameBinding
  • @RolesAllowed

用在其他 Annotation 上的 Annotation

  • @Retention 確定標註的生命周期, 用一個 Enum 的 RetentionPolicy 參數設定
  • @Documented 文檔化
  • @Target 表示標註適用的範圍,也用 Enum 的 EnumType 的參數設定
  • @SessionScope
  • @Inject 針對資料的標註,@JsonProperty 改變 JSON 名稱

Jersey 資料傳輸格式 (@Consumes、@Produces)

Jersey 支援直接將 POJO 轉成 XML (jaxb) 及 JSON (jackson) 的功能,只需要使用兩種 @ 設定即可。

  • @Consumes: @Consumes("text/plain")
  • @Produces: @Produces({"application/xml", "application/json"})
1
2
3
4
5
6
7
8
9
10
11

@POST
@Consumes("text/plain")
public void postClichedMessage(String message) {
}

@GET
@Produces({"application/xml", "application/json"})
public String doGetAsXmlOrJson() {
}

Jersey API 權限管理

一樣是透過 Annotation 針對 API 做權限上的管理

  • @PermitAll
  • @RolesAllowed

底下範例針對同個路徑讓一般使用者、管理員取得不同的資料

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Path("/")
@PermitAll
public class Resource {
@RolesAllowed("user")
@GET
public String get() { return "GET"; }

@RolesAllowed("admin")
@POST
public String post(String content) { return content; }

@Path("sub")
public SubResource getSubResource() {
return new SubResource();
}
}

Jersey Filters & Interceptors

Filters & Interceptors 用途是在 API 的資料流中進行把關和處理

  • Filter 可以針對傳入的 request 或即將送出的 response 的參數操作,像是 header 判斷、HTTP Method 修改等等,自訂 Server Filters 需要 override 以下兩個方法:
    • ContainerRequestFilter
    • ContainerResponseFilter
  • Interceptors 攔截器可以針對 input/output 串流做處理,常見的像是壓縮
    • WriterInterceptor
    • ReaderInterceptor

當一個 Client 發出請求後,執行的粗略順序如下:

  1. ClientRequestFilters
  2. ReaderInterceptor
  3. WriterInterceptor
  4. ClientResponseFilters

@Pre-matching 範例

提前攔截並更改方法,如未加標註則預設都為 Post-matching 僅做判斷後篩選

1
2
3
4
5
6
7
8
9
10
11
@PreMatching
public class PreMatchingFilter implements ContainerRequestFilter {
@Override
public void filter(ContainerRequestContext requestContext)
throws IOException {
// change all PUT methods to POST
if (requestContext.getMethod().equals("PUT")) {
requestContext.setMethod("POST");
}
}
}

NameBinding 和 Dynamic Binding

加上 @Provider 則為預設啟動,若需建立 filter 及 interceptors 與特定 RESTful Services 之間的關連,會利用 @NameBinding 再加上自定標註,或是使用 Dynamic Binding。

NameBinding 範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@NameBinding
@Retention(value =RetentionPolicy.RUNTIME)
public @interface Test {

}
--------------------------------------------------------
@Provider
@Test
public class TestFilter2 implements ContainerRequestFilter,
ContainerResponseFilter {
//
}
--------------------------------------------------------
@Test
@GET
public String hello() {
return "hello >////<";
}

Dynamic Binding 範例

  1. 包在 my.package.admin 裡的
1
2
3
4
5
6
7
8
9
10
11
12
@Provider
public class MyDynamicFeature implements DynamicFeature {
@Override
public void configure(ResourceInfo resourceInfo, FeatureContext context) {
String resourcePackage = resourceInfo.getResourceClass().getPackage().getName();
Method resourceMethod = resourceInfo.getResourceMethod();
if ("my.package.admin".equals(resourcePackage)
&& resourceMethod.getAnnotation(GET.class) != null) {
context.register(LoggingFilter.class);
}
}
}
  1. HelloWorldResource.class 裡的
1
2
3
4
5
6
7
8
9
10
public class CompressionDynamicBinding implements DynamicFeature {
@Override
public void configure(ResourceInfo resourceInfo, FeatureContext context) {
if (HelloWorldResource.class.equals(resourceInfo.getResourceClass())
&& resourceInfo.getResourceMethod()
.getName().contains("VeryLongString")) {
context.register(GZIPWriterInterceptor.class);
}
}
}

JAX-RS Response & Error Message

Return Response 可以設定 status

  1. .status(Response.Status.OK) //200
  2. .status(Response.Status.CREATED) // 201
  3. .status(Response.Status.NO_CONTENT)// 204
    • the resource method’s return type is void
    • the value of the returned entity is null
  4. .seeOther() //303
  5. .notModified()//304
  6. .temporaryRedirect()//307
  7. .status(Response.Status.FORBIDDEN)//403
  8. .status(Response.Status.CONFLICT)//409
    • 取到後可以判斷 Response response = request.get();
    • Assert.assertTrue(response.getStatus() == 200);

Maven 設定

Maven 透過 Project Object Model 的設置,也就是 pom.xml 會定義以下這些東西:

  1. 版本相關資訊
1
2
3
4
<modelVersion>4.0.0</modelVersion>
<groupId>test</groupId>
<artifactId>test</artifactId>
<version>0.0.1-SNAPSHOT</version>
  1. 定義 Project 的 layout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<sourceDirectory>src</sourceDirectory>
<resources>
<resource>
<directory>src</directory>
<excludes>
<exclude>**/*.java</exclude>
</excludes>
</resource>
<resource>
<directory>properties</directory>
<excludes>
<exclude>**/*.java</exclude>
</excludes>
</resource>
</resources>
  1. 定義相關 dependencies
1
2
3
4
5
6
7
<dependencies>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet</artifactId>
<version>2.16</version>
</dependency>
</dependencies>

加入 dependencies 後會自動產生 jar, war 相關的檔案,少去自己加參考的過程,但需要了解 dependencies 是針對什麼而加,像是 POJO to JSON 需要 MOXy 或是 Jackson:

1
2
3
4
5
6
7
8
9
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-moxy</artifactId>
</dependency>

<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
</dependency>

除了 pom.xml 外,在 JAX-RS Application 有個 class 叫 ResourceConfig 提供了註冊資源的方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
ApplicationPath("/rest")
public class TomcatApplication extends ResourceConfig{
public TomcatApplication() {
property(ServerProperties.WADL_FEATURE_DISABLE, "true");
property(ServerProperties.PROVIDER_PACKAGES,
"com.web.tomcat.jersey,com.web.tomcat.filter,com.web.tomcat.interceptor");
register(XXX.class);
}
}

final Application application = new ResourceConfig()
.packages("org.glassfish.jersey.examples.linking")
.register(DeclarativeLinkingFeature.class);

舉例來說要利用 Jersey 上下傳檔案,可能就需要額外註冊以下的資訊:

1
2
3
4
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-multipart</artifactId>
</dependency>

要記得到 ResourceConfig 註冊

1
2
packages("com.rest.resource");
register(MultiPartFeature.class);

Log4J

了解了 Jersey 的相關設定後,使用 Log4J 可以大大幫助我們在開發上的除錯。

  1. POM 檔設定相關相依性
1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.4</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.4</version>
</dependency>
  1. import 相關 Library
1
2
3
4
5
6
7
8
9
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class HelloWorld {
private static final Logger logger = LogManager.getLogger("HelloWorld");
public static void main(String[] args) {
logger.info("Hello, World!");
}
}
  1. XML 設定
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Appenders>
<!-- Console 為 console 顯示 log 格式的設定-->
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d %-5p [%t] %C{2} (%F:%L) - %m%n" />
</Console>
<!-- DailyFile 為以日為單位儲存的 log 檔設定 -->
<RollingFile name="DailyFile" fileName="logs/jarvis-log.log"
filePattern="logs/jarvis-log-%d{yyyy-MM-dd}~%i.log">
<PatternLayout>
<Pattern>[%-5level] %d{yyyy-MM-dd HH:mm:ss} [%t] %c{1} - %msg%n</Pattern>
</PatternLayout>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true" />
<SizeBasedTriggeringPolicy size="100 MB" />
</Policies>
</RollingFile>
</Appenders>
<Loggers>
<Logger name="org.apache.log4j.xml" level="info" />
<Root level="debug">
<AppenderRef ref="Console" />
<AppenderRef ref="DailyFile" />
</Root>
</Loggers>
</Configuration>

喜歡這篇文章,請幫忙拍拍手喔 🤣


share