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。
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依賴關係圖
在下面的堆棧中,可以看到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/