浅谈下我是怎么读源码的 → 授人以渔

161 阅读11分钟

开心一刻

今天上课不小心睡着了,结果被老师叫起来回答问题,这是背景
无奈之下看向同桌寻求帮助,同桌小声说到选C,结果周围的人都说选C
我向同桌投去一个感激的眼神后大声说道:选C
刚说完教室就笑开了
老师一脸恨铁不成钢的表情说:选你个头,我叫你翻译文言文你选C!你出去,你给我出去
看着同桌挤眉弄眼的表情,劳资真想说,这帮畜生!

企业钻洞.gif

互相抱怨道:你是不是又长胖了?

读源码的经历

刚参加工作那会,没想过去读源码,更没想过去改框架的源码
总想着别人的框架应该是完美的、万能的,应该不需要改
另外即使我改了源码,怎么样让我的改动生效了,项目中引用的不还是没改的jar包吗?
回想起来觉得那时候的想法确实挺......
工作了一年多之后准备跳槽了,开始了一轮的面试
其中有几个面试官就问到了相关的源码问题:ArrayListHashMap的底层实现,springmybatis的相关源码
问源码的面试一般就是回去等消息,然后就没然后了
那时候开始意识到,源码这东西在之前的工作的中感受不到,但是在面试中好像面的还挺频繁的,从此有计划的开始了jdk部分源码的阅读(主要是集合)
一开始看源码,看的特别糙,知道个大概,知道ArrayList的底层实现是数组,HashMap的底层是散列表(数组+链表)
更深入一点的扩容、hash碰撞等等就不知道了
Spring源码始于工作中遇到了一个问题(spring jdbcTemplate事务,各种诡异,包你醍醐灌顶!
虽说经过一段时间的排查,最终是解决了,但过程让我非常难受
网上各种查资料、各种尝试,感觉就像大海捞针一样,遥遥无期
我暗下决心,我要看一看Spring的源码,于是我买了一本《spring源码深度解析》,结合着这本书、打开着eclipse,开始了Spring的源码阅读之旅
至此,读源码成了习惯,源码已走进了我的心里!
后来,Spring Boot的火热,让我也想蹭上一蹭,于是有了Spring Boot启动源码系列
工作中用到了Shiro,我又结合着《跟我学Shiro》Shiro的源码看了个大概,有了Shiro源码系列
最近在搭建自己的后台管理系统,用到了Quartz,集成的过程也遇到了一些问题,因此有了quartz的三篇文章
慢慢的,从一味的在网上找资料变成了很多时候会从源码中找答案
不求能读太多的源码,但愿自己接触的技术都能读上一读,路漫漫其修远兮,吾将上下而求索!

为什么读源码

相信很多人和我一样,有这样的的疑问:我就一个写代码的,需要读源码吗?

臭写代码的.jpg

而那些有事没事就扯源码的人,无非就是在装,只是为了提高他们的逼格

装逼.jpg

但慢慢的,我改变了这样的认知
一刚开始为了面试,后来为了解决工作中的问题,再后来就是个人喜好了
说的好听点是有匠人精神
说的委婉点是好奇(底层是怎么实现的?)
说的不自信点是对黑盒的东西我用的没底,怕用错
说的简单直白点是提升自我价值,为了更高的薪资待遇(这里对真正的技术迷说声抱歉)
源码中我们可以学到很多东西:高效的写法、设计模式的使用、架构的设计与实现,等等
如果你还能找出其中的bug,那么恭喜你,你升级了!
会使用固然重要,但知道为什么这么使用同样重要,知其然,知其所以然!
从模仿中学习,从模仿中创新!
读源码不像围城(外面的人想进来,里面的人想出去),它是外面的人不想进来,里面的人不想出去
当我们跨进城内,你会发现城内风光无限,源码的海洋任我们遨游!

747662-20181211151510538-667770651.jpg 747662-20181211151519689-1585325042.jpg

你想好入城了吗?

怎么样读源码

精通内容

首先我们要对我们的目标有所了解,知道她有什么特点,有哪些爱好
对对方都还不了解,就想着闯入人家的内心,那不是耍流氓,臭不要脸嘛

臭不要脸.gif

我们要做一个有着流氓心的绅士,对她有了清晰的了解后,再可以发起攻势,一举拿下
那么怎么样了解她,方式有很多,我这里提供几种,大家可以参考:
1、官方参考指南,亲生父母往往对孩子是最了解的,对孩子的描述也是最详细的
比如Spring Boot Reference Guide就是对Spring Boot最详细的描述
她有哪些特性,如何与她友好相处等等,此指南都有详细记载
熟读此指南,她将犹如刚出浴的美人,在你面前一丝不挂,不对,是一览无遗!
但是,Spring Boot毕竟是外国人的孩子,如果英语不好,估计读起来有点头疼了,不过我们有google翻译呀,咬咬牙也是能看的
源码世界的丈母娘、老岳丈是非常慷慨的!

BD997914490@qq.com582400732864.gif

2、书籍,国外有很多优秀书籍,国内也有很多优秀书籍,比较推荐此方式,自成体系,让我们掌握的知识点不至于太散
这就是好比是源码的闺蜜,对源码非常了解,重点是挺大方,会尽全力帮助我们了解她
3、博客,知识点可能比较单一,但是很有针对性,对彻底掌握某个知识点非常有帮助
比较负责的博主一般会写系列,前后串联起相关的知识点,让大家很容易就能看懂
很明显,楼主就是这样的博主!

鼓掌.png

另外还有社区、论坛、github、码云等等,这就是源码的朋友圈,我们从中也能获取到非常多关于源码的信息

747662-20181212145850325-1820087464.png

掌握设计模式

优秀的框架、技术从不乏设计模式
jdk源码中就应用了很多设计模式,例如IO流中的适配器模式装饰模式GUI观察者模式、集合中的迭代器模式,等等
Spring源码中也是用到了大量的设计模式,例如事件机制中的观察者模式AOP的核心动态代理JdbcTemplate以及RestTemplate涉及的模板方法,等等
设计模式有什么优点、各适用于什么场景,不是本文的内容,你们自行去查阅
我们只需要对一些常用的设计模式有个大致掌握,再去读源码是比较好的
不需要精通23种设计模式,毕竟薛之谦说过:其实感情最怕的就是拖着
推荐书籍:《Head First Design Patterns》(中文版:《Head First 设计模式》)、《Java与模式》
设计模式之于源码,就好比逛街购物之于女人,想顺利勾搭源码,我们需要好好掌握设计模式这个套路

了解断点调试

通过源码的圈子对她的了解,终究只是停在表面,始终未能走进她的内心,接下来我就和大家分享下,我是如何走进她的内心的!
相信看过我的源码博客的小伙伴都知道,我非常喜欢通过idea断点来进行源码追踪,断点追踪源码是我非常推荐的一种方式
断点不仅可以用来调试我们的代码,也可以用来调试我们用到的框架源码
面对未知的、茫茫多的源码,我们往往没有足够的时间、精力和耐心去通读所有源码,我们只需要去读我们关注的部分即可(都看到这了,就不要说:我都不关心了)
为什么要用断掉调试的方式来跟源码,而不是直接从源代码入手去跟我们关注的部分呢?
尝试过的小伙伴应该知道,如果我们对源码不熟悉,直接通过源码的方式去跟,很容易迷路(多态,会有很多子类实现),不知道接下来跟哪一个,当我们跟入的很深的时候,很有可能又忘记上一步跟到哪了
下面我举例来说明我是如何进行断点追踪的,以spring-boot-2.0.3之quartz集成,不是你想的那样哦!和 spring-boot-2.0.3之quartz集成,数据源问题,源码探究 为背景来讲
讲清楚两个点:1、springboot是如何向quartz注入数据源的,2、quartz是如何操作数据库的 我们先来看第1点,QuartzAutoConfigurationspringboot自动配置quartz的入口

QuartzAutoConfiguration.gif

quartz的配置属性设置给SchedulerFactoryBean
将数据源设置给SchedulerFactoryBean:如果有@QuartzDataSource修饰的数据源,则将@QuartzDataSource修饰的数据源设置给SchedulerFactoryBean,否则将应用的数据源(druid数据源)设置给SchedulerFactoryBean
显然我们的应用中没有@QuartzDataSource修饰的数据源,那么SchedulerFactoryBean中的数据源就是应用的数据源
将事务管理器设置给SchedulerFactoryBean,它负责创建和配置quartz Scheduler,并将其注册到spring容器中
SchedulerFactoryBean实现了InitializingBean,其afterPropertiesSet方法里面有设置数据源的过程

quartz_dsName.gif

可以看到通过org.quartz.jobStore.dataSource设置的dsName(值为quartzDs)最后会被替换成springTxDataSource.scheduler实例名(我们的应用中是:springTxDataSource.quartzScheduler)
springboot会注册两个ConnectionProviderquartz
一个dsNamespringTxDataSource.quartzScheduler,有事务
一个dsNamespringNonTxDataSource.quartzScheduler,没事务
我们再来看第2点,通过停止定时任务来跟下quartz对数据库的操作

747662-20181212211122999-1286856550.gif

发现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;
    }
}

明确我们的目的,找到合适的切入点,进入断点调试追踪也就容易了
任我说的天花乱坠,你仍无动于衷,那也只是我一厢情愿,只有局中人才能体会到其中的奥妙!
言外之意就是你得调试起来!

调试起来.gif

断点调试就好比软妹币,能够省去很多中间环节,让你直达她的内心!

总结与感悟

从上至下全部通读的方式,个人不太推荐,这是建立在很熟悉的基础上的
当我们对某个框架已经比较熟悉了,再从上至下进行通读,彻底了解,这是我认为正确的方式
但是从不熟悉到熟悉这个过程,个人不推荐全部通读,而是推荐上面我推荐的方式:断点调试

很多时候,我们的博文都只是授之以鱼,而我们也只是从中得到鱼
而这篇的目的则是授之以渔,我希望大家从中学到捕鱼的方法,而不是一味的等待别人的鱼
希望大家能够自给自足,也能把鱼和渔都授予其他人

只要我们开始去读源码,慢慢的就会形成自己的一套读源码的方式
每个人的方式都不一样,适合自己的才是最好的
行动起来,用合适的方式去俘获你的她吧!