Symfoy2源码分析——启动过程2

  上一篇分析Symfony2框架源码,探究Symfony2如何完成一个请求的前半部分,前半部分可以理解为Symfony2框架为处理请求做准备工作,包括container生成、缓存、bundls初始化等一些列准备工作(Symfoy2源码分析——启动过程1)。而这一篇讲的是Symfony2如何根据请求的数据生成Response对象,向客户端返回响应数据。

  在分析前需要了解Symfony2的事件驱动机制:Symfony2事件驱动

  言归正传,Symfony2请求的工作流程其实是Symfony2内核的事件驱动完成的,下面是Symfony2框架定义好的内核事件:

final class KernelEvents
{
    /**
     * The REQUEST event occurs at the very beginning of request
     * dispatching
     *
     * This event allows you to create a response for a request before any
     * other code in the framework is executed. The event listener method
     * receives a Symfony\Component\HttpKernel\Event\GetResponseEvent
     * instance.
     *
     * @var string
     *
     * @api
     */
    const REQUEST = ‘kernel.request‘;

    /**
     * The EXCEPTION event occurs when an uncaught exception appears
     *
     * This event allows you to create a response for a thrown exception or
     * to modify the thrown exception. The event listener method receives
     * a Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent
     * instance.
     *
     * @var string
     *
     * @api
     */
    const EXCEPTION = ‘kernel.exception‘;

    /**
     * The VIEW event occurs when the return value of a controller
     * is not a Response instance
     *
     * This event allows you to create a response for the return value of the
     * controller. The event listener method receives a
     * Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent
     * instance.
     *
     * @var string
     *
     * @api
     */
    const VIEW = ‘kernel.view‘;

    /**
     * The CONTROLLER event occurs once a controller was found for
     * handling a request
     *
     * This event allows you to change the controller that will handle the
     * request. The event listener method receives a
     * Symfony\Component\HttpKernel\Event\FilterControllerEvent instance.
     *
     * @var string
     *
     * @api
     */
    const CONTROLLER = ‘kernel.controller‘;

    /**
     * The RESPONSE event occurs once a response was created for
     * replying to a request
     *
     * This event allows you to modify or replace the response that will be
     * replied. The event listener method receives a
     * Symfony\Component\HttpKernel\Event\FilterResponseEvent instance.
     *
     * @var string
     *
     * @api
     */
    const RESPONSE = ‘kernel.response‘;

    /**
     * The TERMINATE event occurs once a response was sent
     *
     * This event allows you to run expensive post-response jobs.
     * The event listener method receives a
     * Symfony\Component\HttpKernel\Event\PostResponseEvent instance.
     *
     * @var string
     */
    const TERMINATE = ‘kernel.terminate‘;

    /**
     * The FINISH_REQUEST event occurs when a response was generated for a request.
     *
     * This event allows you to reset the global and environmental state of
     * the application, when it was changed during the request.
     *
     * @var string
     */
    const FINISH_REQUEST = ‘kernel.finish_request‘;
}

  我们可以编写事件监听器,监听相应的内核事件,在Symfony2触发该事件的时候,相应的事件监听器就会执行。监听和唤醒形象的描述,就像,你(事件监听器)参加校运会,去大会(Symfony2)登记(监听)参加50米短跑(事件),当50米短跑比赛开始了(事件被触发),那你就奔跑吧(监听器执行,其实就是一个执行函数,函数完成什么工作就取决于你的需求了),少年。

  Symfony2的内核事件处理流程大部分工作都在HttpKernel::handleRaw方法中:

 1     private function handleRaw(Request $request, $type = self::MASTER_REQUEST)
 2     {
 3         $this->requestStack->push($request);
 4
 5         // request
 6         // 初始化事件,事件对象会被传递给监听器,所以事件可以说是一个信息的载体,事件内存放着监听器感兴趣的数据。
 7         $event = new GetResponseEvent($this, $request, $type);
 8         // 触发kernel.request事件,后续详细讲解EventDispatcher::dispatch方法的实现,
 9         // 这里我们需要知道的是,dispatcher把$event传递给所有监听了kernel.request事件的监听器,监听器将会执行。
10         // kernel.request事件发生在controller执行之前,我们可以在这一步奏完成路由解析等为controller执行提供准备数据,
11         // 在这个过程允许我们直接生成Response对象,向客户端输出数据,那么controller就不会被执行了。
12         $this->dispatcher->dispatch(KernelEvents::REQUEST, $event);
13
14         // 如果我们在kernel.request事件生成了Response对象(响应数据),那么就跳过kernel.controller、kernel.view事件、
15         // controller也会被跳过,直接执行kernel.response事件。
16         if ($event->hasResponse()) {
17             return $this->filterResponse($event->getResponse(), $request, $type);
18         }
19
20         // load controller
21         // 根据路由规则返回 一个对象或者数组或者字符串 ,如果$controller是一个数组,$controller[0]是存放的是要执行的controller对象,
22         // $controller[0]存放的是controller对象执行的方法,即action,方法的参数没有保存在$controller数组中;
23         // 如果$controller是对象,那么该对象就实现了__invoke 方法;
24         // 如果$controller是字符串,那么$controller就是要运行的函数的函数名。
25         // 图2是$controller的一个var_dump例子
26         if (false === $controller = $this->resolver->getController($request)) {
27             throw new NotFoundHttpException(sprintf(‘Unable to find the controller for path "%s". Maybe you forgot to add the matching route in your routing configuration?‘, $request->getPathInfo()));
28         }
29
30         $event = new FilterControllerEvent($this, $controller, $request, $type);
31         // 触发kernel.controller事件,这个事件发生在controller执行前。我们可以通过监听这个事件在controller执行前修改controller,
32         // 或者完成一些动作。
33         $this->dispatcher->dispatch(KernelEvents::CONTROLLER, $event);
34         $controller = $event->getController();
35
36         // controller arguments
37         // 从request对象中获取controller方法的参数
38         $arguments = $this->resolver->getArguments($request, $controller);
39
40         // call controller
41         // 执行controller
42         $response = call_user_func_array($controller, $arguments);
43
44         // view
45         // 如果$response不是Response对象,那么kernel.view事件就会触发,监听kernel.view事件的监听器通过$response值生成Response对象。
46         if (!$response instanceof Response) {
47             $event = new GetResponseForControllerResultEvent($this, $request, $type, $response);
48             $this->dispatcher->dispatch(KernelEvents::VIEW, $event);
49
50             if ($event->hasResponse()) {
51                 $response = $event->getResponse();
52             }
53
54             if (!$response instanceof Response) {
55                 $msg = sprintf(‘The controller must return a response (%s given).‘, $this->varToString($response));
56
57                 // the user may have forgotten to return something
58                 if (null === $response) {
59                     $msg .= ‘ Did you forget to add a return statement somewhere in your controller?‘;
60                 }
61                 throw new \LogicException($msg);
62             }
63         }
64
65         // 触发kernel.response事件,在向客户端输出Response对象前,我们可以对Response对象进行修改,
66         // 例如修改response头部,设置缓存、压缩输出数据等。
67
68         // 接着触发kernel.finish_request事件,把当前请求从请求栈中弹出,当前请求就完成。
69         return $this->filterResponse($response, $request, $type);
70
71         // 千万别忘记了,filterResponse执行完后,Symfony2内核事件处理流程还有最后一步,位于app_dev.php[app.php]最后一行,
72         // $kernel->terminate($request, $response);这个方法触发kernel.terminate事件,此时,Symfony2已经响应了客户端的请求,
73         // 向客户端输出了Response对象。监听kernel.terminate事件的监听器,主要是为了完成一些耗时的操作,操作的结果不需要返回给
74         // 客户端的,例如邮件发送、图片压缩等等。
75         // 到这里,Symfony2的整个流程就走完了。
76     }
HttpKernel::filterResponse方法和HttpKernel::finishRequest方法:

 1     private function filterResponse(Response $response, Request $request, $type)
 2     {
 3         $event = new FilterResponseEvent($this, $request, $type, $response);
 4
 5         $this->dispatcher->dispatch(KernelEvents::RESPONSE, $event);
 6
 7         $this->finishRequest($request, $type);
 8
 9         return $event->getResponse();
10     }
11
12     /**
13      * Publishes the finish request event, then pop the request from the stack.
14      *
15      * Note that the order of the operations is important here, otherwise
16      * operations such as {@link RequestStack::getParentRequest()} can lead to
17      * weird results.
18      *
19      * @param Request $request
20      * @param int     $type
21      */
22     private function finishRequest(Request $request, $type)
23     {
24         $this->dispatcher->dispatch(KernelEvents::FINISH_REQUEST, new FinishRequestEvent($this, $request, $type));
25         $this->requestStack->pop();
26     }

图2

Symfony2框架的事件分发机制的核心代码:

 1     public function dispatch($eventName, Event $event = null)
 2     {
 3         if (null === $event) {
 4             $event = new Event();
 5         }
 6
 7         $event->setDispatcher($this);
 8         $event->setName($eventName);
 9
10         if (!isset($this->listeners[$eventName])) {
11             return $event;
12         }
13
14         // $eventName即:KernelEvents::REQUEST、KernelEvents::CONTROLLER、KernelEvents::VIEW、KernelEvents::RESPONSE、KernelEvents::TERMINATE等
15         // getListeners返回所有监听$eventName事件的监听器
16         $this->doDispatch($this->getListeners($eventName), $eventName, $event);
17
18         return $event;
19     }
20
21     protected function doDispatch($listeners, $eventName, Event $event)
22     {
23         // 监听器执行
24         foreach ($listeners as $listener) {
25             call_user_func($listener, $event, $eventName, $this);
26             // 如果其中一个监听器把$event的propagationStopped属性设置为true,那么表示$eventName这一事件终止执行,
27             // 事件不会往$listeners里尚未执行的监听器传递该事件。
28             if ($event->isPropagationStopped()) {
29                 break;
30             }
31         }
32     }
时间: 2024-08-07 00:11:01

Symfoy2源码分析——启动过程2的相关文章

Tomcat源码分析--启动过程

一直使用Tomcat确不知道它的运行原理.看了源码后对它的启动过程有了一定的了解,特来记录一下. 首先先介绍一下Tomcat的架构图: Tomcat主要有两个核心的组件,一个是Connector(连接器)和容器.所谓连接器就是当有HTTP请求到来时,连接器负责接收这个请求,然后将该请求转发到容器.容器有Engine,Host,Context,Wrapper.Engine:表示整个Catalina servlet引擎:Host:表示包含一个或多个Context容器的虚拟主机:Context:表示一

TOMCAT源码分析(启动框架)

建议: 毕竟TOMCAT的框架还是比较复杂的, 单是从文字上理解, 是不那么容易掌握TOMCAT的框架的. 所以得实践.实践.再实践. 建议下载一份TOMCAT的源码, 调试通过, 然后单步跟踪其启动过程. 如果有不明白的地方, 再来查阅本文, 看是否能得到帮助. 我相信这样效果以及学习速度都会好很多! 1. Tomcat的整体框架结构 Tomcat的基本框架, 分为4个层次. Top Level Elements: Server Service Connector HTTP AJP Conta

Zookeeper 源码分析-启动

Zookeeper 源码分析-启动 博客分类: Zookeeper 本文主要介绍了zookeeper启动的过程 运行zkServer.sh start命令可以启动zookeeper.入口的main函数在类中QuorumPeerMain. main函数主要调用了runFromConfig函数,创建了QuorumPeer对象,并且调用了start函数,从而启动了zookeeper. Java代码   public class QuorumPeerMain { protected QuorumPeer

基于TCP网络通信的自动升级程序源码分析-启动升级文件下载程序

升级程序启动后,首先会连接服务器 private void Connect() { try { int port = int.Parse(System.Configuration.ConfigurationManager.AppSettings["Port"]); connnectionInfo = new ConnectionInfo(IPAddress, port); connection = TCPConnection.GetConnection(connnectionInfo)

Tomcat7.0源码分析——启动与停止服务

前言 熟悉Tomcat的工程师们,肯定都知道Tomcat是如何启动与停止的.对于startup.sh.startup.bat.shutdown.sh.shutdown.bat等脚本或者批处理命令,大家一定知道改如何使用它,但是它们究竟是如何实现的,尤其是shutdown.sh脚本(或者shutdown.bat)究竟是如何和Tomcat进程通信的呢?本文将通过对Tomcat7.0的源码阅读,深入剖析这一过程. 由于在生产环境中,Tomcat一般部署在Linux系统下,所以本文将以startup.s

Tomcat7.0源码分析——启动与停止服务原理

前言 熟悉Tomcat的工程师们,肯定都知道Tomcat是如何启动与停止的.对于startup.sh.startup.bat.shutdown.sh.shutdown.bat等脚本或者批处理命令,大家一定知道改如何使用它,但是它们究竟是如何实现的,尤其是shutdown.sh脚本(或者shutdown.bat)究竟是如何和Tomcat进程通信的呢?本文将通过对Tomcat7.0的源码阅读,深入剖析这一过程. 由于在生产环境中,Tomcat一般部署在Linux系统下,所以本文将以startup.s

Tomcat源码分析——启动与停止服务

前言 熟悉Tomcat的工程师们,肯定都知道Tomcat是如何启动与停止的.对于startup.sh.startup.bat.shutdown.sh.shutdown.bat等脚本或者批处理命令,大家一定知道改如何使用它,但是它们究竟是如何实现的,尤其是shutdown.sh脚本(或者shutdown.bat)究竟是如何和Tomcat进程通信的呢?本文将通过对Tomcat7.0的源码阅读,深入剖析这一过程. 由于在生产环境中,Tomcat一般部署在Linux系统下,所以本文将以startup.s

Spring MVC源码(一) ----- 启动过程与组件初始化

SpringMVC作为MVC框架近年来被广泛地使用,其与Mybatis和Spring的组合,也成为许多公司开发web的套装.SpringMVC继承了Spring的优点,对业务代码的非侵入性,配置的便捷和灵活,再加上注解方式的简便与流行,SpringMVC自然成为web开发中MVC框架的首选. SpringMVC的设计理念,简单来说,就是将Spring的IOC容器与Servlet结合起来,从而在IOC容器中维护Servlet相关对象的生命周期,同时将Spring的上下文注入到Servlet的上下文

【转】Android 4.0 Launcher2源码分析——启动过程分析

Android的应用程序的入口定义在AndroidManifest.xml文件中可以找出:[html] <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.launcher">  <original-package android:name="com.android.launcher2" /> .