反编译到源码-如何阅读源码(推荐收藏)

前言

阅读源代码是大多数程序员进步的重要途径。 最近了解到很多同学反映看了​​一些源码,并没有太大收获。 看了一些源码后,总是半途而废,遇到很多麻烦。

主要表现有:

网上也会有一些关于特定开源代码的系列文章,一般比较繁琐,授“鱼”而不是“钓鱼”。 古语云:授人以鱼,不如授人以渔。

相信大部分朋友都希望得到方法论级别的文章,更系统地介绍如何更好地阅读源码。 因此,在这里我将我的源码阅读经验传授给大家。 相信很多人都会明白问题的根源,并给出一些“意想不到”的实用建议,让那些为阅读源码而困扰的朋友找到出路。

文章重点阐述了以下内容:

总体概述:

为什么很多人阅读源码却收效甚微?

在我看来,大多数人阅读源码并没有多大收获的主要原因如下:

读源码要注意什么?

我们做事一定要“以终为始”。 只有读完源码才知道自己想要得到什么,才能避免出现“到处乱飞”却收效甚微的尴尬场面。

你这样读源码干什么? 我们应该关注什么?

阅读目的:该框架解决了什么问题? 与同类框架相比有何优缺点? 这对于理解框架非常重要。

阅读注释:很多人在阅读源代码时会忽略注释。 建议大家在阅读源码时注意注释。 由于优秀的开源项目,在注释中都会提到某个类的用途、某个功能、核心逻辑、核心参数解释、异常发生场景等,这对于我们学习源码和学习有很大帮助。分析问题。

阅读逻辑:这里所谓的逻辑是指句子或子功能的顺序。 我们一定要注意作者编码的顺序,明白为什么先写A再写B,背后的原因是什么。

阅读思路:所谓的思路是指源码背后的设计原则,比如是否符合设计模式的六大原则? 是否符合高内聚低耦合? 它是否体现了某种性能优化的想法?

阅读原则:阅读核心实现步骤,而不是死记硬背每一行代码。 核心原则和步骤是最重要的。

阅读编码风格:一般来说,优秀源码的代码风格是比较高贵的。 我们可以通过源代码来学习编码标准。

阅读编程方法:作者是否采用了某种设计模式,某种编程方法取得了意想不到的疗效。

阅读设计方案:除了具体的代码之外,更重要的是阅读源码时的设计方案。 比如我们下载一个秒杀系统/商城系统的代码,我们可以学习密码加密方案、分布式事务处理方案、幂等设计方案、超买问题的解决方案等等。因为掌握这种方案对于提高自己的工作经验,我们在工作中制定技术方案时可以参考那些优秀的项目方案。

阅读源码时的错误

很多人读源码不顺利,疗效不好,而且普遍存在一些共性。

这样看源码有哪些误区呢?

开始玩老大

经常玩游戏的同学都知道,一开始就直接打boss,无异于送人头。

通常先从中路开始,积累经验,然后挑战boss。

如果你开始尝试学习源码,从一个小型的开源框架入手,很容易失去信心,甚至放弃。

佛教青年

经常玩游戏的同学也知道,玩游戏需要策略,随便玩很容易失败。

有的同事决定去读源码,但缺乏规划,为所欲为,疗效往往不是很好。

回答提问

我们知道,很多高中生、小学生,甚至很多大学生在学习上都会显得不如前辈。

有些人做题的时候不会先思考,而是先看答案,然后根据答案的思路来理解问题。 在这些模式下,大部分问题都被认为是理所当然的,你会误以为你真的理解了。 而且即使是原题,也会出错,想不出思路。

同样,很多人在阅读源码时也会陷入这样的误区。 直接看源码的分析,直接看源码的写法,缺少关键的后期步骤,即先自己思考再对比源码。

​​​会采用先读源码再读源码的思路

在学习某个源码之前,必须对源码的基本用法有一个初步的了解。

如果对框架没有基本了解就直接阅读源码,效果一般不会很好。

通常优秀的开源项目都会给出一些简单的官方示例代码,你可以运行官方示例代码来了解基本用法。

还可以去GitHub上搜索并拉取某项技术的demo、某项技术的helloworld项目,快速使用。

例如Dubbo官方文档给出了快速入门示例代码; 轻量级分布式服务框架jupiterREADME.md给出了简单的调用示例。 一些开源项目提供了多个框架的示例代码,例如教程。

先易后难

循序渐进是学习的大法则。

一方面,可以先尝试阅读比较简单的开源项目的源码反编译到源码,比如commons-lang、commons-collection、guava、mapstruct等工具类源码。

另外,你也可以尝试寻找某个框架的简单版本。 先从简单的版本入手,看完之后学习小型开源项目会容易很多。

很多人可能会说很难找,虽然大多数著名的开源项目都会有简易版本反编译到源码,但只要认真搜索,大部分都能找到,比如 Spring 的简易版、Dubbo 的简易版。

先整体,后部分

先整体后部分是一个非常重要的认知规则,它凸显了“整体思维”。

如果缺乏对框架的整体了解,很容易陷入局部细节。

先整体后局部包含多重含义,下面介绍几个核心含义。

先看结构再看源码

你可以通过官方的框架文档了解它的整体结构和核心原理,然后去具体的源码。

然而很多人总是忽视这一步。

比如轻量级分布式服务框架jupiter框架的README.md给出了该框架的整体框架:

(图片来自:jupiter项目README.md文档)

对框架有了整体的了解之后,再看具体的实现就会容易很多。

先看项目结构再看源码

先整体后局部,包括先看项目的总体封装,再详细看源码。

(图片来自:jupiter项目结构)

通过项目的应用,如monitor、registry、serialization、example、common等,可以了解包下的代码意图。

先看类的函数列表再看源码

通过IDEA的函数列表功能,可以快速了解某个类所包含的函数,初步了解该类的核心功能。

这些方法在阅读单个源代码时非常有效。

更重要的是,如果能养成查看函数列表的习惯,就能发现很多重要的、被忽视的函数,这些函数在以后的项目开发中很可能会用到。

右图是commons-lang3 3.9版本中StringUtils类的函数列表示意图:

先看整体逻辑再看某个步骤

例如,一个大函数可以分为多个步骤。 我们首先要明白某个步骤的意图,明白为什么先执行子函数1,然后执行子函数2。

然后再去观察一个子功能的细节。

以spring-context 5.1.0.RELEASE版本的IOC容器的核心org.springframework.context.support.AbstractApplicationContext的核心功能刷新为例:

@Override
public void refresh() throws BeansException, IllegalStateException {
   synchronized (this.startupShutdownMonitor) {
      // Prepare this context for refreshing.
      // 1 初始化前的预处理
      prepareRefresh();
      // Tell the subclass to refresh the internal bean factory.
      // 2 告诉子类去 refresh 内部的 bean Factory 
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
      // Prepare the bean factory for use in this context.
      // 3 BeanFactory 的预处理配置
      prepareBeanFactory(beanFactory);
      try {
         // Allows post-processing of the bean factory in context subclasses.
        // 4 准备 BeanFactory 完成后进行后置处理
         postProcessBeanFactory(beanFactory);
         // Invoke factory processors registered as beans in the context.
         // 5 执行 BeanFactory 创建后的后置处理器
         invokeBeanFactoryPostProcessors(beanFactory);
         // Register bean processors that intercept bean creation.
         // 6 注册 Bean 的后置处理器
         registerBeanPostProcessors(beanFactory);
         // Initialize message source for this context.
         // 7 初始化 MessageSource
         initMessageSource();
         // Initialize event multicaster for this context.
         // 8 初始化事件派发器
         initApplicationEventMulticaster();
         // Initialize other special beans in specific context subclasses.
         // 9 子类的多态 onRefresh
         onRefresh();
         // Check for listener beans and register them.
         // 10 检查监听器并注册
         registerListeners();
         // Instantiate all remaining (non-lazy-init) singletons.
         // 11 实例化所有剩下的单例 Bean (非懒初始化)
         finishBeanFactoryInitialization(beanFactory);
         // Last step: publish corresponding event.
         // 12 最后一步,完成容器的创建
         finishRefresh();
      }
      catch (BeansException ex) {
         if (logger.isWarnEnabled()) {
            logger.warn("Exception encountered during context initialization - " +
                  "cancelling refresh attempt: " + ex);
         }
         // Destroy already created singletons to avoid dangling resources.
        // 销毁已经常见的单例 bean 
         destroyBeans();
         // Reset 'active' flag.
         // 重置  active 标志
         cancelRefresh(ex);
         // Propagate exception to caller.
         // 将异常丢给调用者
         throw ex;
      }
      finally {
         // Reset common introspection caches in Spring's core, since we
         // might not ever need metadata for singleton beans anymore...
        // 重置缓存
         resetCommonCaches();
      }
   }
}

我们可以高度关注每一步的意义,思考为什么要这样设计,然后单步进入一个子函数来了解具体的实现。

比如去了解第7步的具体编码实现。

/**
 * Initialize the MessageSource.
 * Use parent's if none defined in this context.
 */
protected void initMessageSource() {
   ConfigurableListableBeanFactory beanFactory = getBeanFactory();
   if (beanFactory.containsLocalBean(MESSAGE_SOURCE_BEAN_NAME)) {
      this.messageSource = beanFactory.getBean(MESSAGE_SOURCE_BEAN_NAME, MessageSource.class);
      // Make MessageSource aware of parent MessageSource.
      if (this.parent != null && this.messageSource instanceof HierarchicalMessageSource) {
         HierarchicalMessageSource hms = (HierarchicalMessageSource) this.messageSource;
         if (hms.getParentMessageSource() == null) {
            // Only set parent context as parent MessageSource if no parent MessageSource
            // registered already.
            hms.setParentMessageSource(getInternalParentMessageSource());
         }
      }
      if (logger.isTraceEnabled()) {
         logger.trace("Using MessageSource [" + this.messageSource + "]");
      }
   }
   else {
      // Use empty MessageSource to be able to accept getMessage calls.
      DelegatingMessageSource dms = new DelegatingMessageSource();
      dms.setParentMessageSource(getInternalParentMessageSource());
      this.messageSource = dms;
      beanFactory.registerSingleton(MESSAGE_SOURCE_BEAN_NAME, this.messageSource);
      if (logger.isTraceEnabled()) {
         logger.trace("No '" + MESSAGE_SOURCE_BEAN_NAME + "' bean, using [" + this.messageSource + "]");
      }
   }
}

从这个子函数来看,“整体”是if和else两个代码块,“部分”是if和else代码块的具体步骤。

从设计师的角度学习源代码

从设计者的角度去阅读源代码是一个非常重要的思想。 凸显了“先推测,后验证”的思想。

这样,你就可以走出“做题去回答”的误区。

在学习源码的时候,无论是框架的整体结构,还是某个具体的类或者某个函数,你都要设想如果你是作者,如何设计框架,如何编译某个类或者某个函数的代码一定的功能。

然后和最终的源码进行对比,找出自己的想法和对方的差异。 这样,你会对源代码印象更深刻,更好地理解作者的意图。

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

悟空资源网 源码编译 反编译到源码-如何阅读源码(推荐收藏) https://www.wkzy.net/game/178012.html

常见问题

相关文章

官方客服团队

为您解决烦忧 - 24小时在线 专业服务