一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

Java RESTful Web Service實(shí)戰(zhàn)

 yespon 2016-09-16

編者按: InfoQ開設(shè)欄目“品味書香”,精選技術(shù)書籍的精彩章節(jié),以及分享看完書留下的思考和收獲,歡迎大家關(guān)注。本文節(jié)選自韓陸著《Java RESTful Web Service實(shí)戰(zhàn)》中的章節(jié)“REST API 設(shè)計(jì)”,介紹REST請求流程等內(nèi)容。

3.3 REST請求流程

從REST請求處理的擴(kuò)展點(diǎn)上,我們已經(jīng)講述了用于實(shí)體處理的Provider接口以及上下文處理和異常處理的Provider。本章還將講述兩種在面向切面編程中非常重要的特殊Provider:過濾器(3.4節(jié))和攔截器(3.5節(jié))。在進(jìn)入這個(gè)主題之前,我們需要對REST請求處理的流程這條線有明確的認(rèn)識,如圖3-5所示,這樣以來,才會知道這些點(diǎn)都處于流程的什么位置。只有這樣才能清楚地實(shí)現(xiàn)對擴(kuò)展點(diǎn)的開發(fā)和調(diào)試。

在圖3-5中,請求流程中存在3種角色,分別是用戶、REST客戶端和REST服務(wù)器。請求始于請求的發(fā)送,止于調(diào)用Response類的readeEntity()方法,獲取響應(yīng)實(shí)體。

1)用戶提交請求數(shù)據(jù),客戶端接收請求,進(jìn)入第一個(gè)擴(kuò)展點(diǎn):“客戶端請求過濾器ClientRequestFilter實(shí)現(xiàn)類”的filter()方法。

2)請求過濾處理完畢后,流程進(jìn)入第二個(gè)擴(kuò)展點(diǎn):“客戶端寫攔截器WriterInterceptor實(shí)現(xiàn)類”的aroundWriteTo()方法,實(shí)現(xiàn)對客戶端序列化操作的攔截。

3)“客戶端消息體寫處理器MessageBodyWriter”執(zhí)行序列化,流程從客戶端過渡到服務(wù)器端。

4)服務(wù)器接收請求,流程進(jìn)入第三個(gè)擴(kuò)展點(diǎn):“服務(wù)器前置請求過濾器Container-RequestFilter實(shí)現(xiàn)類”的filter()方法。

圖3-5 Jersey的REST請求處理流程

5)過濾處理完畢后,服務(wù)器根據(jù)請求匹配資源方法,如果匹配到相應(yīng)的資源方法,流程進(jìn)入第四個(gè)擴(kuò)展點(diǎn):“服務(wù)器后置請求過濾器ContainerRequestFilter實(shí)現(xiàn)類”的filter()方法。

6)后置請求過濾處理完畢后,流程進(jìn)入第五個(gè)擴(kuò)展點(diǎn):“服務(wù)器讀攔截器ReaderInterceptor實(shí)現(xiàn)類”的aroundReadFrom()方法,攔截服務(wù)器端反序列化操作。

7)“服務(wù)器消息體讀處理器MessageBodyReader”完成對客戶端數(shù)據(jù)流的反序列化。服務(wù)器執(zhí)行匹配的資源方法。

8)REST請求資源的處理完畢后,流程進(jìn)入第六個(gè)擴(kuò)展點(diǎn):“服務(wù)器響應(yīng)過濾器ContainerResponseFilter實(shí)現(xiàn)類”的filter()方法。

9)過濾處理完畢后,流程進(jìn)入第七個(gè)擴(kuò)展點(diǎn):“服務(wù)器寫攔截器WriterInterceptor實(shí)現(xiàn)類”的aroundWriteTo()方法,對服務(wù)器端序列化到客戶端這個(gè)操作的攔截。

10)“服務(wù)器消息體寫處理器MessageBodyWriter”執(zhí)行序列化,流程返回到客戶端一側(cè)。

11)客戶端接收響應(yīng),流程進(jìn)入第八個(gè)擴(kuò)展點(diǎn):“客戶端響應(yīng)過濾器Client-ResponseFilter實(shí)現(xiàn)類”的filter()方法。

12)過濾處理完畢后,客戶端響應(yīng)實(shí)例response返回到用戶一側(cè),用戶執(zhí)行response.readEntity()流程進(jìn)入第九個(gè)擴(kuò)展點(diǎn):“客戶端讀攔截器ReaderInterceptor實(shí)現(xiàn)類”的aroundReadFrom()方法,對客戶端反序列化進(jìn)行攔截。

13)“客戶端消息體讀處理器MessageBodyReader”執(zhí)行反序列化,將Java類型的對象最終作為readEntity()方法的返回值。到此,一次REST請求處理的完整流程完畢。這期間,如果出現(xiàn)異?;蛸Y源不匹配等情況,會從出錯(cuò)點(diǎn)開始結(jié)束流程。

3.4 REST過濾器

從上一節(jié)的流程講述中,我們了解JAX-RS2定義的4種過濾器擴(kuò)展點(diǎn)(Extension Point)接口,供開發(fā)者實(shí)現(xiàn)業(yè)務(wù)邏輯,按請求處理流程的先后順序?yàn)椋嚎蛻舳苏埱筮^濾器(ClientRequestFilter)->服務(wù)器請求過濾器(ContainerRequestFilter)->服務(wù)器響應(yīng)過濾器(ContainerResponseFilter)->客戶端響應(yīng)過濾器(ClientResponseFilter)。本節(jié)將全面講述4種過濾器的使用。

3.4.1 ClientRequestFilter

客戶端請求過濾器(ClientRequestFilter)定義的過濾方法filter()包含一個(gè)輸入?yún)?shù),是客戶端請求的上下文類ClientRequestContext。從該上下文中可以獲取請求信息,典型的示例包括獲取請求方法context.getMethod(),獲取請求資源地址context.getUri()和獲取請求頭信息context.getHeaders()等。過濾器的實(shí)現(xiàn)類中可以利用這些信息,覆寫該方法以實(shí)現(xiàn)該類特有的過濾功能。ClientRequestFilter接口的實(shí)現(xiàn)類如圖3-6所示。

圖3-6 ClientRequestFilter接口的實(shí)現(xiàn)類

圖3-6展所示了ClientRequestFilter接口的實(shí)現(xiàn)類,包括Jersey內(nèi)部提供的實(shí)現(xiàn)類和本書示例代碼中的實(shí)現(xiàn)類。我們選擇HTTP認(rèn)證過濾器類HttpAuthenticationFilter作為例子,來感受上面的講述,(HTTP基本認(rèn)證的內(nèi)容請參考10.1節(jié)。)示例代碼如下所示。

@Override
public void filter(ClientRequestContext request) throws IOException {
  if(!"true".equals(request.getProperty("org.glassfish.jersey.client.authentication.HttpAuthenticationFilter.reused"))) {
    if(!request.getHeaders().containsKey("Authorization")) {
      HttpAuthenticationFilter.Type operation = null;
      if(this.mode == Mode.BASIC_PREEMPTIVE) {
        this.basicAuth.filterRequest(request);
        operation = HttpAuthenticationFilter.Type.BASIC;
...
      if(operation != null) {
        request.setProperty("org.glassfish.jersey.client.authentication.HttpAuthenticationFilter.operation", operation);
      }

在這段代碼中,HTTP基本認(rèn)證過濾器類在filter()方法中,判斷請求頭信息中是否包含"Authorization",如果不包含則通過filterRequest()方法添加請求頭"Authorization"為authentication,authentication的內(nèi)容是"Basic" + Base64.encodeAsString(usernamePassword)。這樣以來,經(jīng)過HTTP基本認(rèn)證過濾器類過濾處理后,可以確保請求頭信息中包含"Authorization"。

3.4.2 ContainerRequestFilter

針對過濾切面,服務(wù)器請求過濾器接口ContainerRequestFilter的實(shí)現(xiàn)類可以定義為預(yù)處理和后處理。默認(rèn)情況下,采用后處理方式。即先執(zhí)行容器接收請求操作,當(dāng)服務(wù)器接收并處理請求后,流程才進(jìn)入過濾器實(shí)現(xiàn)類的filter()方法。而預(yù)處理是在服務(wù)器處理接收到的請求之前就執(zhí)行過濾。如果希望實(shí)現(xiàn)一個(gè)預(yù)處理的過濾器實(shí)現(xiàn)類,需要在類名上定義注解@PreMatching。

服務(wù)器請求過濾器定義的過濾方法filter()包含一個(gè)輸入?yún)?shù),即容器請求上下文類ContainerRequestContext。ContainerRequestFilter接口的實(shí)現(xiàn)類,如圖3-7所示。

圖3-7 ContainerRequestFilter接口的實(shí)現(xiàn)類

圖3-7展示了ContainerRequestFilter接口的實(shí)現(xiàn)類,我們以CsrfProtectionFilter為例來說明,示例代碼如下所示。

package org.glassfish.jersey.server.filter;

@Priority(Priorities.AUTHENTICATION) //post-matching 
public class CsrfProtectionFilter implements ContainerRequestFilter {
  public static final String HEADER_NAME = "X-Requested-By";
  //關(guān)注點(diǎn)1 忽略方法集合 
  private static final Set<String> METHODS_TO_IGNORE;
  static {
    HashSet<String> mti = new HashSet<>();
    mti.add("GET");
    mti.add("OPTIONS");
    mti.add("HEAD");
    METHODS_TO_IGNORE = Collections.unmodifiableSet(mti);
  }

  @Override
  public void filter(ContainerRequestContext rc) throws IOException {
      //關(guān)注點(diǎn)2 判斷方法名稱是否符合條件 
    if (!METHODS_TO_IGNORE.contains(rc.getMethod()) && !rc.getHeaders().containsKey(HEADER_NAME)) {
        throw new BadRequestException();
    }
  }
}

在這段代碼中,CsrfProtectionFilter定義了一個(gè)特殊的頭信息"X-Requested-By"和CSRF忽略監(jiān)控的方法集合,見關(guān)注點(diǎn)1。在過濾器的filter()方法中,首先從上下文中獲取頭信息rc.getHeaders()和請求方法信息rc.getMethod(),然后判斷頭信息是否包含"X-Requested-By",方法信息是否是安全的請求方法,即"GET"、"OPTIONS"或"HEAD"。如果兩個(gè)條件都不成立,過濾器會拋出一個(gè)運(yùn)行時(shí)異常BadRequestException,見關(guān)注點(diǎn)2。通過CsrfProtectionFilter過濾器,可以確保請求是CSRF安全的。

閱讀指南

CsrfProtectionFilter類使用了注解@Priority(Priorities.AUTHENTICATION)來定義該類,明確了該過濾器具有最高的優(yōu)先級。同時(shí),以注解的文字告訴開發(fā)者,需要將其放在過濾器鏈的第一個(gè)位置。因此,在定義和使用過濾器時(shí),需要考慮運(yùn)行中過濾器的執(zhí)行先后順序,否則無法實(shí)現(xiàn)過濾器的功能或者使流程混亂。

3.4.3 ContainerResponseFilter

服務(wù)器響應(yīng)過濾器接口ContainerResponseFilter定義的過濾方法filter()包含兩個(gè)輸入?yún)?shù),一個(gè)是容器請求上下文類ContainerRequestContext,另一個(gè)是容器響應(yīng)上下文類ContainerResponseContext。ContainerResponseFilter接口的實(shí)現(xiàn)類如圖3-8所示。

圖3-8 ContainerResponseFilter接口的實(shí)現(xiàn)類

圖3-8展示了ContainerResponseFilter接口的實(shí)現(xiàn)類,我們以EncodingFilter為例來說明。該過濾器的作用是完成內(nèi)容協(xié)商中編碼匹配的工作(內(nèi)容協(xié)商這個(gè)知識點(diǎn)請參考3.6節(jié)),示例代碼如下所示。

@Priority(Priorities.HEADER_DECORATOR)
public final class EncodingFilter implements ContainerResponseFilter {
    @Override
    public void filter(ContainerRequestContext request, ContainerResponseContext response) throws IOException {
...
      List<String> varyHeader = ((ContainerResponse) response).getStringHeaders().get(HttpHeaders.VARY);
      //關(guān)注點(diǎn)1:Vary頭信息 
      if (varyHeader == null || !varyHeader.contains(HttpHeaders.ACCEPT_ENCODING)) {
        response.getHeaders().add(HttpHeaders.VARY, HttpHeaders.ACCEPT_ENCODING);
      }
...
      //關(guān)注點(diǎn)1:Content-Encoding頭信息 
      if (!IDENTITY_ENCODING.equals(contentEncoding)) {
        response.getHeaders().putSingle(HttpHeaders.CONTENT_ENCODING, contentEncoding);
      }
    }

EncodingFilter過濾器的filter()方法通過對請求頭信息“Accept-Encoding”的分析,先后為響應(yīng)頭信息“Vary”和“Content-Encoding”賦值,以實(shí)現(xiàn)編碼部分的內(nèi)容協(xié)商。見關(guān)注點(diǎn)1。

3.4.4 ClientResponseFilter

客戶端響應(yīng)過濾器(ClientResponseFilter)定義的過濾方法filter()包含兩個(gè)輸入?yún)?shù),一個(gè)是客戶端請求的上下文類ClientRequestContext,另一個(gè)是客戶端響應(yīng)的上下文類ClientResponseContext。ClientResponseFilter接口的實(shí)現(xiàn)類,如圖3-9所示。

圖3-9 ClientResponseFilter接口的實(shí)現(xiàn)類

圖3-9展示了ClientResponseFilter接口的實(shí)現(xiàn)類,包括Jersey內(nèi)部提供的實(shí)現(xiàn)類和本書示例代碼中的實(shí)現(xiàn)類。我們以HTTP摘要認(rèn)證過濾器類HttpAuthenticationFilter為例進(jìn)行演示,(HTTP摘要認(rèn)證請參考10.1節(jié)。)示例代碼如下所示。

@Override
public void filter(ClientRequestContext request, ClientResponseContext response) throws IOException {
...
    if(response.getStatus() == Status.UNAUTHORIZED.getStatusCode()) {
    String operation = (String)response.getHeaders().getFirst("WWW-Authenticate");
    if(operation != null) {
      String success = operation.trim().toUpperCase();
      if(success.startsWith("BASIC")) {
          result = HttpAuthenticationFilter.Type.BASIC;
      } else {
        if(!success.startsWith("DIGEST")) {
          return;
        }
        result = HttpAuthenticationFilter.Type.DIGEST;
      }
    }
    authenticate = true;
    } else {
    authenticate = false;
    }

    if(this.mode != Mode.BASIC_PREEMPTIVE) {
    if(this.mode == Mode.BASIC_NON_PREEMPTIVE) {
      if(authenticate && result == HttpAuthenticationFilter.Type.BASIC) {
        this.basicAuth.filterResponseAndAuthenticate(request, response);
      }
    } else if(this.mode == Mode.DIGEST) {
      if(authenticate && result == HttpAuthenticationFilter.Type.DIGEST) {
        this.digestAuth.filterResponse(request, response);
      }
    }
    ...
    }

3.4.5 訪問日志

3.4.1?3.4.5節(jié)完成了對JAX-RS2定義的4種過濾器的講述,本節(jié)利用上述知識,演示如何綜合運(yùn)用過濾器,完成一個(gè)記錄REST請求的訪問日志。

1.訪問日志實(shí)現(xiàn)類

訪問日志類AirLogFilter實(shí)現(xiàn)了上述的4種過濾器,旨在記錄服務(wù)器和客戶端的請求和響應(yīng)運(yùn)行時(shí)的信息。AirLogFilter類定義如下所示。

@PreMatching
public class AirLogFilter implements ContainerRequestFilter, ClientRequestFilter,
ContainerResponseFilter, ClientResponseFilter {

AirLogFilter為每一種過濾器接口定義的filter()方法提供了實(shí)現(xiàn)。在客戶端請求過濾中,輸出請求資源地址信息和請求頭信息;在容器請求過濾中,輸出請求方法、請求資源地址信息和請求頭信息;在容器響應(yīng)過濾中,輸出HTTP狀態(tài)碼和請求頭信息;在客戶端響應(yīng)過濾中,輸出HTTP狀態(tài)碼和請求頭信息。4個(gè)階段的filter()示例代碼如下所示。

@Override 
public void filter(ClientRequestContext context) throws
IOException {
    long id = logSequence.incrementAndGet();
    StringBuilder b = new StringBuilder();
    //關(guān)注點(diǎn)1:獲取請求方法和地址 
    printRequestLine(CLIENT_REQUEST, b, id, context.getMethod(), context.getUri());
    //關(guān)注點(diǎn)2:獲取請求頭信息 
    printPrefixedHeaders(CLIENT_REQUEST, b, id, HeadersFactory.asStringHeaders (context.getHeaders()));
    LOGGER.info(b.toString()); }

在這段代碼中,AirLogFilter類實(shí)現(xiàn)了客戶端響應(yīng)過濾。從客戶端響應(yīng)上下文實(shí)例中,可以獲取到響應(yīng)狀態(tài)信息和響應(yīng)頭信息。分別見關(guān)注點(diǎn)1和關(guān)注點(diǎn)2。

@Override
public void filter(ContainerRequestContext context) throws IOException {
    long id = logSequence.incrementAndGet();
    StringBuilder b = new StringBuilder();
    //關(guān)注點(diǎn)1:獲取容器請求方法和請求地址信息 
    printRequestLine(SERVER_REQUEST, b, id, context.getMethod(), context.getUriInfo().getRequestUri());
    //關(guān)注點(diǎn)2:獲取請求頭信息 
    printPrefixedHeaders(SERVER_REQUEST, b, id, context.getHeaders());
    LOGGER.info(b.toString());
}

在這段代碼中,AirLogFilter類實(shí)現(xiàn)了容器請求過濾。從容器請求上下文實(shí)例中,可以獲取到請求方法和請求資源地址信息,見關(guān)注點(diǎn)1。同樣,可以從中獲取請求頭信息,見關(guān)注點(diǎn)2。

@Override
public void filter(ContainerRequestContext requestContext,
ContainerResponseContext responseContext) throws IOException {
    long id = logSequence.incrementAndGet();
    StringBuilder b = new StringBuilder();
    //關(guān)注點(diǎn)1:獲取容器響應(yīng)狀態(tài) 
    printResponseLine(SERVER_RESPONSE, b, id, responseContext.getStatus());
    //關(guān)注點(diǎn)2:獲取容器響應(yīng)頭信息 
    printPrefixedHeaders(SERVER_RESPONSE, b, id, HeadersFactory.asStringHeaders(responseContext.getHeaders()));
    LOGGER.info(b.toString());
}

在這段代碼中,AirLogFilter類實(shí)現(xiàn)了容器響應(yīng)過濾。從容器響應(yīng)上下文實(shí)例中,可以獲取到容器的響應(yīng)狀態(tài)信息和響應(yīng)頭信息。分別見關(guān)注點(diǎn)1和關(guān)注點(diǎn)2。 2. 單元測試類

訪問日志的單元測試示例如下所示。

public class TIResourceJtfTest extends JerseyTest {
    @Override
    protected Application configure() {
        ResourceConfig config = new ResourceConfig(BookResource.class);
        return config.register(com.example.filter.log.AirLogFilter.class);
    }
    @Override
    protected void configureClient(ClientConfig config) {
        config.register(new AirLogFilter());
    }

在這段代碼中,為了使訪問日志類生效,需要測試類TIResourceJtfTest在Jersey測試框架的服務(wù)器端和客戶端,分別注冊服務(wù)日志類AirLogFilter。 單元測試的結(jié)果如下所示,在4種過濾器中分別打印了該階段的日志信息。

main - 1 * AirLog - Request received on thread main
1 / GET http://localhost:9998/books/1
1 / Accept: application/json
Grizzly-worker(1) - 2 * AirLog - Request received on thread Grizzly-worker(1)
2 > GET http://localhost:9998/books/1
2 > accept: application/json
...
Grizzly-worker(1) - 3 * AirLog - Response received on thread Grizzly-worker(1)
3 < 200
3 < Content-Type: application/json
main - 4 * AirLog - Response received on thread main
4 \ 200
4 \ Content-Type: application/json
...

3.5 REST攔截器

攔截器和過濾器的相同點(diǎn)是都是一種在請求—響應(yīng)模型中,用做切面處理的Provider。兩者的不同除了功能性上的差異(一個(gè)用于過濾消息,一個(gè)用于攔截處理)之外,形式上也不同。攔截器通常讀寫成對,而且沒有服務(wù)器端和客戶端的區(qū)分。Jersey提供的攔截器類,如圖3-10所示。

圖3-10 讀寫攔截器的實(shí)現(xiàn)類

在圖3-10中,Jersey內(nèi)部實(shí)現(xiàn)了幾個(gè)典型應(yīng)用的攔截器,它們是成對出現(xiàn)的。比如GZiPEncoder同時(shí)實(shí)現(xiàn)了讀寫攔截器,以實(shí)現(xiàn)使用GZip壓縮格式壓縮消息體的功能。

1. ReaderInterceptor

讀攔截器接口ReaderInterceptor定義的攔截方法是aroundReadFrom(),該方法包含一個(gè)輸入?yún)?shù),即讀攔截器的上下文接口ReaderInterceptorContext,從中可以獲取頭信息、輸入流以及父接口InterceptorContext提供的媒體類型等上下文信息。接口方法示例如下。

public Object aroundReadFrom(ReaderInterceptorContext context) throws java.io.IOException, javax.ws.rs.WebApplicationException;

2. WriterInterceptor

寫攔截器接口WriterInterceptor定義的攔截方法是aroundWriteTo(),該方法包含一個(gè)輸入?yún)?shù),寫攔截器上下文接口WriterInterceptorContext,從中可以獲取頭信息、輸出流以及父接口InterceptorContext提供的媒體類型等上下文信息。接口方法示例如下所示。

void aroundWriteTo(WriterInterceptorContext context) throws java.io.IOException, javax.ws.rs.WebApplicationException;

3. 編解碼約束攔截器

編解碼約束攔截器類ContentEncoder是一個(gè)位于org.glassfish.jersey.spi包中的攔截器,SPI包下的工具是可插拔的。ContentEncoder攔截器用于約束序列化和反序列化的過程中,編解碼的內(nèi)容協(xié)商,示例代碼如下所示。

@Override
public final Object aroundReadFrom(ReaderInterceptorContext context)
throws IOException, WebApplicationException {
    String contentEncoding = context.getHeaders().getFirst(HttpHeaders.CONTENT_ENCODING);
    //關(guān)注點(diǎn)1:判斷是否包含Content-Encoding頭信息 
    if (contentEncoding != null && getSupportedEncodings().contains(contentEncoding)) {
    //關(guān)注點(diǎn)2:解碼處理 
        context.setInputStream(decode(contentEncoding, context.getInputStream()));
    }
    //關(guān)注點(diǎn)3:繼續(xù)攔截鏈的下一個(gè)攔截處理 
    return context.proceed();
}

@Override
public final void aroundWriteTo(WriterInterceptorContext context)
throws IOException, WebApplicationException {
    String contentEncoding = (String) context.getHeaders().getFirst (HttpHeaders.CONTENT_ENCODING);
    //關(guān)注點(diǎn)1:判斷是否包含Content-Encoding頭信息 
    if (contentEncoding != null && getSupportedEncodings().contains (contentEncoding)){
    //關(guān)注點(diǎn)2:編碼處理 
        context.setOutputStream(encode(contentEncoding, context.getOutputStream()));
    }
    //關(guān)注點(diǎn)3:繼續(xù)攔截鏈的下一個(gè)攔截處理 
    context.proceed();
}

在這段代碼中,分別給出了ContentEncoder攔截器的讀、寫攔截處理,只有當(dāng)頭信息包含“Content-Encoding”信息,編解碼才被執(zhí)行,見關(guān)注點(diǎn)1。讀取階段進(jìn)行解碼,寫入階段進(jìn)行編碼,見關(guān)注點(diǎn)2。上下文的proceed()方法用于執(zhí)行攔截器鏈的下一個(gè)攔截器,見關(guān)注點(diǎn)3。

3.6 綁定機(jī)制

在我們了解了面向切面的Providers的功能后,需要掌握它們是如何加載的,以及其作用域。這些容器級別的Providers,通常使用編碼的方式注冊到Application中,但這不是唯一的辦法。本節(jié)將詳細(xì)討論P(yáng)roviders的綁定機(jī)制。 默認(rèn)情況下,過濾器和攔截器都是全局綁定的。也就是說,如下之一的過濾器或攔截器是全局有效的。

  • 通過手動(dòng)注冊到Application或者Configuration;
  • 注解為@Provider,被自動(dòng)探測。

下面介紹其他的綁定機(jī)制。

3.6.1 名稱綁定

過濾器或攔截器可以使用特定的注解來指定其作用范圍,這種特定的注解被稱為名稱綁定。

1. 名稱綁定注解

使用@NameBinding注解可以定義一個(gè)運(yùn)行時(shí)的自定義注解,該注解用于定義類級別名稱和類的方法名,代碼示例如下所示。

@NameBinding
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(value = RetentionPolicy.RUNTIME)
public @interface AirLog {
}

在這段代碼中,自定義注解AirLog使用了@NameBinding,在運(yùn)行時(shí)該注解將被解析為一個(gè)名稱綁定的注解。

2. 綁定Provider

在定義了@AirLog注解后,既可以在Provider中使用該注解,示例代碼如下所示。

//關(guān)注點(diǎn)1:使用自定義注解@AirLog 
@AirLog
@Priority(Priorities.USER)
public class AirNameBindingFilter implements ContainerRequestFilter, ContainerResponseFilter {
  private static final Logger LOGGER = Logger.getLogger(AirNameBindingFilter.class);
  public AirNameBindingFilter() {
    LOGGER.info("Air-NameBinding-Filter initialized");
  }
  @Override
  //關(guān)注點(diǎn)2:filter實(shí)現(xiàn)訪問日志 
  public void filter(final ContainerRequestContext containerRequest) throws IOException {
    LOGGER.debug("Air-NameBinding-ContainerRequestFilter invoked:" +
    containerRequest.getMethod());
    LOGGER.debug(containerRequest.getUriInfo().getRequestUri());
  }
  @Override
  //關(guān)注點(diǎn)3:filter實(shí)現(xiàn)訪問日志 
  public void filter(ContainerRequestContext containerRequest,
  ContainerResponseContext responseContext) throws IOException {
    LOGGER.debug("Air-NameBinding-ContainerResponseFilter invoked:" +
    containerRequest.getMethod());
    LOGGER.debug("status=" + responseContext.getStatus());
  }
}

在這段代碼中,過濾器類AirNameBindingFilter使用了自定義注解@AirLog,這樣AirNameBindingFilter類就實(shí)現(xiàn)了名稱綁定,見關(guān)注點(diǎn)1。該類實(shí)現(xiàn)了容器的請求和響應(yīng)過濾器接口,功能是記錄訪問日志,見關(guān)注點(diǎn)2。

3. 綁定方法

接下來,我們在資源方法級別使用自定義注解@AirLog,來實(shí)現(xiàn)在資源類的指定方法上啟用AirNameBindingFilter過濾器,示例代碼如下所示。

@Path("books")
public class BookResource {
  //關(guān)注點(diǎn)1:綁定方法 
  @AirLog
  @GET
  @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
  public Books getBooks() {
...
    return books;
  }
...

在這段代碼中,資源類BookResource包含多個(gè)方法,我們只在getBooks()方法上使用了注解@AirLog,而其他方法并沒有綁定,見關(guān)注點(diǎn)1。

4. 單元測試類

接下來,通過單元測試來校驗(yàn)名稱綁定的設(shè)計(jì)和實(shí)現(xiàn)是否正確,示例代碼如下所示。

public class TestNamingBinding extends JerseyTest {
    @Override
    protected Application configure() {
    //關(guān)注點(diǎn)1:AirAopConfig內(nèi)部注冊了AirNameBindingFilter 
    return new AirAopConfig();
    }
    @Test
    //關(guān)注點(diǎn)2:測試getBookByPath()方法 
    public void testPathGetJSON() {
        final WebTarget pathTarget = target(BASE_URI).path("1");
        final Invocation.Builder invocationBuilder = pathTarget.request (MediaType.APPLICATION_JSON_TYPE);
        final Book result = invocationBuilder.get(Book.class);
        Assert.assertNotNull(result.getBookId());
    }
    @Test
    //關(guān)注點(diǎn)3:測試getBooks()方法 
    public void testGetAll() {
        final Invocation.Builder invocationBuilder = target(BASE_URI).request();
        final Books result = invocationBuilder.get(Books.class);
        Assert.assertNotNull(result.getBookList());
    }
}

在這段代碼中,測試類TestNamingBinding通過AirAopConfig注冊了AirNameBindingFilter過濾器,見關(guān)注點(diǎn)1。該類包含兩個(gè)測試方法,分別是測試資源類BookResource的getBooks()和getBookByPath()兩個(gè)方法,見關(guān)注點(diǎn)2和關(guān)注點(diǎn)3。

我們可以從終端打印的信息來檢驗(yàn)名稱綁定的運(yùn)行結(jié)果,示例如下。

Air-NameBinding-ContainerRequestFilter invoked:GET
http://localhost:9998/books/
Air-NameBinding-ContainerResponseFilter invoked:GET
status=200

從上述測試結(jié)果中可以看到,只有在testGetAll()方法輸出的日志中輸出了AirNameBindingFilter類中定義的日志信息。這和預(yù)期的“只有使用注解@AirLog定義的方法,才會在請求流程中啟用相應(yīng)的Provider”一致。

3.6.2 動(dòng)態(tài)綁定

名稱綁定需要通過自定義的注解名稱來綁定Provider和擴(kuò)展點(diǎn)方法或者類,相比而言,動(dòng)態(tài)綁定無須新增注解,而是使用編碼的方式,實(shí)現(xiàn)動(dòng)態(tài)特征接口javax.ws.rs.container.DynamicFeature,定義擴(kuò)展點(diǎn)方法的名稱、請求方法類型等匹配信息。在運(yùn)行期,一旦Provider匹配當(dāng)前處理類或方法,面向切面的Provider方法即被觸發(fā)。

1. 定義綁定Provider

AirDynamicFeature類實(shí)現(xiàn)了DynamicFeature接口,示例代碼如下。
public class AirDynamicFeature implements DynamicFeature {
  @Override
  public void configure(final ResourceInfo resourceInfo, final FeatureContext context) {
    boolean classMatched = BookResource.class.isAssignableFrom(resourceInfo.getResourceClass());
    boolean methodNameMatched = resourceInfo.getResourceMethod().getName().contains("getBookBy");
    boolean methodTypeMatched = resourceInfo.getResourceMethod().isAnnotationPresent(POST.class);
    //關(guān)注點(diǎn)1:匹配成功才注冊AirDynamicBindingFilter 
    if (classMatched &&(methodNameMatched || methodTypeMatched)) {
      context.register(AirDynamicBindingFilter.class);
    }
  }
}
public class AirDynamicBindingFilter implements ContainerRequestFilter {
  @Override
  public void filter(final ContainerRequestContext requestContext) throws IOException {
    AirDynamicBindingFilter.LOGGER.debug("Air-Dynamic-Binding-Filter invoked");
  }
}

在這段代碼中,在AirDynamicFeature的配置方法中,啟用了如下匹配規(guī)則。

1)類匹配:對BookResource類及其子類的匹配。 2)方法名稱匹配:方法名包含getBookBy()的匹配。 3)請求方法類型匹配:與POST方法的匹配。

只有當(dāng)匹配成功時(shí),才會注冊AirDynamicBindingFilter。對于Provider的實(shí)現(xiàn)類,并沒有特殊的要求。

2. 單元測試類

測試類TestDynamicBinding注冊了動(dòng)態(tài)綁定特征實(shí)現(xiàn)類AirDynamicFeature,示例代碼如下所示。

    public class TestDynamicBinding extends JerseyTest {
      @Override
      protected Application configure() {
        ResourceConfig config = new ResourceConfig(BookResource.class);
        config.register(AirDynamicFeature.class);
        return config;
      }
     
      @Test
      public void testPost() {
        final Book newBook = new Book("Java Restful Web Service使用指南-" + System.nanoTime());
        final Entity<Book> bookEntity = Entity.entity(newBook, MediaType.APPLICATION_JSON_TYPE);
        final Book savedBook = target(BASE_URI).request(MediaType.APPLICATION_JSON_TYPE).post(bookEntity, Book.class);
        Assert.assertNotNull(savedBook.getBookId());
      }
    }

運(yùn)行測試方法,AirDynamicBindingFilter的日志信息如預(yù)期輸出,示例代碼如下所示。

Air-Dynamic-Binding-Filter initialized
Air-Dynamic-Binding-Filter invoked

書籍介紹

本書系統(tǒng)、深度講解了如何基于Java標(biāo)準(zhǔn)規(guī)范實(shí)現(xiàn)REST風(fēng)格的Web服務(wù),由擁有10余年開發(fā)經(jīng)驗(yàn)的阿里云大數(shù)據(jù)架構(gòu)師撰寫,第1版上市后廣獲贊譽(yù),成為該領(lǐng)域的暢銷書。第2版對全書進(jìn)行了優(yōu)化和重構(gòu),不僅根據(jù)新的技術(shù)版本對原有過時(shí)內(nèi)容進(jìn)行了更新,而且還根據(jù)整個(gè)技術(shù)領(lǐng)域的發(fā)展增添了新的內(nèi)容。除此之外,還對第1版中存在的不足進(jìn)行了優(yōu)化,使得內(nèi)容更加與時(shí)具進(jìn)、更加有價(jià)值。不僅深刻解讀了新的JAX-RS標(biāo)準(zhǔn)和其API設(shè)計(jì),以及Jersey的使用要點(diǎn)和實(shí)現(xiàn)原理,還系統(tǒng)講解了REST的基本理論,更重要的是從實(shí)踐角度深度講解了如何基于Jersey實(shí)現(xiàn)完整的、安全的、高性能的REST式的Web服務(wù),書中包含大量示例代碼,實(shí)戰(zhàn)性強(qiáng)。

    本站是提供個(gè)人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多

    亚洲欧美日韩在线看片| 日本一本在线免费福利| 精品午夜福利无人区乱码| 日韩成人免费性生活视频| 亚洲中文字幕在线视频频道| 精品少妇人妻一区二区三区| 中文字幕在线区中文色| 欧美一区二区在线日韩| 日韩三级黄色大片免费观看| 国产成人精品国产成人亚洲| 欧美偷拍一区二区三区四区| 在线免费不卡亚洲国产| 国产综合一区二区三区av| 国产精品涩涩成人一区二区三区| 久久综合日韩精品免费观看| 国产免费观看一区二区| 亚洲国产一区精品一区二区三区色| 国产在线成人免费高清观看av| 美女激情免费在线观看| 精品少妇一区二区视频| 婷婷激情四射在线观看视频| 国产在线不卡中文字幕| 欧美亚洲三级视频在线观看| 亚洲av在线视频一区| 国产精品久久香蕉国产线| 亚洲av熟女一区二区三区蜜桃| 欧美日韩亚洲巨色人妻| 国产内射一级二级三级| 日韩成人免费性生活视频| 亚洲精品国产主播一区| 日本加勒比中文在线观看| 黄男女激情一区二区三区| 久久99亚洲小姐精品综合| 国产亚洲欧美日韩国亚语| 亚洲视频在线观看你懂的| 韩日黄片在线免费观看| 中文字幕人妻综合一区二区| 日韩成人午夜福利免费视频| 亚洲一区二区三区av高清| 国产一区二区不卡在线播放| 欧美亚洲91在线视频|