MyBatis源码分析(三):MyBatis初始化(配置文件读取和解析)
一、 介绍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初始化(配置文件读取和解析)的更多相关文章
- mybatis底层源码分析之--配置文件读取和解析
现在企业级开发中ssm是很常见的技术标配,mybatis比hibernate轻量了很多,而且学习成本相对较低,简单易上手. 那么,问题来了,简单好用的mybatis底层到底是如何实现的呢?都使用了什么 ...
- MyBatis源码分析(1)-MapConfig文件的解析
1.简述 MyBatis是一个优秀的轻ORM框架,由最初的iBatis演化而来,可以方便的完成sql语句的输入输出到java对象之间的相互映射,典型的MyBatis使用的方式如下: String re ...
- mybatis源码分析(二)------------配置文件的解析
这篇文章中,我们将讲解配置文件中 properties,typeAliases,settings和environments这些节点的解析过程. 一 properties的解析 private void ...
- 精尽 MyBatis 源码分析 - MyBatis 初始化(三)之 SQL 初始化(上)
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
- MyBatis 源码分析 - 配置文件解析过程
* 本文速览 由于本篇文章篇幅比较大,所以这里拿出一节对本文进行快速概括.本篇文章对 MyBatis 配置文件中常用配置的解析过程进行了较为详细的介绍和分析,包括但不限于settings,typeAl ...
- 精尽 MyBatis 源码分析 - MyBatis 初始化(一)之加载 mybatis-config.xml
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
- 精尽MyBatis源码分析 - MyBatis初始化(二)之加载Mapper接口与XML映射文件
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
- 精尽MyBatis源码分析 - MyBatis初始化(四)之 SQL 初始化(下)
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
- MyBatis源码分析-MyBatis初始化流程
MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生Map使用简 ...
- 精尽MyBatis源码分析 - SQL执行过程(三)之 ResultSetHandler
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
随机推荐
- Vue项目-初始化之 vue-cli
1.初始化项目 a.Vue CLI 是一个基于 Vue.js 进行快速开发的完整系统,提供: 通过 @vue/cli 搭建交互式的项目脚手架. 通过 @vue/cli + @vue/cli-servi ...
- CodeForce-812B Sagheer, the Hausmeister(DFS)
Sagheer, the Hausmeister CodeForces - 812B 题意:有一栋楼房,里面有很多盏灯没关,为了节约用电小L决定把这些灯都关了. 这楼有 n 层,最左边和最右边有楼梯. ...
- PHP的可变变量与可变函数
什么叫可变.在程序世界中,可变的当然是变量.常量在定义之后都是不可变的,在程序执行过程中,这个常量都是不能修改的.但是变量却不同,它们可以修改.那么可变变量和可变函数又是什么意思呢?很明显,就是用另一 ...
- 让tp6显示详细的错误信息及行号
方法一:默认情况下Ttp6不会显示错误信息,在开发环境下想要查看错误信息需要将Config目录下的app.php文件的show_error_msg改成true 但是这样显示的信息也不够完整, 要看到更 ...
- 网站URL Rewrite(伪静态)设置方法
1.如果您的服务器支持.htaccess,则无需设置,网站根目录下的.htaccess已经设置好规则.规则详情:http://download.destoon.com/rewrite/htaccess ...
- javascript 定时器 timer setTimeout setInterval (js for循环如何等待几秒再循环)
实现一个打点计时器,要求1.从 start 到 end(包含 start 和 end),每隔 100 毫秒 console.log 一个数字,每次数字增幅为 12.返回的对象中需要包含一个 cance ...
- C# 在PPT中添加数学公式
本次内容介绍在C#程序中给PPT幻灯片添加Latex数学公式,添加公式前,首先需要在幻灯片中插入一个Shape形状,在形状的段落中通过方法Paragraphs.AddParagraphFromLate ...
- es相关监控指标梳理
###################ElasticSearch监控指标梳理########################### #author:lugh1 # #date:2021-09-26 # ...
- Python3入门系列之-----看完这一篇文章我终于学会了类
前言 类顾名思义,就是一类事物.或者叫做实例,它用来描述具有共同特征的一类事物.我们在Python中声明类的关键词是class,类还有功能和属性,属性就是这类事物的特征,而功能就是它能做什么,也是就是 ...
- Unity3D组成
从宏观的角度来看,分为七个模块: 1.图形模块(Graphics). 2.物理模块(Physics) 3. 音效模块(Audio) 4.动作模块(Animation) 5.导航模块(Navigatio ...