读Flask源代码学习Python--config原理
读Flask源代码学习Python--config原理
个人学习笔记,水平有限。如果理解错误的地方,请大家指出来,谢谢!第一次写文章,发现好累--!。
起因
莫名其妙在第一份工作中使用了从来没有接触过的Python,从那之后就对Python有了莫名其妙的好感。前段时间用了Flask做了几个不大不小的项目,项目过程中学到不少以前没注意到的知识点。于是就有了这个系列,希望最后能坚持完成。
Flask是什么?
根据Flask官网介绍: Flask 是一个用于 Python 的微型网络开发框架。
Flask, HelloWorld!
from flask import Flask
app = Flask(__name__)
@app.route("/")
def index():
return "Flask, HelloWorld!"
if __name__ == "__main__":
app.run(host="localhost", port=5000, debug=True)
这个系列我会怎样读Flask源码?
我会根据一个问题的回答顺序进行来进行这个系列的文章。该问题地址。
- config原理。
- import原理。
- WSGI接口调用。
- 路由原理。
- 理解session。
- 理解threading.local。
- 理解flask自己封装的thread local。
- 理解g和request。
- 理解app context和request context。
后续如果个人水平提高了,会从更高的层面继续这个系列。
根据官方教程, Flask中常见Config用法有以下几种。
- 直接赋值。
- 通过config的update方法一次更新多个属性值。
- 部分配置值可以通过属性赋值。
- 通过文件读取初始化config信息。
直接赋值
app = Flask(__name__)
app.config['DEBUG'] = True
从中可以猜测config可能是一个字典或者至少提供一个通过key获取对应值的方法。
class Flask(_PackageBoundObject):
#: The class that is used for the ``config`` attribute of this app.
#: Defaults to :class:`~flask.Config`.
#:
#: Example use cases for a custom class:
#:
#: 1. Default values for certain config options.
#: 2. Access to config values through attributes in addition to keys.
#:
#: .. versionadded:: 1.0
config_class = Config
def __init__(self, import_name, static_path=None, static_url_path=None,
static_folder='static', template_folder='templates',
instance_path=None, instance_relative_config=False,
root_path=None):
self.config = self.make_config(instance_relative_config)
def make_config(self, instance_relative=False):
"""Used to create the config attribute by the Flask constructor.
The `instance_relative` parameter is passed in from the constructor
of Flask (there named `instance_relative_config`) and indicates if
the config should be relative to the instance path or the root path
of the application.
.. versionadded:: 0.8
"""
root_path = self.root_path
if instance_relative:
root_path = self.instance_path
return self.config_class(root_path, self.default_config)
def from_envvar(self, variable_name, silent=False):
pass
def from_pyfile(self, filename, silent=False):
pass
def from_object(self, obj):
pass
def from_json(self, filename, silent=False):
pass
def from_mapping(self, *mapping, **kwargs):
pass
def get_namespace(self, namespace, lowercase=True, trim_namespace=True):
pass
这是最新的Flask源代码,它出现在app.py文件中的Flask的类定义中。config_class默认值为Config类,Config类又是什么?
在config.py文件中,有下面这样的代码块。
class Config(dict):
"""Works exactly like a dict but provides ways to fill it from files
or special dictionaries.
"""
def __init__(self, root_path, defaults=None):
dict.__init__(self, defaults or {})
self.root_path = root_path
从Config的构造函数和make_config方法中,可以看到之前猜测config是一个字典的结论确实是正确的。
通过config的update方法一次更新多个属性值。
有了app的config属性是一个字典这个事实,通过update方法更新多个属性值就很好理解了。
app.config.update(
DEBUG=True,
SECRET_KEY='maeorwmarfomferw')
知识点
类属性(class attribute)和对象属性(object attribute,有时候也称为实例属性)
注意,这里config_class是一个Flask的类属性,但是却好像“被当作对象属性”使用。
self.config_class(root_path, self.default_config)
这又是怎么一回事?首先,先来看看下面这段示例代码。
def attribute_step_001():
class ClassAttribute(object):
class_attribute = "class_attribute_001"
def __init__(self):
super(ClassAttribute, self).__init__()
self.object_attribute = "object_attribute_002"
class ClassAttributeWithOverrideGetAttr(object):
class_attribute = "class_attribute_001"
def __init__(self):
super(ClassAttributeWithOverrideGetAttr, self).__init__()
self.class_attribute = "object_attribute_001"
self.object_attribute = "object_attribute_002"
def __getattr__(self, attributename):
pass
print("=== two ===")
two = ClassAttributeWithOverrideGetAttr()
print(two.__dict__)
print(two.class_attribute)
print(two.class_attribute_no_exist)
print("=== one ===")
one = ClassAttribute()
print(one.__dict__)
print(one.class_attribute)
print(one.class_attribute_no_exist) #tip001这一行可以注释掉,重新运行一遍这样输出比较清晰。
attribute_step_001()
执行之后输出的结果是:
=== two ===
{'class_attribute': 'object_attribute_001', 'object_attribute': 'object_attribute_002'}
object_attribute_001
None
=== one ===
{'object_attribute': 'object_attribute_002'}
class_attribute_001
Traceback (most recent call last):
File "D:\work_space\Dev_For_Python\flask_hello_world\application.py", line 128, in <module>
attribute_step_001()
File "D:\work_space\Dev_For_Python\flask_hello_world\application.py", line 125, in attribute_step_001
print(one.class_attribute_no_exist)
AttributeError: 'ClassAttribute' object has no attribute 'class_attribute_no_exist'
从结果可以发现,当获取一个对象属性时,如果它没有进行设置的话,默认返回的是这个这个对象的类型的同名类属性的值(如果同名类属性存在的话)。
实际上Python获取一个属性值(objectname.attributename)的搜索流程是(新式类):
1:如果attributename对于对象来说是一个特殊的(比如是Python提供的)属性,直接返回它。
2:从对象的__dict__(obj.__dict__)查找。
3:从对象的类型的__dict__(obj.__class__.__dict__)中查找。
4:从对象类型的基类中obj.__class__.__bases__.__dict__查找,基类的基类,直到object。如果__bases__中有多个值,最后的结果依赖于Python的方法解析顺序(MRO)。
5:如果上面都没有找到并且对象的类定义中重写__getattr__(self, attributename)方法,那么会得到对应的返回值。如果没有重写__getattr__(self, attributename)方法,Python会抛出异常AttributeError。
部分配置值可以通过属性赋值
app = Flask(__name__)
app.debug = True
这些配置值为什么能直接通过属性赋值?答案还是在Flask类和ConfigAttribute类的定义中。
#Flask类定义代码片段
class Flask(_PackageBoundObject):
debug = ConfigAttribute('DEBUG')
testing = ConfigAttribute('TESTING')
session_cookie_name = ConfigAttribute('SESSION_COOKIE_NAME')
send_file_max_age_default = ConfigAttribute('SEND_FILE_MAX_AGE_DEFAULT',
get_converter=_make_timedelta)
def __init__(self, import_name, static_path=None, static_url_path=None,
static_folder='static', template_folder='templates',
instance_path=None, instance_relative_config=False,
root_path=None):
pass
#ConfigAttribute类定义代码片段
class ConfigAttribute(object):
"""Makes an attribute forward to the config"""
def __init__(self, name, get_converter=None):
self.__name__ = name
self.get_converter = get_converter
def __get__(self, obj, type=None):
if obj is None:
return self
rv = obj.config[self.__name__]
if self.get_converter is not None:
rv = self.get_converter(rv)
return rv
def __set__(self, obj, value):
obj.config[self.__name__] = value
在Flask类的类定义中可以看到debug被定义成一个ConfigAttribute对象。ConfigAttribute是什么东西?为什么通过app.config["debug"]=false和app.debug=false得到的效果是一样的?这得从Python的描述符说起。
知识点
- Python描述符(也有文章称为描述器)
什么是描述符?官方给的定义是:
In general, a descriptor is an object attribute with “binding behavior”, one whose attribute access has been overridden by methods in the descriptor protocol. Those methods are__get__(), __set__(), and __delete__(). If any of those methods are defined for an object, it is said to be a descriptor.
简单的说,只要一个对象实现了描述符协议中的任一方法,那么就可以称之为描述符。描述符协议有哪些方法?
"""
描述符中定义的方法
"""
descriptor.__get__(self, obj, type=None) --> value
descriptor.__set__(self, obj, value) --> None
descriptor.__delete__(self, obj) --> None
这是描述符协议的所有方法。一个对象只要重写了上面任意一个方法就可以称之为描述符。这里还有几个概念需要提的是,如果一个对象同时定义了__get__()和__set__(),它叫做数据描述符(data descriptor)。仅定义了__get__()的描述符叫非数据描述符(non-data descriptor)。
"""
这是描述符的示例代码,通过代码了解下描述符。
"""
class DataDescriptor(object):
"""
这是一个数据描述符
"""
def __init__(self):
pass
def __get__(self, obj, objtype):
return "value from DataDescriptor"
def __set__(self, obj, val):
pass
def __delete__(self, obj):
pass
class NoDataDescriptor(object):
"""
这是一个非数据描述符
"""
def __init__(self):
pass
def __get__(self, obj, objtype):
return "value from DataDescriptor"
def attribute_test_001():
class ClassAttributeWithOverrideGetAttr(object):
class_attribute = "class_attribute_001"
class_attribute2 = NoDataDescriptor()
def __init__(self):
super(ClassAttributeWithOverrideGetAttr, self).__init__()
self.class_attribute = "object_attribute_001"
self.object_attribute = "object_attribute_002"
self.class_attribute2 = "object_attribute_003"
def __getattr__(self, attributename):
return "value from __getattr__"
class ClassAttribute(object):
class_attribute = "class_attribute_001"
class_attribute2 = DataDescriptor()
def __init__(self):
super(ClassAttribute, self).__init__()
self.object_attribute = "object_attribute_001"
self.class_attribute2 = "object_attribute_002"
print("=== ClassAttributeWithOverrideGetAttr ===")
a = ClassAttributeWithOverrideGetAttr()
print("[a01]: a.__dict__ = ", a.__dict__)
print("[a02]: a.__class__.__dict__ = ", a.__class__.__dict__)
print("[a03]: a.class_attribute = ", a.class_attribute)
print("[a04]: a.class_attribute2 = ", a.class_attribute2)
print("[a05]: a.class_attribute_no_exist = ", a.class_attribute_no_exist)
print("\r\n=== ClassAttribute ===")
b = ClassAttribute()
print("[b01]: b.__dict__ = ", b.__dict__)
print("[b02]: b.__class__.__dict__ = ", b.__class__.__dict__)
print("[b03]: b.class_attribute = ", b.class_attribute)
print("[b04]: b.class_attribute2 = ", b.class_attribute2)
print("[b05]: b.class_attribute_no_exist = ", b.class_attribute_no_exist)
attribute_test_001()
代码的输出结果是:
=== ClassAttributeWithOverrideGetAttr ===
[a01]: a.__dict__ = {'class_attribute2': 'object_attribute_003', 'class_attribute': 'object_attribute_001', 'object_attribute': 'object_attribute_002'}
[a02]: a.__class__.__dict__ = {'__dict__': <attribute '__dict__' of 'ClassAttributeWithOverrideGetAttr' objects>, '__weakref__': <attribute '__weakref__' of 'ClassAttributeWithOverrideGetAttr' objects>, '__getattr__': <function attribute_test_001.<locals>.ClassAttributeWithOverrideGetAttr.__getattr__ at 0x01929F60>, '__module__': '__main__', '__doc__': None, 'class_attribute2': <__main__.NoDataDescriptor object at 0x0192ED70>, 'class_attribute': 'class_attribute_001', '__init__': <function attribute_test_001.<locals>.ClassAttributeWithOverrideGetAttr.__init__ at 0x01929ED0>}
[a03]: a.class_attribute = object_attribute_001
[a04]: a.class_attribute2 = object_attribute_003
[a05]: a.class_attribute_no_exist = value from __getattr__
=== ClassAttribute ===
[b01]: b.__dict__ = {'object_attribute': 'object_attribute_001'}
[b02]: b.__class__.__dict__ = {'__dict__': <attribute '__dict__' of 'ClassAttribute' objects>, '__weakref__': <attribute '__weakref__' of 'ClassAttribute' objects>, '__module__': '__main__', '__doc__': None, 'class_attribute2': <__main__.DataDescriptor object at 0x0192EDD0>, 'class_attribute': 'class_attribute_001', '__init__': <function attribute_test_001.<locals>.ClassAttribute.__init__ at 0x01929FA8>}
[b03]: b.class_attribute = class_attribute_001
[b04]: b.class_attribute2 = value from DataDescriptor
Traceback (most recent call last):
File "D:\work_space\Dev_For_Python\flask_hello_world\hello.py", line 104, in <module>
attribute_test_001()
File "D:\work_space\Dev_For_Python\flask_hello_world\hello.py", line 101, in attribute_test_001
print("[b05]: b.class_attribute_no_exist = ", b.class_attribute_no_exist)
AttributeError: 'ClassAttribute' object has no attribute 'class_attribute_no_exist'
[Finished in 0.1s]
从两组输出我们可以得出的结论有:
1: 对比a01, a02, a03 ===> 实例字典和类属性中都存在同样key的时候,实例字典(obj.__dict__) > 类属性(obj.__class__.__dict__)
2: 对比b01, b02, b03 ===> 实例字典不存在key的时候,会返回同名key的类属性的值。
3: 对比a05, b05 ===> 实例字典和类属性都不存在key的时候,会返回重写的(__getattr__)函数的返回值,如果没有重写的话,会抛出异常AttributeError。
4: 对比a04, a04 ===> 实例字典和类属性都存在key的时候,数据描述符 > 实例字典(obj.__dict__) > 非数据描述符。
描述符的调用逻辑。
当Python获取一个属性时(objectname.attributename),** 发现要获取的属性是描述符时 **,它的搜索流程会有所改变,转而调用描述的方法。注意,描述符只适用于新式的类和对象。
- 如果attributename对于对象来说是一个特殊的(比如是Python提供的)属性,直接返回它。
- 从
objectname.__class__.__dict__查找attributename,如果找到并且attributename是一个** 数据描述符 **,那么就返回数据描述符的执行结果。(objectname.__class__.__dict__["attributename"].__get__(objectname, type(objectname)))。在objectname.__class__全部基类中进行同样的搜索。 - 从对象的__dict__(
objectname.__dict__)查找,找到就返回,不管是否为数据描述符。唯一的区别是,如果是数据描述符就返回描述符(__get__逻辑的返回值)。 - 从对象的类型的__dict__(
objectname.__class__.__dict__)中查找。 - 从对象类型的基类中
objectname.__class__.__bases__.__dict__查找,找到就返回,不管是否为数据描述符。唯一的区别是,如果是非数据描述符就返回描述符(__get__逻辑的返回值)。PS:这里不用考虑搜索到数据描述符的情况,因为第二步已经把所有数据描述符的情况考虑在内了。 - 如果上面都没有找到并且对象的类定义中重写
__getattr__(self, attributename)方法,那么会得到对应的返回值。如果没有重写__getattr__(self, attributename)方法,Python会抛出异常AttributeError。
通过上面的知识点,可以清楚的知道,首先app.debug是一个数据描述符,其次:当通过app.debug = True对配置值就行修改的时候,实际上调用的是描述符的逻辑type(app).__dict__["debug"].__get__(app, type(app)),最后通过ConfigAttribute中重写的__get__逻辑,可以看出还是修改了app.config字典中key为debug的值。
最后,对Python涉及到的几点进行总结。
1:在没有描述符出现的的情况下,实例字典(obj.__dict__) > 类属性(obj.__class__.__dict__) > __getattr__()方法 > 抛出异常AttributeError
2:数据描述符 > 实例字典(obj.__dict__) > 非数据描述符。
3:Python中有几个内置的描述符:函数,属性(property), 静态方法(static method) 感兴趣的自行查找相关文章研究下。
通过文件读取初始化config信息。
app = Flask(__name__)
app.config.from_object('yourapplication.default_settings')
app.config.from_envvar('YOURAPPLICATION_SETTINGS')
通过上面这几种方法初始化config信息的源代码都相对简单。个人觉得没有什么好分析的。
"""
这是Flask中通过文件或者对象初始化涉及到的源代码
"""
def from_envvar(self, variable_name, silent=False):
"""Loads a configuration from an environment variable pointing to
a configuration file. This is basically just a shortcut with nicer
error messages for this line of code::
app.config.from_pyfile(os.environ['YOURAPPLICATION_SETTINGS'])
:param variable_name: name of the environment variable
:param silent: set to ``True`` if you want silent failure for missing
files.
:return: bool. ``True`` if able to load config, ``False`` otherwise.
"""
rv = os.environ.get(variable_name)
if not rv:
if silent:
return False
raise RuntimeError('The environment variable %r is not set '
'and as such configuration could not be '
'loaded. Set this variable and make it '
'point to a configuration file' %
variable_name)
return self.from_pyfile(rv, silent=silent)
def from_pyfile(self, filename, silent=False):
"""Updates the values in the config from a Python file. This function
behaves as if the file was imported as module with the
:meth:`from_object` function.
:param filename: the filename of the config. This can either be an
absolute filename or a filename relative to the
root path.
:param silent: set to ``True`` if you want silent failure for missing
files.
.. versionadded:: 0.7
`silent` parameter.
"""
filename = os.path.join(self.root_path, filename)
d = types.ModuleType('config')
d.__file__ = filename
try:
with open(filename) as config_file:
exec(compile(config_file.read(), filename, 'exec'), d.__dict__)
except IOError as e:
if silent and e.errno in (errno.ENOENT, errno.EISDIR):
return False
e.strerror = 'Unable to load configuration file (%s)' % e.strerror
raise
self.from_object(d)
return True
def from_object(self, obj):
"""Updates the values from the given object. An object can be of one
of the following two types:
- a string: in this case the object with that name will be imported
- an actual object reference: that object is used directly
Objects are usually either modules or classes.
Just the uppercase variables in that object are stored in the config.
Example usage::
app.config.from_object('yourapplication.default_config')
from yourapplication import default_config
app.config.from_object(default_config)
You should not use this function to load the actual configuration but
rather configuration defaults. The actual config should be loaded
with :meth:`from_pyfile` and ideally from a location not within the
package because the package might be installed system wide.
:param obj: an import name or object
"""
if isinstance(obj, string_types):
obj = import_string(obj)
for key in dir(obj):
if key.isupper():
self[key] = getattr(obj, key)
def from_json(self, filename, silent=False):
"""Updates the values in the config from a JSON file. This function
behaves as if the JSON object was a dictionary and passed to the
:meth:`from_mapping` function.
:param filename: the filename of the JSON file. This can either be an
absolute filename or a filename relative to the
root path.
:param silent: set to ``True`` if you want silent failure for missing
files.
.. versionadded:: 1.0
"""
filename = os.path.join(self.root_path, filename)
try:
with open(filename) as json_file:
obj = json.loads(json_file.read())
except IOError as e:
if silent and e.errno in (errno.ENOENT, errno.EISDIR):
return False
e.strerror = 'Unable to load configuration file (%s)' % e.strerror
raise
return self.from_mapping(obj)
def from_mapping(self, *mapping, **kwargs):
"""Updates the config like :meth:`update` ignoring items with non-upper
keys.
.. versionadded:: 1.0
"""
mappings = []
if len(mapping) == 1:
if hasattr(mapping[0], 'items'):
mappings.append(mapping[0].items())
else:
mappings.append(mapping[0])
elif len(mapping) > 1:
raise TypeError(
'expected at most 1 positional argument, got %d' % len(mapping)
)
mappings.append(kwargs.items())
for mapping in mappings:
for (key, value) in mapping:
if key.isupper():
self[key] = value
return True
在项目中如何使用初始化config?
推荐一篇相关文章
最后分享下写这篇文章学习到的几个Markdown的小技巧。
- 段落缩进使用.
  
- 类似__set__, __delete__, __init__ 双下划线的文本,而且又不想放在代码块的情况。可以把在下划线前面加上\,这样就不会被吃了。
参考资料
- Descriptior HowTo Guide
- Python Types and Objects
- Python Attributes and Methods
- 编写高质量代码 改善Python程序的91个建议 (第58,59,60建议)这里有关于属性拦截和获取更详细的解读,当中涉及到__getattribute__, __getattr__
读Flask源代码学习Python--config原理的更多相关文章
- python flask框架学习(二)——第一个flask程序
第一个flask程序 学习自:知了课堂Python Flask框架——全栈开发 1.用pycharm新建一个flask项目 2.运行程序 from flask import Flask # 创建一个F ...
- 在学习python的Django\Flask\Tornado前你需要知道的,what is web?
我们都在讲web开发web开发,那到底什么是web呢? 如果你正在学习python三大主流web框架,那这些你必须要知道了 软件开发架构: C/S架构:Client/Server 客户端与服务端 ...
- 关于sqlmap当中tamper脚本编码绕过原理的一些总结(学习python没多久有些地方肯定理解有些小问题)
sqlmap中tamper脚本分析编写 置十对一些编码实现的脚本,很多sqlmap里面需要引用的无法实现,所以有一部分例如keywords就只写写了几个引用了一下,其实这里很多脚本运用是可以绕过安全狗 ...
- 编程零基础应当如何开始学习 Python?
提前说一下,这篇福利多多,别的不说,直接让你玩回最有手感的怀旧游戏,参数贴图很方便自己可以根据喜好修改哦. 本篇通过以下四块展开,提供大量资源对应. 选一个好版本 有没有看过<在下坂本,有何贵干 ...
- 学习Python要知道哪些重要的库和工具
本文转自:https://github.com/jobbole/awesome-python-cn 环境管理 管理 Python 版本和环境的工具 p:非常简单的交互式 python 版本管理工具. ...
- 国内某Python大神自创完整版,系统性学习Python
很多小伙伴纠结于这个一百天的时间,我觉得完全没有必要,也违背了我最初放这个大纲上来的初衷,我是觉得这个学习大纲还不错,自学按照这个来也能相对系统的学习知识,而不是零散细碎的知识最后无法整合,每个人的基 ...
- 学习 Python,这 22 个包怎能不掌握?
如今全球各个行业内 Python 的使用状况怎么样呢? 很多人学习python,不知道从何学起.很多人学习python,掌握了基本语法过后,不知道在哪里寻找案例上手.很多已经做案例的人,却不知道如何去 ...
- 学习Python的三种境界
前言 王国维在<人间词话>中将读书分为了三种境界:"古今之成大事业.大学问者,必经过三种之境界:'昨夜西风凋碧树,独上高楼,望尽天涯路'.此第一境也.'衣带渐宽终不悔,为伊消得人 ...
- 学习Python的第一课(简单的单元测试)
由于有C#开发基础,感觉学习Python应该不难,主要是一些语法了,再加上现在互联网这么发达. 感觉还是要有思路,否则学什么也只能是什么. 话不多说,简单发下这几天的学习成果吧: 第一次写博客,大家不 ...
随机推荐
- 利用SQL语句产生分组序号
partition by关键字是分析性函数的一部分,它和聚合函数不同的地方在于它能返回一个分组中的多条记录,而聚合函数一般只有一条反映统计值的记录,partition by用于给结果集分组,如果没 ...
- 利用sql里的xpath生成表格
SELECT (SELECT * from Common.Common_Setup_CityData FOR XML PATH('tr'), TYPE).query(' for $item ...
- 实现一个对象验证库系列 -- 3) Fluent以及扩展方法实现 (请大神批评)
前情回顾: 上一篇 2) 验证器实现 简单描述了下验证器的简单实现 本文将说说Fluent方式的实现,欢迎大神们指点指点 3) Fluent以及扩展方法实现 我们按照之前 Fluent 的设想以及我们 ...
- C# 数组的应用
//数组的应用: //(一).冒泡排序. //1.冒泡排序是用双层循环解决.外层循环的是趟数,里层循环的是次数. //2.趟数=n-1:次数=n-趟数. //3.里层循环使用if比较相临的两个数的大小 ...
- JavaScript计算加减乘除
//加法函数 function jiafa(a,b){ var a=parseInt(document.getElementById("number1").value); //pa ...
- ArrayList--卧槽这就是源码
最近在<数据结构与算法分析(java版)>中看到了实现ArrayList的简易代码,亲自做了一下.个中的疑点还是在代码中实现了一下.其中就包括内部类Iterator对外部成员访问的问题. ...
- C语言基础04
什么叫数组呢?我们的第一反应是很多数组合在一起就是数组,字面意思,当然不仅仅是数字,字符也是可以的. 数组属于构造类型 .指相同类型的若干变量组织起来. 类型说明符 数组名 [常量表达式] int ...
- 关于linux下内存使用的一些疑惑[转载]
Linux内存机制-Cache与Buffer 在linux中经常发现空闲内存很少,似乎所有的内存都被系统占用了,表面感觉内存不够用了,其实不然.这是Linux内存管理的一个优秀特性,在这方面,区别于w ...
- Hibernate 、继承关联映射
一个继承树一张表(subclass) 一个继承树一张表是指将一个类和他下面的所有派生类的所有的属性都放在一张表中,举例有Employee这个实体,在他下面分别派生了两个类skill和sell,通过Hi ...
- 关于nodejs,request模块的一个bug
今天在使用request时发生了一个错误, 对方网站的证书设置的不正确导致本地请求不能返回数据: 解决方案是在配置request时加入一个忽略证书验证得字段: 具体代码如下 request.post( ...