一、 介绍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. 在windows中给git修改默认的编辑器为sublime

    首先,需要配置sublime的为环境变量,这是为了让git能通过命令调用sublime.也可以写一个.bat脚本.然后,让git调用bat脚本也可以 配置环境变量path到subl.exe的目录 脚本 ...

  2. python中reduce filter map lambda函数

    lambda函数 python 使用 lambda 来创建匿名函数,lambda返回值是一个函数的地址,也就是函数对象. 语法:lambda [arg1 [,arg2,.....argn]]:expr ...

  3. String底层使用是char数组还是byte数组

    结论:jdk1.8及以前String底层使用是char[],1.9开始使用byte[] jdk1.8 jdk13

  4. 怎样在Linux中查看apache是用那个httpd.conf

    第一步:找到apache启动命令: [root@WAPBJ01 ~]# ps -ef|grep httpdroot     10575     1  0 19:45 ?        00:00:03 ...

  5. 使用uView UI+UniApp开发微信小程序--判断用户是否登录并跳转

    在<使用uView UI+UniApp开发微信小程序>的随笔中,介绍了基于uView UI+UniApp开发微信小程序的一些基础知识和准备工作,其中也大概介绍了一下基本的登录过程,本篇随笔 ...

  6. css 常用语法

    1.禁止某个元素内的任何选中操作: .classname{ -webkit-user-select: none; -moz-user-select: none; -ms-user-select: no ...

  7. dbus中的数据类型

    DBus中也是类似于静态语言,使用了"强类型"数据格式.在DBus上传递的所有数据都需要声明其对应的类型,下面整理了下,DBus中的数据类型,以及在DBus中声明的数据类型是什么意 ...

  8. 数据结构与算法——克鲁斯卡尔(Kruskal)算法

    目录 应用场景-公交站问题 克鲁斯卡尔算法介绍 克鲁斯卡尔算法图解 克鲁斯卡尔算法分析 如何判断回路? 代码实现 无向图构建 克鲁斯卡尔算法实现 获取一个点的终点解释 应用场景-公交站问题 某城市新增 ...

  9. 数据结构与算法——迪杰斯特拉(Dijkstra)算法

    tip:这个算法真的很难讲解,有些地方只能意会了,多思考多看几遍还是可以弄懂的. 应用场景-最短路径问题 战争时期,胜利乡有 7 个村庄 (A, B, C, D, E, F, G) ,现在有六个邮差, ...

  10. 使用CEF(三)— 从CEF官方Demo源码入手解析CEF架构与CefApp、CefClient对象

    在上文<使用CEF(2)- 基于VS2019编写一个简单CEF样例>中,我们介绍了如何编写一个CEF的样例,在文章中提供了一些代码清单,在这些代码清单中提到了一些CEF的定义的类,例如Ce ...