>点我返回上一篇:Play! Framework 学习笔记(一):初识Play <
再往下看,我被雷到了= =# 第一遍看没反应过来,因为我见识较浅,也从来没这么写过代码,之前也没看过这样写的。就是这句:
throw new RenderTemplate(template, templateBinding.data);
这里用RenderTemplate的构造方法new了一个RenderTemplate对象,
然后.......抛出去了。
看看这里抛出去的是什么,先进去看看RenderTemplate类的实现:
public class RenderTemplate extends Result {
再看看Result
public abstract class Result extends RuntimeException {
原来RenderTemplate是个RuntimeException
既然是个异常,下一步则是抛向上级调用者,往下走,我们找这个“不是异常的异常”是在何处被截获的。。。 (对比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方法。
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完成的模板。
ActionInvoker源码分析
既然现在Debug走到ActionInvoker,不妨看看这个类:
由类上的注释:
这个类是根据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中受保护的静态变量强行赋值!!!
又跑题了,回到主题....这部分是判断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的过程
呼,写了半天今天就写了这么短个小节,这么写下去不知写到猴年马月了,~~~~(>_<)~~~~
下一篇会重点分析HttpHandler源码,从而更加深刻理解此流程,这篇就到此为止。