目录


简介[返回到目录]

summermvc是博主自研的一个Python IoC和web MVC框架。
github:https://github.com/tim-chow/summermvc
PYPI:https://pypi.org/project/summermvc/


安装[返回到目录]

easy_install summermvc

pip install summermvc


[root@iZj6chejzrsqpclb7miryaZ ~]# git clone https://github.com/tim-chow/summermvc
正克隆到 'summermvc'...
remote: Counting objects: 50, done.
remote: Compressing objects: 100% (40/40), done.
remote: Total 50 (delta 1), reused 47 (delta 1), pack-reused 0
Unpacking objects: 100% (50/50), done.
[root@iZj6chejzrsqpclb7miryaZ ~]# cd summermvc/
[root@iZj6chejzrsqpclb7miryaZ summermvc]# python setup.py install
...


第一个demo[返回到目录]

创建一个示例项目:


[root@iZj6chejzrsqpclb7miryaZ ~]# summermvc demo --name test_summermvc
demo is running
copying files
deleting .pyc/.pyo 
demo is created in [test_summermvc]

安装测试项目的依赖:


easy_install dicttoxml requests

开始测试服务器:


[root@iZj6chejzrsqpclb7miryaZ ~]# cd test_summermvc/
[root@iZj6chejzrsqpclb7miryaZ test_summermvc]# python application.py 
user dao is constructed
user service is constructed
user controller is constructed

打开另外一个窗口,执行请求:


# 先cd到test_summermvc目录

[root@iZj6chejzrsqpclb7miryaZ test_summermvc]# python test.py 
200
{'Date': 'Tue, 15 May 2018 05:39:44 GMT', 'Content-Length': '47', 'Content-Type': 'application/json', 'Server': 'WSGIServer/0.1 Python/2.7.5'}
{"user_info": {"age": 100, "user_id": "30000"}}
==========

...


使用文档[返回到目录]

如果对IoC和SpringMVC不了解,那么请先阅读后面的部分。

application.py是程序的入口点:

    
from wsgiref.simple_server import make_server
from summermvc.mvc import FilePathDispatcherApplication

application = FilePathDispatcherApplication("src")

if __name__ == "__main__":
    httpd = make_server("0.0.0.0", 8081, application)
    httpd.serve_forever()

    

FilePathDispatcherApplication和BasePackageDispatcherApplication都是DispatcherApplication的子类,它们会扫描目录或“基包”下的类,并创建IoC容器实例。DispatcherApplication对象是一个WSGI Application。在创建DispatcherApplication对象的时候,会扫描IoC容器中的所有bean,如果某个bean实现了HandlerMapping接口,那么则把它保存到handler mapping列表;如果某个bean实现了HandlerAdapter接口,则把它保存到handler adapter列表;如果某个bean实现了HandlerInterceptor接口,则把它保存到handler interceptor列表;如果某个bean实现了ViewResolver接口,则把它当作view resolver。
在收到请求的时候,DispatcherApplication会逐个调用handler mapping列表中的每个HandlerMapping对象的get_handler(request)方法,如果某个HandlerMapping对象能够映射请求,则返回一个Handler对象。每个Handler对象包含一个page_handler和若干个exception_handler。当容器中没有注册任何HandlerMapping时,那么使用RequestMappingHandlerMapping作为默认的HandlerMapping。RequestMappingHandlerMapping会扫描每个bean的实例方法,如果某个方法使用了request_mapping装饰器,并且request_mapping装饰器的uri、请求方法、Content-Type与请求相匹配,那么则把它当作page_handler;如果某个方法使用了exception_handler装饰器,并且exception_handler装饰器的uri和请求的uri相匹配,则把它当作一个exception_handler。
在获取到处理请求的Handler对象之后,DispatcherApplication会扫描handler interceptor列表,如果某个HandlerInterceptor对象的path_pattern()方法的返回值和请求的uri相匹配,则将其添加到本次请求的handler interceptor列表。之后DispatcherApplication使用Handler对象和这个列表,生成一个HandlerExecutionChain对象。
接下来,DispatcherApplication会使用Handler对象逐个调用handler adapter列表中的每个HandlerAdpater对象的supports(handler)方法。如果某个HandlerAdapter对象,能够适配该Handler对象的page_handler,则停止扫描,并使用这个HandlerAdapter对象。当容器中没有注册任何HandlerAdpater的时候,那么使用RequestMappingHandlerAdapter作为默认值,它能够适配RequestMappingHandlerMapping返回的Handler对象。
之后,DispatcherApplication会调用HandlerAdpater对象的handle()方法,该方法用来适配Handler对象。RequestMappingHandlerAdapter的行为是:它首先构造page_handler所需要的参数,然后调用HandlerExecutionChain的handle()方法,如果调用过程中,出现异常,则执行相应的execetion_handler。在page_handler和exception_handler中,可以使用arg_name、args_name、request、model等形参,RequestMappingHandlerAdapter在构造参数的时候,会自动的将查询参数name的值绑定到arg_name,将path pattern中的分组的值绑定到path_var_groupname,将本次请求对应的request对象绑定到request参数,将本次请求对应的response对象绑定到response参数,将本次请求的ModelAndView对象绑定到mv参数,将本次请求的Model对象绑定到model参数等等。
HandlerExcutionChain对象的handle()方法,首先会逐个的调用绑定到该请求的每个HandlerInterceptor对象的pre_handle()方法,如果某个HandlerInterceptor对象的pre_handle()方法抛出InterceptError异常,则停止后续的流程;否则,执行Handler对象的page_handler处理请求。之后,会逐个的调用每个HandlerInterceptor对象的post_handle()方法,如果某个HandlerInterceptor对象的post_handle()方法抛出InteceptError,则终止post_handle链的执行。
HandlerAdapter对象的handle()方法,返回一个ModelAndView对象,其view属性表示视图名;model属性是保存模型数据的map。DispatcherApplication会使用视图名调用view resolver对象,view resolver对象应该根据视图名返回一个合适的View对象。DisaptcherApplication接下来会使用模型数据渲染View对象。并将其作为响应,返回给客户端。
如demo中所示:


控制反转和依赖注入[返回到目录]

下面看一下,处理对象之间的依赖关系的方式。
1,依赖具体:
比如,有一个具体实现类A,被B、C、D...等依赖,随着需求方和需求的增加,类A可能改动很大,维护成本也会随之增加。
这种设计方式不满足依赖倒置(DIP)原则
2,依赖抽象:
依赖倒置原则要求:

比如,有一个Logger实现(这里只是一个例子,真正的Logger实现应该使用桥接模式,里面涉及到Logger、Handler、Formatter等概念),初始的时候,只有debug()、info()、warn()、error()等将日志打印到标准输出的方法。

    
class Logger(object):
    def debug(self, message):
        print("debug: %s" % message)

    def info(self, message):
        print("info: %s" % message)

    def warn(self, message):
        print("warn: %s" % message)

    def error(self, message):
        print("error: %s" % message)

if __name__ == "__main__":
    class Client1(object):
        def __init__(self):
            self._logger = Logger()

        def run(self):
            self._logger.info("%s begins to running" % self.__class__.__name__)

    class Client2(object):
        def __init__(self):
            self._logger = Logger()

        def run(self):
            self._logger.info("%s begins to running" % self.__class__.__name__)

    client1 = Client1()
    client2 = Client2()

    client1.run()
    client2.run()

    

之后,Client2这个应用方不想再把日志打印到标准输出,而是输出到一个文件。这个时候,我们就要改造Logger类,并且还要兼容之前的使用方式,也就是说维护Logger类的成本会增加。这是因为,我们依赖了具体实现。下面根据DIP原则,改为依赖抽象。

    
from abc import ABCMeta, abstractmethod

class Logger(object):
    __metaclass__ = ABCMeta

    @abstractmethod
    def debug(self, message):
        pass

    @abstractmethod
    def info(self, message):
        pass

    @abstractmethod
    def warn(self, message):
        pass

    @abstractmethod
    def error(self, message):
        pass

class ConsoleLogger(Logger):
    def debug(self, message):
        print("debug: %s" % message)

    def info(self, message):
        print("info: %s" % message)

    def warn(self, message):
        print("warn: %s" % message)

    def error(self, message):
        print("error: %s" % message)

class FileLogger(Logger):
    def debug(self, message):
        print("Log to File:: debug: %s" % message)

    def info(self, message):
        print("Log to File:: info: %s" % message)

    def warn(self, message):
        print("Log to File:: warn: %s" % message)

    def error(self, message):
        print("Log to File:: error: %s" % message)

if __name__ == "__main__":
    class Client1(object):
        def __init__(self):
            self._logger = None

        def run(self):
            self._logger.info("%s begins to running" % self.__class__.__name__)

        def configure_logging(self, logger_name):
            self._logger = globals()[logger_name]()
            return self

    class Client2(object):
        def __init__(self):
            self._logger = None

        def run(self):
            self._logger.info("%s begins to running" % self.__class__.__name__)

        def configure_logging(self, logger_name):
            self._logger = globals()[logger_name]()
            return self

    client1 = Client1().configure_logging("ConsoleLogger")
    client2 = Client2().configure_logging("FileLogger")

    client1.run()
    client2.run()
    

此时,我们为不同的需求,创建了不同的实现类:ConsoleLogger将日志打印到标准输出;FileLogger将日志打印到文件。然后客户端通过通过反射多态等特性,使用自己想要的实现。
3,工厂模式:
在上面两种方式中,都是 客户端 直接创建产品对象的。它们都违反了“高内聚,低耦合”的原则。因为客户端只是“消费”产品对象,无需关心产品对象是如何组织和创建的。因此,可以将产品对象的创建细节,封装到工厂角色中。工厂模式有三种:

继续上面的例子:

    
# coding: utf8

import threading
from abc import ABCMeta, abstractmethod

class Logger(object):
    __metaclass__ = ABCMeta

    @abstractmethod
    def debug(self, message):
        pass

    @abstractmethod
    def info(self, message):
        pass

    @abstractmethod
    def warn(self, message):
        pass

    @abstractmethod
    def error(self, message):
        pass

class ConsoleLogger(Logger):
    def debug(self, message):
        print("debug: %s" % message)

    def info(self, message):
        print("info: %s" % message)

    def warn(self, message):
        print("warn: %s" % message)

    def error(self, message):
        print("error: %s" % message)

class FileLogger(Logger):
    def debug(self, message):
        print("Log to File:: debug: %s" % message)

    def info(self, message):
        print("Log to File:: info: %s" % message)

    def warn(self, message):
        print("Log to File:: warn: %s" % message)

    def error(self, message):
        print("Log to File:: error: %s" % message)

class LogFactory(object):
    _instances = {}
    _lock = threading.Lock()

    # 工厂是单例的
    def __new__(cls, *a, **kw):
        if cls in LogFactory._instances:
            return LogFactory._instances[cls]

        with LogFactory._lock:
            if cls in LogFactory._instances:
                return LogFactory._instances[cls]

            LogFactory._instances[cls] = super(LogFactory, cls).__new__(cls, *a, **kw)
            return LogFactory._instances[cls]
        

    def create(self):
        logger = self.create_logger()
        return logger

    @abstractmethod
    def create_logger(self):
        pass

class ConsoleLogFactory(LogFactory):
    def create_logger(self):
        return ConsoleLogger()

class FileLogFactory(LogFactory):
    def create_logger(self):
        return FileLogger()

if __name__ == "__main__":
    class Client1(object):
        def __init__(self):
            self._logger = ConsoleLogFactory().create()

        def run(self):
            self._logger.info("%s begins to running" % self.__class__.__name__)

    class Client2(object):
        def __init__(self):
            self._logger = FileLogFactory().create()

        def run(self):
            self._logger.info("%s begins to running" % self.__class__.__name__)

    client1 = Client1()
    client2 = Client2()

    client1.run()
    client2.run()

    

4,控制反转(IoC,Inversion of Control)和依赖注入(DI, Dependency Injection):
值得说明的是,上面三种情况,都是客户端 主动地获取资源对象 的。在控制反转这个设计思想中,由一个容器负责创建对象,以及处理对象之间的依赖关系,并且它反转了资源的获取方式,也就是由容器主动地将资源推送给它所管理的组件,组件所要做的仅仅是选择一种方式接受资源。
依赖注入控制反转的一种实现方法.它是指:组件以一些预定好的方式,接受来自容器的资源注入。
ioc.png
我们把IoC容器所管理的对象叫做bean,一个bean就是一个类,并且有一个唯一的id(可以是和类名不同的一个别名)。只有在创建了IoC容器的实例之后,才能从容器中获取bean的实例。
依赖注入通常有三种方式:


在summermvc中创建IoC容器[返回到目录]

在summermvc中,一个ApplicationContext实例就是一个IoC容器。ApplicationContext有两个子类:

其中,BasePackageApplicationContext会递归地扫描给定的若干个“基包”及其子包中的所有模块,并将使用了component装饰器的类作为一个bean。当不给component传递参数的时候,会自动地将类名作为bean的名称,同时,默认该bean是单例的。也可以使用component的name参数为bean指定名称,使用is_singleton参数,来指定bean是否是单例的。如果bean是单例的,那么多次从容器中,获取该bean的实例,得到的都是同一个实例,否则,每次都返回一个新的实例。
repository、service、controller和component是等价的,但是一般使用repository标注持久层的组件,使用service标注业务层的组件,使用controller标注控制器层的组件。
FilePathApplication与BasePackageApplicationContent类似,不同的是,它会递归地扫描给定的若干目录下的所有包及其子包中的所有模块
值得说明的是:在创建IoC容器实例的时候,就会实例化所有单例的bean
下面看一个简单的例子:

    
[root@iZj6chejzrsqpclb7miryaZ test_summermvc]# tree .
.
├── ioc.py
└── project
    ├── controller.py
    ├── controller.pyc
    ├── dao.py
    ├── dao.pyc
    ├── service.py
    └── service.pyc

1 directory, 7 files
[root@iZj6chejzrsqpclb7miryaZ test_summermvc]# python ioc.py 
False
True
[root@iZj6chejzrsqpclb7miryaZ test_summermvc]# cat ioc.py 
from summermvc import FilePathApplicationContext

ctx = FilePathApplicationContext("project")

controller1 = ctx.get_bean("controller")
controller2 = ctx.get_bean("controller") 
print controller1 is controller2

service1 = ctx.get_bean("Service")
service2 = ctx.get_bean("Service")
print service1 is service2
[root@iZj6chejzrsqpclb7miryaZ test_summermvc]# cat project/controller.py
from summermvc.decorator import *

@controller(name="controller", is_singleton=False)
class Controller(object):
    pass

[root@iZj6chejzrsqpclb7miryaZ test_summermvc]# cat project/service.py
from summermvc.decorator import *

@service
class Service(object):
    pass

[root@iZj6chejzrsqpclb7miryaZ test_summermvc]# cat project/dao.py
from summermvc.decorator import *

@repository
class Dao(object):
    pass


    

我们知道,controller层依赖service层,service层依赖dao层。在summermvc使用auto_wired装饰器配置依赖关系。auto_wired装饰器用来标注实例方法,在获取bean实例的时候,summermvc会将该实例方法替换为依赖的bean的实例。因此,不要在该方法中写任何业务逻辑。
auto_wired装饰器的参数是依赖的bean的名字,当不给它指定参数时,那么会自动生成名称(将实例方法名按_切分,然后将每一部分首字母变大写,其余字母变小写,再连接起来。比如,实例方法名为_service_impl,那么自动生成的名称是ServiceImpl)。
除了配置bean实例之间的依赖关系,还可以使用value装饰器,为bean实例注入值。value装饰器装饰的也是一个实例方法,并且在获取bean实例的时候,该方法会被替换为通过value装饰器的参数所指定的值。
每个bean都需要提供一个无参的构造方法。获取bean实例时,IoC容器在调用该无参的构造方法创建bean实例之后,会注入值和依赖的bean实例,然后调用使用post_construct装饰的方法(该方法也必须是无参的),进行初始化。对于单例的bean,当容器关闭或销毁时,会调用它的使用pre_destory装饰的方法(该方法必须是无参的),进行析构。

    
[root@iZj6chejzrsqpclb7miryaZ test_summermvc]# python ioc.py 
Dao post_construct: create connection pool using parameters: [{'host': '127.0.0.1', 'user': 'root'}].
Controller: do_sth()
Service: do_sth()
Dao: do_some_query()
Dao pre_destroy: close connection pool
[root@iZj6chejzrsqpclb7miryaZ test_summermvc]# cat ioc.py 
# coding: utf8

from summermvc import FilePathApplicationContext

# 创建IoC容器实例
ctx = FilePathApplicationContext("project")

controller = ctx.get_bean("controller")
controller.do_sth()
[root@iZj6chejzrsqpclb7miryaZ test_summermvc]# cat project/controller.py
from summermvc.decorator import *

@controller(name="controller", is_singleton=False)
class Controller(object):
    @auto_wired("Service")
    def _service(self):
        pass

    def do_sth(self):
        print("Controller: do_sth()")
        self._service.do_sth()
[root@iZj6chejzrsqpclb7miryaZ test_summermvc]# cat project/service.py
from summermvc.decorator import *

@service
class Service(object):
    @auto_wired
    def _dao(self):
        pass

    def do_sth(self):
        print("Service: do_sth()")
        self._dao.do_some_query()

[root@iZj6chejzrsqpclb7miryaZ test_summermvc]# cat project/dao.py
from summermvc.decorator import *

@repository
class Dao(object):
    @value({"host": "127.0.0.1", "user": "root"})
    def _connection_args(self):
        pass

    @post_construct
    def setup(self):
        print("Dao post_construct: create connection pool "
            "using parameters: [%s]." % self._connection_args)

    def do_some_query(self):
        print("Dao: do_some_query()")

    @pre_destroy
    def teardown(self):
        print("Dao pre_destroy: close connection pool")


    

我们可以看到在使用IoC容器的时候,客户端不必自己创建对象,而是从容器中获取对象,容器会自动的处理对象之间的依赖关系,并对对象进行管理。


summermvc的执行流程[返回到目录]

summermvc深受SpringMVC影响。SpringMVC的请求处理流程,如下图所示: springmvc.jpg

在summermvc中,DispatcherApplication是一个WSGI应用程序(关于WSGI协议,可以参考:这篇文档)。其执行流程和SpringMVC非常类似。


参考文档[返回到目录]