Play!Framework 學習筆記(二):ActionInvoker源碼分析

再往下看,我被雷到了= =# 第一遍看沒反應過來,因爲我見識較淺,也從來沒這麼寫過代碼,之前也沒看過這樣寫的。就是這句:

Java代碼  收藏代碼
  1. throw new RenderTemplate(template, templateBinding.data);  

這裏用RenderTemplate的構造方法new了一個RenderTemplate對象,

然後.......拋出去了。

看看這裏拋出去的是什麼,先進去看看RenderTemplate類的實現:

Java代碼  收藏代碼
  1. public class RenderTemplate extends Result {  

 再看看Result

Java代碼  收藏代碼
  1. public abstract class Result extends RuntimeException {  

 原來RenderTemplate是個RuntimeException

@
關於RuntimeException 
http://java.sun.com/docs/books/tutorial/essential/exceptions/runtime.html 
提到RuntimeException,我們最熟悉的可能就是NullPointerException,由於程序編寫疏漏,造成訪問空指針時,就會拋出此異常。 
我們寫個最簡單的代碼就是這樣 
public class TestString { 
public static void main(String[] args){ 
testNull(null); 

public static void testNull(String a){ 
a.charAt(0); 


main方法裏對testNull傳了個null,但是charAt方法需要String obj,此時卻是null,觸發NullPointerException退出程序。 
在看看console的信息: 
Exception in thread "main" java.lang.NullPointerException 
at com.langtest.TestString.testNull(TestString.java:9) 
at com.langtest.TestString.main(TestString.java:6) 
main 線程引發空指針異常,程序到main後也沒對此異常處理的邏輯,導致程序退出,並在控制檯打印出錯信息。 
throw之後的異常對象沿着方法棧往回扔(即調用此方法的方法),如果一直沒有截獲異常,則一直扔到棧底。

 

既然是個異常,下一步則是拋向上級調用者,往下走,我們找這個“不是異常的異常”是在何處被截獲的。。。 (對比Java官網那篇對運行時異常小心翼翼的陳述,這種做法簡直有點#_#,要麼是因爲我太菜,不能理解這麼用的高明之處吧)

 

Debug F6後,程序轉至play.mvc.ActionInvoker的invoke方法中的catch語句

Java代碼  收藏代碼
  1. Result actionResult = null;  
  2. ControllerInstrumentation.initActionCall();  
  3. try {  
  4.     Java.invokeStatic(actionMethod, getActionMethodArgs(actionMethod));  
  5. catch (InvocationTargetException ex) {  
  6.     // It's a Result ? (expected)  
  7.     if (ex.getTargetException() instanceof Result) {  
  8.         actionResult = (Result) ex.getTargetException();  
  9.     } else {  
  10.         // @Catch  
  11.         Object[] args = new Object[]{ex.getTargetException()};  
  12.         List<Method> catches = Java.findAllAnnotatedMethods(Controller.getControllerClass(), Catch.class);  
  13.         Collections.sort(catches, new Comparator<Method>() {  

 

try... catch塊中:

try塊:用JAVA的反射機制invoke靜態方法,這裏其實就是invoke了我們在控制器中寫的index方法。

 

 

@
如果不理解反射,一定得去先弄明白,任何框架的源碼及要實現一些共同的對象實例化或方法調用的通用接口,基本都得用到反射= =# 
自編最土最俗的方式理解反射幫助同學理解反射(可能不一定準確): 
反射說:“我是個萬能的對象生成器和方法調用器,只要你給我類名,方法名以及相應的參數,我就能幫你new出對象或者調用相應的方法” 
不過任何事情都是雙刃劍,反射的能力過於強大,可以任意的生成和操作對象,所以就賦予程序員Do evil的可能. 
而JAVA本身的設計是個儘量安全的語言,沒有指針運算,虛擬機幫着整理內存,異常機制讓做出健壯性的程序變得簡化不少,所以我覺得反射機制對於JAVA的總體設計思想來說,還是不小的challenge。 
此處的invokeStatic方法的兩個參數,用debug工具的watch功能可以看到 
actionMethod就是通過反射被invoke的方法。

 

catch塊:攔截InvocationTargetException,這個exception是當通過反射的方式invoke的方法throw異常時,反射機制會觸發這個異常,並將上一級throw出的異常存爲這個異常的taget變量。

本例的過程是這樣的,Play框架通過反射的方式invoke 控制器中的index方法(Application.index()),然後進入render(),在render方法裏調用renderTemplate方法,在此方法將RenderTemplate這個異常(再次汗)拋出,反射機制發現有異常拋出,隨後拋出InvocationTargetException異常,並將RenderTemplate存入InvocationTargetException的target變量..Play在使用反射invoke方法處catch了此異常,然後把target引用的RenderTemplate取出,則得到了render完成的模板。

 

 

from Java Api
InvocationTargetException is a checked exception that wraps an exception thrown by an invoked method or constructor.
@
此處雖然搞明白了這段詭異代碼的用途,其實也只是個學習的開始,我覺得學習的入門是讀懂,能應用是其次,能對某個事物有批判性思維(知道好壞,知道適用何處纔是更高層次)。 
無論何時,我們都得有追本溯源的精神,下面是幾個疑問: 
①爲什麼框架這麼設計 
②如果可以,自己想一個替代的實現方式,對比此方式看看爲何要用這麼怪的設計

 

 

ActionInvoker源碼分析

 

既然現在Debug走到ActionInvoker,不妨看看這個類:

由類上的註釋:

 

Invoke an action after an HTTP request.

 

這個類是根據Http request Invoke相應的action。

這個類沒有成員變量和函數,只有三個共有的靜態方法,這三個方法分別是(用附加註釋的方法解釋):

Java代碼  收藏代碼
  1. public class ActionInvoker {  
  2.         //響應請求的主函數,其實ActionInvoker這個類主要用途就是放置這個方法,因此這個類也同樣也不具備面向對象特性的類,這個類注重的是響應HTTP請求的邏輯  
  3.         public static void invoke(Http.Request request, Http.Response response) {  
  4.         //通過傳入的action(ie:Application.index),得到對應的method,以便反射時invoke使用  
  5.         public static Object[] getActionMethod(String fullAction) {  
  6.         //從method中取出方法的參數,這兩個get方法都是爲反射調用服務的。  
  7.         public static Object[] getActionMethodArgs(Method method) throws Exception {  
  8. }  

 

可見invoke是Play框架的運行的核心控件(說是核心是因爲web框架的主要職責就是完成處理HTTP請求的過程)

爲了瞭解Play的核心運行機制,我們斷開debug線程,在invoke方法設上斷點,重新跑Debug

進入方法,傳入該方法的兩個參數是由上一層調用者HttpHandler的內部類MinaInvocation的execute方法傳入的。由於HttpHandler裏做的工作比ActionInvoker更加基礎(Mina應用服務器下的http協議處理及session管理),我們到後面再研究。

 

 

Java代碼  收藏代碼
  1. public static void invoke(Http.Request request, Http.Response response) {      
  2.    Monitor monitor = null;  
  3.         try {  
  4.             if (!Play.started) {  
  5.                 return;  
  6.             }  
  7.   
  8.             Http.Request.current.set(request);  
  9.             Http.Response.current.set(response);  
  10.   
  11.             Scope.Params.current.set(new Scope.Params());  
  12.             Scope.RenderArgs.current.set(new Scope.RenderArgs());  
  13.             Scope.Session.current.set(Scope.Session.restore());  
  14.             Scope.Flash.current.set(Scope.Flash.restore());  
  15. ......  
  16. }  

 

先new一個Monitor ,用來監控

然後判斷Play是否啓動

隨後的是一系列xxx.xxx.current.set方法:

 

這裏的current變量都是ThreadLocal

 

Java代碼  收藏代碼
  1. public static ThreadLocal<Request> current = new ThreadLocal<Request>();  

    @

Java代碼  收藏代碼
  1. //對於Java開發,ThreadLocal是必須要了解的概念。   
  2. //ThreadLocal雖然是個對象,但是ThreadLocal的set方法存的東西並不是放在ThreadLocal對象裏   
  3. /**  
  4. * Sets the current thread's copy of this thread-local variable  
  5. * to the specified value. Many applications will have no need for  
  6. * this functionality, relying solely on the {@link #initialValue}  
  7. * method to set the values of thread-locals.  
  8.  
  9. * @param value the value to be stored in the current threads' copy of  
  10. * this thread-local.  
  11. */   
  12. public void set(T value) {   
  13. Thread t = Thread.currentThread();   
  14. ThreadLocalMap map = getMap(t);   
  15. if (map != null)   
  16. map.set(this, value);   
  17. else   
  18. createMap(t, value);   
  19. }   
  20. //由上可見,set方法首先取得當前的Thread對象,然後取得該線程的ThreadLocalMap ,如果map不爲空,則寫入map,以當前的ThreadLocal對象爲key,將傳入的value存入map。   
  21. //這裏也只是個引子,沒概念的可能很難理解清楚,畢竟ThreadLocal也不是我這麼三言兩語能說清的,建議同學多谷哥一下,多看多用多體會。   

  將Request,Response以及Scope的引用放入當前線程後,實際上是完成了線程的初始化過程。

 

Java代碼  收藏代碼
  1. // 1. Route and resolve format if not already done  
  2. if (request.action == null) {  
  3.     for (PlayPlugin plugin : Play.plugins) {  
  4.         plugin.routeRequest(request);  
  5.     }  
  6.     Router.route(request);  
  7. }  
  8. request.resolveFormat();  

 

Router.route(request); 根據請求的URL找到router中相應的action,並將action的名字賦值給request.action.

request.resolveFormat();此時request中format爲html,如果request中format爲null,則根據http頭來取得相應的format

 

往下走:

Java代碼  收藏代碼
  1. // 2. Find the action method  
  2.  Method actionMethod = null;  
  3.  try {  
  4.      Object[] ca = getActionMethod(request.action);  
  5.      actionMethod = (Method) ca[1];  
  6.      request.controller = ((Class<?>) ca[0]).getName().substring(12);  
  7.      request.actionMethod = actionMethod.getName();  
  8.      request.action = request.controller + "." + request.actionMethod;  
  9.      request.invokedMethod = actionMethod;  
  10.  } catch (ActionNotFoundException e) {  
  11.      throw new NotFound(String.format("%s action not found", e.getAction()));  
  12.  }  

 

  聲明一個Method變量,供後面反射Invoke.

getActionMethod(request.action) 前面提到過了,通過request.action這個String得到存有application.index()方法相應Method對象的obj數組

得到Method對象(ca[1],ca[0]存放的是對應controllers.Application的Class對象)後,將request對象中與Action相關的成員變量賦值

此處:request.controller值爲Application,request.actionMethod值爲index,後面兩個變量,一個是照前兩個拼出來的action,另一個傳入的是Method對象

 

繼續:下面的代碼爲合併action用到的參數:

Java代碼  收藏代碼
  1. // 3. Prepare request params  
  2. Scope.Params.current().__mergeWith(request.routeArgs);  
  3. // add parameters from the URI query string   
  4. Scope.Params.current()._mergeWith(UrlEncodedParser.parseQueryString(new ByteArrayInputStream(request.querystring.getBytes("utf-8"))));  
  5. Lang.resolvefrom(request);  

 
routeArgs是在route中附加的http參數

Java代碼  收藏代碼
  1. /** 
  2.  * Additinal HTTP params extracted from route 
  3.  */  
  4. public Map<String, String> routeArgs;  

 

除此之外還將QueryString中的參數也合並進來

後面的Lang.resolvefrom(request)沒仔細看實現,看Lang的包名中與i18n有關,這部分等以後專門看國際化的實現單獨寫吧(繼續欠賬)

 

下面的代碼,又看到雷人的片段了...

Java代碼  收藏代碼
  1. // 4. Easy debugging ...  
  2. if (Play.mode == Play.Mode.DEV) {  
  3.     Controller.class.getDeclaredField("params").set(null, Scope.Params.current());  
  4.     Controller.class.getDeclaredField("request").set(null, Http.Request.current());  
  5.     Controller.class.getDeclaredField("response").set(null, Http.Response.current());  
  6.     Controller.class.getDeclaredField("session").set(null, Scope.Session.current());  
  7.     Controller.class.getDeclaredField("flash").set(null, Scope.Flash.current());  
  8.     Controller.class.getDeclaredField("renderArgs").set(null, Scope.RenderArgs.current());  
  9.     Controller.class.getDeclaredField("validation").set(null, Java.invokeStatic(Validation.class"current"));  
  10. }  

 

!!!!!!

Controller.class.getDeclaredField("xxx").set(null,xxx);

這裏Play用反射的方式將Controller中受保護的靜態變量強行賦值!!!

 

@
如果之前將程序處理結果實現爲運行時異常並在產生結果後直接拋出可以暫時理解爲一種策略,那這種做法簡直就是簡單粗暴了,和我們讀的各種經典書籍中的教誨大相徑庭。 

而且還是自己設置了變量的訪問權限後又自己暴力破解賦值...... 

不過作者在註釋里加了句// 4. Easy debugging ...,可能是對此做法無奈的解釋吧。 

讓我不禁又想起那句"Java中的關鍵字只是給編譯器看的"... 

不過仔細想想,這可能也是開源項目的特色之一,如果在企業裏寫這種代碼直接破壞框架,不知道老闆的臉會怎麼黑- -|||.. 

這裏廢話這麼多,是因爲我們學生在校時往往喜歡比較另類的代碼,所以學弟學妹需要醒目一下: 
仔細想想 
我們初涉編程是不是很佩服算出i=3,k=(i++)+(++i)+(i++)的同學 
我們是不是佩服過能看懂16層嵌套並且變量名沒任何意義的if else 
我們是不是對於能寫出令人頭暈的指針運算的同學無比崇敬過。 

自己玩玩可以,但是以後工作,一份代碼的生命期有可能是伴隨着企業的生命期。 
寫一些另類代碼有很多惡果,最明顯的是難以維護。 
至於類似修改私有變量的危險行爲,雖然用起來比較cool,但是對於個人學習來說,不提倡,儘量不要給自己灌輸用非正常途徑解決問題的思想。 

不過這裏,沒說play不好的意思,只是對於咱們來說,學生階段對語言的理解力掌控力還太差,需要不斷深入學習,假以時日,能有技術方面的批判性思維,能把這些java特性用的恰到好處當然是好事。
 

 

 

又跑題了,回到主題....這部分是判斷play的模式(play有兩種運行模式DEV和實際運行模式,在config裏文件配置切換),在開發模式下,直接將request,response和scope等賦值給Cotroller類相應的靜態變量

可能便於實際invoke控制器時訪問這些值.

 

#遍歷各個PlugIn看在action invoke前做些動作

Java代碼  收藏代碼
  1. ControllerInstrumentation.stopActionCall();  
  2. for (PlayPlugin plugin : Play.plugins) {  
  3.     plugin.beforeActionInvocation(actionMethod);  
  4. }  

 ControllerInstrumentation這個類的作用是對allow這個標誌位進行操作,allow是個ThreadLocal<Boolean>,對其set值則將其引用存入當前Thread內,換句話說,其實是對Thread做了標記

Java代碼  收藏代碼
  1. public static class ControllerInstrumentation {  
  2.        
  3.      public static boolean isActionCallAllowed() {  
  4.          return allow.get();  
  5.      }  
  6.        
  7.      public static void initActionCall() {  
  8.          allow.set(true);  
  9.      }  
  10.        
  11.      public static void stopActionCall() {  
  12.          allow.set(false);  
  13.      }  
  14.        
  15.      static ThreadLocal<Boolean> allow = new ThreadLocal<Boolean>();         
  16.        
  17.  }  

 
beforeActionInvocation方法則是在action前Plugin做的事情,這裏我看了一下都是空的實現.

 

#打開monitor

Java代碼  收藏代碼
  1. // Monitoring  
  2. monitor = MonitorFactory.start(request.action + "()");  

 

#找到標記@Before Annotation的方法,並先於action invoke執行

Java代碼  收藏代碼
  1. // 5. Invoke the action  
  2. try {  
  3.     // @Before  
  4.     List<Method> befores = Java.findAllAnnotatedMethods(Controller.getControllerClass(), Before.class);  
  5.     Collections.sort(befores, new Comparator<Method>() {  
  6.   
  7.         public int compare(Method m1, Method m2) {  
  8.             Before before1 = m1.getAnnotation(Before.class);  
  9.             Before before2 = m2.getAnnotation(Before.class);  
  10.             return before1.priority() - before2.priority();  
  11.         }  
  12.     });  

 

Controller.getControllerClass()方法返回class controllers.Application

Java.findAllAnnotatedMethods()找到所有帶有@Before Annotation的方法

再根據各個方法的優先級,來對befores中的Method排序

此處實現比較器用了匿名內部類,按Before的priority進行排序

 

Java代碼  收藏代碼
  1. @Retention(RetentionPolicy.RUNTIME)  
  2. @Target(ElementType.METHOD)  
  3. public @interface Before {  
  4.       
  5.     /** 
  6.      * Does not intercept these actions 
  7.      */  
  8.     String[] unless() default {};  
  9.       
  10.     /** 
  11.      * Interceptor priority (0 is high priority) 
  12.      */  
  13.     int priority() default 0;  
  14.       
  15. }  

 

  Annotation Before除了成員變量priority外,還有一個String數組變量unless,存放的是action的名字,表示不攔截這些action。

看看這部分的實現

Java代碼  收藏代碼
  1. ControllerInstrumentation.stopActionCall();  
  2.    //遍歷包含Before Annotation的方法  
  3. for (Method before : befores) {  
  4.        //取出當前Before action的unless數組  
  5.     String[] unless = before.getAnnotation(Before.class).unless();  
  6.        //設置標誌位  
  7.     boolean skip = false;  
  8.        //遍歷unless數組  
  9.     for (String un : unless) {  
  10.         if (!un.contains(".")) {  
  11.             un = before.getDeclaringClass().getName().substring(12) + "." + un;  
  12.         }  
  13.           //如果unless與當前被調用的action名字相同,標誌位skip設爲true,退出循環  
  14.         if (un.equals(request.action)) {  
  15.             skip = true;  
  16.             break;  
  17.         }  
  18.     }  
  19.       //如果skip爲false,調用before方法  
  20.     if (!skip) {  
  21.            //加個保護,判斷被調用方法是否爲靜態,因爲下面用到得是invokeStatic..  
  22.         if (Modifier.isStatic(before.getModifiers())) {  
  23.             before.setAccessible(true);  
  24.             Java.invokeStatic(before, getActionMethodArgs(before));  
  25.         }  
  26.     }  
  27. }  

 

 通過Before攔截器後,再往下就是我們前面看到的實際執行Action的地方:

Java代碼  收藏代碼
  1. //聲明一個Result變量用來保存方法調用的結構  
  2. Result actionResult = null;  
  3.       //與之前stopActionCall()相反,這裏調用initActionCall()將allow設爲true,意思是允許此線程invoke方法  
  4.       ControllerInstrumentation.initActionCall();  
  5.       try {  
  6.           //invoke action  
  7.           Java.invokeStatic(actionMethod, getActionMethodArgs(actionMethod));  
  8.       } catch (InvocationTargetException ex) {  
  9.           // It's a Result ? (expected)    
  10.           if (ex.getTargetException() instanceof Result) {  
  11.               //得到調用action後返回的Result  
  12.               actionResult = (Result) ex.getTargetException();  
  13.               //else部分本例未涉及,先跳過不管  
  14.           } else {  
  15.           .....  

 

執行完action,下面的代碼部分是After攔截器,和Before基本一致,不贅述。

隨後將monitor關閉

 

之後...繼續將返回結果往上扔。

Java代碼  收藏代碼
  1. // Ok, rethrow the original action result  
  2. if (actionResult != null) {  
  3.     throw actionResult;  
  4. }  
  5.   
  6. throw new NoResult();  
Java代碼  收藏代碼
  1. catch (InvocationTargetException ex) {  
  2.                 // It's a Result ? (expected)  
  3.                 if (ex.getTargetException() instanceof Result) {  
  4.                     throw (Result) ex.getTargetException();  
  5.                 }  
  6.                 // Rethrow the enclosed exception  
  7.                 if (ex.getTargetException() instanceof PlayException) {  
  8.                     throw (PlayException) ex.getTargetException();  
  9.                 }  
  10.                 StackTraceElement element = PlayException.getInterestingStrackTraceElement(ex.getTargetException());  
  11.                 if (element != null) {  
  12.                     throw new JavaExecutionException(Play.classes.getApplicationClass(element.getClassName()), element.getLineNumber(), ex.getTargetException());  
  13.                 }  
  14.                 throw new JavaExecutionException(Http.Request.current().action, ex);  
  15.             }  

 一直扔到invoke方法的第一個try..catch塊

Java代碼  收藏代碼
  1. public static void invoke(Http.Request request, Http.Response response) {  
  2.      Monitor monitor = null;  
  3.      try {  
  4.          .......  
  5.      }catch (Result result) {  
  6.          //遍歷執行plugin的onActionInvocationResult()方法,對結果進行處理  
  7.          for (PlayPlugin plugin : Play.plugins) {  
  8.              plugin.onActionInvocationResult(result);  
  9.          }  
  10.   
  11.          // Ok there is a result to apply  
  12.          // Save session & flash scope now  
  13.   
  14.          Scope.Session.current().save();  
  15.          Scope.Flash.current().save();  
  16.          //相應結果的apply方法,此處result實際是RenderTemplate對象,它的apply方法最終的HTML輸出  
  17.          result.apply(request, response);  
  18.   
  19.          //這裏可見Plugin的功能是非常靈活的,因爲幾乎在action生命期的每階段都出現,其實到後面可以發現PlugIn幾乎隨處可見,否則怎麼能叫做框架的插件呢= =#  
  20.          for (PlayPlugin plugin : Play.plugins) {  
  21.              plugin.afterActionInvocation();  
  22.          }  

 

 最後看看RenderTemplate類

Java代碼  收藏代碼
  1. public class RenderTemplate extends Result {  
  2.       
  3.     private Template template;  
  4.     private String content;  
  5.     Map<String,Object> args;  
  6.       
  7.     public RenderTemplate(Template template, Map<String,Object> args) {  
  8.         this.template = template;  
  9.         this.args = args;  
  10.         this.content = template.render(args);  
  11.     }  
  12.   
  13.     //apply方法是在invoke方法截獲Result後,確認其是需要的返回結果後,調用的結果最終執行代碼  
  14.     public void apply(Request request, Response response) {  
  15.         try {  
  16.             setContentTypeIfNotSet(response, MimeTypes.getContentType(template.name, "text/plain"));  
  17.             response.out.write(content.getBytes("utf-8"));  
  18.         } catch(Exception e) {  
  19.             throw new UnexpectedException(e);  
  20.         }  
  21.     }  
  22.   
  23. }  

 

   執行完結果代碼後,來到Invoke方法的結尾處,仍處於catch塊,即找到@final的方法並執行。

Java代碼  收藏代碼
  1. // @Finally  
  2.    //這個if判斷不知道有什麼意義,前面在get action的時候,就是找Application(Controller Class)的action方法,此處怎麼會得到null呢,等以後理解加深再解釋吧。  
  3.    if (Controller.getControllerClass() != null) {  
  4.        try {  
  5.            List<Method> allFinally = Java.findAllAnnotatedMethods(Controller.getControllerClass(), Finally.class);  
  6.            //後面略,與@before和@after同  
  7.            }  

 Controller.getControllerClass()這個方法,涉及到Play的classloader,大概看了一下,比較複雜,等以後專門研究。

 不過其中發現一些比較核心的與play熱加載功能相關的代碼,如下:

Java代碼  收藏代碼
  1. byte[] bc = BytecodeCache.getBytecode(name, applicationClass.javaSource);  
  2.                if (bc != null) {  
  3.                    applicationClass.enhancedByteCode = bc;  
  4.                    applicationClass.javaClass = defineClass(applicationClass.name, applicationClass.enhancedByteCode, 0, applicationClass.enhancedByteCode.length, protectionDomain);  
  5.                    resolveClass(applicationClass.javaClass);  
  6.                    Logger.trace("%sms to load class %s from cache", System.currentTimeMillis() - start, name);  
  7.                    return applicationClass.javaClass;  
  8.                }  

 

這裏大概能看出,play可以直接通過讀java源代碼來動態的生成Java class.這應該與Play修改代碼不需編譯就能運行有關。

 

小結:到此處,從兩個層次學習了Play框架的中處理和響應請求的模塊。

最裏面一層是Controller層,就是Application,這裏放置的是Request最終invoke的action

往外一層是ActionInvoker,負責通過Http Request來判斷需要調用的action,並執行調用,此外,還對action起攔截器作用分別在action的生命期的幾個階段Before,After和Finally階段進行攔截並執行有相應Annotation的方法。除了上述兩個作用,ActionInvoker還負責執行PlugIn。可以看出ActionInvoker的職責是控制action。

由此容易想到,ActionInvoker外面應該還有一層,負責實際獲取客戶端的HTTP Request,並轉給ActionInvoker,是的,這個類就是HttpHandler,在下一篇我會詳細分析。

 畫圖表示從客戶端的Request進入Play到Response返回並跳出Play的過程

 

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