playframework拦截器和热加载 源码浅析

标签: playframework 加载 源码 | 发表时间:2013-12-24 18:47 | 作者:rain082900
出处:http://blog.csdn.net

继上一篇 playframework拦截器,这次来看看play怎么样实现拦截行为,同时看看play magic—— hot swap的实现原理。(本文基于play1.2版本)

要想实现http拦截行为,需要在拿到request信息后在路由分发到具体实现类之前做点文章,我们把目光锁定到PlayHandler的messageReceived方法下,发现最终会执行这行代码:

 Invoker.invoke(new NettyInvocation(request, response, ctx, nettyRequest, messageEvent));

NettyInvocation类实现了Runable接口,它是带有所有用户请求信息的一个线程。

跟进到invoke方法 :

 /**
     * Run the code in a new thread took from a thread pool.
     * @param invocation The code to run
     * @return The future object, to know when the task is completed
     */
    public static Future<?> invoke(final Invocation invocation) {
        Monitor monitor = MonitorFactory.getMonitor("Invoker queue size", "elmts.");
        monitor.add(executor.getQueue().size());
        invocation.waitInQueue = MonitorFactory.start("Waiting for execution");
        return executor.submit(invocation);
    }
上面的方法把带有完整信息的线程加入到executor线程池,线程池会决定是否立即执行该线程。

当执行submit(invocation)时会回调NettyInvocation的run方法:

  @Override
        public void run() {
            try {
                if (Logger.isTraceEnabled()) {
                    Logger.trace("run: begin");
                }
                super.run();
            } catch (Exception e) {
                serve500(e, ctx, nettyRequest);
            }
            if (Logger.isTraceEnabled()) {
                Logger.trace("run: end");
            }
        }

进入 super.run(),看注释就知道,高潮来了:

 /**
         * It's time to execute.
         */
        public void run() {
            if (waitInQueue != null) {
                waitInQueue.stop();
            }
            try {
                preInit();
                if (init()) {
                    before();
                    execute();
                    after();
                    onSuccess();
                }
            } catch (Suspend e) {
                suspend(e);
                after();
            } catch (Throwable e) {
                onException(e);
            } finally {
                _finally();
            }
        }
    }
preInit()清理当前的线程池。
before() 和 after() 为自定义的classLoader和加载plugin做准备和善后工作

追踪execute()方法,发现最终会执行ActionInvoker的invoke方法

public static void invoke(Http.Request request, Http.Response response) {
        Monitor monitor = null;

        try {

            resolve(request, response);
            Method actionMethod = request.invokedMethod;

            // 1. Prepare request params
            Scope.Params.current().__mergeWith(request.routeArgs);

            // add parameters from the URI query string
            String encoding = Http.Request.current().encoding;
            Scope.Params.current()._mergeWith(UrlEncodedParser.parseQueryString(new ByteArrayInputStream(request.querystring.getBytes(encoding))));

            // 2. 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("routeArgs").set(null, Scope.RouteArgs.current());
                Controller.class.getDeclaredField("validation").set(null, Validation.current());
            }

            ControllerInstrumentation.stopActionCall();
            Play.pluginCollection.beforeActionInvocation(actionMethod);

            // Monitoring
            monitor = MonitorFactory.start(request.action + "()");

            // 3. Invoke the action
            try {
                // @Before
                handleBefores(request);

                // Action

                Result actionResult = null;
                String cacheKey = null;

                // Check the cache (only for GET or HEAD)
                if ((request.method.equals("GET") || request.method.equals("HEAD")) && actionMethod.isAnnotationPresent(CacheFor.class)) {
                    cacheKey = actionMethod.getAnnotation(CacheFor.class).id();
                    if ("".equals(cacheKey)) {
                        cacheKey = "urlcache:" + request.url + request.querystring;
                    }
                    actionResult = (Result) play.cache.Cache.get(cacheKey);
                }

                if (actionResult == null) {
                    ControllerInstrumentation.initActionCall();
                    try {
                        inferResult(invokeControllerMethod(actionMethod));
                    } catch(Result result) {
                        actionResult = result;
                        // Cache it if needed
                        if (cacheKey != null) {
                            play.cache.Cache.set(cacheKey, actionResult, actionMethod.getAnnotation(CacheFor.class).value());
                        }
                    } catch (InvocationTargetException ex) {
                        // It's a Result ? (expected)
                        if (ex.getTargetException() instanceof Result) {
                            actionResult = (Result) ex.getTargetException();
                            // Cache it if needed
                            if (cacheKey != null) {
                                play.cache.Cache.set(cacheKey, actionResult, actionMethod.getAnnotation(CacheFor.class).value());
                            }

                        } else {
                            // @Catch
                            Object[] args = new Object[]{ex.getTargetException()};
                            List<Method> catches = Java.findAllAnnotatedMethods(Controller.getControllerClass(), Catch.class);
                            Collections.sort(catches, new Comparator<Method>() {

                                public int compare(Method m1, Method m2) {
                                    Catch catch1 = m1.getAnnotation(Catch.class);
                                    Catch catch2 = m2.getAnnotation(Catch.class);
                                    return catch1.priority() - catch2.priority();
                                }
                            });
                            ControllerInstrumentation.stopActionCall();
                            for (Method mCatch : catches) {
                                Class[] exceptions = mCatch.getAnnotation(Catch.class).value();
                                if (exceptions.length == 0) {
                                    exceptions = new Class[]{Exception.class};
                                }
                                for (Class exception : exceptions) {
                                    if (exception.isInstance(args[0])) {
                                        mCatch.setAccessible(true);
                                        inferResult(invokeControllerMethod(mCatch, args));
                                        break;
                                    }
                                }
                            }

                            throw ex;
                        }
                    }
                }

                // @After
                handleAfters(request);

                monitor.stop();
                monitor = null;

                // OK, re-throw the original action result
                if (actionResult != null) {
                    throw actionResult;
                }

                throw new NoResult();

            } catch (IllegalAccessException ex) {
                throw ex;
            } catch (IllegalArgumentException ex) {
                throw ex;
            } catch (InvocationTargetException ex) {
                // It's a Result ? (expected)
                if (ex.getTargetException() instanceof Result) {
                    throw (Result) ex.getTargetException();
                }
                // Re-throw 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);
            }

        } catch (Result result) {

            Play.pluginCollection.onActionInvocationResult(result);

            // OK there is a result to apply
            // Save session & flash scope now

            Scope.Session.current().save();
            Scope.Flash.current().save();

            result.apply(request, response);

            Play.pluginCollection.afterActionInvocation();

            // @Finally
            handleFinallies(request, null);

        } catch (PlayException e) {
            handleFinallies(request, e);
            throw e;
        } catch (Throwable e) {
            handleFinallies(request, e);
            throw new UnexpectedException(e);
        } finally {
            if (monitor != null) {
                monitor.stop();
            }
        }
    }
第37、74、105、152行分别定义了play的各种拦截顺序和行为,方法体中定义了自己具体的拦截规则。

 private static void handleBefores(Http.Request request) throws Exception {
        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();
            }
        });
        ControllerInstrumentation.stopActionCall();
        for (Method before : befores) {
            String[] unless = before.getAnnotation(Before.class).unless();
            String[] only = before.getAnnotation(Before.class).only();
            boolean skip = false;
            for (String un : only) {
                if (!un.contains(".")) {
                    un = before.getDeclaringClass().getName().substring(12).replace("$", "") + "." + un;
                }
                if (un.equals(request.action)) {
                    skip = false;
                    break;
                } else {
                    skip = true;
                }
            }
            for (String un : unless) {
                if (!un.contains(".")) {
                    un = before.getDeclaringClass().getName().substring(12).replace("$", "") + "." + un;
                }
                if (un.equals(request.action)) {
                    skip = true;
                    break;
                }
            }
            if (!skip) {
                before.setAccessible(true);
                inferResult(invokeControllerMethod(before));
            }
        }
    }


第56行会真正的执行用户请求。在这里我们可以非常清晰的看到 所有httpRequest过来后都会先检查是否有@Before注解的方法需要优先执行。用户请求执行完毕后,又会检查是否有@After 和@Finally 注解的方法需要执行。当用户请求执行抛出异常后这里会catch住,同时检查是否有@Catch注解的方法需要执行,这就是play的filter chain。


然后我们再来谈谈 play的热部署机制

所谓的热加载是指无需重启JVM就可以加载修改过的类,更新运行时的class行为。

play做到了只需刷新页面就可以实现play的热加载

因为load class是在JVM 里面的native方法执行的,要想执行热加载有两条路:

1.修改JVM源码。这个就像给自己挖个大坑,随时都存在性能隐患,各种专家强烈不建议,play也没走这条路。

2.实现自己的classLoader,并且创建对象的行为,指定为用自定义的classLoader加载的class,play就是这么干的。

在上面的run()方法中 执行 execute()前有一个init()方法

  /**
         * Init the call (especially usefull in DEV mode to detect changes)
         */
        public boolean init() {
            Thread.currentThread().setContextClassLoader(Play.classloader);
            Play.detectChanges();
            if (!Play.started) {
                if (Play.mode == Mode.PROD) {
                    throw new UnexpectedException("Application is not started");
                }
                Play.start();
            }
            InvocationContext.current.set(getInvocationContext());
            return true;
        }
我们可以发现
 Play.detectChanges()

里面有这么一句

 classloader.detectChanges();
这就是play自己的classLoader了

 /**
     * Detect Java changes
     */
    public void detectChanges() {
        // Now check for file modification
        List<ApplicationClass> modifieds = new ArrayList<ApplicationClass>();
        for (ApplicationClass applicationClass : Play.classes.all()) {
            if (applicationClass.timestamp < applicationClass.javaFile.lastModified()) {
                applicationClass.refresh();
                modifieds.add(applicationClass);
            }
        }
        Set<ApplicationClass> modifiedWithDependencies = new HashSet<ApplicationClass>();
        modifiedWithDependencies.addAll(modifieds);
        if (modifieds.size() > 0) {
            modifiedWithDependencies.addAll(Play.pluginCollection.onClassesChange(modifieds));
        }
        List<ClassDefinition> newDefinitions = new ArrayList<ClassDefinition>();
        boolean dirtySig = false;
        for (ApplicationClass applicationClass : modifiedWithDependencies) {
            if (applicationClass.compile() == null) {
                Play.classes.classes.remove(applicationClass.name);
                currentState = new ApplicationClassloaderState();//show others that we have changed..
            } else {
                int sigChecksum = applicationClass.sigChecksum;
                applicationClass.enhance();
                if (sigChecksum != applicationClass.sigChecksum) {
                    dirtySig = true;
                }
                BytecodeCache.cacheBytecode(applicationClass.enhancedByteCode, applicationClass.name, applicationClass.javaSource);
                newDefinitions.add(new ClassDefinition(applicationClass.javaClass, applicationClass.enhancedByteCode));
                currentState = new ApplicationClassloaderState();//show others that we have changed..
            }
        }
        if (newDefinitions.size() > 0) {
            Cache.clear();
            if (HotswapAgent.enabled) {
                try {
                    HotswapAgent.reload(newDefinitions.toArray(new ClassDefinition[newDefinitions.size()]));
                } catch (Throwable e) {
                    throw new RuntimeException("Need reload");
                }
            } else {
                throw new RuntimeException("Need reload");
            }
        }
        // Check signature (variable name & annotations aware !)
        if (dirtySig) {
            throw new RuntimeException("Signature change !");
        }

        // Now check if there is new classes or removed classes
        int hash = computePathHash();
        if (hash != this.pathHash) {
            // Remove class for deleted files !!
            for (ApplicationClass applicationClass : Play.classes.all()) {
                if (!applicationClass.javaFile.exists()) {
                    Play.classes.classes.remove(applicationClass.name);
                    currentState = new ApplicationClassloaderState();//show others that we have changed..
                }
                if (applicationClass.name.contains("$")) {
                    Play.classes.classes.remove(applicationClass.name);
                    currentState = new ApplicationClassloaderState();//show others that we have changed..
                    // Ok we have to remove all classes from the same file ...
                    VirtualFile vf = applicationClass.javaFile;
                    for (ApplicationClass ac : Play.classes.all()) {
                        if (ac.javaFile.equals(vf)) {
                            Play.classes.classes.remove(ac.name);
                        }
                    }
                }
            }
            throw new RuntimeException("Path has changed");
        }
    }

这个方法会遍历所有发生改变的class然后重现加载之,因为是放在用户请求的过程中,所以我们的直观感受就是刷新页面就热加载了一切,这就是play的 hotswap magic!

关于自定义classLoader的思路给个传送门

http://www.ibm.com/developerworks/cn/java/j-lo-hotdeploy/index.html?ca=drs-




作者:rain082900 发表于2013-12-24 10:47:09 原文链接
阅读:57 评论:0 查看评论

相关 [playframework 加载 源码] 推荐:

playframework拦截器和热加载 源码浅析

- - CSDN博客架构设计推荐文章
playframework拦截器,这次来看看play怎么样实现拦截行为,同时看看play magic—— hot swap的实现原理. (本文基于play1.2版本). 要想实现http拦截行为,需要在拿到request信息后在路由分发到具体实现类之前做点文章,我们把目光锁定到PlayHandler的messageReceived方法下,发现最终会执行这行代码:.

自己动手实现springboot运行时执行java源码(运行时编译、加载、注册bean、调用)

- - 掘金 后端
  看来断点、单步调试还不够硬核,根本没多少人看,这次再来个硬核的. 依然是由于apaas平台越来越流行了,如果apaas平台选择了java语言作为平台内的业务代码,那么不仅仅面临着IDE外的断点、单步调试,还面临着为了实现预览效果,需要将写好的java源码动态的装载到spring容器中然后调用源码内的某个方法.

动态加载HQL

- senyo - BlogJava-首页技术区
Java代码如下:(ReloadableDynamicHibernate.java). 135         private Map qlMap;                //查询的映射. 这样就实现了每次修改SQL or HQL语句后不用重启服务器,立刻看到结果,加快了开发速度.

java动态加载

- - Java - 编程语言 - ITeye博客
第一部分:Java虚拟机启动时,关于类加载方面的一些动作. 当使用java ProgramName.class运行程序时,Java找到JRE,接着找到jvm.dll,把该动态库载入内存,这就是JVM. 然后加载其它动态库, 并激活JVM. JVM激活之后会进行一些初始化工作,之后生成BootstrapLoader,该Class Loader是由C++写的.

spring 加载顺序

- - 企业架构 - ITeye博客
web.xml文件加载顺序.      1 、启动一个 WEB 项目的时候, WEB 容器会去读取它的配置文件 web.xml ,读取 两个结点.      2 、紧急着,容创建一个 ServletContext ( servlet 上下文),这个 web 项目的所有部分都将共享这个上下文.

“按需加载”的应用

- - TID-财付通设计中心
按需加载是前端性能优化中的一项重要措施,按需加载是如何定义的呢. 顾名思义,指的是当用户触发了动作时才加载对应的功能. 触发的动作,是要看具体的业务场景而言,包括但不限于以下几个情况:鼠标点击、输入文字、拉动滚动条,鼠标移动、窗口大小更改等. 加载的文件,可以是JS、图片、CSS、HTML等. 后面将会详细介绍“按需”的理解.

hibernate的延迟加载

- - ITeye博客
    代码的逻辑是:查询出id为1的major, 并输出其名字. 很明显, 代码的逻辑是对的.   可一运行就会报错:. 翻译过来就是:  延迟加载异常:不能初始化代理 --- session不存在. 出现这个报错信息的原因就在于hibernate的延迟加载机制.     所谓的延迟加载就是程序在使用load, iterator方法执行查询及关联查询时, 并不会马上发送并执行sql语句, 而是在调用(被查询)对象属性的getter方法时才去执行查询.

javaSE之类加载器

- - ITeye博客
类加载器,说白了就是加载类的呵呵.   .类加载器负责将.class文件(可能在磁盘上,也可能在网络上)加载到内存中,并为之生成对应的java.lang.Class对象.   .当JVM启动时,会形成由三个类加载器组成的初始类加载器层次结构:.                  BootStrap----------JRE/lib.jar      根节点类加载器.

Hibernate延迟加载机制

- - 企业架构 - ITeye博客
转自:http://blog.163.com/xi_zh_qi/blog/static/8501594200812695053939/.    延迟加载机制是为了避免一些无谓的性能开销而提出来的,所谓延迟加载就是当在真正需要数据的时候,才真正执行数据加载操作. 在Hibernate中提供了对实体对象的延迟加载以及对集合的延迟加载,另外在Hibernate3中还提供了对属性的延迟加载.

JVM 类加载机制

- - 码蜂笔记
虚拟机类加载机制:把描述类的数据从class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的 Java 类型. 在Java语言里面,类型的加载、连接和初始化都是在程序运行期间完成的. 类的整个生命周期包括:加载(loading)、验证(verification)、准备(preparation)、解析(resolution)、初始化(initialization)、使用(using)和卸载(unloading) 7个阶段.