Merge pull request #2 from doocs/master

merge doocs
pull/5/head
huifer 6 years ago committed by GitHub
commit e3d4507f6d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -7,14 +7,14 @@
[![issues](https://badgen.net/github/open-issues/doocs/source-code-hunter)](https://github.com/doocs/source-code-hunter/issues) [![issues](https://badgen.net/github/open-issues/doocs/source-code-hunter)](https://github.com/doocs/source-code-hunter/issues)
[![PRs Welcome](https://badgen.net/badge/PRs/welcome/green)](http://makeapullrequest.com) [![PRs Welcome](https://badgen.net/badge/PRs/welcome/green)](http://makeapullrequest.com)
有被“读过哪些知名的开源项目源码?”这种问题所困扰过吗?加入我们,一起通读互联网公司主流框架及中间件源码,成为强大的“源码猎人”,目前开放的有 Spring 系列框架、Mybatis 框架、Netty 框架及Redis中间件等让我们一起开拓新的领地揭开这些源码的神秘面纱。本项目主要用于记录框架及中间件源码的阅读经验、个人理解及解析希望能够使阅读源码变成一件更简单有趣且有价值的事情抽空更新中... 有被“读过哪些知名的开源项目源码?”这种问题所困扰过吗?加入我们,一起通读互联网公司主流框架及中间件源码,成为强大的“源码猎人”,目前开放的有 Spring 系列框架、Mybatis 框架、Netty 框架及Redis中间件等让我们一起开拓新的领地揭开这些源码的神秘面纱。本项目主要用于记录框架及中间件源码的阅读经验、个人理解及解析希望能够使阅读源码变成一件更简单有趣且有价值的事情抽空更新中...(如果本项目对您有帮助请watch、star、fork 素质三连一波,鼓励一下作者,谢谢)
## spring系列 ## spring系列
### IoC容器 ### IoC容器
- [BeanDefinition 的资源定位过程](/docs/Spring/IoC/1、BeanDefinition的资源定位过程.md) - [BeanDefinition 的资源定位过程](/docs/Spring/IoC/1、BeanDefinition的资源定位过程.md)
- [将 bean 解析封装成 BeanDefinition](/docs/Spring/IoC/2、将bean解析封装成BeanDefinition.md) - [将 bean 解析封装成 BeanDefinition](/docs/Spring/IoC/2、将bean解析封装成BeanDefinition.md)
- [将 BeanDefinition 注册进 IoC 容器](/docs/Spring/IoC/3、将BeanDefinition注册进IoC容器.md) - [将 BeanDefinition 注册进 IoC 容器](/docs/Spring/IoC/3、将BeanDefinition注册进IoC容器.md)
- [依赖注入(DI)](/docs/Spring/IoC/、依赖注入(DI).md) - [依赖注入(DI)](/docs/Spring/IoC/4、依赖注入(DI).md)
### AOP ### AOP
- [AOP 源码实现及分析](/docs/Spring/AOP/AOP源码实现及分析.md) - [AOP 源码实现及分析](/docs/Spring/AOP/AOP源码实现及分析.md)
@ -24,10 +24,12 @@
### SpringJDBC ### SpringJDBC
### Spring事务 ### Spring事务
### Spring源码故事瞎编版 ### Spring源码故事瞎编版
- [面筋哥 IoC 容器的一天(上)](/docs/Spring/IoC/面筋哥IoC容器的一天(上).md) - [面筋哥 IoC 容器的一天(上)](/docs/Spring/Spring源码故事瞎编版/面筋哥IoC容器的一天(上).md)
## MyBatis ## MyBatis
### 基础支持层 ### 基础支持层
@ -37,6 +39,10 @@
- [缓存模块](docs/Mybatis/基础支持层/4、缓存模块.md) - [缓存模块](docs/Mybatis/基础支持层/4、缓存模块.md)
### 核心处理层 ### 核心处理层
- [MyBatis初始化](docs/Mybatis/核心处理层/1、MyBatis初始化.md) - [MyBatis初始化](docs/Mybatis/核心处理层/1、MyBatis初始化.md)
- [SqlNode和SqlSource](docs/Mybatis/核心处理层/2、SqlNode和SqlSource.md)
- [ResultSetHandler](docs/Mybatis/核心处理层/3、ResultSetHandler.md)
- [StatementHandler](docs/Mybatis/核心处理层/4、StatementHandler.md)
- [Executor组件](docs/Mybatis/核心处理层/5、Executor组件.md)
## Netty ## Netty
### IO ### IO
@ -46,6 +52,8 @@
## Redis ## Redis
## Tomcat
## 学习心得 ## 学习心得
### 个人经验 ### 个人经验
- [初级开发者应该从 spring 源码中学什么](docs/学习心得/个人经验/初级开发者应该从spring源码中学什么.md) - [初级开发者应该从 spring 源码中学什么](docs/学习心得/个人经验/初级开发者应该从spring源码中学什么.md)

@ -1,5 +1,5 @@
binding模块主要为了解决一个历史遗留问题原先查询一个VO对象时需要调用SqlSession.queryForObject(“selectXXVOById”, primaryKey)方法执行指定的sql语句第一个参数selectXXVOById指定了执行的sql语句id如果我们不小心写错了参数mybatis是无法在初始化时发现这个错误的只会在实际调用queryForObject(“selectXXVOById”, primaryKey)方法时才会抛出异常,这对于工程师来说是非常难受的,就像泛型出来之前,很多类型转换不会在编译期发现错误一样。 binding模块主要为了解决一个历史遗留问题原先查询一个VO对象时需要调用SqlSession.queryForObject(“selectXXVOById”, primaryKey)方法执行指定的sql语句第一个参数selectXXVOById指定了执行的sql语句id如果我们不小心写错了参数mybatis是无法在初始化时发现这个错误的只会在实际调用queryForObject(“selectXXVOById”, primaryKey)方法时才会抛出异常,这对于工程师来说是非常难受的,就像泛型出来之前,很多类型转换不会在编译期发现错误一样。而binding模块就像Java的泛型机制一样将程序的错误提前暴露出来为开发人员省去不少排查问题的精力。
而binding模块就像Java的泛型机制一样将程序的错误提前暴露出来为开发人员省去不少排查问题的精力。
binding模块的解决方案是定义一个Mapper接口在接口中定义sql语句对应的方法名(Id)及参数这些方法在mybatis的初始化过程中会与该Mapper接口对应的映射配置文件中的sql语句相关联如果存在无法关联的sql语句mybatis就会抛出异常帮助我们及时发现问题。示例代码如下 binding模块的解决方案是定义一个Mapper接口在接口中定义sql语句对应的方法名(Id)及参数这些方法在mybatis的初始化过程中会与该Mapper接口对应的映射配置文件中的sql语句相关联如果存在无法关联的sql语句mybatis就会抛出异常帮助我们及时发现问题。示例代码如下
```java ```java
public interface HeroMapper { public interface HeroMapper {
@ -544,4 +544,4 @@ public class MapperMethod {
return result; return result;
} }
} }
``` ```

@ -0,0 +1,698 @@
和spring框架的IoC容器初始化一样mybatis也会通过定位、解析相应的配置文件完成自己的初始化。mybatis的配置文件主要有mybatis-config.xml核心配置文件及一系列映射配置文件另外mybatis也会根据注解进行配置。
## 1 BaseBuilder
mybatis初始化的主要内容是加载并解析mybatis-config.xml配置文件、映射配置文件以及相关的注解信息。mybatis的初始化入口是SqlSessionFactoryBuilder的build()方法。
```java
public class SqlSessionFactoryBuilder {
public SqlSessionFactory build(Reader reader) {
return build(reader, null, null);
}
public SqlSessionFactory build(Reader reader, String environment) {
return build(reader, environment, null);
}
public SqlSessionFactory build(Reader reader, Properties properties) {
return build(reader, null, properties);
}
/**
* build方法的主要实现
*/
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
// SqlSessionFactory会创建XMLConfigBuilder对象来解析mybatis-config.xml配置文件
// XMLConfigBuilder继承自BaseBuilder抽象类顾名思义这一系的类使用了 建造者设计模式
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
// 解析配置文件的内容 到Configuration对象根据到Configuration对象
// 创建DefaultSqlSessionFactory对象然后返回
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
// 关闭配置文件输入流
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
```
BaseBuilder中的核心字段如下
```java
public abstract class BaseBuilder {
// 保存了mybatis的几乎所以核心配置信息全局唯一
protected final Configuration configuration;
// 在mybatis-config.xml中可以通过<typeAliases>标签定义别名
protected final TypeAliasRegistry typeAliasRegistry;
// 在mybatis-config.xml中可以通过<typeHandlers>标签添加自定义TypeHandler
// TypeHandler用于完成JDBC数据类型与Java类型的相互转换所有的TypeHandler
// 都保存在typeHandlerRegistry中
protected final TypeHandlerRegistry typeHandlerRegistry;
public BaseBuilder(Configuration configuration) {
this.configuration = configuration;
this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
}
}
```
BaseBuilder中的typeAliasRegistry和typeHandlerRegistry字段均来自于configuration通过BaseBuilder的构造方法可以看到详细内容。
## 2 XMLConfigBuilder
XMLConfigBuilder是BaseBuilder的众多子类之一主要负责解析mybatis-config.xml配置文件。它通过调用parseConfiguration()方法实现整个解析过程其中mybatis-config.xml配置文件中的每个节点都被封装成了一个个相应的解析方法parseConfiguration()方法只是依次调用了这些解析方法而已。
```java
public class XMLConfigBuilder extends BaseBuilder {
// 标记是否解析过mybatis-config.xml文件
private boolean parsed;
// 用于解析mybatis-config.xml的解析器
private final XPathParser parser;
// 标识<environment>配置的名称,默认读取<environment>标签的default属性
private String environment;
// 创建并缓存Reflector对象
private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();
/**
* 解析的入口调用了parseConfiguration()进行后续的解析
*/
public Configuration parse() {
// parsed标志位的处理
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// 在mybatis-config.xml配置文件中查找<configuration>节点,并开始解析
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
private void parseConfiguration(XNode root) {
try {
// 根据root.evalNode("properties")中的值就可以知道具体是解析哪个标签的方法咯
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
```
mybatis中的标签很多所以相对应的解析方法也很多这里挑几个比较重要的标签进行分析。
### 2.1 解析&lt;typeHandlers&gt;标签
```java
private void typeHandlerElement(XNode parent) throws Exception {
if (parent != null) {
// 处理<typeHandlers>下的所有子标签
for (XNode child : parent.getChildren()) {
// 处理<package>标签
if ("package".equals(child.getName())) {
// 获取指定的包名
String typeHandlerPackage = child.getStringAttribute("name");
// 通过typeHandlerRegistry的register(packageName)方法
// 扫描指定包中的所有TypeHandler类并进行注册
typeHandlerRegistry.register(typeHandlerPackage);
} else {
// Java数据类型
String javaTypeName = child.getStringAttribute("javaType");
// JDBC数据类型
String jdbcTypeName = child.getStringAttribute("jdbcType");
String handlerTypeName = child.getStringAttribute("handler");
Class<?> javaTypeClass = resolveClass(javaTypeName);
JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
Class<?> typeHandlerClass = resolveClass(handlerTypeName);
// 注册
if (javaTypeClass != null) {
if (jdbcType == null) {
typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
} else {
typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
}
} else {
typeHandlerRegistry.register(typeHandlerClass);
}
}
}
}
}
```
### 2.2 解析&lt;environments&gt;标签
```java
/**
* mybatis可以配置多个<environment>环境,分别用于开发、测试及生产等,
* 但每个SqlSessionFactory实例只能选择其一
*/
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
// 如果未指定XMLConfigBuilder的environment字段则使用default属性指定的<environment>环境
if (environment == null) {
environment = context.getStringAttribute("default");
}
// 遍历<environment>节点
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("id");
if (isSpecifiedEnvironment(id)) {
// 实例化TransactionFactory
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
// 创建DataSourceFactory和DataSource
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
// 创建的Environment对象中封装了上面的TransactionFactory对象和DataSource对象
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
// 为configuration注入environment属性值
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
```
### 2.3 解析&lt;databaseIdProvider&gt;标签
mybatis不像hibernate那样通过hql的方式直接帮助开发人员屏蔽不同数据库产品在sql语法上的差异针对不同的数据库产品mybatis往往要编写不同的sql语句。但在mybatis-config.xml配置文件中可以通过&lt;databaseIdProvider&gt;定义所有支持的数据库产品的databaseId然后在映射配置文件中定义sql语句节点时通过databaseId指定该sql语句应用的数据库产品也可以达到类似的屏蔽数据库产品的功能。
mybatis初始化时会根据前面解析到的DataSource来确认当前使用的数据库产品然后在解析映射文件时加载不带databaseId属性的sql语句 及 带有databaseId属性的sql语句其中带有databaseId属性的sql语句优先级更高会被优先选中。
```java
/**
* 解析<databaseIdProvider>节点并创建指定的DatabaseIdProvider对象
* 该对象会返回databaseId的值mybatis会根据databaseId选择对应的sql语句去执行
*/
private void databaseIdProviderElement(XNode context) throws Exception {
DatabaseIdProvider databaseIdProvider = null;
if (context != null) {
String type = context.getStringAttribute("type");
// 为了保证兼容性修改type取值
if ("VENDOR".equals(type)) {
type = "DB_VENDOR";
}
// 解析相关配置信息
Properties properties = context.getChildrenAsProperties();
// 创建DatabaseIdProvider对象
databaseIdProvider = (DatabaseIdProvider) resolveClass(type).newInstance();
// 配置DatabaseIdProvider完成初始化
databaseIdProvider.setProperties(properties);
}
Environment environment = configuration.getEnvironment();
if (environment != null && databaseIdProvider != null) {
// 根据前面解析到的DataSource获取databaseId并记录到configuration的configuration属性上
String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
configuration.setDatabaseId(databaseId);
}
}
```
mybatis提供了DatabaseIdProvider接口该接口的核心方法为getDatabaseId(DataSource dataSource)主要根据dataSource查找对应的databaseId并返回。该接口的主要实现类为VendorDatabaseIdProvider。
```java
public class VendorDatabaseIdProvider implements DatabaseIdProvider {
private static final Log log = LogFactory.getLog(VendorDatabaseIdProvider.class);
private Properties properties;
@Override
public void setProperties(Properties p) {
this.properties = p;
}
@Override
public String getDatabaseId(DataSource dataSource) {
if (dataSource == null) {
throw new NullPointerException("dataSource cannot be null");
}
try {
return getDatabaseName(dataSource);
} catch (Exception e) {
log.error("Could not get a databaseId from dataSource", e);
}
return null;
}
private String getDatabaseName(DataSource dataSource) throws SQLException {
// 解析到数据库产品名
String productName = getDatabaseProductName(dataSource);
if (this.properties != null) {
// 根据<databaseIdProvider>子节点配置的数据库产品 和 databaseId之间的对应关系
// 确定最终使用的databaseId
for (Map.Entry<Object, Object> property : properties.entrySet()) {
if (productName.contains((String) property.getKey())) {
return (String) property.getValue();
}
}
// 没有合适的databaseId则返回null
return null;
}
return productName;
}
// 根据dataSource获取 数据库产品名的具体实现
private String getDatabaseProductName(DataSource dataSource) throws SQLException {
Connection con = null;
try {
con = dataSource.getConnection();
DatabaseMetaData metaData = con.getMetaData();
return metaData.getDatabaseProductName();
} finally {
if (con != null) {
try {
con.close();
} catch (SQLException e) {
// ignored
}
}
}
}
}
```
### 2.4 解析&lt;mappers&gt;标签
mybatis初始化时除了加载mybatis-config.xml文件还会加载全部的映射配置文件mybatis-config.xml文件的&lt;mapper&gt;节点会告诉mybatis去哪里查找映射配置文件及使用了配置注解标识的接口。
```java
/**
* 解析<mappers>节点本方法会创建XMLMapperBuilder对象加载映射文件如果映射配置文件存在
* 相应的Mapper接口也会加载相应的Mapper接口解析其中的注解 并完成向MapperRegistry的注册
*/
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
// 处理<mappers>的子节点
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
// 获取<package>子节点中的包名
String mapperPackage = child.getStringAttribute("name");
// 扫描指定的包目录然后向MapperRegistry注册Mapper接口
configuration.addMappers(mapperPackage);
} else {
// 获取<mapper>节点的resource、url、mapperClass属性这三个属性互斥只能有一个不为空
// mybatis提供了通过包名、映射文件路径、类全名、URL四种方式引入映射器。
// 映射器由一个接口和一个XML配置文件组成XML文件中定义了一个命名空间namespace
// 它的值就是接口对应的全路径。
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
// 如果<mapper>节点指定了resource或是url属性则创建XMLMapperBuilder对象解析
// resource或是url属性指定的Mapper配置文件
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
// 如果<mapper>节点指定了class属性则向MapperRegistry注册该Mapper接口
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
```
## 3 XMLMapperBuilder
和XMLConfigBuilder一样XMLMapperBuilder也继承了BaseBuilder其主要负责解析映射配置文件其解析配置文件的入口方法也是parse()另外XMLMapperBuilder也将各个节点的解析过程拆分成了一个个小方法然后由configurationElement()方法统一调用。
```java
public class XMLMapperBuilder extends BaseBuilder {
public void parse() {
// 是否已经加载过该配置文件
if (!configuration.isResourceLoaded(resource)) {
// 解析<mapper>节点
configurationElement(parser.evalNode("/mapper"));
// 将resource添加到configuration的loadedResources属性中
// 该属性是一个HashSet<String>类型的集合,其中记录了已经加载过的映射文件
configuration.addLoadedResource(resource);
// 注册Mapper接口
bindMapperForNamespace();
}
// 处理configurationElement()方法中解析失败的<resultMap>节点
parsePendingResultMaps();
// 处理configurationElement()方法中解析失败的<cacheRef>节点
parsePendingCacheRefs();
// 处理configurationElement()方法中解析失败的<statement>节点
parsePendingStatements();
}
private void configurationElement(XNode context) {
try {
// 获取<mapper>节点的namespace属性
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
// 使用MapperBuilderAssistant对象的currentNamespace属性 记录namespace命名空间
builderAssistant.setCurrentNamespace(namespace);
// 解析<cache-ref>节点,后面的解析方法 也都见名知意
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
}
```
XMLMapperBuilder也根据配置文件进行了一系列节点解析我们着重分析一下比较重要且常见的&lt;resultMap&gt;节点和&lt;sql&gt;节点
### 3.1 解析&lt;resultMap&gt;节点
select语句查询得到的结果是一张二维表水平方向上是一个个字段垂直方向上是一条条记录。而Java是面向对象的程序设计语言对象是根据类的定义创建的类之间的引用关系可以认为是嵌套结构。JDBC编程中为了将结果集中的数据映射成VO对象我们需要自己写代码从结果集中获取数据然后将数据封装成对应的VO对象并设置好对象之间的关系这种ORM的过程中存在大量重复的代码。
mybatis通过&lt;resultMap&gt;节点定义了ORM规则可以满足大部分的映射需求减少重复代码提高开发效率。
在分析&lt;resultMap&gt;节点的解析过程之前先看一下该过程使用的数据结构。每个ResultMapping对象记录了结果集中的一列与JavaBean中一个属性之间的映射关系。&lt;resultMap&gt;节点下除了&lt;discriminator&gt;子节点的其它子节点 都会被解析成对应的ResultMapping对象。
```java
public class ResultMapping {
private Configuration configuration;
// 对应节点的property属性表示 该列进行映射的属性
private String property;
// 对应节点的column属性表示 从数据库中得到的列名或列名的别名
private String column;
// 表示 一个JavaBean的完全限定名或一个类型别名
private Class<?> javaType;
// 进行映射列的JDBC类型
private JdbcType jdbcType;
// 类型处理器
private TypeHandler<?> typeHandler;
// 该属性通过id引用了另一个<resultMap>节点,它负责将结果集中的一部分列映射成
// 它所关联的结果对象。这样我们就可以通过join方式进行关联查询然后直接映射成
// 多个对象,并同时设置这些对象之间的组合关系(nested嵌套的)
private String nestedResultMapId;
// 该属性通过id引用了另一个<select>节点它会把指定的列值传入select属性指定的
// select语句中 作为参数进行查询。使用该属性可能会导致ORM中的N+1问题请谨慎使用
private String nestedQueryId;
private Set<String> notNullColumns;
private String columnPrefix;
// 处理后的标志共有两个id和constructor
private List<ResultFlag> flags;
private List<ResultMapping> composites;
private String resultSet;
private String foreignColumn;
// 是否延迟加载
private boolean lazy;
}
```
另一个比较重要的类是ResultMap每个&lt;resultMap&gt;节点都会被解析成一个ResultMap对象其中每个节点所定义的映射关系则使用ResultMapping对象表示。
```java
public class ResultMap {
private Configuration configuration;
// 这些属性一一对应了<resultMap>中的属性
private String id;
private Class<?> type;
// 记录了除<discriminator>节点之外的其它映射关系(即ResultMapping对象集合)
private List<ResultMapping> resultMappings;
// 记录了映射关系中带有ID标志的映射关系<id>节点和<constructor>节点的<idArg>子节点
private List<ResultMapping> idResultMappings;
// 记录了映射关系中带有Constructor标志的映射关系<constructor>所有子元素
private List<ResultMapping> constructorResultMappings;
// 记录了映射关系中不带有Constructor标志的映射关系
private List<ResultMapping> propertyResultMappings;
// 记录了所有映射关系中涉及的column属性的集合
private Set<String> mappedColumns;
// 记录了所有映射关系中涉及的property属性的集合
private Set<String> mappedProperties;
// 鉴别器,对应<discriminator>节点
private Discriminator discriminator;
// 是否含有嵌套的结果映射如果某个映射关系中存在resultMap属性
// 且不存在resultSet属性则为true
private boolean hasNestedResultMaps;
// 是否含有嵌套查询如果某个属性映射存在select属性则为true
private boolean hasNestedQueries;
// 是否开启自动映射
private Boolean autoMapping;
}
```
了解了ResultMapping 和ResultMap 记录的信息之后,下面开始介绍&lt;resultMap&gt;节点的解析过程。在XMLMapperBuilder中通过resultMapElements()方法解析映射配置文件中的全部&lt;resultMap&gt;节点该方法会循环调用resultMapElement()方法处理每个resultMap节点。
```java
private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
return resultMapElement(resultMapNode, Collections.<ResultMapping> emptyList());
}
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
// <resultMap>的id属性默认值会拼装所有父节点的id 或value或property属性值
String id = resultMapNode.getStringAttribute("id",
resultMapNode.getValueBasedIdentifier());
// <resultMap>的type属性表示结果集将被映射成type指定类型的对象
String type = resultMapNode.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
// 该属性指定了该<resultMap>节点的继承关系
String extend = resultMapNode.getStringAttribute("extends");
// 为true则启动自动映射功能该功能会自动查找与列明相同的属性名并调用setter方法
// 为false则需要在<resultMap>节点内注明映射关系才会调用对应的setter方法
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
// 解析type类型
Class<?> typeClass = resolveClass(type);
Discriminator discriminator = null;
// 该集合用来记录解析结果
List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
resultMappings.addAll(additionalResultMappings);
// 获取并处理<resultMap>的子节点
List<XNode> resultChildren = resultMapNode.getChildren();
// child单数形式children复数形式
for (XNode resultChild : resultChildren) {
// 处理<constructor>节点
if ("constructor".equals(resultChild.getName())) {
processConstructorElement(resultChild, typeClass, resultMappings);
// 处理<discriminator>节点
} else if ("discriminator".equals(resultChild.getName())) {
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
} else {
// 处理<id>,<result>,<association>,<collection>等节点
List<ResultFlag> flags = new ArrayList<ResultFlag>();
if ("id".equals(resultChild.getName())) {
flags.add(ResultFlag.ID);
}
// 创建ResultMapping对象并添加到resultMappings集合
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
}
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
}
```
从上面的代码我们可以看到mybatis从&lt;resultMap&gt;节点获取到id属性和type属性值之后就会通过XMLMapperBuilder的buildResultMappingFromContext()方法为&lt;result&gt;节点创建对应的ResultMapping 对象。
```java
/**
* 根据上下文环境构建ResultMapping
*/
private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) throws Exception {
// 获取各个节点的属性,见文知意
String property;
if (flags.contains(ResultFlag.CONSTRUCTOR)) {
property = context.getStringAttribute("name");
} else {
property = context.getStringAttribute("property");
}
String column = context.getStringAttribute("column");
String javaType = context.getStringAttribute("javaType");
String jdbcType = context.getStringAttribute("jdbcType");
String nestedSelect = context.getStringAttribute("select");
String nestedResultMap = context.getStringAttribute("resultMap",
processNestedResultMappings(context, Collections.<ResultMapping> emptyList()));
String notNullColumn = context.getStringAttribute("notNullColumn");
String columnPrefix = context.getStringAttribute("columnPrefix");
String typeHandler = context.getStringAttribute("typeHandler");
String resultSet = context.getStringAttribute("resultSet");
String foreignColumn = context.getStringAttribute("foreignColumn");
boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
Class<?> javaTypeClass = resolveClass(javaType);
@SuppressWarnings("unchecked")
Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler);
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
// 创建ResultMapping对象并返回
return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
}
```
得到ResultMapping对象集合之后会调用ResultMapResolver的resolve()方法该方法会调用MapperBuilderAssistant的addResultMap()方法创建ResultMap对象并将ResultMap对象添加到Configuration的resultMaps集合中保存。
```java
public class MapperBuilderAssistant extends BaseBuilder {
public ResultMap addResultMap(String id, Class<?> type, String extend,
Discriminator discriminator, List<ResultMapping> resultMappings, Boolean autoMapping) {
// ResultMap的完整id是"namespace.id"的格式
id = applyCurrentNamespace(id, false);
// 获取 父ResultMap的完整id
extend = applyCurrentNamespace(extend, true);
// 针对extend属性进行的处理
if (extend != null) {
if (!configuration.hasResultMap(extend)) {
throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'");
}
// 父ResultMap对象
ResultMap resultMap = configuration.getResultMap(extend);
// 父ResultMap对象的ResultMapping集合
List<ResultMapping> extendedResultMappings = new ArrayList<ResultMapping>(resultMap.getResultMappings());
// 删除需要覆盖的ResultMapping集合
extendedResultMappings.removeAll(resultMappings);
// Remove parent constructor if this resultMap declares a constructor.
boolean declaresConstructor = false;
for (ResultMapping resultMapping : resultMappings) {
if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
declaresConstructor = true;
break;
}
}
if (declaresConstructor) {
Iterator<ResultMapping> extendedResultMappingsIter = extendedResultMappings.iterator();
while (extendedResultMappingsIter.hasNext()) {
if (extendedResultMappingsIter.next().getFlags().contains(ResultFlag.CONSTRUCTOR)) {
extendedResultMappingsIter.remove();
}
}
}
// 添加需要被继承下来的ResultMapping集合
resultMappings.addAll(extendedResultMappings);
}
ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
.discriminator(discriminator)
.build();
configuration.addResultMap(resultMap);
return resultMap;
}
}
```
### 3.2 解析&lt;sql&gt;节点
在映射配置文件中,可以使用&lt;sql&gt;节点定义可重用的SQL语句片段当需要重用&lt;sql&gt;节点中定义的SQL语句片段时只需要使用&lt;include&gt;节点引入相应的片段即可这样在编写SQL语句以及维护这些SQL语句时都会比较方便。XMLMapperBuilder的sqlElement()方法负责解析映射配置文件中定义的全部&lt;sql&gt;节点。
```java
private void sqlElement(List<XNode> list) throws Exception {
if (configuration.getDatabaseId() != null) {
sqlElement(list, configuration.getDatabaseId());
}
sqlElement(list, null);
}
private void sqlElement(List<XNode> list, String requiredDatabaseId) throws Exception {
// 遍历<sql>节点
for (XNode context : list) {
String databaseId = context.getStringAttribute("databaseId");
String id = context.getStringAttribute("id");
// 为id添加命名空间
id = builderAssistant.applyCurrentNamespace(id, false);
// 检测<sql>的databaseId与当前Configuration中记录的databaseId是否一致
if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
// 记录到sqlFragments(Map<String, XNode>)中保存
sqlFragments.put(id, context);
}
}
}
```
## 4 XMLStatementBuilder
这一部分看的不是很懂,暂时保留,日后深入理解了再写。
## 5 绑定Mapper接口
通过之前对binding模块的解析可知每个映射配置文件的命名空间可以绑定一个Mapper接口并注册到MapperRegistry中。XMLMapperBuilder的bindMapperForNamespace()方法中完成了映射配置文件与对应Mapper 接
口的绑定。
```java
public class XMLMapperBuilder extends BaseBuilder {
private void bindMapperForNamespace() {
// 获取映射配置文件的命名空间
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
// 解析命名空间对应的类型
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
if (boundType != null) {
// 是否已加载boundType接口
if (!configuration.hasMapper(boundType)) {
// 追加个"namespace:"的前缀并添加到Configuration的loadedResources集合中
configuration.addLoadedResource("namespace:" + namespace);
// 添加到Configuration的mapperRegistry集合中另外往这个方法栈的更深处看 会发现
// 其创建了MapperAnnotationBuilder对象并调用了该对象的parse()方法解析Mapper接口
configuration.addMapper(boundType);
}
}
}
}
}
public class MapperRegistry {
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new MapperProxyFactory<T>(type));
// 解析Mapper接口type中的信息
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
}
public class MapperAnnotationBuilder {
public void parse() {
String resource = type.toString();
// 是否已经加载过该接口
if (!configuration.isResourceLoaded(resource)) {
// 检查是否加载过该接口对应的映射文件如果未加载则创建XMLMapperBuilder对象
// 解析对应的映射文件,该过程就是前面介绍的映射配置文件解析过程
loadXmlResource();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
// 解析@CacheNamespace注解
parseCache();
// 解析@CacheNamespaceRef注解
parseCacheRef();
// type接口的所有方法
Method[] methods = type.getMethods();
for (Method method : methods) {
try {
if (!method.isBridge()) {
// 解析SelectKey、ResultMap等注解并创建MappedStatement对象
parseStatement(method);
}
} catch (IncompleteElementException e) {
// 如果解析过程出现IncompleteElementException异常可能是因为引用了
// 未解析的注解,这里将出现异常的方法记录下来,后面提供补偿机制,重新进行解析
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
// 遍历configuration中的incompleteMethods集合集合中记录了未解析的方法
// 重新调用这些方法进行解析
parsePendingMethods();
}
}
```
另外在MapperAnnotationBuilder的parse()方法中解析的注解都能在映射配置文件中找到与之对应的XML节点且两者的解析过程也非常相似。

@ -0,0 +1,357 @@
Executor是MyBatis的核心接口之一其中定义了数据库操作的基本方法。在实际应用中经常涉及的SqISession接口的功能都是基于Executor 接口实现的。
```java
public interface Executor {
ResultHandler NO_RESULT_HANDLER = null;
// 执行update、insert、delete三种类型的SQL语句
int update(MappedStatement ms, Object parameter) throws SQLException;
// 执行select类型的SQL语句返回值分为结果对象列表或游标对象
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
<E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;
// 批量执行SQL语句
List<BatchResult> flushStatements() throws SQLException;
// 提交事务
void commit(boolean required) throws SQLException;
// 回滚事务
void rollback(boolean required) throws SQLException;
// 创建缓存中用到的CacheKey对象
CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);
// 根据CacheKey对象查找缓存
boolean isCached(MappedStatement ms, CacheKey key);
// 清空一级缓存
void clearLocalCache();
// 延迟加载一级缓存中的数据
void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);
// 获取事务
Transaction getTransaction();
// 关闭事务
void close(boolean forceRollback);
// 是否关闭
boolean isClosed();
}
```
## 1 BaseExecutor
BaseExecutor是一个实现了Executor接口的抽象类它实现了Executor接口的大部分方法。BaseExecutor中主要提供了缓存管理和事务管理的基本功能继承BaseExecutor的子类只要实现四个基本方法来完成数据库的相关操作即可这四个方法分别是doUpdate()方法、doQuery()方法、doQueryCursor()方法、doFlushStatement()方法。
```java
public abstract class BaseExecutor implements Executor {
private static final Log log = LogFactory.getLog(BaseExecutor.class);
// 事务对象,用于实现事务的提交、回滚和关闭
protected Transaction transaction;
// 其中封装的Executor对象
protected Executor wrapper;
// 延迟加载队列
protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
// 一级缓存用于缓存该Executor对象查询结果集映射得到的结果对象
protected PerpetualCache localCache;
// 一级缓存,用于缓存输出类型的参数
protected PerpetualCache localOutputParameterCache;
protected Configuration configuration;
// 记录嵌套查询的层数
protected int queryStack;
// 是否关闭
private boolean closed;
}
```
### 1.1 一级缓存简介
常见的应用系统中,数据库是比较珍贵的资源,很容易成为整个系统的瓶颈。在设计和维护系统时,会进行多方面的权衡,并且利用多种优化手段,减少对数据库的直接访问。
使用缓存是一种比较有效的优化手段,使用缓存可以减少应用系统与数据库的网络交互、减少数据库访问次数、降低数据库的负担、降低重复创建和销毁对象等一系列开销,从而提高整个系统的性能。
MyBatis提供的缓存功能分别为一级缓存和二级缓存。BaseExecutor主要实现了一级缓存的相关内容。一级缓存是会话级缓存在MyBatis中每创建一个SqlSession对象就表示开启一次数据库会话。在一次会话中应用程序可能会在短时间内(一个事务内),反复执行完全相同的查询语句,如果不对数据进行缓存,那么每一次查询都会执行一次数据库查询操作,而多次完全相同的、时间间隔较短的查询语句得到的结果集极有可能完全相同,这会造成数据库资源的浪费。
为了避免上述问题MyBatis会在Executor对象中建立一个简单的一级缓存将每次查询的结果集缓存起来。在执行查询操作时会先查询一级缓存如果存在完全一样的查询情况则直接从一级缓存中取出相应的结果对象并返回给用户减少数据库访问次数从而减小了数据库的压力。
一级缓存的生命周期与SqlSession相同其实也就与SqISession中封装的Executor 对象的生命周期相同。当调用Executor对象的close()方法时断开连接该Executor 对象对应的一级缓存就会被废弃掉。一级缓存中对象的存活时间受很多方面的影响例如在调用Executor的update()方法时,也会先请空一级缓存。一级缓存默认是开启的,一般情况下,不需要用户进行特殊配置。
### 1.2 一级缓存的管理
BaseExecutor的query()方法会首先创建CacheKey对象并根据该CacheKey对象查找一级缓存如果缓存命中则返回缓存中记录的结果对象如果缓存未命中则查询数据库得到结果集之后将结果集映射成结果对象并保存到一级缓存中同时返回结果对象。
```java
public abstract class BaseExecutor implements Executor {
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 获取BoundSql对象
BoundSql boundSql = ms.getBoundSql(parameter);
// 创建CacheKey对象该对象由多个参数组装而成
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
// query方法的重载进行后续处理
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
// 可以看到CacheKey对象由MappedStatement的id、RowBounds的offset和limit
// sql语句(包含占位符"?")、用户传递的实参组成
CacheKey cacheKey = new CacheKey();
cacheKey.update(ms.getId());
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
cacheKey.update(boundSql.getSql());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
// 获取用户传入的实参并添加到CacheKey对象中
for (ParameterMapping parameterMapping : parameterMappings) {
// 过滤掉输出类型的参数
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) {
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
// 将实参添加到CacheKey对象中
cacheKey.update(value);
}
}
// 如果configuration的environment不为空则将该environment的id
// 添加到CacheKey对象中
if (configuration.getEnvironment() != null) {
cacheKey.update(configuration.getEnvironment().getId());
}
return cacheKey;
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
// 检查当前Executor是否已关闭
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
// 非嵌套查询,且<select>节点配置的flushCache属性为true时才会清空一级缓存
clearLocalCache();
}
List<E> list;
try {
// 增加查询层数
queryStack++;
// 根据传入的CacheKey对象 查询一级缓存
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
// 针对存储过程调用的处理,在一级缓存命中时,获取缓存中保存的输出类型参数
// 并设置到用户传入的实参parameter对象中
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 缓存未命中则从数据库查询结果集其中会调用doQuery()方法完成数据库查询操作,
// 该方法为抽象方法由BaseExecutor的子类实现
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
// 当前查询完成,查询层数减少
queryStack--;
}
if (queryStack == 0) {
// 延迟加载的相关内容
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
}
```
从上面的代码中可以看到BaseExecutor的query()方法会根据flushCache属性和localCacheScope配置 决定是否清空一级缓存。
另外BaseExecutor的update()方法在调用doUpdate()方法之前也会清除一级缓存。update()方法负责执行insert、update、delete三类SQL 语句它是调用doUpdate()方法实现的。
```java
@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
// 判断当前的Executor是否已经关闭
ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
// 清除一级缓存该方法会调用localCache和localOutputParameterCache
// 的clear()方法清除缓存
clearLocalCache();
// 抽象方法,交由子类实现
return doUpdate(ms, parameter);
}
@Override
public void clearLocalCache() {
if (!closed) {
localCache.clear();
localOutputParameterCache.clear();
}
}
```
### 1.3 事务相关操作
在BatchExecutor实现中可以缓存多条SQL语句等待合适时机将缓存的多条SQL 语句一并发送到数据库执行。Executor的flushStatements()方法主要是针对批处理多条SQL语句的它会调用doFlushStatements()这个基本方法处理Executor中缓存的多条SQL语句。在BaseExecutor的commit()及rollback()等方法中都会首先调用flushStatements()方法,然后再执行相关事务操作。
```java
@Override
public void commit(boolean required) throws SQLException {
// 检查当前连接是否已关闭
if (closed) {
throw new ExecutorException("Cannot commit, transaction is already closed");
}
// 清除一级缓存
clearLocalCache();
// 不执行Executor中缓存的SQL语句
flushStatements();
// 根据参数required决定是否提交事务
if (required) {
transaction.commit();
}
}
@Override
public List<BatchResult> flushStatements() throws SQLException {
return flushStatements(false);
}
public List<BatchResult> flushStatements(boolean isRollBack) throws SQLException {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
// 这是一个交由子类实现的抽象方法参数isRollBack表示
// 是否执行Executor中缓存的SQL语句false表示执行true表示不执行
return doFlushStatements(isRollBack);
}
@Override
public void rollback(boolean required) throws SQLException {
if (!closed) {
try {
// 清除一级缓存
clearLocalCache();
// 批量执行缓存的sql语句
flushStatements(true);
} finally {
// 根据required决定是否回滚事务
if (required) {
transaction.rollback();
}
}
}
}
@Override
public void close(boolean forceRollback) {
try {
try {
// 根据forceRollback参数决定 是否强制回滚该事务
rollback(forceRollback);
} finally {
if (transaction != null) {
transaction.close();
}
}
} catch (SQLException e) {
// Ignore. There's nothing that can be done at this point.
log.warn("Unexpected exception on closing transaction. Cause: " + e);
} finally {
transaction = null;
deferredLoads = null;
localCache = null;
localOutputParameterCache = null;
closed = true;
}
}
```
## 2 SimpleExecutor
SimpleExecutor继承了BaseExecutor抽象类它是最简单的Executor接口实现。Executor组件使用了模板方法模式一级缓存等固定不变的操作都封装到了BaseExecutor中在SimpleExecutor中就不必再关心一级缓存等操作只需要专注实现4 个基本方法的实现即可。
```java
public class SimpleExecutor extends BaseExecutor {
public SimpleExecutor(Configuration configuration, Transaction transaction) {
super(configuration, transaction);
}
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
// 获取配置对象
Configuration configuration = ms.getConfiguration();
// 创建StatementHandler对象
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 完成Statement的创建和初始化该方法首先会调用StatementHandler的prepare()方法
// 创建Statement对象然后调用StatementHandler的parameterize()方法处理占位符
stmt = prepareStatement(handler, ms.getStatementLog());
// 调用StatementHandler的query()方法执行sql语句并通过ResultSetHandler
// 完成结果集的映射
return handler.<E>query(stmt, resultHandler);
} finally {
// 关闭Statement对象
closeStatement(stmt);
}
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
// 创建Statement对象
stmt = handler.prepare(connection, transaction.getTimeout());
// 处理占位符
handler.parameterize(stmt);
return stmt;
}
/**
* 与前面doQuery()方法的实现非常类似
*/
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}
@Override
protected <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, null, boundSql);
Statement stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>queryCursor(stmt);
}
@Override
public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
// SimpleExecutor不提供sql语句批处理所以直接返回空集合
return Collections.emptyList();
}
}
```
## 3 ReuseExecutor
## 4 BatchExecutor
## 5 CachingExecutor

@ -1,8 +1,12 @@
## 前言 ## 前言
之前一直想系统的拜读一下 spring 的源码,看看它到底是如何吸引身边的大神们对它的设计赞不绝口,虽然每天工作很忙,每天下班后总感觉脑子内存溢出,想去放松一下,但总是以此为借口,恐怕会一直拖下去。所以每天下班虽然有些疲惫,但还是按住自己啃下这块硬骨头。 之前一直想系统的拜读一下 spring 的源码,看看它到底是如何吸引身边的大神们对它的设计赞不绝口,虽然每天工作很忙,每天下班后总感觉脑子内存溢出,想去放松一下,但总是以此为借口,恐怕会一直拖下去。所以每天下班虽然有些疲惫,但还是按住自己啃下这块硬骨头。
spring 源码这种东西真的是一回生二回熟,第一遍会被各种设计模式和繁杂的方法调用搞得晕头转向,不知道这个方法调用的是哪个父类的实现,另一个方法又调的是哪个子类的实现,但当你耐下心来多走几遍,会发现越看越熟练,每次都能 get 到新的点。 spring 源码这种东西真的是一回生二回熟,第一遍会被各种设计模式和繁杂的方法调用搞得晕头转向,不知道这个方法调用的是哪个父类的实现,另一个方法又调的是哪个子类的实现,但当你耐下心来多走几遍,会发现越看越熟练,每次都能 get 到新的点。
另外,对于第一次看 spring 源码的同学,建议先在 B 站上搜索相关视频看一下然后再结合计文柯老师的《spring 技术内幕》深入理解,最后再输出自己的理解加强印象。 另外,对于第一次看 spring 源码的同学,建议先在 B 站上搜索相关视频看一下然后再结合计文柯老师的《spring 技术内幕》深入理解,最后再输出自己的理解加强印象。
首先对于我们新手来说,还是从我们最常用的两个 IoC 容器开始分析,这次我们先分析 FileSystemXmlApplicationContext 这个 IoC 容器的具体实现ClassPathXmlApplicationContext 留着下次讲解。 首先对于我们新手来说,还是从我们最常用的两个 IoC 容器开始分析,这次我们先分析 FileSystemXmlApplicationContext 这个 IoC 容器的具体实现ClassPathXmlApplicationContext 留着下次讲解。
PS可以结合我 GitHub 上对 spring 框架源码的翻译注解一起看,会更有助于各位开发姥爷的理解。 PS可以结合我 GitHub 上对 spring 框架源码的翻译注解一起看,会更有助于各位开发姥爷的理解。
地址: 地址:
spring-beans https://github.com/AmyliaY/spring-beans-reading spring-beans https://github.com/AmyliaY/spring-beans-reading
@ -345,4 +349,4 @@ FileSystemXmlApplicationContext 从上层体系的各抽象类中继承了大量
return new FileSystemResource(path); return new FileSystemResource(path);
} }
``` ```
至此我们可以看到FileSystemXmlApplicationContext 的 getResourceByPath() 方法返回了一个 FileSystemResource 对象,接下来 spring 就可以对这个对象进行相关的 I/O 操作,进行 BeanDefinition 的读取和载入了。 至此我们可以看到FileSystemXmlApplicationContext 的 getResourceByPath() 方法返回了一个 FileSystemResource 对象,接下来 spring 就可以对这个对象进行相关的 I/O 操作,进行 BeanDefinition 的读取和载入了。

@ -1,4 +1,5 @@
接着上一篇的 BeanDefinition 资源定位开始讲。Spring IoC 容器 BeanDefinition 解析过程就是把用户在配置文件中定义好的 bean解析并封装成容器可以装载的 BeanDefinitionBeanDefinition 是 spring 定义的基本数据结构,也是为了方便对 bean 进行管理和操作。 接着上一篇的 BeanDefinition 资源定位开始讲。Spring IoC 容器 BeanDefinition 解析过程就是把用户在配置文件中定义好的 bean解析并封装成容器可以装载的 BeanDefinitionBeanDefinition 是 spring 定义的基本数据结构,也是为了方便对 bean 进行管理和操作。
PS可以结合我 GitHub 上对 spring 框架源码的阅读及个人理解一起看,会更有助于各位开发大佬理解。 PS可以结合我 GitHub 上对 spring 框架源码的阅读及个人理解一起看,会更有助于各位开发大佬理解。
spring-beans https://github.com/AmyliaY/spring-beans-reading spring-beans https://github.com/AmyliaY/spring-beans-reading
spring-context https://github.com/AmyliaY/spring-context-reading spring-context https://github.com/AmyliaY/spring-context-reading
@ -884,4 +885,4 @@ spring-context https://github.com/AmyliaY/spring-context-reading
} }
} }
``` ```
经过这样逐层地解析,我们在配置文件中定义的 Bean 就被整个解析成了可以被 IoC 容器装载和使用的 BeanDefinition这种数据结构可以让 IoC 容器执行索引、查询等操作。经过上述解析得到的 BeanDefinition接下来我们就可以将它注册到 IoC 容器中咯。 经过这样逐层地解析,我们在配置文件中定义的 Bean 就被整个解析成了可以被 IoC 容器装载和使用的 BeanDefinition这种数据结构可以让 IoC 容器执行索引、查询等操作。经过上述解析得到的 BeanDefinition接下来我们就可以将它注册到 IoC 容器中咯。

@ -1,5 +1,7 @@
前面我们主要分析了 FileSystemXmlApplicationContext 这个具体的 IoC 容器的初始化源码实现,在 IoC 容器中建立了 BeanDefinition 的数据映射,将其和 beanName 一起绑定在一个 ConcurrentHashMap 中。现在我们来看一下 spring 是如何将 IoC 容器中的 Bean 根据配置关联在一起的。 前面我们主要分析了 FileSystemXmlApplicationContext 这个具体的 IoC 容器的初始化源码实现,在 IoC 容器中建立了 BeanDefinition 的数据映射,将其和 beanName 一起绑定在一个 ConcurrentHashMap 中。现在我们来看一下 spring 是如何将 IoC 容器中的 Bean 根据配置关联在一起的。
Spring 中触发 IoC 容器“依赖注入”的方式有两种,一个是通过 getBean() 向容器索要 bean 时触发依赖注入;另一个是给 bean 配置 lazy-init 属性spring 会自动调用此 bean 的 getBean() 方法,提前完成依赖注入。总的来说,想提高运行时获取 bean 的效率,可以考虑配置此属性。 Spring 中触发 IoC 容器“依赖注入”的方式有两种,一个是通过 getBean() 向容器索要 bean 时触发依赖注入;另一个是给 bean 配置 lazy-init 属性spring 会自动调用此 bean 的 getBean() 方法,提前完成依赖注入。总的来说,想提高运行时获取 bean 的效率,可以考虑配置此属性。
下面我将分别解读这两种依赖注入的触发方式,先看 getBean() 的,因为 lazy-init 最后也是通过调用 getBean 完成的依赖注入。 下面我将分别解读这两种依赖注入的触发方式,先看 getBean() 的,因为 lazy-init 最后也是通过调用 getBean 完成的依赖注入。
PS可以结合我 GitHub 上对 spring 框架源码的阅读及个人理解一起看,会更有助于各位开发姥爷理解,地址: PS可以结合我 GitHub 上对 spring 框架源码的阅读及个人理解一起看,会更有助于各位开发姥爷理解,地址:
@ -1443,4 +1445,4 @@ lazy-init 触发的预实例化和依赖注入,发生在 IoC 容器完成对 B
beanFactory.preInstantiateSingletons(); beanFactory.preInstantiateSingletons();
} }
``` ```

@ -1,10 +1,13 @@
## 六大原则 ## 六大原则
1. 单一职责:一个类只负责唯一一项职责 1. 单一职责:一个类只负责唯一一项职责
2. 依赖倒置:即面向接口编程,系统的高层模块(顶层接口、顶层抽象类等)不应该依赖底层模块(具体实现类),当需求发生变化时,对外接口不变,只要提供新的实现类即可。 2. 依赖倒置:即面向接口编程,系统的高层模块(顶层接口、顶层抽象类等)不应该依赖底层模块(具体实现类),当需求发生变化时,对外接口不变,只要提供新的实现类即可。
3. 接口隔离:尽量设计出功能单一的接口,避免实现类实现很多不必要的接口方法 3. 接口隔离:尽量设计出功能单一的接口,避免实现类实现很多不必要的接口方法
4. **开放-封闭:对扩展开放,对修改关闭**,本原则是设计模式的终极目标 4. **开放-封闭:对扩展开放,对修改关闭**,本原则是设计模式的终极目标
5. 迪米特法则:尽量减少类之间的耦合性 5. 迪米特法则:尽量减少类之间的耦合性
6. 里氏替换:继承体系的设计要合理 6. 里氏替换:继承体系的设计要合理
## 装饰器模式

Loading…
Cancel
Save