目錄
2. 根據這個架構寫出一個MyBatis的1.0版本Demo
5. 查看MyBatis源碼中的各模塊代碼,查看相互聯繫和代碼實現
一、 我是如何看源碼的
看框架原碼是提高代碼設計能力的一個重要的途徑。通過學習大牛的優秀代碼和設計思想,我們既能夠更深地理解框架的底層原理,又能夠強化代碼的架構能力。
但是開源框架通常都是比較大的項目,盲目地一頭扎到代碼中去逐行看,可能會摸不着頭腦,沒有成就感,而一點點地耗盡熱情,最終收效甚微。
我的建議是—— 先抓梗概,後看模塊,帶着問題看代碼。
我通常的做法是:
1. 在看源碼之前,設想如果自己來設計這個框架的話會怎麼寫。
根據框架的功能和暴露的接口,寫一個小Demo, 可以寫的很簡單,接口裏甚至只打印一句話都行,關鍵是思考着對框架進行分層和功能模塊劃分,只實現最核心的邏輯。
這樣能讓自己提起好奇心,以便看代碼時不會倦怠。同時這是自己設計能力的一個答卷,後面將對照着標準答案(真實的框架代碼設計情況)進行評判,找到自己的不足和更好的優化方式。
2. 先看框架梗概,不陷入具體細節,列出整個框架的骨架圖和各模塊之間的關係;之後再按功能看各個模塊的代碼實現。
過早地陷入代碼細節,會導致只見樹木不見森林;先搭好骨架再去填充細節要相對容易得多。
3. 看完代碼之後按真實的框架再寫一個demo,前面寫的demo哪個地方不好?該怎樣優化?
不懂的地方可以猜測再debug一下。
多問些關鍵問題,找關鍵類聯繫起來。
二、MyBastis架構
我們以MyBastis爲例來看一下框架源碼。
1. 預先分析一下MyBatis的骨架
我們先看一下MyBatis的常用例子:
SqlSession session = null;
try{
//創建SessionFactory對象,讀取配置信息
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(
Resources.getResourceAsStream("mybatis-config.xml"));
//創建一個session
session = factory.openSession();
PersonMapper personMapper = session.getMapper(PersonMapper.class);
//Mybatis通過mapper代理直接執行查詢方法
Person person= personMapper.selectOne(1001);
System.out.println(person);
}
catch(Exception e){
e.printStackTrace();
}
finally{
if(null != session){
session.close();
}
}
從這些應用信息來看,我們能知道MyBatis:
(1)要有一個Configuration類,負責解析xml配置文件, 將相應的配置讀進來並存儲到Map中,包括DB相應的配置、包名.類名.方法名 與SQL語句之間的轉換配置、以及其他的配置等。
(2)Session類, 要建立與DB的連接,根據調用的情況執行相應的SQL語句並返回相應的結果;如果Session類的功能太多會導致耦合,不妨將與DB打交道的功能單獨封裝到一個Executor類中(至於叫什麼無所謂)。
(3)Session類需要提供一個getMapper() 方法, 根據類名生成相應的代理來調用想要的方法,肯定要用到動態代理,再封裝一個動態代理類MapperProxy。
至於SessionFactory和SessionFactoryBuilder不屬於核心功能,後續再去添加。
這些是最基本的骨架,我們根據這些基本的功能來搭建MyBatis的1.0版本的Demo。
2. 根據這個架構寫出一個MyBatis的1.0版本Demo
Session類:
package mybatisdemo;
import java.lang.reflect.Proxy;
public class MSession {
private String xmlPath;
public MSession(String xmlPath) {
this.xmlPath = xmlPath;
}
public <T> T getMapper(Class clazz){
return (T) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
new Class[]{clazz},
new MProxyHandler(this.xmlPath));
}
}
ProxyHandler類:
package mybatisdemo;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MProxyHandler implements InvocationHandler {
private MConfiguration config;
private MExecutor executor;
public MProxyHandler(String xmlPath) {
config = new MConfiguration(xmlPath);
executor = new MExecutor(config);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (this.config.getNameSpace().equals(method.getDeclaringClass().getName())){
//這裏應該要根據所調用的方法選擇不同的調用分支,先寫個最簡單版本,之後再豐富
return executor.selectOne(method.getName(), args);
}
//不屬於配置的方法,則去調用Object原有的方法
return method.invoke(this, args);
}
}
Configuration類
package mybatisdemo;
import java.util.HashMap;
public class MConfiguration{
//存儲數據庫配置信息
private HashMap<String, String> DBMap;
//存儲類名.方法名 與SQL之間的映射
private HashMap<String, String> SQLMap;
//包名.類名,本應讀xml配置,這裏模擬直接寫死
private String nameSpace;
public MConfiguration(String xml) {
//直接在這裏賦值模擬讀xml文件
DBMap = new HashMap<>();
DBMap.put("user", "root");
DBMap.put("password", "passwd");
DBMap.put("driver", "com.mysql.jdbc.Driver");
DBMap.put("url", "jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8");
//SQLMap的內部也應該是讀xml進來的,這裏直接賦值模擬
SQLMap = new HashMap<>();
SQLMap.put("selectOne", "select * from person where id = ?");
this.nameSpace = "mybatisdemo.PersonMapper";
}
public HashMap<String, String> getDBMap() {
return DBMap;
}
public HashMap<String, String> getSQLMap() {
return SQLMap;
}
public String getNameSpace() {
return nameSpace;
}
}
Executor類:
package mybatisdemo;
import java.sql.*;
public class MExecutor {
private static MConfiguration config;
private Connection connection = null;
private PreparedStatement statement = null;
public MExecutor(final MConfiguration config) {
this.config = config;
}
public void init(){
//獲取連接
try {
Class.forName(config.getDBMap().get("driver"));
if (null == this.connection){
String url = config.getDBMap().get("url");
String user = config.getDBMap().get("user");
String passwd = config.getDBMap().get("password");
connection = DriverManager.getConnection(url, user, passwd);
}
} catch (SQLException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public <T> T selectOne(String methodName, Object[] args){
String sql = config.getSQLMap().get(methodName);
if (null != sql && 0 != sql.length()){
return query(sql, args);
}
return null;
}
public <T> T query(String sql, Object[] args){
//這裏也是用的硬編碼,後續需要改進
PersonInfo person = new PersonInfo();
try {
init();
statement = connection.prepareStatement(sql);
statement.setLong(1, (Long) args[0]);
ResultSet rs = statement.executeQuery();
//這裏結果集處理用的硬編碼,需改進
while (rs.next()){
person.setId(rs.getLong(1));
person.setName(rs.getString(2));
person.setAge(rs.getInt(3));
person.setGender( rs.getByte("gender"));
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
try {
connection.close();
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return (T) person;
}
}
PersonInfo類: 一個信息封裝類
package mybatisdemo;
public class PersonInfo {
private long id;
private String name;
private int age;
private byte gender;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public byte getGender() {
return gender;
}
public void setGender(byte gender) {
this.gender = gender;
}
@Override
public String toString() {
return "PersonInfo{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", gender=" + gender +
'}';
}
}
PersonMapper interface: 一個用來測試的接口,不做implements,
package mybatisdemo;
import java.util.List;
public interface PersonMapper {
PersonInfo selectOne(Long id);
List<PersonInfo> selectAll();
int insert(PersonInfo info);
int delete(Long id);
int update(PersonInfo info);
}
Main函數, 直接session調用PersonMapper.class 生成動態代理,不用寫實現直接根據文件就能查出結果
package mybatisdemo;
public class TestMain {
public static void main(String[] args) {
MSession session = new MSession("mybatis.xml");
PersonMapper mapper = session.getMapper(PersonMapper.class);
PersonInfo info = mapper.selectOne(1L);
System.out.println(info);
}
}
運行結果:
3. 看1.0版本有哪些不足,該添加些什麼,該怎樣升級
1.0版本能夠運行了,已經能夠像MyBatis那樣,不用寫實現、直接通過配置文件就能夠在調用方法時輸出結果。
但這個簡陋的版本,也存在着不少的問題,還需要繼續優化。我們看看存在哪些問題:
(1)讀取xml部分還沒有寫
(2)ProxyHandler 的invoke() 裏只寫了一個方法,這裏需要根據不同的調用情況選擇不同的調用分支
(3)結果集處理寫的是硬編碼,這裏需要由xml配置給出resultSet或 resultMap,並且加上ORM 進行自動處理
(4)Session加上工廠類SessionFactory、 SessionFactoryBuilder
(5)加上一級緩存、二級緩存,提高查詢性能, Cache類及Cache過期策略, LRU/FIFO, 以及增/刪/改時Cache失效
(6)連接管理、事務管理
(7)日誌,插件,註解,異常處理,以及其他的擴展功能
4. 開始看真實的MyBatis源碼, 理出其骨架
別急着深入看各個類,先粗看整理出枝節,不要陷入細節
這是一個MyBatis的層級架構和功能模塊圖
5. 查看MyBatis源碼中的各模塊代碼,查看相互聯繫和代碼實現
根據功能模塊,查看各代碼。 看細節代碼時,時刻記得整體的架構,自己寫的與它們的差距在哪裏,怎樣寫的更好。並注意各模塊之間的聯繫。
後面博文,將分別解讀各核心模塊, 共包括 Configuration、Plugin、 MapperProxy、 SqlSession、 Executor 幾個部分。
謝謝!