Jersey2.x對REST請求處理流程的分析

Jersey2.x對REST請求處理流程的分析

一個REST請求,始於一個RESTful Web Service資源的地址,終於一個可接受的對資源的表述(比如JSON)。

因此,流程分析的關鍵點有2個:

  • 將請求地址對應到資源類的相應方法上,並觸發該方法。
  • 將返回值轉換成請求所需要的表述,並返回給客戶端。

我們使用Eclipse的斷點調試服務器端(代碼對應本例https://github.com/feuyeux/jax-rs2-guide/),使用cURL腳本作爲客戶端。

1 cURL測試

curl -H "Accept:application/json" http://localhost:8080/simple-service-webapp-spring-jpa-jquery/webapi/books/book?id=1

該腳本發送了一個REST GET請求,地址是:

http://localhost:8080/simple-service-webapp-spring-jpa-jquery/webapi/books/book?id=1

對應的服務器端方法是:

com.example.resource.BookResource.getBookByQuery(Integer)

要求返回的類型是JSON:

Accept:application/json

2 Jersey2.x流程

2.1 請求地址到REST方法

2.1.1 ServletContainer.service

ServletContainer.service(HttpServletRequest, HttpServletResponse) line: 248

  • baseUri http://localhost:8080/simple-service-webapp-spring-jpa-jquery/webapi/
  • requestUri http://localhost:8080/simple-service-webapp-spring-jpa-jquery/webapi/books/book?id=1

ServletContainer是HttpServlet的子類,位於Jersey容器包(.m2\repository\org\glassfish\jersey\containers\jersey-container-servlet-core\2.2\jersey-container-servlet-core-2.2.jar)。我們知道,Servlet的service方法是請求的入口,作爲子類,HTTP任何方法的請求都要先經過該類的service方法。

斷點堆棧中,重要的兩個變量是請求地址信息的baseUri和requestUri。

dependencies 

Jersey包依賴關係圖

ServletContainer.service(URI, URI, HttpServletRequest, HttpServletResponse) line: 372

  • requestContext.header {user-agent=[curl/7.26.0], host=[localhost:8080], accept=[application/json]}

在容器層級,請求上下文變量除了包括請求地址信息外,還包括請求頭信息。這裏我們關注accept信息。

2.1.2 對應方法

ApplicationHandler.handle(ContainerRequest) line: 982

ServerRuntime.process(ContainerRequest) line: 211 final Runnable task

new Runnable() {
 public void run() {
  final ContainerRequest data = Stages.process(request, requestProcessingRoot, endpointRef);
  final Endpoint endpoint = endpointRef.get();
  • endpoint ResourceMethodInvoker (id=2553)
    public com.example.domain.Book com.example.resource.BookResource.getBookByQuery(java.lang.Integer)

從上面的代碼片段,可以看到請求被對應到了一個Endpoint對象,該對象是一個資源方法Invoker,從斷點堆棧中可以看到,其內容就是我們期待的那個方法,此後的invoke將調用這個對應REST請求的處理方法。

2.1.3 調用方法

ResourceMethodInvoker.invoke(ContainerRequest, Object) line: 353
dispatcher.dispatch(resource, requestContext);

JavaResourceMethodDispatcherProvider$TypeOutInvoker(AbstractJavaResourceMethodDispatcher).invoke(Object, Object...) line: 158
invokeMethodAction.run();

ResourceMethodInvocationHandlerFactory$1.invoke(Object, Method, Object[]) line: 81

從堆棧中可以輕鬆定位這個方法:**BookResource.getBookByQuery(Integer) line: 71**

到此,請求地址到方法調用的流程就結束了。在處理業務邏輯方法BookResource.getBookByQuery後,Jersey將開始處理響應信息。

2.2 REST方法到表述

JavaResourceMethodDispatcherProvider$TypeOutInvoker.doDispatch(Object, Request) line: 198

  • o Book (id=2686)
  • Response response = Response.ok().entity(o).build();

REST請求的返回信息應包括表述信息和HTTP響應代碼(通常一個成功的請求應該得到200 OK)。這就是上面代碼所做的事情。在設置好response的entity後,需要將該entity對象轉化成請求所接受的表述,流程如下。

2.2.1 寫向response

ServerRuntime$Responder.process(ContainerResponse) line: 351ServerRuntime$Responder.writeResponse(ContainerResponse) line: 486

  • entity Book (id=7076)
  • executor WriterInterceptorExecutor (id=7125)

從上面的堆棧中可以看到,這個階段重點的兩個對象是返回對象和寫處理對象,就是使用後者將前者恰當地寫入response中。

2.2.2 JSON表述

REST的表述不侷限於JSON,這裏我們以JSON作爲常用的表述類型爲例,來討論Jersey的表述處理。 Jersey對JSON的支持有4種方式,在第3章會有詳細的講解,本例使用的是EclipseLink項目的MOXy。其底層依賴是JPA的實現之一(另一個著名的JPA實現是JBoss項目下大名鼎鼎的Hibernate)。

    <dependency>
        <groupId>org.glassfish.jersey.media</groupId>
        <artifactId>jersey-media-moxy</artifactId>
        <version>${jersey.version}</version>
    </dependency>

moxy

MOXy依賴關係圖

在下面的堆棧中,可以看到JAXB的身影,因爲MOXy同樣實現了JAXB,其內部實現是以XML的Marshal方式處理對象(OXM:Object-XML-Mapping),然後轉化爲JSON數據(XML-2-JSON)。

JsonWithPaddingInterceptor.aroundWriteTo(WriterInterceptorContext) line: 91

WriterInterceptorExecutor$TerminalWriterInterceptor.aroundWriteTo(WriterInterceptorContext) line: 188

ConfigurableMoxyJsonProvider(MOXyJsonProvider).**writeTo**(Object, Class<?>, Type, Annotation[], MediaType, MultivaluedMap<string,object style="margin: 0px; padding: 0px;">, OutputStream) line: 782

  • object Book (id=7076)
  • type Class (com.example.domain.Book) (id=568)
  • genericType Class (com.example.domain.Book) (id=568)
  • annotations Annotation3
  • mediaType AcceptableMediaType (id=7120)
  • httpHeaders StringKeyIgnoreCaseMultivaluedMap (id=7121)
  • entityStream CommittingOutputStream (id=7124)

開始從對象轉化爲JSON:

JAXBMarshaller.marshal(Object, OutputStream) line: 395

  • object Book (id=7076)
  • outputStream CommittingOutputStream (id=7124)

XMLMarshaller(XMLMarshaller<abstract_session,context,descriptor,media_type,namespace_prefix_mapper,object_builder style="margin: 0px; padding: 0px;">).marshal(Object, OutputStream, ABSTRACT_SESSION, DESCRIPTOR) line: 852

XMLMarshaller(XMLMarshaller<abstract_session,context,descriptor,media_type,namespace_prefix_mapper,object_builder style="margin: 0px; padding: 0px;">).marshal(Object, Writer, ABSTRACT_SESSION, DESCRIPTOR) line: 1031

  • object Book (id=7305)
  • writer OutputStreamWriter (id=7310)
    -> BufferedWriter (id=7333)

XMLMarshaller(XMLMarshaller<abstract_session,context,descriptor,media_type,namespace_prefix_mapper,object_builder style="margin: 0px; padding: 0px;">).marshal(Object, MarshalRecord, ABSTRACT_SESSION, DESCRIPTOR, boolean) line: 583

  • object Book (id=7305)
  • marshalRecord JSONWriterRecord (id=7342)
  • session DatabaseSessionImpl (id=7351)
  • descriptor XMLDescriptor (id=7353)
  • isXMLRoot false

幾經輾轉,寫入對象變成了MarshalRecord。

JSONWriterRecord.startDocument(String, String) line: 171

TreeObjectBuilder.marshalAttributes(MarshalRecord, Object, CoreAbstractSession) line: 122

XPathObjectBuilder.marshalAttributes(MarshalRecord, Object, CoreAbstractSession) line: 552

這裏是可以想見的流程...

ServerRuntime$Responder.processResponse(ContainerResponse) line: 362

最後是將response返回並釋放。流程到此結束。

 

本文收錄在我的新書《Java Restful Web Service使用指南》中

https://code.csdn.net/delva/jax-rs2-guide/tree/master/sample/2/5simple-service-webapp-spring-jpa-jquery/request_flow.md

 

請留意寫作動態

https://code.csdn.net/delva/jax-rs2-guide/

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章