一、 介绍MyBatis初始化过程

  项目是简单的Mybatis应用,编写SQL Mapper,还有编写的SqlSessionFactoryUtil里面用了Mybatis的IO包里面的Resources获取配置文件的输入流,利用SqlSessionFactoryBuilder获取创建Session的工厂。

  首先构建的是承载mybatis-config配置的Configuration类,它是由SqlSessionFactoryBuilder的build开始的,时序图如下:

二、相关代码

自己编写的SqlSessionFactoryUtil.java

 1 public class SqlSessionFactoryUtil {
2 //SQLSessionFactory对象
3 private static SqlSessionFactory sqlSessionFactory = null;
4 //类线程锁
5 private static final Class CLASS_LOCK = SqlSessionFactoryUtil.class;
6
7 private SqlSessionFactoryUtil() {}
8
9 /**
10 * 构建SqlSessionFactory
11 */
12 public static SqlSessionFactory init() {
13 String resource = "mybatis-config.xml";
14 InputStream inputStream = null;
15 try {
16 inputStream = Resources.getResourceAsStream(resource);
17 } catch (IOException ex) {
18 Logger.getLogger(SqlSessionFactoryUtil.class.getName()).log(Level.SEVERE, null, ex);
19 }
20 synchronized(CLASS_LOCK) {
21 if(sqlSessionFactory == null) {
22 sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
23 }
24 }
25 return sqlSessionFactory;
26 }
27
28 /**
29 * 打开SqlSession
30 */
31 public static SqlSession openSqlSession() {
32 if (sqlSessionFactory == null) {
33 init();
34 }
35 return sqlSessionFactory.openSession();
36 }
37 }

源码 SqlSessionFactoryBuilder.java,首先是读取配置到Configuration类,再利用读取出来的config构建DefaultSqlSessionFactory

 1 public class SqlSessionFactoryBuilder {
2
3 public SqlSessionFactory build(Reader reader) {
4 return build(reader, null, null);
5 }
6
7 public SqlSessionFactory build(Reader reader, String environment) {
8 return build(reader, environment, null);
9 }
10
11 public SqlSessionFactory build(Reader reader, Properties properties) {
12 return build(reader, null, properties);
13 }
14
15 public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
16 try {
17 XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
18 return build(parser.parse());
19 } catch (Exception e) {
20 throw ExceptionFactory.wrapException("Error building SqlSession.", e);
21 } finally {
22 ErrorContext.instance().reset();
23 try {
24 reader.close();
25 } catch (IOException e) {
26 // Intentionally ignore. Prefer previous error.
27 }
28 }
29 }
30 //使用的是这个方法构建SqlSessionFactory
31 public SqlSessionFactory build(InputStream inputStream) {
32 return build(inputStream, null, null);
33 }
34
35 public SqlSessionFactory build(InputStream inputStream, String environment) {
36 return build(inputStream, environment, null);
37 }
38
39 public SqlSessionFactory build(InputStream inputStream, Properties properties) {
40 return build(inputStream, null, properties);
41 }
42 //构建build
43 public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
44 try {
45 XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); //XMLConfigBuilder解析mybatis-config.xml配置
46 return build(parser.parse());
47 } catch (Exception e) {
48 throw ExceptionFactory.wrapException("Error building SqlSession.", e);
49 } finally {
50 ErrorContext.instance().reset();
51 try {
52 inputStream.close();
53 } catch (IOException e) {
54 // Intentionally ignore. Prefer previous error.
55 }
56 }
57 }
58
59 public SqlSessionFactory build(Configuration config) {
60 return new DefaultSqlSessionFactory(config);
61 }
62
63 }


源码 XMLConfigBuilder.java,读取并保存mybatis-config配置文件中大部分节点属性
  1 public class XMLConfigBuilder extends BaseBuilder {
2
3 private boolean parsed;
4 private final XPathParser parser;
5 private String environment;
6 private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();
7
8 public XMLConfigBuilder(Reader reader) {
9 this(reader, null, null);
10 }
11
12 public XMLConfigBuilder(Reader reader, String environment) {
13 this(reader, environment, null);
14 }
15
16 public XMLConfigBuilder(Reader reader, String environment, Properties props) {
17 this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
18 }
19
20 public XMLConfigBuilder(InputStream inputStream) {
21 this(inputStream, null, null);
22 }
23
24 public XMLConfigBuilder(InputStream inputStream, String environment) {
25 this(inputStream, environment, null);
26 }
27
28 public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
29 this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
30 }
31
32 private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
33 super(new Configuration());
34 ErrorContext.instance().resource("SQL Mapper Configuration");
35 this.configuration.setVariables(props);
36 this.parsed = false;
37 this.environment = environment;
38 this.parser = parser;
39 }
40
41 public Configuration parse() {
42 if (parsed) {
43 throw new BuilderException("Each XMLConfigBuilder can only be used once.");
44 }
45 parsed = true;
46 parseConfiguration(parser.evalNode("/configuration"));
47 return configuration;
48 }
49
50 private void parseConfiguration(XNode root) {
51 try {
52 //issue #117 read properties first
53 propertiesElement(root.evalNode("properties"));
54 Properties settings = settingsAsProperties(root.evalNode("settings"));
55 loadCustomVfs(settings);
56 loadCustomLogImpl(settings);
57 typeAliasesElement(root.evalNode("typeAliases"));
58 pluginElement(root.evalNode("plugins"));
59 objectFactoryElement(root.evalNode("objectFactory"));
60 objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
61 reflectorFactoryElement(root.evalNode("reflectorFactory"));
62 settingsElement(settings);
63 // read it after objectFactory and objectWrapperFactory issue #631
64 environmentsElement(root.evalNode("environments"));
65 databaseIdProviderElement(root.evalNode("databaseIdProvider"));
66 typeHandlerElement(root.evalNode("typeHandlers"));
67 mapperElement(root.evalNode("mappers"));
68 } catch (Exception e) {
69 throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
70 }
71 }
72
73 private Properties settingsAsProperties(XNode context) {
74 if (context == null) {
75 return new Properties();
76 }
77 Properties props = context.getChildrenAsProperties();
78 // Check that all settings are known to the configuration class
79 MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
80 for (Object key : props.keySet()) {
81 if (!metaConfig.hasSetter(String.valueOf(key))) {
82 throw new BuilderException("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive).");
83 }
84 }
85 return props;
86 }
87
88 private void loadCustomVfs(Properties props) throws ClassNotFoundException {
89 String value = props.getProperty("vfsImpl");
90 if (value != null) {
91 String[] clazzes = value.split(",");
92 for (String clazz : clazzes) {
93 if (!clazz.isEmpty()) {
94 @SuppressWarnings("unchecked")
95 Class<? extends VFS> vfsImpl = (Class<? extends VFS>)Resources.classForName(clazz);
96 configuration.setVfsImpl(vfsImpl);
97 }
98 }
99 }
100 }
101
102 private void loadCustomLogImpl(Properties props) {
103 Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl"));
104 configuration.setLogImpl(logImpl);
105 }
106
107 private void typeAliasesElement(XNode parent) {
108 if (parent != null) {
109 for (XNode child : parent.getChildren()) {
110 if ("package".equals(child.getName())) {
111 String typeAliasPackage = child.getStringAttribute("name");
112 configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
113 } else {
114 String alias = child.getStringAttribute("alias");
115 String type = child.getStringAttribute("type");
116 try {
117 Class<?> clazz = Resources.classForName(type);
118 if (alias == null) {
119 typeAliasRegistry.registerAlias(clazz);
120 } else {
121 typeAliasRegistry.registerAlias(alias, clazz);
122 }
123 } catch (ClassNotFoundException e) {
124 throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
125 }
126 }
127 }
128 }
129 }
130
131 private void pluginElement(XNode parent) throws Exception {
132 if (parent != null) {
133 for (XNode child : parent.getChildren()) {
134 String interceptor = child.getStringAttribute("interceptor");
135 Properties properties = child.getChildrenAsProperties();
136 Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
137 interceptorInstance.setProperties(properties);
138 configuration.addInterceptor(interceptorInstance);
139 }
140 }
141 }
142
143 private void objectFactoryElement(XNode context) throws Exception {
144 if (context != null) {
145 String type = context.getStringAttribute("type");
146 Properties properties = context.getChildrenAsProperties();
147 ObjectFactory factory = (ObjectFactory) resolveClass(type).newInstance();
148 factory.setProperties(properties);
149 configuration.setObjectFactory(factory);
150 }
151 }
152
153 private void objectWrapperFactoryElement(XNode context) throws Exception {
154 if (context != null) {
155 String type = context.getStringAttribute("type");
156 ObjectWrapperFactory factory = (ObjectWrapperFactory) resolveClass(type).newInstance();
157 configuration.setObjectWrapperFactory(factory);
158 }
159 }
160
161 private void reflectorFactoryElement(XNode context) throws Exception {
162 if (context != null) {
163 String type = context.getStringAttribute("type");
164 ReflectorFactory factory = (ReflectorFactory) resolveClass(type).newInstance();
165 configuration.setReflectorFactory(factory);
166 }
167 }
168
169 private void propertiesElement(XNode context) throws Exception {
170 if (context != null) {
171 Properties defaults = context.getChildrenAsProperties();
172 String resource = context.getStringAttribute("resource");
173 String url = context.getStringAttribute("url");
174 if (resource != null && url != null) {
175 throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other.");
176 }
177 if (resource != null) {
178 defaults.putAll(Resources.getResourceAsProperties(resource));
179 } else if (url != null) {
180 defaults.putAll(Resources.getUrlAsProperties(url));
181 }
182 Properties vars = configuration.getVariables();
183 if (vars != null) {
184 defaults.putAll(vars);
185 }
186 parser.setVariables(defaults);
187 configuration.setVariables(defaults);
188 }
189 }
190
191 private void settingsElement(Properties props) {
192 configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
193 configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
194 configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
195 configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
196 configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
197 configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
198 configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
199 configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
200 configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
201 configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
202 configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
203 configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
204 configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
205 configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
206 configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
207 configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
208 configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
209 configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
210 configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
211 configuration.setDefaultEnumTypeHandler(resolveClass(props.getProperty("defaultEnumTypeHandler")));
212 configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
213 configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
214 configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
215 configuration.setLogPrefix(props.getProperty("logPrefix"));
216 configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
217 }
218
219 private void environmentsElement(XNode context) throws Exception {
220 if (context != null) {
221 if (environment == null) {
222 environment = context.getStringAttribute("default");
223 }
224 for (XNode child : context.getChildren()) {
225 String id = child.getStringAttribute("id");
226 if (isSpecifiedEnvironment(id)) {
227 TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
228 DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
229 DataSource dataSource = dsFactory.getDataSource();
230 Environment.Builder environmentBuilder = new Environment.Builder(id)
231 .transactionFactory(txFactory)
232 .dataSource(dataSource);
233 configuration.setEnvironment(environmentBuilder.build());
234 }
235 }
236 }
237 }
238
239 private void databaseIdProviderElement(XNode context) throws Exception {
240 DatabaseIdProvider databaseIdProvider = null;
241 if (context != null) {
242 String type = context.getStringAttribute("type");
243 // awful patch to keep backward compatibility
244 if ("VENDOR".equals(type)) {
245 type = "DB_VENDOR";
246 }
247 Properties properties = context.getChildrenAsProperties();
248 databaseIdProvider = (DatabaseIdProvider) resolveClass(type).newInstance();
249 databaseIdProvider.setProperties(properties);
250 }
251 Environment environment = configuration.getEnvironment();
252 if (environment != null && databaseIdProvider != null) {
253 String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
254 configuration.setDatabaseId(databaseId);
255 }
256 }
257
258 private TransactionFactory transactionManagerElement(XNode context) throws Exception {
259 if (context != null) {
260 String type = context.getStringAttribute("type");
261 Properties props = context.getChildrenAsProperties();
262 TransactionFactory factory = (TransactionFactory) resolveClass(type).newInstance();
263 factory.setProperties(props);
264 return factory;
265 }
266 throw new BuilderException("Environment declaration requires a TransactionFactory.");
267 }
268
269 private DataSourceFactory dataSourceElement(XNode context) throws Exception {
270 if (context != null) {
271 String type = context.getStringAttribute("type");
272 Properties props = context.getChildrenAsProperties();
273 DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();
274 factory.setProperties(props);
275 return factory;
276 }
277 throw new BuilderException("Environment declaration requires a DataSourceFactory.");
278 }
279
280 private void typeHandlerElement(XNode parent) {
281 if (parent != null) {
282 for (XNode child : parent.getChildren()) {
283 if ("package".equals(child.getName())) {
284 String typeHandlerPackage = child.getStringAttribute("name");
285 typeHandlerRegistry.register(typeHandlerPackage);
286 } else {
287 String javaTypeName = child.getStringAttribute("javaType");
288 String jdbcTypeName = child.getStringAttribute("jdbcType");
289 String handlerTypeName = child.getStringAttribute("handler");
290 Class<?> javaTypeClass = resolveClass(javaTypeName);
291 JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
292 Class<?> typeHandlerClass = resolveClass(handlerTypeName);
293 if (javaTypeClass != null) {
294 if (jdbcType == null) {
295 typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
296 } else {
297 typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
298 }
299 } else {
300 typeHandlerRegistry.register(typeHandlerClass);
301 }
302 }
303 }
304 }
305 }
306
307 private void mapperElement(XNode parent) throws Exception {
308 if (parent != null) {
309 for (XNode child : parent.getChildren()) {
310 if ("package".equals(child.getName())) {
311 String mapperPackage = child.getStringAttribute("name");
312 configuration.addMappers(mapperPackage);
313 } else {
314 String resource = child.getStringAttribute("resource");
315 String url = child.getStringAttribute("url");
316 String mapperClass = child.getStringAttribute("class");
317 if (resource != null && url == null && mapperClass == null) {
318 ErrorContext.instance().resource(resource);
319 InputStream inputStream = Resources.getResourceAsStream(resource);
320 XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
321 mapperParser.parse();
322 } else if (resource == null && url != null && mapperClass == null) {
323 ErrorContext.instance().resource(url);
324 InputStream inputStream = Resources.getUrlAsStream(url);
325 XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
326 mapperParser.parse();
327 } else if (resource == null && url == null && mapperClass != null) {
328 Class<?> mapperInterface = Resources.classForName(mapperClass);
329 configuration.addMapper(mapperInterface);
330 } else {
331 throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
332 }
333 }
334 }
335 }
336 }
337
338 private boolean isSpecifiedEnvironment(String id) {
339 if (environment == null) {
340 throw new BuilderException("No environment specified.");
341 } else if (id == null) {
342 throw new BuilderException("Environment requires an id attribute.");
343 } else if (environment.equals(id)) {
344 return true;
345 }
346 return false;
347 }
348
349 }

  最后由SqlSessionFactoryBuilder返回的DefaultSqlSessionFactory的openSession()方法获取session,这里Mybatis的初始化就完成了,剩下的是mapper接口的映射工作了。



MyBatis源码分析(三):MyBatis初始化(配置文件读取和解析)的更多相关文章

  1. mybatis底层源码分析之--配置文件读取和解析

    现在企业级开发中ssm是很常见的技术标配,mybatis比hibernate轻量了很多,而且学习成本相对较低,简单易上手. 那么,问题来了,简单好用的mybatis底层到底是如何实现的呢?都使用了什么 ...

  2. MyBatis源码分析(1)-MapConfig文件的解析

    1.简述 MyBatis是一个优秀的轻ORM框架,由最初的iBatis演化而来,可以方便的完成sql语句的输入输出到java对象之间的相互映射,典型的MyBatis使用的方式如下: String re ...

  3. mybatis源码分析(二)------------配置文件的解析

    这篇文章中,我们将讲解配置文件中 properties,typeAliases,settings和environments这些节点的解析过程. 一 properties的解析 private void ...

  4. 精尽 MyBatis 源码分析 - MyBatis 初始化(三)之 SQL 初始化(上)

    该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

  5. MyBatis 源码分析 - 配置文件解析过程

    * 本文速览 由于本篇文章篇幅比较大,所以这里拿出一节对本文进行快速概括.本篇文章对 MyBatis 配置文件中常用配置的解析过程进行了较为详细的介绍和分析,包括但不限于settings,typeAl ...

  6. 精尽 MyBatis 源码分析 - MyBatis 初始化(一)之加载 mybatis-config.xml

    该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

  7. 精尽MyBatis源码分析 - MyBatis初始化(二)之加载Mapper接口与XML映射文件

    该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

  8. 精尽MyBatis源码分析 - MyBatis初始化(四)之 SQL 初始化(下)

    该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

  9. MyBatis源码分析-MyBatis初始化流程

    MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生Map使用简 ...

  10. 精尽MyBatis源码分析 - SQL执行过程(三)之 ResultSetHandler

    该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

随机推荐

  1. ansible 批量安装yum包

    1.首先安装一下ansible yum install ansible 2.修改一下ansible的参数以防ssh过去的时候需要首次判断yes  或者no sed -i 's/#host_key_ch ...

  2. Android使用Fragment+ViewPager +TabLayout实现顶部标题栏

    参考资料: https://blog.csdn.net/Tobey_r1/article/details/93221486 项目背景是Android开发新闻APP,实现新闻文本的分类,内容的展示,并实 ...

  3. 树莓派修改默认pi帐号亲测有效

    # 树莓派修改默认pi帐号亲测有效### 1.我的树莓派机型:3B+,系统:Raspbian桌面标准版,连接的屏幕:电视机..###2.打开树莓派LX终端,快捷键:Ctrl+Alt+t ###3.输入 ...

  4. PHP方法的返回值

    不仅是PHP,大部分编程语言的函数或者叫方法,都可以用return来定义方法的返回值.从函数这个叫法来看,本身它就是一个计算操作,因此,计算总会有个结果,如果你在方法体中处理了结果,比如进行了持久化保 ...

  5. ssh 执行 shell脚本执行jps时:-bash: jps: command not found

    转至: https://www.codeleading.com/article/67592908468/ 我构建了hadoop集群.我们一定会写一个shell脚本去每一个节点上去jps,查看每个节点的 ...

  6. dede调用文章内第一张原始图片(非缩略图)的实现方法

    第一步,修改include/extend.func.php文件,最下面插入函数,查询的是文章附加表,如需查询图片集什么的,改表名即可 //取原图地址 function GetFirstImg($arc ...

  7. Linux系列(32) - rpm命令管理之RPM查询(4)

    RPM包默认安装位置 RPM包默认安装路径 /etc/ 配置文件安装目录 /usr/bin/ 可执行的命令安装目录 /usr/lib/ 程序所使用的函数库保存位置 /usr/share/doc/ 基本 ...

  8. docker run 参数

    一.格式 docker run [OPTIONS] IMAGE [COMMAND] [ARG...] 二.OPTIONS 参数 简写, 名称参数 默认参数 描述 --add-host 添加自定义主机到 ...

  9. 软件测试工程师简历要怎么写,才能让HR看到

    作为软件测试的从业者,面试或者被面试都是常有的事. 可是不管怎样,和简历有着理不清的关系,面试官要通过简历了解面试者的基本信息.过往经历等. 面试者希望通过简历把自己最好的一面体现给面试官,所以在这场 ...

  10. Python3入门系列之-----函数

    什么是函数 函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段. 函数能提高应用的模块性,和代码的重复利用率.你已经知道Python提供了许多内建函数,比如print().但你也可以自己 ...