2019独角兽企业重金招聘Python工程师标准>>>
一:源码分析流程图
二:源码分析开始
public class TestMyBatis {
public static void main(String[] args) {
try {
// 基本mybatis环境
// 1.定义mybatis_config文件地址
String resources = "mybatis_config.xml";
// 2.获取InputStreamReaderIo流
Reader reader = Resources.getResourceAsReader(resources);
// 3.获取SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
// 4.获取Session
SqlSession sqlSession = sqlSessionFactory.openSession();
// 5.操作Mapper接口
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
UserEntity user = mapper.getUser(2);
System.out.println(user.getName());
} catch (Exception e) {
e.printStackTrace();
}
}
}
1.首先分析目标有两个
1.mybatis SqlSessionFactoryBuilder源码分析 (建造者模式)
2.MybatisMapper接口绑定原理(代理设计模式)
目标一:SqlSessionFactoryBuilder源码分析 (建造者模式)
1.读取resources获取对应的Reader对象,进入getResourceAsReader(resources)源码片段
Reader reader = Resources.getResourceAsReader(resources);
/* *读取resources获取对应的Reader对象 */ public static Reader getResourceAsReader(String resource) throws IOException { Reader reader; //判断编码 if (charset == null) { //调用javaioAPI 读取resources配置文件,获取InputStreamReader reader = new InputStreamReader(getResourceAsStream(resource)); } else { reader = new InputStreamReader(getResourceAsStream(resource), charset); } return reader; }
2.进入SqlSessionFactoryBuilder()去看看无参构造函数做了什么事情,我们发现无参构造函数没有做什么事情,那么我们就点到build(reader)去看这个方法具体如何实现的。
我们发现SqlSessionFactoryBuilder , 通过XMLConfigBuilder解析mybatis配置文件内容。下面的代码都是对配置文件的解析过程。
SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
/* *创建 SqlSessionFactory */ public class SqlSessionFactoryBuilder { //第一步进入这个方法,Reader读取mybatis配置文件,传入构造方法 public SqlSessionFactory build(Reader reader) { //调用重载的方法,我们点进去 return build(reader, null, null); } ....省略部分源码 //第二步进入这个重载方法 public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { //通过XMLConfigBuilder解析mybatis配置文件,源码分析 XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); //源码分析 return build(parser.parse()); ...省略部分源码 }
下面就接着看看 XMLConfigBuilder 源码片段:具体如何解析配置文件的,是通过xml解析器去解析配置文件
/** * 使用xml解析器去解析mybatis 配置文件信息 */ public class XMLConfigBuilder extends BaseBuilder { private boolean parsed; //xml解析器 private XPathParser parser; .... //第一步进入到这个带参数的构造方法中 public XMLConfigBuilder(Reader reader, String environment, Properties props) { //调用具体的执行逻辑方法,点进去,进入重载的方法 this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props); } .... //第二步执行具体逻辑 private XMLConfigBuilder(XPathParser parser, String environment, Properties props) { super(new Configuration()); ErrorContext.instance().resource("SQL Mapper Configuration"); this.configuration.setVariables(props); //在构造函数设置了parsed 为fasle this.parsed = false; this.environment = environment; this.parser = parser; }
xml解析器通过return build(parser.parse())这个方法去解析配置文件内容,我们去看看parse()方法源码
//外部调用此方法对mybatis配置文件进行解析 public Configuration parse() { //因为在构造函数设置了parsed 为fasle,xml解析器只解析一次 if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } //只解析一次,Configuration配置文件是全局的,只能被解析一次 parsed = true; //源码分析,从根节点configuration parseConfiguration(parser.evalNode("/configuration")); return configuration; }
在上面这段代码调用了:parseConfiguration(parser.evalNode("/configuration")),我们点进源码看看具体怎么做的
//1.此方法就是解析configuration节点下的子节点 //2.由此也可看出,我们在configuration下面能配置的节点为以下10个节点 //3.首先要看的就是properties节点和environments节点 private void parseConfiguration(XNode root) { try { //1.解析properties元素,源码分析 propertiesElement(root.evalNode("properties")); typeAliasesElement(root.evalNode("typeAliases")); pluginElement(root.evalNode("plugins")); objectFactoryElement(root.evalNode("objectFactory")); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); reflectionFactoryElement(root.evalNode("reflectionFactory")); settingsElement(root.evalNode("settings")); //2.解析environments元素,源码分析 environmentsElement(root.evalNode("environments")); databaseIdProviderElement(root.evalNode("databaseIdProvider")); typeHandlerElement(root.evalNode("typeHandlers")); //3.这里源码分析 mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } }
下面我门来看看解析properties的具体方法, propertiesElement(root.evalNode("properties")),这里点进去看源码如何写的
/* * 解析properties具体方法 */ private void propertiesElement(XNode context) throws Exception { if (context != null) { //将子节点的 name 以及value属性set进properties对象 //这儿可以注意一下顺序,xml配置优先, 外部指定properties配置其次 Properties defaults = context.getChildrenAsProperties(); //获取properties节点上 resource属性的值 String resource = context.getStringAttribute("resource"); //获取properties节点上 url属性的值, resource和url不能同时配置 String url = context.getStringAttribute("url"); if (resource != null && url != null) { throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other."); } //把解析出的properties文件set进Properties对象 if (resource != null) { defaults.putAll(Resources.getResourceAsProperties(resource)); } else if (url != null) { defaults.putAll(Resources.getUrlAsProperties(url)); } //将configuration对象中已配置的Properties属性与刚刚解析的融合 //configuration这个对象会装载所解析mybatis配置文件的所有节点元素,以后也会频频提到这个对象 //既然configuration对象用有一系列的get/set方法, 那是否就标志着我们可以使用java代码直接配置? //答案是肯定的, 不过使用配置文件进行配置,优势不言而喻 Properties vars = configuration.getVariables(); if (vars != null) { defaults.putAll(vars); } //把装有解析配置propertis对象set进解析器, 因为后面可能会用到 parser.setVariables(defaults); //set进configuration对象 configuration.setVariables(defaults); } }
下面来看看解析envioments元素节点的方法,environmentsElement(root.evalNode("environments")),这里点进去看源码如何实现的
/* * 解析envioments元素节点的方法 */ private void environmentsElement(XNode context) throws Exception { if (context != null) { if (environment == null) { //解析environments节点的default属性的值 //例如:
environment = context.getStringAttribute("default"); } //递归解析environments子节点 for (XNode child : context.getChildren()) { // , 只有enviroment节点有id属性,那么这个属性有何作用? //environments 节点下可以拥有多个 environment子节点 // //类似于这样: //意思就是我们可以对应多个环境,比如开发环境,测试环境等, 由environments的default属性去选择对应的 //enviroment String id = child.getStringAttribute("id"); //isSpecial就是根据由environments的default属性去选择对应的enviroment if (isSpecifiedEnvironment(id)) { //事务, mybatis有两种:JDBC 和 MANAGED, 配置为JDBC则直接使用JDBC的事务,配置为MANAGED则是将事务托管 //给容器 TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager")); //enviroment节点下面就是dataSource节点了,解析dataSource节点(下面会贴出解析dataSource的具体方法) //这里源码分析 DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource")); DataSource dataSource = dsFactory.getDataSource(); Environment.Builder environmentBuilder = new Environment.Builder(id) .transactionFactory(txFactory) .dataSource(dataSource); //老规矩,会将dataSource设置进configuration对象 configuration.setEnvironment(environmentBuilder.build()); } } } } ... // ... 下面来看看解析datasource元素节点的方法,dataSourceElement(child.evalNode("dataSource")),这里看源码怎么实现datasource解析
/* * 解析datasource元素节点的方法 */ private DataSourceFactory dataSourceElement(XNode context) throws Exception { if (context != null) { //dataSource的连接池 String type = context.getStringAttribute("type"); //子节点 name, value属性set进一个properties对象 Properties props = context.getChildrenAsProperties(); //创建dataSourceFactory DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance(); factory.setProperties(props); return factory; } throw new BuilderException("Environment declaration requires a DataSourceFactory."); }
3.SqlSessionFactoryBuilder源码分析 (建造者模式)通过以上源码,我们就能看出,在mybatis的配置文件中:
1. configuration节点为根节点。 2. 在configuration节点之下,我们可以配置10个子节点, 分别为:properties、typeAliases、plugins、objectFactory、objectWrapperFactory、settings、environments、databaseIdProvider、typeHandlers、mappers。 3.解析配置文件完成了之后,都会装配到configuration 4.Configuration作用:mybatis核心的配置文件内容 ,使用xml转换bean