利用 Jersey 開發 Java RESTful API 教學 如何使用 Java 框架來開發 RESTful API?

me
林彥成
2018-04-06 | 3 min.

Jersey 是基於 JAX-RS 來實現 RESTful Web Service 的一個 Framework,使用大量的 Annotation 來簡化撰寫,支援 XML(jaxb)及 JSON(jackson),使用 Jersey 開發 RESTful API 完成後,將應用程式放到任何一種像是 jettyt、Tomcat、JBoss 就完成了 http server 的架設。

  • 常用的 Annotation
  • Filters & Interceptors
  • NameBinding 範例
  • Dynamic Binding 範例
  • JAX-RS Response & Error Message
  • Maven
  • Log4J

常用的 Annotation

像是在 Jersey 中都支援直接將 POJO 轉成 XML(jaxb)及 JSON(jackson) 的功能,只需要使用@Consumes @Produces 兩種@設定即可。底下是常用的 Annotation:

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
@PATH
@GET, @POST, @PUT, @DELETE
@Consumes
@Produces
@PathParam
// (http://localhost:你開的洞XD/專案名稱/rest/test/我是PathParam)
@FormParam
// 可以用 REST client 製造一個Form 的 POST
@QueryParam
// (http://localhost:你開的洞XD/專案名稱/rest/test?我是QueryParam = 我的值)
@DefaultValue
// 也可以修飾POJO裡的變數
@HeaderParam
@Context
// 進階款:
@Provider
@NameBinding
@Target
@Retention
@ApplicationPath
@XmlRootElement
@BeanParam
@CookieParam
@RolesAllowed
@HeaderParam

Java RESTful API Example

簡單範例
url:http://localhost: 你開的 port XD/專案名稱/rest/test
rest 就是用 @ApplicationPath 來設置,這樣就透過 Annotation 來幫你分配底層的 Servelt。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@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 >////<";
}
}

Filters & Interceptors

Filter can modify any request or response parameters like HTTP headers, URIs and/or HTTP method.
自訂 Server Filters 需要 override 以下兩個方法:
ContainerRequestFilter
ContainerResponseFilter
Interceptors manipulate entities, via manipulating entity input/output streams.
WriterInterceptor
ReaderInterceptor

加上 @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);
}
}
}

如果實作多個,可利用 @Priority 來標註優先權~
###@Pre-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");
}
}
}

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

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

  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>

2.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