阿里九年架構師教你如何學會閱讀原始碼

JavaDoop發表於2019-03-25

讀原始碼的經歷

剛參加工作那會,沒想過去讀原始碼,更沒想過去改框架的原始碼;總想著別人的框架應該是完美的、萬能的,應該不需要改;另外即使我改了原始碼,怎麼樣讓我的改動生效了? 專案中引用的不還是沒改的jar包嗎。回想起來覺得那時候的想法確實挺……

工作了一年多之後準備跳槽了,開始了一輪的面試,其中有幾個面試官就問到了相關的原始碼問題:ArrayList、HashMap的底層實現,spring、mybatis的相關原始碼。問原始碼的面試一般就是回去等訊息,然後就沒然後了。

那時候開始意識到,原始碼這東西在之前的工作的中感受不到,但是在面試中好像面的還挺頻繁的,從此有意識的開始了jdk部分原始碼的閱讀(主要是集合)。一開始看原始碼,看的特別糙,知道個大概,知道ArrayList的底層實現是陣列,HashMap的底層是雜湊表(陣列+連結串列);更深入一點的擴容、hash碰撞等等就不知道了。

讀spring原始碼起於工作中遇到了一個問題(spring jdbcTemplate事務,各種詭異,包你醍醐灌頂!),排查一段時間最終是解決了,但過程讓我非常難受,各種上網查資料、各種嘗試,感覺就像大海撈針一樣,遙遙無期。我下定決心,我要看一看spring的原始碼,於是我買了一本《spring原始碼深度解析》,結合著這本書、開啟著eclipse,開始了spring的原始碼閱讀之旅。至此,讀原始碼成了習慣,原始碼已經進入了我的心裡。

後來,springboot的火熱,讓我也想蹭上一蹭,於是有了springboot的啟動原始碼系列,雖然還在進行中,但是我相信我能將其完成;工作中用到了shiro,我又結合著《跟我學shiro》將shiro的原始碼看了個大概,有了shiro原始碼系列博文,還差一篇認證與授權(應該很快就能面世),shiro原始碼系列就封筆了。最近在搭建自己的後臺管理系統,用到了quartz,整合的過程也遇到了一些問題,因此有了quartz的兩篇文章。

慢慢的,從一味的網上找資料變成了很多時候會從原始碼中找答案。不求能讀太多的原始碼,但願自己接觸的技術都能讀上一讀,路漫漫其修遠兮,吾將上下而求索!

我為什麼讀原始碼

很多人一定和我一樣的感受:原始碼在工作中有用嗎? 用處大嗎?很長一段時間內我也有這樣的疑問,認為哪些有事沒事扯原始碼的人就是在裝,只是為了提高他們的逼格而已。

那為什麼我還要讀原始碼呢? 一剛開始為了面試,後來為了解決工作中的問題,再後來就是個人喜好了。說的好聽點是有匠人精神;說的委婉點是好奇(底層是怎麼實現的);說的不自信點是對黑盒的東西我用的沒底,怕用錯;說的簡單直白點是提升自我價值,為了更高的薪資待遇(這裡對真正的技術迷說聲抱歉)。

原始碼中我們可以學到很多東西,學習別人高效的程式碼書寫、學習別人對設計模式的熟練使用、學習別人對整個架構的佈局,等等。如果你還能找出其中的不足,那麼恭喜你,你要飛昇了!會使用固然重要,但知道為什麼這麼使用同樣重要。從模仿中學習,從模仿中創新。

讀原始碼不像圍城(外面的人想進來,裡面的人想出去),它是外面的人不想進來,裡面的人不想出去;當我們跨進城內,你會發現(還是城外好,皮!)城內風光無限,原始碼的海洋任我們遨遊!

我是怎麼樣讀原始碼的

內容瞭解

首先我們要對我們的目標有所瞭解,知道她有什麼特點,有些什麼功能。對對方都還不瞭解,就想著進入別人的內心世界,那不是臭不要臉嘛,我們要做一個有著流氓心的紳士;對她有個大致的瞭解了,就可以發起攻勢,一舉拿下。

那麼怎麼樣瞭解了,方式有很多,我這裡提供幾種,僅供參考

最好的方式就是官方參考指南,親生父母往往對孩子是最瞭解的,對孩子的描述也是最詳細的;比如Spring Boot Reference Guide就是對springboot最詳細的描述,怎麼樣使用springboot、springboot特性等等,通過此指南,springboot在你面前一覽無遺;

但是,springboot畢竟是外國人的孩子,如果英語不好,估計讀起來有點頭疼了,不過我們有google翻譯呀,咬咬牙也是能看的。原始碼世界的丈母孃、老岳丈是非常慷慨的!

其次是書籍,國外優秀的有很多,國內也不乏好書,比較推薦此方式,自成體系,讓我們掌握的知識點不至於太散。這就是好比是原始碼的閨蜜,對原始碼非常瞭解,重點是挺大方,會盡全力幫助我們瞭解原始碼。

再次就是部落格,雖然可能覺得知識點比較散,但是針對某個知識點卻特別的細,對徹底掌握非常有幫助,園子內就有很多技術大牛,寫的部落格自然也是非常棒,非常具有學習價值。當然還有社群、論壇、github、碼雲等等。這就是原始碼的朋友圈,我們從中也能獲取到非常多關於原始碼的資訊。

阿里九年架構師教你如何學會閱讀原始碼

設計模式的瞭解

優秀的框架、技術從不乏設計模式;jdk原始碼中就應用了很多設計模式,比如IO流中的介面卡模式與裝飾模式、GUI的觀察者模式、集合中的迭代器模式等等;spring原始碼中也是用到了大量的設計模式。設計模式有什麼優點、各適用於什麼場景,不是本文的內容,需要我們大家自行去了解。

我們只需要對一些常用的設計模式有個大致瞭解,再去讀原始碼是比較好的;不需要將23種設計模式都通讀,也不需要將常用設計模式完全理解透;對於全部通讀,我們時間有限,另外有些模式確實不太好理解、用的少,價效比不高,沒必要全部都讀。

推薦書籍:《Head First Design Patterns》(中文版:《Head First 設計模式》)、《Java與模式》;

常用設計模式:單例模式、工廠模式、介面卡模式、裝飾模式、外觀模式、代理模式、迭代器模式、觀察者模式、命令模式

另外我比較推薦的一種學習設計模式的方式是讀別人部落格:java_my_life,劉偉技術部落格,chenssy的設計模式;

設計模式之於原始碼,就好比逛街購物之於女人,想順利勾搭原始碼,我們需要好好掌握設計模式這個套路。

配合ide進行斷點追蹤

我們通過原始碼的圈子對原始碼的瞭解終究只是停在表面,終究還是沒有走進她的內心,接下來我就和大家分享下,我是如何走進她的內心的!

相信看過我的原始碼部落格的小夥伴都知道,我非常喜歡通過idea斷點來進行原始碼追蹤,斷點追蹤原始碼是我非常推薦的一種方式。斷點不僅可以用來除錯我們的程式碼,也可以用來除錯我們用到的框架原始碼。

面對未知的、茫茫多的原始碼,我們往往沒有足夠的時間、經歷和耐心去通讀所有原始碼,我們只需要去讀我們關注的部分即可(有人可能會說我都不關心,這…)。那為什麼要用斷掉除錯的方式來跟原始碼,而不是直接從原始碼入手去跟我們關注的部分呢?

嘗試過的小夥伴應該知道,如果我們對原始碼不熟悉,直接通過原始碼的方式去跟,一方面很容易迷路(多型,會有很多子類實現),不知道接下來跟哪一個,另一方面也很容易跟丟,當我們跟入的很深的時候,很有可能就忘記上一步跟到哪了。

下面我會舉例來說明我是如何進行斷點追蹤的,以spring-boot-2.0.3之quartz整合,不是你想的那樣哦!和 spring-boot-2.0.3之quartz整合,資料來源問題,原始碼探究 為背景來講,需要搞清楚兩個點:springboot是如何向quartz注入資料來源的,quartz是如何運算元據庫的

springboot向quartz注入資料來源

QuartzAutoConfiguration是springboot自動配置quartz的入口

阿里九年架構師教你如何學會閱讀原始碼

將quartz的配置屬性設定給SchedulerFactoryBean;將資料來源設定給SchedulerFactoryBean:如果有@QuartzDataSource修飾的資料來源,則將@QuartzDataSource修飾的資料來源設定給SchedulerFactoryBean,否則將應用的資料來源(druid資料來源)設定給SchedulerFactoryBean,顯然我們的應用中沒有@QuartzDataSource修飾的資料來源,那麼SchedulerFactoryBean中的資料來源就是應用的資料來源;將事務管理器設定給SchedulerFactoryBean。SchedulerFactoryBean,負責建立和配置quartz Scheduler,並將其註冊到spring容器中。SchedulerFactoryBean實現InitializingBean的afterPropertiesSet方法,裡面有可以設定資料來源的過程

阿里九年架構師教你如何學會閱讀原始碼

可以看到通過org.quartz.jobStore.dataSource設定的dsName(值為quartzDs)最後會被替換成springTxDataSource.加scheduler例項名(我們的應用中是:springTxDataSource.quartzScheduler)。springboot會註冊兩個ConnectionProvider給quartz:一個dsName叫springTxDataSource.quartzScheduler,有事務;一個dsName叫springNonTxDataSource.quartzScheduler,沒事務。

quartz如何運算元據庫

我們通過停止定時任務來跟下quartz對資料庫的操作

阿里九年架構師教你如何學會閱讀原始碼

發現quartz用如下方式獲取connection

conn = DBConnectionManager.getInstance().getConnection(getDataSource());
複製程式碼

那麼我們的job中就可以按如下方式運算元據庫了

package com.lee.quartz.job;

import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.utils.DBConnectionManager;
import org.springframework.scheduling.quartz.LocalDataSourceJobStore;
import org.springframework.scheduling.quartz.QuartzJobBean;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class FetchDataJob extends QuartzJobBean {

    // private String dataSourceName = "quartzDs";                                  // 用此會找不到
    // private String dataSourceName = "springNonTxDataSource.quartzScheduler";     // 不支援事務
    // private String dataSourceName = "springTxDataSource.quartzScheduler";        // 支援事務
    private final String insertSql = "INSERT INTO tbl_sys_user(name, age) VALUES(?,?) ";

    private String schedulerInstanceName = "quartzScheduler";                       // 可通過jobDataMap注入進來

    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        String dsName = LocalDataSourceJobStore.NON_TX_DATA_SOURCE_PREFIX
                + schedulerInstanceName;    // 不支援事務
        //String dsName = LocalDataSourceJobStore.TX_DATA_SOURCE_PREFIX + schedulerInstanceName;    // 支援事務
        try {
            Connection connection = DBConnectionManager.getInstance().getConnection(dsName);
            PreparedStatement ps = connection.prepareStatement(insertSql);
            ps.setString(1, "張三");
            ps.setInt(2, 25);
            ps.executeUpdate();

            ps.close();
            connection.close();             // 將連線歸還給連線池
            System.out.println("插入成功");
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public void setSchedulerInstanceName(String schedulerInstanceName) {
        this.schedulerInstanceName = schedulerInstanceName;
    }
}
複製程式碼

明確我們的目的,找到合適的切入點,進入斷點除錯追蹤也就容易了。

任我說的天花亂墜,你仍無動於衷,那也只是我一廂情願,只有局中人才能體會到其中的奧妙!

總結與感悟

從上至下全部通讀的方式,個人不太推薦,這是建立在很熟悉的基礎上的,當我們對某個框架已經比較熟悉了,再從上至下進行通讀,徹底瞭解,這是我認為正確的方式;但是從不熟悉到熟悉這個過程,個人不推薦全部通讀,而是推薦上面我推薦的方式 - 斷點區域性追蹤。

很多時候,我們的博文都只是授之以魚,而我們也只是從中得到魚;而這篇的目的則是授之以漁,我希望大家從中學到捕魚的方法,而不是一味的等待別人的魚;希望大家能夠自給自足,也能把魚和漁都授予其他人。

只要我們開始去讀原始碼,慢慢的就會形成自己的一套讀原始碼的方式;每個人的方式都不一樣,合適自己的才是最好的。行動起來,用合適的方式去俘獲你的的她吧!

當然,針對Java程式設計師,如果剛好你的技術又遇到了瓶頸但是你又拒絕平庸,期待蛻變,想進入一線網際網路公司或者給自己漲薪。我這裡剛好有一套自己儲存的Java進階學習資料。包含了Spring框架、Mybatis框架SpringBoot框架、SpringMVC框架、SpringCloud微服務、Dubbo框架、Redis快取、RabbitMq訊息、JVM調優、Tomcat容器、MySQL資料庫等等。之前的兩千人群滿了 這個是新群Java高階進階群:963944895,免費傳送的喲

阿里九年架構師教你如何學會閱讀原始碼

相關文章