幫大家複習開發一個 Web API 提供給前端使用主要流程如下:
建立 Java Web Appication 並提供 RESTful Web Service 透過 War 檔發佈至容器: 將程式發佈至伺服器供前端存取 讓撰寫的 RESTful Web Service 存取實體資料庫 在前一篇文章整理了網路上教學範例實作心得 透過 Java 開發 Web API (RESTful API) ,這篇文章將進一步整理 Jersey 在使用上需要了解的六個地方:
常用的 Annotation Filters & Interceptors API 權限管理 NameBinding 和 Dynamic Binding JAX-RS Response & Error Message 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" ); } } @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:你開的洞
/專案名稱/rest/test/我是 PathParam @FormParam: 可以用 REST client 製造一個 Form 的 POST @QueryParam: http://localhost:你開的洞
/專案名稱/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 做權限上的管理
底下範例針對同個路徑讓一般使用者、管理員取得不同的資料
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 發出請求後,執行的粗略順序如下:
ClientRequestFilters ReaderInterceptor WriterInterceptor 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 { 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 範例 包在 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); } } }
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
.status(Response.Status.OK)
//200.status(Response.Status.CREATED)
// 201.status(Response.Status.NO_CONTENT)
// 204the resource method’s return type is void the value of the returned entity is null .seeOther()
//303.notModified()
//304.temporaryRedirect()
//307.status(Response.Status.FORBIDDEN)
//403.status(Response.Status.CONFLICT)
//409取到後可以判斷 Response response = request.get(); Assert.assertTrue(response.getStatus() == 200); Maven 設定 Maven 透過 Project Object Model 的設置,也就是 pom.xml 會定義以下這些東西:
版本相關資訊 1 2 3 4 <modelVersion > 4.0.0</modelVersion > <groupId > test</groupId > <artifactId > test</artifactId > <version > 0.0.1-SNAPSHOT</version >
定義 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 >
定義相關 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 可以大大幫助我們在開發上的除錯。
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 >
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!" ); } }
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 name ="Console" target ="SYSTEM_OUT" > <PatternLayout pattern ="%d %-5p [%t] %C{2} (%F:%L) - %m%n" /> </Console > <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 >
喜歡這篇文章,請幫忙拍拍手喔 🤣