我们使用mybatis操作数据库都是通过SqlSession的API调用,而创建SqlSession是通过SqlSessionFactory。下面我们就看看SqlSessionFactory的创建过程。

配置文件解析入口

我们看看第一篇文章中的测试方法

  1. public static void main(String[] args) throws IOException {
  2. String resource = "mybatis-config.xml";
  3. InputStream inputStream = Resources.getResourceAsStream(resource);
  4. SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
  5. SqlSession sqlSession = sqlSessionFactory.openSession();
  6. try {
  7. Employee employeeMapper = sqlSession.getMapper(Employee.class);
  8. List<Employee> all = employeeMapper.getAll();
  9. for (Employee item : all)
  10. System.out.println(item);
  11. } finally {
  12. sqlSession.close();
  13. }
  14. }

首先,我们使用 MyBatis 提供的工具类 Resources 加载配置文件,得到一个输入流。然后再通过 SqlSessionFactoryBuilder 对象的build方法构建 SqlSessionFactory 对象。所以这里的 build 方法是我们分析配置文件解析过程的入口方法。我们看看build里面是代码:

  1. public SqlSessionFactory build(InputStream inputStream) {
  2. // 调用重载方法
  3. return build(inputStream, null, null);
  4. }
  5.  
  6. public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
  7. try {
  8. // 创建配置文件解析器
  9. XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
  10. // 调用 parser.parse() 方法解析配置文件,生成 Configuration 对象
  11. return build(parser.parse());
  12. } catch (Exception e) {
  13. throw ExceptionFactory.wrapException("Error building SqlSession.", e);
  14. } finally {
  15. ErrorContext.instance().reset();
  16. try {
  17. inputStream.close();
  18. } catch (IOException e) {
  19. // Intentionally ignore. Prefer previous error.
  20. }
  21. }
  22. }
  23.  
  24. public SqlSessionFactory build(Configuration config) {
  25. // 创建 DefaultSqlSessionFactory,将解析配置文件后生成的Configuration传入
  26. return new DefaultSqlSessionFactory(config);
  27. }

SqlSessionFactory是通过SqlSessionFactoryBuilder的build方法创建的,build方法内部是通过一个XMLConfigBuilder对象解析mybatis-config.xml文件生成一个Configuration对象。XMLConfigBuilder从名字可以看出是解析Mybatis配置文件的,其实它是继承了一个父类BaseBuilder,其每一个子类多是以XMLXXXXXBuilder命名的,也就是其子类都对应解析一种xml文件或xml文件中一种元素。

我们看看XMLConfigBuilder的构造方法:

  1. private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
  2. super(new Configuration());
  3. ErrorContext.instance().resource("SQL Mapper Configuration");
  4. this.configuration.setVariables(props);
  5. this.parsed = false;
  6. this.environment = environment;
  7. this.parser = parser;
  8. }

可以看到调用了父类的构造方法,并传入一个new Configuration()对象,这个对象也就是最终的Mybatis配置对象

我们先来看看其基类BaseBuilder

  1. public abstract class BaseBuilder {
  2. protected final Configuration configuration;
  3. protected final TypeAliasRegistry typeAliasRegistry;
  4. protected final TypeHandlerRegistry typeHandlerRegistry;
  5.  
  6. public BaseBuilder(Configuration configuration) {
  7. this.configuration = configuration;
  8. this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
  9. this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
  10. }
  11. ....
  12. }

BaseBuilder中只有三个成员变量,而typeAliasRegistry和typeHandlerRegistry都是直接从Configuration的成员变量获得的,接着我们看看Configuration这个类

Configuration类位于mybatis包的org.apache.ibatis.session目录下,其属性就是对应于mybatis的全局配置文件mybatis-config.xml的配置,将XML配置中的内容解析赋值到Configuration对象中。

由于XML配置项有很多,所以Configuration类的属性也很多。先来看下Configuration对于的XML配置文件示例:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2.  
  3. <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
  4. <!-- 全局配置顶级节点 -->
  5. <configuration>
  6.  
  7. <!-- 属性配置,读取properties中的配置文件 -->
  8. <properties resource="db.propertis">
  9. <property name="XXX" value="XXX"/>
  10. </properties>
  11.  
  12. <!-- 类型别名 -->
  13. <typeAliases>
  14. <!-- 在用到User类型的时候,可以直接使用别名,不需要输入User类的全部路径 -->
  15. <typeAlias type="com.luck.codehelp.entity.User" alias="user"/>
  16. </typeAliases>
  17.  
  18. <!-- 类型处理器 -->
  19. <typeHandlers>
  20. <!-- 类型处理器的作用是完成JDBC类型和java类型的转换,mybatis默认已经由了很多类型处理器,正常无需自定义-->
  21. </typeHandlers>
  22.  
  23. <!-- 对象工厂 -->
  24. <!-- mybatis创建结果对象的新实例时,会通过对象工厂来完成,mybatis有默认的对象工厂,正常无需配置 -->
  25. <objectFactory type=""></objectFactory>
  26.  
  27. <!-- 插件 -->
  28. <plugins>
  29. <!-- 可以自定义拦截器通过plugin标签加入 -->
  30. <plugin interceptor="com.lucky.interceptor.MyPlugin"></plugin>
  31. </plugins>
  32.  
  33. <!-- 全局配置参数 -->
  34. <settings>
  35. <setting name="cacheEnabled" value="false" />
  36. <setting name="useGeneratedKeys" value="true" /><!-- 是否自动生成主键 -->
  37. <setting name="defaultExecutorType" value="REUSE" />
  38. <setting name="lazyLoadingEnabled" value="true"/><!-- 延迟加载标识 -->
  39. <setting name="aggressiveLazyLoading" value="true"/><!--有延迟加载属性的对象是否延迟加载 -->
  40. <setting name="multipleResultSetsEnabled" value="true"/><!-- 是否允许单个语句返回多个结果集 -->
  41. <setting name="useColumnLabel" value="true"/><!-- 使用列标签而不是列名 -->
  42. <setting name="autoMappingBehavior" value="PARTIAL"/><!-- 指定mybatis如何自动映射列到字段属性;NONE:自动映射;PARTIAL:只会映射结果没有嵌套的结果;FULL:可以映射任何复杂的结果 -->
  43. <setting name="defaultExecutorType" value="SIMPLE"/><!-- 默认执行器类型 -->
  44. <setting name="defaultFetchSize" value=""/>
  45. <setting name="defaultStatementTimeout" value="5"/><!-- 驱动等待数据库相应的超时时间 ,单位是秒-->
  46. <setting name="safeRowBoundsEnabled" value="false"/><!-- 是否允许使用嵌套语句RowBounds -->
  47. <setting name="safeResultHandlerEnabled" value="true"/>
  48. <setting name="mapUnderscoreToCamelCase" value="false"/><!-- 下划线列名是否自动映射到驼峰属性:如user_id映射到userId -->
  49. <setting name="localCacheScope" value="SESSION"/><!-- 本地缓存(session是会话级别) -->
  50. <setting name="jdbcTypeForNull" value="OTHER"/><!-- 数据为空值时,没有特定的JDBC类型的参数的JDBC类型 -->
  51. <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/><!-- 指定触发延迟加载的对象的方法 -->
  52. <setting name="callSettersOnNulls" value="false"/><!--如果setter方法或map的put方法,如果检索到的值为null时,数据是否有用 -->
  53. <setting name="logPrefix" value="XXXX"/><!-- mybatis日志文件前缀字符串 -->
  54. <setting name="logImpl" value="SLF4J"/><!-- mybatis日志的实现类 -->
  55. <setting name="proxyFactory" value="CGLIB"/><!-- mybatis代理工具 -->
  56. </settings>
  57.  
  58. <!-- 环境配置集合 -->
  59. <environments default="development">
  60. <environment id="development">
  61. <transactionManager type="JDBC"/><!-- 事务管理器 -->
  62. <dataSource type="POOLED"><!-- 数据库连接池 -->
  63. <property name="driver" value="com.mysql.jdbc.Driver" />
  64. <property name="url" value="jdbc:mysql://localhost:3306/test?useUnicode=true&amp;characterEncoding=UTF-8" />
  65. <property name="username" value="root" />
  66. <property name="password" value="root" />
  67. </dataSource>
  68. </environment>
  69. </environments>
  70.  
  71. <!-- mapper文件映射配置 -->
  72. <mappers>
  73. <mapper resource="com/luck/codehelp/mapper/UserMapper.xml"/>
  74. </mappers>
  75. </configuration>

而对于XML的配置,Configuration类的属性是和XML配置对应的。Configuration类属性如下:

  1. public class Configuration {
  2. protected Environment environment;//运行环境
  3.  
  4. protected boolean safeRowBoundsEnabled = false;
  5. protected boolean safeResultHandlerEnabled = true;
  6. protected boolean mapUnderscoreToCamelCase = false;
  7. protected boolean aggressiveLazyLoading = true; //true:有延迟加载属性的对象被调用时完全加载任意属性;false:每个属性按需要加载
  8. protected boolean multipleResultSetsEnabled = true;//是否允许多种结果集从一个单独的语句中返回
  9. protected boolean useGeneratedKeys = false;//是否支持自动生成主键
  10. protected boolean useColumnLabel = true;//是否使用列标签
  11. protected boolean cacheEnabled = true;//是否使用缓存标识
  12. protected boolean callSettersOnNulls = false;//
  13. protected boolean useActualParamName = true;
  14.  
  15. protected String logPrefix;
  16. protected Class <? extends Log> logImpl;
  17. protected Class <? extends VFS> vfsImpl;
  18. protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
  19. protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
  20. protected Set<String> lazyLoadTriggerMethods = new HashSet<String>(Arrays.asList(new String[] { "equals", "clone", "hashCode", "toString" }));
  21. protected Integer defaultStatementTimeout;
  22. protected Integer defaultFetchSize;
  23. protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
  24. protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;//指定mybatis如果自动映射列到字段和属性,PARTIAL会自动映射简单的没有嵌套的结果,FULL会自动映射任意复杂的结果
  25. protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;
  26.  
  27. protected Properties variables = new Properties();
  28. protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
  29. protected ObjectFactory objectFactory = new DefaultObjectFactory();
  30. protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
  31.  
  32. protected boolean lazyLoadingEnabled = false;//是否延时加载,false则表示所有关联对象即使加载,true表示延时加载
  33. protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL
  34.  
  35. protected String databaseId;
  36.  
  37. protected Class<?> configurationFactory;
  38.  
  39. protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
  40. protected final InterceptorChain interceptorChain = new InterceptorChain();
  41. protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
  42. protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
  43. protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
  44.  
  45. protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
  46. protected final Map<String, Cache> caches = new StrictMap<Cache>("Caches collection");
  47. protected final Map<String, ResultMap> resultMaps = new StrictMap<ResultMap>("Result Maps collection");
  48. protected final Map<String, ParameterMap> parameterMaps = new StrictMap<ParameterMap>("Parameter Maps collection");
  49. protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<KeyGenerator>("Key Generators collection");
  50.  
  51. protected final Set<String> loadedResources = new HashSet<String>(); //已经加载过的resource(mapper)
  52. protected final Map<String, XNode> sqlFragments = new StrictMap<XNode>("XML fragments parsed from previous mappers");
  53.  
  54. protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<XMLStatementBuilder>();
  55. protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<CacheRefResolver>();
  56. protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<ResultMapResolver>();
  57. protected final Collection<MethodResolver> incompleteMethods = new LinkedList<MethodResolver>();
  58.  
  59. protected final Map<String, String> cacheRefMap = new HashMap<String, String>();
  60.  
  61. //其他方法略
  62. }

加载的过程是SqlSessionFactoryBuilder根据xml配置的文件流,通过XMLConfigBuilder的parse方法进行解析得到一个Configuration对象,我们再看看其构造函数

  1. public Configuration() {
  2. this.safeRowBoundsEnabled = false;
  3. this.safeResultHandlerEnabled = true;
  4. this.mapUnderscoreToCamelCase = false;
  5. this.aggressiveLazyLoading = true;
  6. this.multipleResultSetsEnabled = true;
  7. this.useGeneratedKeys = false;
  8. this.useColumnLabel = true;
  9. this.cacheEnabled = true;
  10. this.callSettersOnNulls = false;
  11. this.localCacheScope = LocalCacheScope.SESSION;
  12. this.jdbcTypeForNull = JdbcType.OTHER;
  13. this.lazyLoadTriggerMethods = new HashSet(Arrays.asList("equals", "clone", "hashCode", "toString"));
  14. this.defaultExecutorType = ExecutorType.SIMPLE;
  15. this.autoMappingBehavior = AutoMappingBehavior.PARTIAL;
  16. this.autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;
  17. this.variables = new Properties();
  18. this.reflectorFactory = new DefaultReflectorFactory();
  19. this.objectFactory = new DefaultObjectFactory();
  20. this.objectWrapperFactory = new DefaultObjectWrapperFactory();
  21. this.mapperRegistry = new MapperRegistry(this);
  22. this.lazyLoadingEnabled = false;
  23. this.proxyFactory = new JavassistProxyFactory();
  24. this.interceptorChain = new InterceptorChain();
  25. this.typeHandlerRegistry = new TypeHandlerRegistry();
  26. this.typeAliasRegistry = new TypeAliasRegistry();
  27. this.languageRegistry = new LanguageDriverRegistry();
  28. this.mappedStatements = new Configuration.StrictMap("Mapped Statements collection");
  29. this.caches = new Configuration.StrictMap("Caches collection");
  30. this.resultMaps = new Configuration.StrictMap("Result Maps collection");
  31. this.parameterMaps = new Configuration.StrictMap("Parameter Maps collection");
  32. this.keyGenerators = new Configuration.StrictMap("Key Generators collection");
  33. this.loadedResources = new HashSet();
  34. this.sqlFragments = new Configuration.StrictMap("XML fragments parsed from previous mappers");
  35. this.incompleteStatements = new LinkedList();
  36. this.incompleteCacheRefs = new LinkedList();
  37. this.incompleteResultMaps = new LinkedList();
  38. this.incompleteMethods = new LinkedList();
  39. this.cacheRefMap = new HashMap();
  40. this.typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
  41. this.typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
  42. this.typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
  43. this.typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
  44. this.typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
  45. this.typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
  46. this.typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
  47. this.typeAliasRegistry.registerAlias("LRU", LruCache.class);
  48. this.typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
  49. this.typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
  50. this.typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);
  51. this.typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
  52. this.typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);
  53. this.typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
  54. this.typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
  55. this.typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
  56. this.typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
  57. this.typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
  58. this.typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
  59. this.typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);
  60. this.typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
  61. this.typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
  62. this.languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
  63. this.languageRegistry.register(RawLanguageDriver.class);
  64. }

我们看到第26行this.typeAliasRegistry = new TypeAliasRegistry();,并且第40到61行向 typeAliasRegistry 注册了很多别名,我们看看TypeAliasRegistry

  1. public class TypeAliasRegistry {
  2. private final Map<String, Class<?>> TYPE_ALIASES = new HashMap();
  3. public TypeAliasRegistry() {
  4. this.registerAlias("string", String.class);
  5. this.registerAlias("byte", Byte.class);
  6. this.registerAlias("long", Long.class);
  7. this.registerAlias("short", Short.class);
  8. this.registerAlias("int", Integer.class);
  9. this.registerAlias("integer", Integer.class);
  10. this.registerAlias("double", Double.class);
  11. this.registerAlias("float", Float.class);
  12. this.registerAlias("boolean", Boolean.class);
  13. this.registerAlias("byte[]", Byte[].class);
  14. this.registerAlias("long[]", Long[].class);
  15. this.registerAlias("short[]", Short[].class);
  16. this.registerAlias("int[]", Integer[].class);
  17. this.registerAlias("integer[]", Integer[].class);
  18. this.registerAlias("double[]", Double[].class);
  19. this.registerAlias("float[]", Float[].class);
  20. this.registerAlias("boolean[]", Boolean[].class);
  21. this.registerAlias("_byte", Byte.TYPE);
  22. this.registerAlias("_long", Long.TYPE);
  23. this.registerAlias("_short", Short.TYPE);
  24. this.registerAlias("_int", Integer.TYPE);
  25. this.registerAlias("_integer", Integer.TYPE);
  26. this.registerAlias("_double", Double.TYPE);
  27. this.registerAlias("_float", Float.TYPE);
  28. this.registerAlias("_boolean", Boolean.TYPE);
  29. this.registerAlias("_byte[]", byte[].class);
  30. this.registerAlias("_long[]", long[].class);
  31. this.registerAlias("_short[]", short[].class);
  32. this.registerAlias("_int[]", int[].class);
  33. this.registerAlias("_integer[]", int[].class);
  34. this.registerAlias("_double[]", double[].class);
  35. this.registerAlias("_float[]", float[].class);
  36. this.registerAlias("_boolean[]", boolean[].class);
  37. this.registerAlias("date", Date.class);
  38. this.registerAlias("decimal", BigDecimal.class);
  39. this.registerAlias("bigdecimal", BigDecimal.class);
  40. this.registerAlias("biginteger", BigInteger.class);
  41. this.registerAlias("object", Object.class);
  42. this.registerAlias("date[]", Date[].class);
  43. this.registerAlias("decimal[]", BigDecimal[].class);
  44. this.registerAlias("bigdecimal[]", BigDecimal[].class);
  45. this.registerAlias("biginteger[]", BigInteger[].class);
  46. this.registerAlias("object[]", Object[].class);
  47. this.registerAlias("map", Map.class);
  48. this.registerAlias("hashmap", HashMap.class);
  49. this.registerAlias("list", List.class);
  50. this.registerAlias("arraylist", ArrayList.class);
  51. this.registerAlias("collection", Collection.class);
  52. this.registerAlias("iterator", Iterator.class);
  53. this.registerAlias("ResultSet", ResultSet.class);
  54. }
  55.  
  56. public void registerAliases(String packageName) {
  57. this.registerAliases(packageName, Object.class);
  58. }
  59. //略
  60. }

其实TypeAliasRegistry里面有一个HashMap,并且在TypeAliasRegistry的构造器中注册很多别名到这个hashMap中,好了,到现在我们只是创建了一个 XMLConfigBuilder,在其构造器中我们创建了一个 Configuration 对象,接下来我们看看将mybatis-config.xml解析成Configuration中对应的属性,也就是parser.parse()方法:

XMLConfigBuilder

  1. public Configuration parse() {
  2. if (parsed) {
  3. throw new BuilderException("Each XMLConfigBuilder can only be used once.");
  4. }
  5. parsed = true;
  6. // 解析配置
  7. parseConfiguration(parser.evalNode("/configuration"));
  8. return configuration;
  9. }

我们看看第7行,注意一个 xpath 表达式 - /configuration。这个表达式代表的是 MyBatis 的<configuration/>标签,这里选中这个标签,并传递给parseConfiguration方法。我们继续跟下去。

  1. private void parseConfiguration(XNode root) {
  2. try {
  3. // 解析 properties 配置
  4. propertiesElement(root.evalNode("properties"));
  5. // 解析 settings 配置,并将其转换为 Properties 对象
  6. Properties settings = settingsAsProperties(root.evalNode("settings"));
  7. // 加载 vfs
  8. loadCustomVfs(settings);
  9.  
  10. // 解析 typeAliases 配置
  11. typeAliasesElement(root.evalNode("typeAliases"));
  12. // 解析 plugins 配置
  13. pluginElement(root.evalNode("plugins"));
  14. // 解析 objectFactory 配置
  15. objectFactoryElement(root.evalNode("objectFactory"));
  16.  
  17. // 解析 objectWrapperFactory 配置
  18. objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
  19.  
  20. // 解析 reflectorFactory 配置
  21. reflectorFactoryElement(root.evalNode("reflectorFactory"));
  22.  
  23. // settings 中的信息设置到 Configuration 对象中
  24. settingsElement(settings);
  25. // 解析 environments 配置
  26. environmentsElement(root.evalNode("environments"));
  27. // 解析 databaseIdProvider,获取并设置 databaseId 到 Configuration 对象
  28. databaseIdProviderElement(root.evalNode("databaseIdProvider"));
  29.  
  30. // 解析 typeHandlers 配置
  31. typeHandlerElement(root.evalNode("typeHandlers"));
  32.  
  33. // 解析 mappers 配置
  34. mapperElement(root.evalNode("mappers"));
  35. } catch (Exception e) {
  36. throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  37. }
  38. }

解析 properties 配置

先来看一下 properties 节点的配置内容。如下:

  1. <properties resource="db.properties">
  2. <property name="username" value="root"/>
  3. <property name="password" value="123456"/>
  4. </properties>

我为 properties 节点配置了一个 resource 属性,以及两个子节点。接着我们看看propertiesElement的逻辑

  1. private void propertiesElement(XNode context) throws Exception {
  2. if (context != null) {
  3. // 解析 propertis 的子节点,并将这些节点内容转换为属性对象 Properties
  4. Properties defaults = context.getChildrenAsProperties();
  5. // 获取 propertis 节点中的 resource 和 url 属性值
  6. String resource = context.getStringAttribute("resource");
  7. String url = context.getStringAttribute("url");
  8.  
  9. // 两者都不用空,则抛出异常
  10. if (resource != null && url != null) {
  11. throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other.");
  12. }
  13. if (resource != null) {
  14. // 从文件系统中加载并解析属性文件
  15. defaults.putAll(Resources.getResourceAsProperties(resource));
  16. } else if (url != null) {
  17. // 通过 url 加载并解析属性文件
  18. defaults.putAll(Resources.getUrlAsProperties(url));
  19. }
  20. Properties vars = configuration.getVariables();
  21. if (vars != null) {
  22. defaults.putAll(vars);
  23. }
  24. parser.setVariables(defaults);
  25. // 将属性值设置到 configuration 中
  26. configuration.setVariables(defaults);
  27. }
  28. }
  29.  
  30. public Properties getChildrenAsProperties() {
  31. //创建一个Properties对象
  32. Properties properties = new Properties();
  33. // 获取并遍历子节点
  34. for (XNode child : getChildren()) {
  35. // 获取 property 节点的 name 和 value 属性
  36. String name = child.getStringAttribute("name");
  37. String value = child.getStringAttribute("value");
  38. if (name != null && value != null) {
  39. // 设置属性到属性对象中
  40. properties.setProperty(name, value);
  41. }
  42. }
  43. return properties;
  44. }
  45.  
  46. // -☆- XNode
  47. public List<XNode> getChildren() {
  48. List<XNode> children = new ArrayList<XNode>();
  49. // 获取子节点列表
  50. NodeList nodeList = node.getChildNodes();
  51. if (nodeList != null) {
  52. for (int i = 0, n = nodeList.getLength(); i < n; i++) {
  53. Node node = nodeList.item(i);
  54. if (node.getNodeType() == Node.ELEMENT_NODE) {
  55. children.add(new XNode(xpathParser, node, variables));
  56. }
  57. }
  58. }
  59. return children;
  60. }

解析properties主要分三个步骤:

  1. 解析 properties 节点的子节点,并将解析结果设置到 Properties 对象中。
  2. 从文件系统或通过网络读取属性配置,这取决于 properties 节点的 resource 和 url 是否为空。
  3. 将解析出的属性对象设置到 XPathParser 和 Configuration 对象中。

需要注意的是,propertiesElement 方法是先解析 properties 节点的子节点内容,后再从文件系统或者网络读取属性配置,并将所有的属性及属性值都放入到 defaults 属性对象中。这就会存在同名属性覆盖的问题,也就是从文件系统,或者网络上读取到的属性及属性值会覆盖掉 properties 子节点中同名的属性和及值。

解析 settings 配置

settings 节点的解析过程

下面先来看一个settings比较简单的配置,如下:

  1. <settings>
  2. <setting name="cacheEnabled" value="true"/>
  3. <setting name="lazyLoadingEnabled" value="true"/>
  4. <setting name="autoMappingBehavior" value="PARTIAL"/>
  5. </settings>

接着来看看settingsAsProperties

  1. private Properties settingsAsProperties(XNode context) {
  2. if (context == null) {
  3. return new Properties();
  4. }
  5. // 获取 settings 子节点中的内容,解析成Properties,getChildrenAsProperties 方法前面已分析过
  6. Properties props = context.getChildrenAsProperties();
  7.  
  8. // 创建 Configuration 类的“元信息”对象
  9. MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
  10. for (Object key : props.keySet()) {
  11. // 检测 Configuration 中是否存在相关属性,不存在则抛出异常
  12. if (!metaConfig.hasSetter(String.valueOf(key))) {
  13. throw new BuilderException("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive).");
  14. }
  15. }
  16. return props;
  17. }

设置 settings 配置到 Configuration 中

接着我们看看将 settings 配置设置到 Configuration 对象中的过程。如下:

  1. private void settingsElement(Properties props) throws Exception {
  2. // 设置 autoMappingBehavior 属性,默认值为 PARTIAL
  3. configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
  4. configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
  5. // 设置 cacheEnabled 属性,默认值为 true
  6. configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
  7.  
  8. // 省略部分代码
  9.  
  10. // 解析默认的枚举处理器
  11. Class<? extends TypeHandler> typeHandler = (Class<? extends TypeHandler>)resolveClass(props.getProperty("defaultEnumTypeHandler"));
  12. // 设置默认枚举处理器
  13. configuration.setDefaultEnumTypeHandler(typeHandler);
  14. configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
  15. configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
  16.  
  17. // 省略部分代码
  18. }

上面代码处理调用 Configuration 的 setter 方法

解析 typeAliases 配置

在 MyBatis 中,可以为我们自己写的有些类定义一个别名。这样在使用的时候,我们只需要输入别名即可,无需再把全限定的类名写出来。在 MyBatis 中,我们有两种方式进行别名配置。第一种是仅配置包名,让 MyBatis 去扫描包中的类型,并根据类型得到相应的别名

  1. <typeAliases>
  2. <package name="com.mybatis.model"/>
  3. </typeAliases>

第二种方式是通过手动的方式,明确为某个类型配置别名。这种方式的配置如下:

  1. <typeAliases>
  2. <typeAlias alias="employe" type="com.mybatis.model.Employe" />
  3. <typeAlias type="com.mybatis.model.User" />
  4. </typeAliases>

下面我们来看一下两种不同的别名配置是怎样解析的。代码如下:

XMLConfigBuilder

  1. private void typeAliasesElement(XNode parent) {
  2. if (parent != null) {
  3. for (XNode child : parent.getChildren()) {
  4. // 从指定的包中解析别名和类型的映射
  5. if ("package".equals(child.getName())) {
  6. String typeAliasPackage = child.getStringAttribute("name");
  7. configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
  8. // 从 typeAlias 节点中解析别名和类型的映射
  9. } else {
  10. // 获取 alias 和 type 属性值,alias 不是必填项,可为空
  11. String alias = child.getStringAttribute("alias");
  12. String type = child.getStringAttribute("type");
  13. try {
  14. // 加载 type 对应的类型
  15. Class<?> clazz = Resources.classForName(type);
  16.  
  17. // 注册别名到类型的映射
  18. if (alias == null) {
  19. typeAliasRegistry.registerAlias(clazz);
  20. } else {
  21. typeAliasRegistry.registerAlias(alias, clazz);
  22. }
  23. } catch (ClassNotFoundException e) {
  24. throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
  25. }
  26. }
  27. }
  28. }
  29. }

我们看到通过包扫描和手动注册时通过子节点名称是否package来判断的

从 typeAlias 节点中解析并注册别名

在别名的配置中,type属性是必须要配置的,而alias属性则不是必须的。

  1. private final Map<String, Class<?>> TYPE_ALIASES = new HashMap<String, Class<?>>();
  2.  
  3. public void registerAlias(Class<?> type) {
  4. // 获取全路径类名的简称
  5. String alias = type.getSimpleName();
  6. Alias aliasAnnotation = type.getAnnotation(Alias.class);
  7. if (aliasAnnotation != null) {
  8. // 从注解中取出别名
  9. alias = aliasAnnotation.value();
  10. }
  11. // 调用重载方法注册别名和类型映射
  12. registerAlias(alias, type);
  13. }
  14.  
  15. public void registerAlias(String alias, Class<?> value) {
  16. if (alias == null) {
  17. throw new TypeException("The parameter alias cannot be null");
  18. }
  19. // 将别名转成小写
  20. String key = alias.toLowerCase(Locale.ENGLISH);
  21. /*
  22. * 如果 TYPE_ALIASES 中存在了某个类型映射,这里判断当前类型与映射中的类型是否一致,
  23. * 不一致则抛出异常,不允许一个别名对应两种类型
  24. */
  25. if (TYPE_ALIASES.containsKey(key) && TYPE_ALIASES.get(key) != null && !TYPE_ALIASES.get(key).equals(value)) {
  26. throw new TypeException(
  27. "The alias '" + alias + "' is already mapped to the value '" + TYPE_ALIASES.get(key).getName() + "'.");
  28. }
  29. // 缓存别名到类型映射
  30. TYPE_ALIASES.put(key, value);
  31. }

若用户为明确配置 alias 属性,MyBatis 会使用类名的小写形式作为别名。比如,全限定类名com.mybatis.model.User的别名为user。若类中有@Alias注解,则从注解中取值作为别名。

从指定的包中解析并注册别名

  1. public void registerAliases(String packageName) {
  2. registerAliases(packageName, Object.class);
  3. }
  4.  
  5. public void registerAliases(String packageName, Class<?> superType) {
  6. ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
  7. /*
  8. * 查找包下的父类为 Object.class 的类。
  9. * 查找完成后,查找结果将会被缓存到resolverUtil的内部集合中。
  10. */
  11. resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
  12. // 获取查找结果
  13. Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();
  14. for (Class<?> type : typeSet) {
  15. // 忽略匿名类,接口,内部类
  16. if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {
  17. // 为类型注册别名
  18. registerAlias(type);
  19. }
  20. }
  21. }

主要分为两个步骤:

  1. 查找指定包下的所有类
  2. 遍历查找到的类型集合,为每个类型注册别名

我们看看查找指定包下的所有类

  1. private Set<Class<? extends T>> matches = new HashSet();
  2.  
  3. public ResolverUtil<T> find(ResolverUtil.Test test, String packageName) {
  4. //将包名转换成文件路径
  5. String path = this.getPackagePath(packageName);
  6. try {
  7. //通过 VFS(虚拟文件系统)获取指定包下的所有文件的路径名,比如com/mybatis/model/Employe.class
  8. List<String> children = VFS.getInstance().list(path);
  9. Iterator i$ = children.iterator();
  10.  
  11. while(i$.hasNext()) {
  12. String child = (String)i$.next();
  13. //以.class结尾的文件就加入到Set集合中
  14. if (child.endsWith(".class")) {
  15. this.addIfMatching(test, child);
  16. }
  17. }
  18. } catch (IOException var7) {
  19. log.error("Could not read package: " + packageName, var7);
  20. }
  21.  
  22. return this;
  23. }
  24.  
  25. protected String getPackagePath(String packageName) {
  26. //将包名转换成文件路径
  27. return packageName == null ? null : packageName.replace('.', '/');
  28. }
  29.  
  30. protected void addIfMatching(ResolverUtil.Test test, String fqn) {
  31. try {
  32. //将路径名转成全限定的类名,通过类加载器加载类名,比如com.mybatis.model.Employe.class
  33. String externalName = fqn.substring(0, fqn.indexOf(46)).replace('/', '.');
  34. ClassLoader loader = this.getClassLoader();
  35. if (log.isDebugEnabled()) {
  36. log.debug("Checking to see if class " + externalName + " matches criteria [" + test + "]");
  37. }
  38.  
  39. Class<?> type = loader.loadClass(externalName);
  40. if (test.matches(type)) {
  41. //加入到matches集合中
  42. this.matches.add(type);
  43. }
  44. } catch (Throwable var6) {
  45. log.warn("Could not examine class '" + fqn + "'" + " due to a " + var6.getClass().getName() + " with message: " + var6.getMessage());
  46. }
  47.  
  48. }

主要有以下几步:

  1. 通过 VFS(虚拟文件系统)获取指定包下的所有文件的路径名,比如 com/mybatis/model/Employe.class
  2. 筛选以.class结尾的文件名
  3. 将路径名转成全限定的类名,通过类加载器加载类名
  4. 对类型进行匹配,若符合匹配规则,则将其放入内部集合中

这里我们要注意,在前面我们分析Configuration对象的创建时,就已经默认注册了很多别名,可以回到文章开头看看。

解析 plugins 配置

插件是 MyBatis 提供的一个拓展机制,通过插件机制我们可在 SQL 执行过程中的某些点上做一些自定义操作。比喻分页插件,在SQL执行之前动态拼接语句,我们后面会单独来讲插件机制,先来了解插件的配置。如下:

  1. <plugins>
  2. <plugin interceptor="com.github.pagehelper.PageInterceptor">
  3. <property name="helperDialect" value="mysql"/>
  4. </plugin>
  5. </plugins>

解析过程分析如下:

  1. private void pluginElement(XNode parent) throws Exception {
  2. if (parent != null) {
  3. for (XNode child : parent.getChildren()) {
  4. String interceptor = child.getStringAttribute("interceptor");
  5. // 获取配置信息
  6. Properties properties = child.getChildrenAsProperties();
  7. // 解析拦截器的类型,并创建拦截器
  8. Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
  9. // 设置属性
  10. interceptorInstance.setProperties(properties);
  11. // 添加拦截器到 Configuration 中
  12. configuration.addInterceptor(interceptorInstance);
  13. }
  14. }
  15. }

首先是获取配置,然后再解析拦截器类型,并实例化拦截器。最后向拦截器中设置属性,并将拦截器添加到 Configuration 中。

  1. private void pluginElement(XNode parent) throws Exception {
  2. if (parent != null) {
  3. for (XNode child : parent.getChildren()) {
  4. String interceptor = child.getStringAttribute("interceptor");
  5. // 获取配置信息
  6. Properties properties = child.getChildrenAsProperties();
  7. // 解析拦截器的类型,并实例化拦截器
  8. Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
  9. // 设置属性
  10. interceptorInstance.setProperties(properties);
  11. // 添加拦截器到 Configuration 中
  12. configuration.addInterceptor(interceptorInstance);
  13. }
  14. }
  15. }
  16.  
  17. public void addInterceptor(Interceptor interceptor) {
  18. //添加到Configuration的interceptorChain属性中
  19. this.interceptorChain.addInterceptor(interceptor);
  20. }

我们来看看InterceptorChain

  1. public class InterceptorChain {
  2. private final List<Interceptor> interceptors = new ArrayList();
  3.  
  4. public InterceptorChain() {
  5. }
  6.  
  7. public Object pluginAll(Object target) {
  8. Interceptor interceptor;
  9. for(Iterator i$ = this.interceptors.iterator(); i$.hasNext(); target = interceptor.plugin(target)) {
  10. interceptor = (Interceptor)i$.next();
  11. }
  12.  
  13. return target;
  14. }
  15.  
  16. public void addInterceptor(Interceptor interceptor) {
  17. this.interceptors.add(interceptor);
  18. }
  19.  
  20. public List<Interceptor> getInterceptors() {
  21. return Collections.unmodifiableList(this.interceptors);
  22. }
  23. }

实际上是一个 interceptors 集合,关于插件的原理我们后面再讲。

解析 environments 配置

在 MyBatis 中,事务管理器和数据源是配置在 environments 中的。它们的配置大致如下:

  1. <environments default="development">
  2. <environment id="development">
  3. <transactionManager type="JDBC"/>
  4. <dataSource type="POOLED">
  5. <property name="driver" value="${jdbc.driver}"/>
  6. <property name="url" value="${jdbc.url}"/>
  7. <property name="username" value="${jdbc.username}"/>
  8. <property name="password" value="${jdbc.password}"/>
  9. </dataSource>
  10. </environment>
  11. </environments>

我们来看看environmentsElement方法

  1. private void environmentsElement(XNode context) throws Exception {
  2. if (context != null) {
  3. if (environment == null) {
  4. // 获取 default 属性
  5. environment = context.getStringAttribute("default");
  6. }
  7. for (XNode child : context.getChildren()) {
  8. // 获取 id 属性
  9. String id = child.getStringAttribute("id");
  10. /*
  11. * 检测当前 environment 节点的 id 与其父节点 environments 的属性 default
  12. * 内容是否一致,一致则返回 true,否则返回 false
  13. * 将其default属性值与子元素environment的id属性值相等的子元素设置为当前使用的Environment对象
  14. */
  15. if (isSpecifiedEnvironment(id)) {
  16. // 将environment中的transactionManager标签转换为TransactionFactory对象
  17. TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
  18. // 将environment中的dataSource标签转换为DataSourceFactory对象
  19. DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
  20. // 创建 DataSource 对象
  21. DataSource dataSource = dsFactory.getDataSource();
  22. Environment.Builder environmentBuilder = new Environment.Builder(id)
  23. .transactionFactory(txFactory)
  24. .dataSource(dataSource);
  25. // 构建 Environment 对象,并设置到 configuration 中
  26. configuration.setEnvironment(environmentBuilder.build());
  27. }
  28. }
  29. }
  30. }

看看TransactionFactory和 DataSourceFactory的获取

  1. private TransactionFactory transactionManagerElement(XNode context) throws Exception {
  2. if (context != null) {
  3. String type = context.getStringAttribute("type");
  4. Properties props = context.getChildrenAsProperties();
  5. //通过别名获取Class,并实例化
  6. TransactionFactory factory = (TransactionFactory)this.resolveClass(type).newInstance();
  7. factory.setProperties(props);
  8. return factory;
  9. } else {
  10. throw new BuilderException("Environment declaration requires a TransactionFactory.");
  11. }
  12. }
  13.  
  14. private DataSourceFactory dataSourceElement(XNode context) throws Exception {
  15. if (context != null) {
  16. String type = context.getStringAttribute("type");
  17. //通过别名获取Class,并实例化
  18. Properties props = context.getChildrenAsProperties();
  19. DataSourceFactory factory = (DataSourceFactory)this.resolveClass(type).newInstance();
  20. factory.setProperties(props);
  21. return factory;
  22. } else {
  23. throw new BuilderException("Environment declaration requires a DataSourceFactory.");
  24. }
  25. }

<transactionManager type="JDBC"/>中type有"JDBC"、"MANAGED"这两种配置,而我们前面Configuration中默认注册的别名中有对应的JdbcTransactionFactory.class、ManagedTransactionFactory.class这两个TransactionFactory

<dataSource type="POOLED">中type有"JNDI"、"POOLED"、"UNPOOLED"这三种配置,默认注册的别名中有对应的JndiDataSourceFactory.class、PooledDataSourceFactory.class、UnpooledDataSourceFactory.class这三个DataSourceFactory

而我们的environment配置中transactionManager type="JDBC"和dataSource type="POOLED",则生成的transactionManager为JdbcTransactionFactory,DataSourceFactory为PooledDataSourceFactory

我们来看看PooledDataSourceFactory和UnpooledDataSourceFactory

  1. public class UnpooledDataSourceFactory implements DataSourceFactory {
  2. private static final String DRIVER_PROPERTY_PREFIX = "driver.";
  3. private static final int DRIVER_PROPERTY_PREFIX_LENGTH = "driver.".length();
  4. //创建UnpooledDataSource实例
  5. protected DataSource dataSource = new UnpooledDataSource();
  6.  
  7. public DataSource getDataSource() {
  8. return this.dataSource;
  9. }
  10. //略
  11. }
  12.  
  13. //继承UnpooledDataSourceFactory
  14. public class PooledDataSourceFactory extends UnpooledDataSourceFactory {
  15. public PooledDataSourceFactory() {
  16. //创建PooledDataSource实例
  17. this.dataSource = new PooledDataSource();
  18. }
  19. }

我们发现 UnpooledDataSourceFactory 创建的dataSource是 UnpooledDataSource,PooledDataSourceFactory创建的 dataSource是PooledDataSource

解析 mappers 配置

mapperElement方法会将mapper标签内的元素转换成MapperProxyFactory产生的代理类,和与mapper.xml文件的绑定,我们下一篇文章会详解介绍这个方法

  1. private void mapperElement(XNode parent) throws Exception {
  2. if (parent != null) {
  3. for (XNode child : parent.getChildren()) {
  4. if ("package".equals(child.getName())) {
  5. String mapperPackage = child.getStringAttribute("name");
  6. configuration.addMappers(mapperPackage);
  7. } else {
  8. String resource = child.getStringAttribute("resource");
  9. String url = child.getStringAttribute("url");
  10. String mapperClass = child.getStringAttribute("class");
  11. if (resource != null && url == null && mapperClass == null) {
  12. ErrorContext.instance().resource(resource);
  13. InputStream inputStream = Resources.getResourceAsStream(resource);
  14. XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
  15. mapperParser.parse();
  16. } else if (resource == null && url != null && mapperClass == null) {
  17. ErrorContext.instance().resource(url);
  18. InputStream inputStream = Resources.getUrlAsStream(url);
  19. XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
  20. mapperParser.parse();
  21. } else if (resource == null && url == null && mapperClass != null) {
  22. Class<?> mapperInterface = Resources.classForName(mapperClass);
  23. configuration.addMapper(mapperInterface);
  24. } else {
  25. throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
  26. }
  27. }
  28. }
  29. }
  30. }

创建DefaultSqlSessionFactory

到此为止XMLConfigBuilder的parse方法中的重要步骤都过了一遍了,然后返回的就是一个完整的Configuration对象了,最后通过SqlSessionFactoryBuilder的build的重载方法创建了一个SqlSessionFactory实例DefaultSqlSessionFactory,我们来看看

  1. public SqlSessionFactory build(Configuration config) {
  2. //创建DefaultSqlSessionFactory实例
  3. return new DefaultSqlSessionFactory(config);
  4. }
  5.  
  6. public class DefaultSqlSessionFactory implements SqlSessionFactory {
  7. private final Configuration configuration;
  8.  
  9. //只是将configuration设置为其属性
  10. public DefaultSqlSessionFactory(Configuration configuration) {
  11. this.configuration = configuration;
  12. }
  13.  
  14. //略
  15. }

Mybaits 源码解析 (二)----- 根据配置文件创建SqlSessionFactory(Configuration的创建过程)的更多相关文章

  1. Mybaits 源码解析 (四)----- SqlSession的创建过程(看懂框架源码再也不用死记硬背面试题)

    SqlSession是mybatis的核心接口之一,是myabtis接口层的主要组成部分,对外提供了mybatis常用的api.myabtis提供了两个SqlSesion接口的实现,常用的实现类是De ...

  2. Mybatis源码解析(二) —— 加载 Configuration

    Mybatis源码解析(二) -- 加载 Configuration    正如上文所看到的 Configuration 对象保存了所有Mybatis的配置信息,也就是说mybatis-config. ...

  3. Mybatis 系列7-结合源码解析核心CRUD 配置及用法

    [Mybatis 系列10-结合源码解析mybatis 执行流程] [Mybatis 系列9-强大的动态sql 语句] [Mybatis 系列8-结合源码解析select.resultMap的用法] ...

  4. RxJava2源码解析(二)

    title: RxJava2源码解析(二) categories: 源码解析 tags: 源码解析 rxJava2 前言 本篇主要解析RxJava的线程切换的原理实现 subscribeOn 首先, ...

  5. Sentinel源码解析二(Slot总览)

    写在前面 本文继续来分析Sentinel的源码,上篇文章对Sentinel的调用过程做了深入分析,主要涉及到了两个概念:插槽链和Node节点.那么接下来我们就根据插槽链的调用关系来依次分析每个插槽(s ...

  6. iOS即时通讯之CocoaAsyncSocket源码解析二

    原文 前言 本文承接上文:iOS即时通讯之CocoaAsyncSocket源码解析一 上文我们提到了GCDAsyncSocket的初始化,以及最终connect之前的准备工作,包括一些错误检查:本机地 ...

  7. SpringBoot源码学习1——SpringBoot自动装配源码解析+Spring如何处理配置类的

    系列文章目录和关于我 一丶什么是SpringBoot自动装配 SpringBoot通过SPI的机制,在我们程序员引入一些starter之后,扫描外部引用 jar 包中的META-INF/spring. ...

  8. Common.Logging源码解析二

    Common.Logging源码解析一分析了LogManager主入口的整个逻辑,其中第二步生成日志实例工厂类接口分析的很模糊,本随笔将会详细讲解整个日志实例工厂类接口的生成过程! (1).关于如何生 ...

  9. Mybaits 源码解析 (九)----- 全网最详细,没有之一:一级缓存和二级缓存源码分析

    像Mybatis.Hibernate这样的ORM框架,封装了JDBC的大部分操作,极大的简化了我们对数据库的操作. 在实际项目中,我们发现在一个事务中查询同样的语句两次的时候,第二次没有进行数据库查询 ...

  10. jQuery 源码解析二:jQuery.fn.extend=jQuery.extend 方法探究

    终于动笔开始 jQuery 源码解析第二篇,写文章还真是有难度,要把自已懂的表述清楚,要让别人听懂真的不是一见易事. 在 jQuery 源码解析一:jQuery 类库整体架构设计解析 一文,大致描述了 ...

随机推荐

  1. 30 (OC)* 数据结构和算法

    在描述算法时通常用o(1), o(n), o(logn), o(nlogn) 来说明时间复杂度 o(1):是最低的时空复杂度,也就是耗时/耗空间与输入数据大小无关,无论输入数据增大多少倍,耗时/耗空间 ...

  2. UnicodeDecodeError: 'gbk' codec can't decode byte 0xb0 in position 279: illegal multibyte sequence

    with open(r'E:\yy\mysql.txt') as wk: print(wk.readlines()) Traceback (most recent call last): File & ...

  3. 暑期——第五周总结(Web连接hbase数据库)

    所花时间:7天 代码行:1000(python)+250(java) 博客量:1篇 了解到知识点 : 在尝试使用hbase当作数据库时,林子雨老师教程中有完整代码关于使用hbase,我就像当然认为只需 ...

  4. 小鸟初学Shell编程(七)变量引用及作用范围

    变量引用 那么定义好变量,如何打印变量的值呢?举例下变量引用的方式. ${变量名}称作为对变量的引用 echo ${变量名}查看变量的值 ${变量名}在部分情况下可以省略成 $变量名 [root@li ...

  5. Android开发——Kotlin开发APP使用笔记

    之前一直使用java来开发Android项目,学了新的kotlin语言,前来试一试,并说一下kotlin对Android的一些功能增强 创建项目 我使用的是Android Studio3.0+,所以默 ...

  6. CSS3 box-shadow阴影

    box-shadow: color x-offset v-offset blur spred color: 阴影的颜色 x-offset: 阴影水平距离; 0: 左右阴影平分:正值:阴影在对象的右侧: ...

  7. asp.net core 使用Mysql和Dapper

    序曲:学习编程最好的方式就是敲代码,没有比这个更好的方法,哪怕你看了上百G的视频,都不如你自己敲几行代码更为有效.还有要记得敲完代码然后写一篇随笔来记录一下你所学所想. 大家都知道,.netcore是 ...

  8. httpd2.2

    httpd -t:检查httpd配置文件/etc/httpd/conf/httpd.conf的语法错误 请求报文语法 <method> <request-URL> <ve ...

  9. 用深度学习做命名实体识别(六)-BERT介绍

    什么是BERT? BERT,全称是Bidirectional Encoder Representations from Transformers.可以理解为一种以Transformers为主要框架的双 ...

  10. [LeetCode] 704. Binary Search

    Description Given a sorted (in ascending order) integer array nums of n elements and a target value, ...