1. 程式人生 > >Jersey 2.22.2 官方文件第10章學習筆記

Jersey 2.22.2 官方文件第10章學習筆記

原文地址:https://jersey.java.net/documentation/latest/filters-and-interceptors.html#d0e9818

10.2 Filters

當你想要修改請求或響應引數,如http header。 可以使用過濾器來完成任務。例如,希望新增一個響應頭 X-Powered-By 到每個生成的響應中。可以使用responsefilter來完成新增。Filter分為伺服器端與客戶端filter。

伺服器端的Filter有

ContainerRequestFilter

ContainerResponseFilter

客戶端Filter有

ClientRequestFilter

ClientResponseFilter


10.2.1 伺服器端filter

例10.1演示了一個簡單的 containerresponse filter ,這個filter會將header新增到每個response中。

例10.1 

@Provider
@PreMatching
public class PoweredByResponseFilter implements ContainerResponseFilter{
	public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext)
			throws IOException {
		responseContext.getHeaders().add("X-Powered-By", "Jersey :-)");
	}

}

ResponseFilter必須繼承於ContainerResponseFilter,而且必註冊為Provider,通過@Provider註解定義的Filter。 ResponseFilter 會被執行,即使資源方法沒有執行。Filter()方法有兩個引數,ContainerRequest以及ContainerResponse。可能分別用來讀取請求引數以及寫入響應引數。

下面演示Request filter的使用。

例10.2

@Provider
@PreMatching
public class AuthorizationRequestFilter implements ContainerRequestFilter {

	public void filter(ContainerRequestContext requestContext) throws IOException {
		SecurityContext securityContext = requestContext.getSecurityContext();
		if (securityContext == null || !securityContext.isUserInRole("privileged")) {

			requestContext.abortWith(
					Response.status(Response.Status.UNAUTHORIZED).entity("User cannot access the resource.").build());
		}
	}
}

Request Filter與 Response Filter類似。但filter方法中的引數為ContainerRequestContext.Requestfilter在資源方法執行前執行及在response建立前執行。Request filter能夠操作請求引數,包括Request Header或Request entity。


10.2.1.1Pre-matching 與 post-matching filters


Pre-matching僅處理實際請求的資源方法已經被選擇,filter才會執行。請求匹配是尋找對應資源方法的過程,這些方法基於請求路徑與其它請求引數來執行。Post-matching request filters在當資源方法被選擇後被呼叫,這種filter不會影響資源方法匹配過程。(因為pre-matching可以對請求修改,因此會影響匹配)

將server request filter註解為pre-matching filter.pre-matching filters 是request filters,是在請求匹配開始前執行。因此pre-matching request filters 能夠影響處理請求的方法(能對傳入的物件修改)。


@Provider
@PreMatching
public class PreMatchingFilter implements ContainerRequestFilter{

	public void filter(ContainerRequestContext requestContext) throws IOException {
		if(requestContext.getMethod().equals("GET")){
			 requestContext.setMethod("POST");
		}
	}

}

PreMatchingFilter僅僅是一個簡單的pre-matching filter,來將http get請求方法改為post。當希望將put與post一同處理時,可以通過修改請求方法來完成。接下來處理post請求的方法將會被執行。


10.3 攔截器

Filter主要用來修改request 與 response 引數,比如http headers,URI,或http 請求方法。攔截器主要用來操作實體,操作是通過實體輸入輸出流來完成。例如對請求實體進行編碼。有兩種攔截器,ReaderInterceptor 與WriterInterceptor。 Reader攔截器用來操作輸入實體流(inbound entity streams)。所以,使用reader攔截器,你可以在伺服器端操作請求實體流,在客戶端操作響應實體流。(這個實體是從server response讀取)。Writer 攔截器,writer 攔截器用於將實體寫入”wire”中。在服務端這表示,寫出響應實體。在客戶端表示為傳送到伺服器端的請求寫請求實體。Writer 與 Reader攔截器 在  訊息 readers或writers被執行前執行,其目的是包裝將被用在訊息reader與writer中的實體流。

下面演示writer攔截器,對整個實體進行GZIP壓縮。

public class GZIPWriterInterceptor implements WriterInterceptor {
 
    @Override
    public void aroundWriteTo(WriterInterceptorContext context)
                    throws IOException, WebApplicationException {
        final OutputStream outputStream = context.getOutputStream();
        context.setOutputStream(new GZIPOutputStream(outputStream));
        context.proceed();
    }
}

GZIPReaderInterceptor通過GZIPInputStream包裝原始的輸入流。此後對實體流的讀取或得到壓縮後的流。aroundReadFrom攔截方法必須返回一個實體。實體是從ReaderInterceptorContext的proceed方法中返回。Proceed方法在內部呼叫包裝的攔截器,這個攔截器必須返回一個實體。被在呼叫鏈中的上一個攔截器呼叫的proceed方法會將用message bodyreader,該方法會反序列化實體,並返回結果。如果需要,每次攔截器都能改變實體,但在大多數的情況下,攔截器僅返回從proceed方法中返回的實體。

上文已經提交,攔截器用來操作實體。與WriterInterceptorContext暴漏的方法類似,ReaderInterceptorContext也引入一些用來修改請求或響應屬性的方法。例如,HTTP 頭,URIs,以及HTTP 方法。


10.4 過濾器攔截器的執行順序


接下來看一下過濾器與攔截器的執行上下文。下面的步驟描述了JAX-RS客戶端發出POS請求到伺服器端。伺服器接收到實體,並返回響應。GZIP reader與writer攔截器註冊在客戶端與伺服器端。過濾器也註冊在伺服器端與客戶端,並用來修改請求、響應頭。


1.      客戶端請求呼叫: 在客戶端發起POST請求。

2.      ClientRequestFilters: 客戶端請求過濾器執行,修改請求頭。

3.      Client WriterInterceptor:在客戶端註冊的writer interceptor在MessageBodyWriter執行前執行。它通過GZipOutputStream對實體輸出流進行封裝。

4.      Client MessageBody writer: 客戶端的message body writer被執行,它將實體寫入寫的GZipOutput流中。這個流將資料壓縮,並將資料傳送到”wire”中。

5.      Server:伺服器收到請求。收到的資料實體是壓縮過的資料,如果直接讀,則讀取的是壓縮的資料。

6.      Server pre-matching ContainerRequestFilters: ContainerRequestFilters執行,並可以修改請求,來匹配不同的處理方法。

7.      Server: matching: 資源方法匹配完成。(根據請求找到請求處理方法)

8.      Server: post-matching ContainerRequestFilters:ContainerRequestFilters post matching filters被執行。這包括所有全域性filters(沒有命名繫結)的執行及繫結名稱的filters的執行。

9.      Server ReaderInterceptor:reader 攔截器在伺服器端執行,GZIPReaderInterceptor對輸入流進行包裝,轉換為GZipInputStream並將其新增到上下文中。

10.  Server MessageBodyReader: server message body reader 被執行,並通過GZipInputStream對實體資料解壓縮。這表示,reader將讀取解壓縮後的資料。

11.  伺服器端資源方法被執行:反序列化的實習物件以引數的形式傳遞給資源方法。方法以response實體的形式返回這個實體。

12.  Server ContainerResponseFilters被執行,響應過濾器在伺服器端被執行,並將response headers修改。

13.  Server WriterInterceptor 在伺服器端被執行,並通過GZIPOuptutStream對原始輸出流進行包裝。

14.  Server MessageBodyWriter:message body writer在伺服器端被執行,並將實體序列化,寫入GZIPOutputStream中。GZIPOutputStream會將資料壓縮,隨後壓縮的資料被髮送到客戶端。

15.  客戶端接收到響應:響應包含壓縮的實體資料。

16.  Client ClientResponseFilters:客戶端相應過濾器被執行,並且修改響應頭。

17.  客戶端響應返回。the javax.ws.rs.core.Response從請求呼叫中返回。

18.  客戶端程式碼呼叫response.readEntity():從響應中提取出實體部分。

19.  Client ReaderInterceptor:客戶端reader 攔截器在readEntity被呼叫時執行。攔截器通過GZIPInputStream對實體輸入流封裝。

20.  Client MessageBodyReaders:客戶端訊息體reader被呼叫,並從GZIPInputStream中讀取解壓縮後的資料,並將這些資料反序列化。

21.  客戶端:方法readEntity()返回實體。


需要注意的是在上述的場景中,reader與writer攔截器僅僅當實體存在時才會被呼叫。(當沒有實體流要被寫時,封裝實體流沒有意義)。

message body reader有同樣的行為。攔截器在message body reader/writer之前執行,在實體被讀或寫之前攔截器可以對實體進行包裝。

當攔截器並沒有在messagebodyreader/writers執行前執行,將會出現異常。當對使用內部緩衝區的客戶端response的實體讀取多次,資料僅僅會被攔截一次,然後解碼後的資料被儲存於緩衝區中。


10.5 命名繫結

過濾器與攔截器可以指定名稱。繫結後攔截器或過濾器僅僅對特殊的特定的資源方法執行。如果沒有進行命名保定,那麼過濾器或攔截器被稱為全域性過濾器或攔截器。

可以使用@NameBinding註解,將過濾器或攔截器可以被分配給一個資源方法。這個註解以元註解的形式使用,可以註解其它註解。例子如下:

...
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.zip.GZIPInputStream;
 
import javax.ws.rs.GET;
import javax.ws.rs.NameBinding;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
...
 
 
// @Compress annotation is the name binding annotation
@NameBinding
@Retention(RetentionPolicy.RUNTIME)
public @interface Compress {}
 
 
@Path("helloworld")
public class HelloWorldResource {
 
    @GET
    @Produces("text/plain")
    public String getHello() {
        return "Hello World!";
    }
 
    @GET
    @Path("too-much-data")
    @Compress
    public String getVeryLongString() {
        String str = ... // very long string
        return str;
    }
}
 
// interceptor will be executed only when resource methods
// annotated with @Compress annotation will be executed
@Compress
public class GZIPWriterInterceptor implements WriterInterceptor {
    @Override
    public void aroundWriteTo(WriterInterceptorContext context)
                    throws IOException, WebApplicationException {
        final OutputStream outputStream = context.getOutputStream();
        context.setOutputStream(new GZIPOutputStream(outputStream));
        context.proceed();
    }
}

上述程式碼定義了註解@Compress,並用在方法getVeryLongString()上,以及GZIPWriterInterceptor攔截器上。攔截器僅在被註解的方法執行時才執行。應用程式中可以有多個命名繫結註解。當provider(攔截器與過濾器)通過命名註解註解後,它僅僅在標註有對應註解的方法被執行後才執行。


10.6 動態繫結

動態繫結是一種以動態的方法將攔截器與過濾器與處理方法關聯的方式。10.5中的繫結使用了靜態方法,當改變繫結時,需要重新編譯程式碼。通過動態繫結,你可以在程式初始化時實現繫結的程式碼。

...
import javax.ws.rs.core.FeatureContext;
import javax.ws.rs.container.DynamicFeature;
...
 
@Path("helloworld")
public class HelloWorldResource {
 
    @GET
    @Produces("text/plain")
    public String getHello() {
        return "Hello World!";
    }
 
    @GET
    @Path("too-much-data")
    public String getVeryLongString() {
        String str = ... // very long string
        return str;
    }
}
 
// This dynamic binding provider registers GZIPWriterInterceptor
// only for HelloWorldResource and methods that contain
// "VeryLongString" in their name. It will be executed during
// application initialization phase.
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);
        }
    }
}

10.7 優先順序

如果有多個攔截器或過濾器,那麼可以通過優先順序指定執行的順序。通過註解@Priority可以定義優先順序。註解接受一個整數作為優先順序。過濾器與攔截器執行順序是按優先順序(升序)順序執行。因此@Priority(1000)或在@Priority(2000)之前執行。而優先順序用在響應處理時候,順序正好相反。@Priority(2000)將在@Priority(1000)之前執行。


...
import javax.annotation.Priority;
import javax.ws.rs.Priorities;
...
 
@Priority(2000)
public class ResponseFilter implements ContainerResponseFilter {
 
    @Override
    public void filter(ContainerRequestContext requestContext,
                    ContainerResponseContext responseContext)
                    throws IOException {
 
        responseContext.getHeaders().add("X-Powered-By", "Jersey :-)");
    }
}