您好,登錄后才能下訂單哦!
本篇內容介紹了“怎么進行ActionInvoker源碼分析”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
throw new RenderTemplate(template, templateBinding.data);
這里用RenderTemplate的構造方法new了一個RenderTemplate對象,
然后.......拋出去了。
看看這里拋出去的是什么,先進去看看RenderTemplate類的實現:
public class RenderTemplate extends Result {
再看看Result
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語句。
Result actionResult = null; ControllerInstrumentation.initActionCall(); try { Java.invokeStatic(actionMethod, getActionMethodArgs(actionMethod)); } catch (InvocationTargetException ex) { // It's a Result ? (expected) if (ex.getTargetException() instanceof Result) { actionResult = (Result) ex.getTargetException(); } else { // @Catch Object[] args = new Object[]{ex.getTargetException()}; List<Method> catches = Java.findAllAnnotatedMethods(Controller.getControllerClass(), Catch.class); 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。
這個類沒有成員變量和函數,只有三個共有的靜態方法,這三個方法分別是(用附加注釋的方法解釋):
public class ActionInvoker { //響應請求的主函數,其實ActionInvoker這個類主要用途就是放置這個方法,因此這個類也同樣也不具備面向對象特性的類,這個類注重的是響應HTTP請求的邏輯 public static void invoke(Http.Request request, Http.Response response) { //通過傳入的action(ie:Application.index),得到對應的method,以便反射時invoke使用 public static Object[] getActionMethod(String fullAction) { //從method中取出方法的參數,這兩個get方法都是為反射調用服務的。 public static Object[] getActionMethodArgs(Method method) throws Exception { }
可見invoke是Play框架的運行的核心控件(說是核心是因為web框架的主要職責就是完成處理HTTP請求的過程)。
為了了解Play的核心運行機制,我們斷開debug線程,在invoke方法設上斷點,重新跑Debug。
進入方法,傳入該方法的兩個參數是由上一層調用者HttpHandler的內部類MinaInvocation的execute方法傳入的。由于HttpHandler里做的工作比ActionInvoker更加基礎(Mina應用服務器下的http協議處理及session管理),我們到后面再研究。
public static void invoke(Http.Request request, Http.Response response) { Monitor monitor = null; try { if (!Play.started) { return; } Http.Request.current.set(request); Http.Response.current.set(response); Scope.Params.current.set(new Scope.Params()); Scope.RenderArgs.current.set(new Scope.RenderArgs()); Scope.Session.current.set(Scope.Session.restore()); Scope.Flash.current.set(Scope.Flash.restore()); ...... }
先new一個Monitor ,用來監控。
然后判斷Play是否啟動。
隨后的是一系列xxx.xxx.current.set方法:
這里的current變量都是ThreadLocal:
public static ThreadLocal<Request> current = new ThreadLocal<Request>();
@
//對于Java開發,ThreadLocal是必須要了解的概念。 //ThreadLocal雖然是個對象,但是ThreadLocal的set方法存的東西并不是放在ThreadLocal對象里 /** * Sets the current thread's copy of this thread-local variable * to the specified value. Many applications will have no need for * this functionality, relying solely on the {@link #initialValue} * method to set the values of thread-locals. * * @param value the value to be stored in the current threads' copy of * this thread-local. */ public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } //由上可見,set方法首先取得當前的Thread對象,然后取得該線程的ThreadLocalMap ,如果map不為空,則寫入map,以當前的ThreadLocal對象為key,將傳入的value存入map。 //這里也只是個引子,沒概念的可能很難理解清楚,畢竟ThreadLocal也不是我這么三言兩語能說清的,建議同學多谷哥一下,多看多用多體會。
將Request,Response以及Scope的引用放入當前線程后,實際上是完成了線程的初始化過程。
// 1. Route and resolve format if not already done if (request.action == null) { for (PlayPlugin plugin : Play.plugins) { plugin.routeRequest(request); } Router.route(request); } request.resolveFormat();
Router.route(request); 根據請求的URL找到router中相應的action,并將action的名字賦值給request.action。
request.resolveFormat();此時request中format為html,如果request中format為null,則根據http頭來取得相應的format。
往下走:
// 2. Find the action method Method actionMethod = null; try { Object[] ca = getActionMethod(request.action); actionMethod = (Method) ca[1]; request.controller = ((Class<?>) ca[0]).getName().substring(12); request.actionMethod = actionMethod.getName(); request.action = request.controller + "." + request.actionMethod; request.invokedMethod = actionMethod; } catch (ActionNotFoundException e) { throw new NotFound(String.format("%s action not found", e.getAction())); }
聲明一個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用到的參數:
// 3. Prepare request params Scope.Params.current().__mergeWith(request.routeArgs); // add parameters from the URI query string Scope.Params.current()._mergeWith(UrlEncodedParser.parseQueryString(new ByteArrayInputStream(request.querystring.getBytes("utf-8")))); Lang.resolvefrom(request);
routeArgs是在route中附加的http參數:
/** * Additinal HTTP params extracted from route */ public Map<String, String> routeArgs;
除此之外還將QueryString中的參數也合并進來。
后面的Lang.resolvefrom(request)沒仔細看實現,看Lang的包名中與i18n有關,這部分等以后專門看國際化的實現單獨寫吧(繼續欠賬)。
下面的代碼,又看到雷人的片段了...
// 4. Easy debugging ... if (Play.mode == Play.Mode.DEV) { Controller.class.getDeclaredField("params").set(null, Scope.Params.current()); Controller.class.getDeclaredField("request").set(null, Http.Request.current()); Controller.class.getDeclaredField("response").set(null, Http.Response.current()); Controller.class.getDeclaredField("session").set(null, Scope.Session.current()); Controller.class.getDeclaredField("flash").set(null, Scope.Flash.current()); Controller.class.getDeclaredField("renderArgs").set(null, Scope.RenderArgs.current()); Controller.class.getDeclaredField("validation").set(null, Java.invokeStatic(Validation.class, "current")); }
!!!!!!
Controller.class.getDeclaredField("xxx").set(null,xxx);
這里Play用反射的方式將Controller中受保護的靜態變量強行賦值!!!
@
如果之前將程序處理結果實現為運行時異常并在產生結果后直接拋出可以暫時理解為一種策略,那這種做法簡直就是簡單粗暴了,和我們讀的各種經典書籍中的教誨大相徑庭。 而且還是自己設置了變量的訪問權限后又自己暴力破解賦值...... 不過作者在注釋里加了句// 4. Easy debugging ...,可能是對此做法無奈的解釋吧。 讓我不禁又想起那句"Java中的關鍵字只是給編譯器看的"... 不過仔細想想,這可能也是開源項目的特色之一,如果在企業里寫這種代碼直接破壞框架,不知道老板的臉會怎么黑- -|||.. 這里廢話這么多,是因為我們學生在校時往往喜歡比較另類的代碼,所以學弟學妹需要醒目一下: 自己玩玩可以,但是以后工作,一份代碼的生命期有可能是伴隨著企業的生命期。 |
又跑題了,回到主題....這部分是判斷play的模式(play有兩種運行模式DEV和實際運行模式,在config里文件配置切換),在開發模式下,直接將request,response和scope等賦值給Cotroller類相應的靜態變量。
可能便于實際invoke控制器時訪問這些值。
#遍歷各個PlugIn看在action invoke前做些動作。
ControllerInstrumentation.stopActionCall(); for (PlayPlugin plugin : Play.plugins) { plugin.beforeActionInvocation(actionMethod); }
ControllerInstrumentation這個類的作用是對allow這個標志位進行操作,allow是個ThreadLocal<Boolean>,對其set值則將其引用存入當前Thread內,換句話說,其實是對Thread做了標記。
public static class ControllerInstrumentation { public static boolean isActionCallAllowed() { return allow.get(); } public static void initActionCall() { allow.set(true); } public static void stopActionCall() { allow.set(false); } static ThreadLocal<Boolean> allow = new ThreadLocal<Boolean>(); }
beforeActionInvocation方法則是在action前Plugin做的事情,這里我看了一下都是空的實現。
#打開monitor
// Monitoring monitor = MonitorFactory.start(request.action + "()");
#找到標記@Before Annotation的方法,并先于action invoke執行。
// 5. Invoke the action try { // @Before List<Method> befores = Java.findAllAnnotatedMethods(Controller.getControllerClass(), Before.class); Collections.sort(befores, new Comparator<Method>() { public int compare(Method m1, Method m2) { Before before1 = m1.getAnnotation(Before.class); Before before2 = m2.getAnnotation(Before.class); return before1.priority() - before2.priority(); } });
Controller.getControllerClass()方法返回class controllers.Application。
Java.findAllAnnotatedMethods()找到所有帶有@Before Annotation的方法。
再根據各個方法的優先級,來對befores中的Method排序。
此處實現比較器用了匿名內部類,按Before的priority進行排序。
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Before { /** * Does not intercept these actions */ String[] unless() default {}; /** * Interceptor priority (0 is high priority) */ int priority() default 0; }
Annotation Before除了成員變量priority外,還有一個String數組變量unless,存放的是action的名字,表示不攔截這些action。
看看這部分的實現。
ControllerInstrumentation.stopActionCall(); //遍歷包含Before Annotation的方法 for (Method before : befores) { //取出當前Before action的unless數組 String[] unless = before.getAnnotation(Before.class).unless(); //設置標志位 boolean skip = false; //遍歷unless數組 for (String un : unless) { if (!un.contains(".")) { un = before.getDeclaringClass().getName().substring(12) + "." + un; } //如果unless與當前被調用的action名字相同,標志位skip設為true,退出循環 if (un.equals(request.action)) { skip = true; break; } } //如果skip為false,調用before方法 if (!skip) { //加個保護,判斷被調用方法是否為靜態,因為下面用到得是invokeStatic.. if (Modifier.isStatic(before.getModifiers())) { before.setAccessible(true); Java.invokeStatic(before, getActionMethodArgs(before)); } } }
通過Before攔截器后,再往下就是我們前面看到的實際執行Action的地方:
//聲明一個Result變量用來保存方法調用的結構 Result actionResult = null; //與之前stopActionCall()相反,這里調用initActionCall()將allow設為true,意思是允許此線程invoke方法 ControllerInstrumentation.initActionCall(); try { //invoke action Java.invokeStatic(actionMethod, getActionMethodArgs(actionMethod)); } catch (InvocationTargetException ex) { // It's a Result ? (expected) if (ex.getTargetException() instanceof Result) { //得到調用action后返回的Result actionResult = (Result) ex.getTargetException(); //else部分本例未涉及,先跳過不管 } else { .....
執行完action,下面的代碼部分是After攔截器,和Before基本一致,不贅述。
隨后將monitor關閉。
之后...繼續將返回結果往上扔。
// Ok, rethrow the original action result if (actionResult != null) { throw actionResult; } throw new NoResult();
catch (InvocationTargetException ex) { // It's a Result ? (expected) if (ex.getTargetException() instanceof Result) { throw (Result) ex.getTargetException(); } // Rethrow the enclosed exception if (ex.getTargetException() instanceof PlayException) { throw (PlayException) ex.getTargetException(); } StackTraceElement element = PlayException.getInterestingStrackTraceElement(ex.getTargetException()); if (element != null) { throw new JavaExecutionException(Play.classes.getApplicationClass(element.getClassName()), element.getLineNumber(), ex.getTargetException()); } throw new JavaExecutionException(Http.Request.current().action, ex); }
一直扔到invoke方法的第一個try..catch塊。
public static void invoke(Http.Request request, Http.Response response) { Monitor monitor = null; try { ....... }catch (Result result) { //遍歷執行plugin的onActionInvocationResult()方法,對結果進行處理 for (PlayPlugin plugin : Play.plugins) { plugin.onActionInvocationResult(result); } // Ok there is a result to apply // Save session & flash scope now Scope.Session.current().save(); Scope.Flash.current().save(); //相應結果的apply方法,此處result實際是RenderTemplate對象,它的apply方法最終的HTML輸出 result.apply(request, response); //這里可見Plugin的功能是非常靈活的,因為幾乎在action生命期的每階段都出現,其實到后面可以發現PlugIn幾乎隨處可見,否則怎么能叫做框架的插件呢= =# for (PlayPlugin plugin : Play.plugins) { plugin.afterActionInvocation(); }
最后看看RenderTemplate類
public class RenderTemplate extends Result { private Template template; private String content; Map<String,Object> args; public RenderTemplate(Template template, Map<String,Object> args) { this.template = template; this.args = args; this.content = template.render(args); } //apply方法是在invoke方法截獲Result后,確認其是需要的返回結果后,調用的結果最終執行代碼 public void apply(Request request, Response response) { try { setContentTypeIfNotSet(response, MimeTypes.getContentType(template.name, "text/plain")); response.out.write(content.getBytes("utf-8")); } catch(Exception e) { throw new UnexpectedException(e); } } }
執行完結果代碼后,來到Invoke方法的結尾處,仍處于catch塊,即找到@final的方法并執行。
// @Finally //這個if判斷不知道有什么意義,前面在get action的時候,就是找Application(Controller Class)的action方法,此處怎么會得到null呢,等以后理解加深再解釋吧。 if (Controller.getControllerClass() != null) { try { List<Method> allFinally = Java.findAllAnnotatedMethods(Controller.getControllerClass(), Finally.class); //后面略,與@before和@after同 }
Controller.getControllerClass()這個方法,涉及到Play的classloader,大概看了一下,比較復雜,等以后專門研究。
不過其中發現一些比較核心的與play熱加載功能相關的代碼,如下:
byte[] bc = BytecodeCache.getBytecode(name, applicationClass.javaSource); if (bc != null) { applicationClass.enhancedByteCode = bc; applicationClass.javaClass = defineClass(applicationClass.name, applicationClass.enhancedByteCode, 0, applicationClass.enhancedByteCode.length, protectionDomain); resolveClass(applicationClass.javaClass); Logger.trace("%sms to load class %s from cache", System.currentTimeMillis() - start, name); return applicationClass.javaClass; }
這里大概能看出,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的過程
“怎么進行ActionInvoker源碼分析”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。