大家好,java企业网站源码分享demo相信很多的网友都不是很明白,包括java项目网站也是一样,不过没有关系,接下来就来为大家分享关于java企业网站源码分享demo和java项目网站的一些知识点,大家可以关注收藏,免得下次来找不到哦,下面我们开始吧!
目前网上有很多「XX源码分析」这样的文章,不过这些文章分析源码的范围有限,有时候讲的内容不是读者最关心的。同时我也注意到,源码是在不断更新的,文章里写的源码往往已经过时了。因为这些问题,很多同学都喜欢自己看源码,自己动手,丰衣足食。
这篇文章主要讲的是读大型的前端开源项目比如React、Vue、Webpack、Babel的源码时的一些技巧。目的是让大家在遇到需要阅读源码才能解决的问题时,可以更快的定位到自己想看的代码。授人以鱼不如授人以渔,希望大家可以通过这篇博客,了解到阅读大型前端项目源码时的切入点。在之后遇到好奇的问题时,可以自己去探索。
这篇文章我准备来聊一聊如何去阅读开源项目的源码。
在聊如何去阅读源码之前,先来简单说一下为什么要去阅读源码,大致可分为以下几点原因:
最直接的原因,就是面试需要,面试喜欢问源码,读完源码才可以跟面试官battle提升自己的编程水平,学习编程思想和和代码技巧熟悉技术实现细节,提高设计能力…
那么到底该如何去阅读源码呢?这里我总结了18条心法,助你修炼神功
学好JDK
身为一个Javaer,不论要不要阅读开源项目源码,都要学好JDK相关的技术。
所有的Java类开源项目,本质上其实就是利用JDK已有的类库和关键字实现一种业务功能,所以学会了JDK相关的类库是看其它的源码基础。
如果你不懂JDK,你去阅读源码会发现有太多看不懂的地方,会影响读源码的心情和信心。
学习JDK主要包括使用和原理两部分。内容大致包括以下几部分:
集合相关,比如常见的Map,List,Queue的实现,包括线程安全与不安全并发相关,比如synchronized、volatile、CAS、AQS、锁、线程池、原子类等等io相关,包括bio和nio等等反射相关网络编程相关…
了解设计模式
在一个优秀的开源项目中,设计模式处处存在,所以在你开始阅读源码之前最好先了解一下常见的一些设计模式。当你了解了一些设计模式以后,在源码中遇到了相关的设计模式,你就可以快速明白代码结构的设计,从而以整体的视角去阅读相关代码。
同时,学习设计模式不仅可以帮助我们阅读源码,在日常开发中也可以帮助我们设计出更易于扩展的程序。
学习设计模式的话可以看看《大话设计模式》这本书,如果不想看书也可以找一些视频或者专栏。
之前我也写过一篇关于开源项目中常用的设计模式文章两万字盘点那些被玩烂了的设计模式,有兴趣的小伙伴可以看看。
先从官网入手
官网是介绍开源项目的地方,同时也是学习一个开源项目最开始的地方,通过官网我们可以快速的了解项目,比如:
项目的定位一些核心概念功能使用教程整体的架构和设计常见的问题及解答…
RokcetMQ官网
当你了解了项目的一些概念、功能等信息之后,如果你在读源码一旦发现了代码是实现这些概念或者功能的足迹,那么能够帮助你更好的理解代码。
熟悉源码模块结构
当你对项目有大致的了解之后,就可以从Github上把代码clone下来,官网有项目源码的Github地址。
当成功拉下来代码之后,就可以对项目源码模块进行简单的分析,熟悉模块结构,分析模块功能,混个眼熟。
如上是RocketMQ源码,如果前面阅读过官网相关的一些概念介绍,就大致可以知道这些模块有什么功能。
RocketMQ概念介绍
比如说,源码中的broker模块,官网说broker主要是负责消息存储,那么broker模块代码块肯定就主要实现了消息存储的功能。
还有些模块可以根据单词的意思进行判断,比如common模块,一看就是存储一些公共类的模块,example模块,就是RocketMQ使用代码示例的模块等等。
顺着demo开始读
有的小伙伴在读源码的时候不知道从哪里开始读比较合适,最后随便从源码中的某个模块就开始读,读读越来越发现读不下去。
读源码正确的姿势应该是从demo开始读。
比如说,现在我想要阅读一下RocketMQ生产者是如何发送消息的,整个过程是什么样的,那么我首先至少得写个发送消息的demo,看看代码是如何写的。
demo一般可以从官网中查看
RocketMQ官网发送消息代码示例
除了官网,一般开源项目在源码中也会有相应的demo,代码放在示例模块,就比如上面提到的RocketMQ的example模块。
最后还可以通过谷歌搜索一下demo。
DefaultMQProducerproducer=newDefaultMQProducer(&34;);\n//指定NameServer的地址\nproducer.setNamesrvAddr(&34;);\n//启动生产者\nproducer.start();\n//省略代码。。\nMessagemsg=newMessage(&34;,&34;,&34;.getBytes(RemotingHelper.DEFAULT_CHARSET));\n//发送消息并得到消息的发送结果,然后打印\nSendResultsendResult=producer.send(msg);\n
如上是RocketMQ生产者发送消息的一个demo,消息发送源码阅读就从这块代码开始入手,一步一步进入源码中,这就算开始阅读源码了。
带着目的去读
带着目的去读其实很好理解,就拿上面生产者发送消息流程源码来说,读源码的第一个目的其实就是弄懂生产者发送消息的流程。
除了弄懂生产者发送消息,你还可以带着其它目的去读。
比如说,消息发送的核心逻辑是send方法实现的,那么除了消息发送,是不是可以去弄懂生产者在启动的过程做了哪些事,也就是start方法的作用。
再比如生产者发送消息肯定涉及到网络通信相关的内容,那么了解RocketMQ底层网络通信模型是不是也可以算一个目的。
当你带着这些目的,你读源码就有很强的目的性,读完印象会很深刻。当然如果你最开始想不到这些目的,也没有什么关系,你可以先往下读,在读的过程中再去尝试发现一些其它的目的。
先抓主线,再抓分支
有的小伙伴在读源码的时候,每个方法都使劲一直往下点,最后都不知道代码进入到哪了,这其实是非常不可取的。
正确的方法应该是先抓住主线流程,分支流程先大致看看,知道大概是什么作用,等读完主线之后,再回过头仔细读一下分支代码。
举个例子来说,在Spring中,ApplicationContext在使用之前需要调用一下refresh方法,而refresh方法就定义了整个容器刷新的执行流程代码。
refresh方法部分截图
当在读这段代码,你可以先读一读refresh中各个方法大致都做了什么,等读完之后,你可以具体的去读每个代码的具体实现,比如说prepareRefresh干了什么,obtainFreshBeanFactory是如何获取到BeanFactory的,prepareBeanFactory又在对BeanFactory做了什么事等等。
不要过度抠实现细节
有的小伙伴在阅读的时候特别喜欢深究,想要弄清每行代码是如何实现的,这不仅非常难而且也是不可取的。
就比如说,我们都知道,在SpringBean的生命周期中,当存在基于xml的方式来声明Bean的方式,Spring会去解析xml,生成BeanDefinition。当你想要了解Bean的生命周期过程的时候,其实是没有太大的必要去过度扣Spring是如何解析xml生成BeanDefinition的细节,这对你整体了解Bean的生命周期没有太大的意义,只需要知道最终会转换成BeanDefinition就可以了。
那什么时候去扣实现细节呢?
当你需要使用到的时候,比如说你遇到了一个bug或者是需要扩展阻碍你理解功能实现的时候
大胆猜
读源码的时候也需要我们发挥一点想象力,去猜一猜功能是如何实现的。猜不是瞎猜,而是基于目前了解的一些知识、技术或者是思想合理地去猜。
就比如说,当你已经知道了OpenFeign最终会对每一个FeignClient接口生成动态代理对象,之后注入的对象都是代理对象,代理对象中实现了RPC的请求之后,那么当你在学习dubbo的时候,是不是就可以去猜测注入的dubbo接口最终也是一个动态代理对象,并且这个代理对象也实现了RPC的请求?
之后你在读代码的时候就需要着重注意发现是否有动态代理生成的代码,这就算是一个目的,一旦发现了动态代理相关的代码,那么这块代码很可能就是dubboRPC实现的核心。
学会看类名
不要小看类名,优秀的代码命名都是见名知意的,所以从类名也可能窥探出这个类的一些蛛丝马迹。
如下列举了几个比较常用的命名习惯
以Registry结尾的一般都是存储功能,比如Spring中的SingletonBeanRegistry就是用来保存单例Bean的;Mybatis中的MapperRegistry就是用来保存Mapper接口的以Support、Helper、s、Util(s)结尾的一般都是工具类以Filter,Interceptor结尾的一般都是拦截作用,一般会配合责任链模式(Chain)使用以Event、Listener结尾的一般都是基于观察者模式实现的事件发布订阅模型…
除了一些比较通用的命名习惯,也有一些项目独有的一些命名习惯。
比如说Spring中常见的以PostProcessor结尾的都是扩展接口,实现这些接口可以拿到某个比较核心的组件,从而实现对Spring的扩展。
其实很多开源项目的命名都比较偏向Spring的命名风格,当你遇到了跟Spring的命名比较像的时候,那么可以大胆猜测类的作用。
学会看类结构
类结构也非常重要,他也能够帮助我们窥探类的大致功能。
ApplicationContext
如上图,是Spring中ApplicationContext的继承体系,当你需要了解ApplicationContext的时候,可以先去熟悉一下它的父接口的作用,当你大致弄明白了每个接口的作用,那么ApplicationContext有啥作用就大致就清楚了。
除了可以看类继承体系,还可以浏览一下类大致提供了哪些方法,了解对外提供的功能。
类方法通过快捷键ctrl+F12(mac:fn+command+F12)查看,并且还支持模糊搜索方法名,我本人就非常喜欢这个快捷键
ApplicationContext
总结类的职责
当我们在读完一个类的代码的时候,一定要总结这个类的职责,明白这个类存在的意义。一般情况下一个类核心职责只有一个,遵循单一职责的设计原则。
举个例子,在RocketMQ中有一个类MQClientAPIImpl
MQClientAPIImpl
其实从名字大概看不出这个类主要是有什么功能,但是当我读代码的时候发现每个方法最终都调用RemotingClient方法,而RemotingClient只有一个实现NettyRemotingClient,所以从这个实现和类名可以猜出来RemotingClient是发送网络请求的客户端,所以当读完MQClientAPIImpl源码之后,我就知道了MQClientAPIImpl这个类的职责大致是封装参数,然后通过RemotingClient向MQ发送消息的。
当知道这个类的职责的时候,那么其它地方在调用这个类的方法的时候,就知道大概在做什么事了。
习惯阅读注释
当你在读源码的时候,如果有注释,最好能先读一下注释,这样能帮助你厘清类或者方法的功能,先知道功能,再去读源码就容易多了。
注释一般都是英文,如果看不懂,可以装个插件
写好注释
俗话说的好记性不如烂笔头,写好注释也是阅读源码中很重要的一个环节,好的注释可以帮助快速回忆起实现细节和功能。
注释并不需要对每行代码都注释,当然如果你愿意也没多大问题,但是注释应包括以下几点内容:
核心类和方法实现的核心功能核心功能大致的实现逻辑核心的成员变量的作用方法中不易读懂的代码实现细节
DefaultMessageStore
如图,是我读RocketMQ中对于DefaultMessageStore类阅读的注释,这个类是RocketMQ中一个非常核心的类,从名字可以看出来跟消息的存储有关。这个类的功能非常多,所以我写了很多注释,列举了这个类主要有哪些功能和这些功能实现的一些细节。
总结思想,及时输出
当你读完某个功能模块的时候,就可以尝试对这块功能实现逻辑或者思想进行总结。
比如说,当你了解了CAS思想的时候,你会发现,原来保证线程安全不仅仅可以通过加锁的方式,还可以基于乐观锁的方式来实现。
在总结之后可以输出成一个文档,又或者是流程图。我个人比较喜欢画图,这里推荐两个在线画图工具:
processondraw.io
processon我平时就在用,功能多,但是需要收费;draw.io的话免费,图标和颜色感觉比processon好看,平时文章中的贴图就是用draw.io画的。
这里多说一句,总结思想还是非常重要的,在我阅读了很多源码之后,我发现很多技术或者功能的实现原理最终都是殊途同归。
提前了解依赖的技术
一般一个开源项目不是所有的技术都是自己实现的,它也会依赖一些其它的框架或者是思想,提前了解这些框架或者是思想,可以帮助你更好地阅读和理清代码。
比如说,RocketMQ底层是基于Netty框架实现网络通信的,当你对Netty有所了解,知道Netty在启动的时候需要注册一堆ChannelHandler用来处理网络请求,那么在读RocketMQ底层网络通信功能的时候你就可以去找一下Netty启动的代码,看看都注册了哪些ChannelHandler,然后就知道RocketMQ是如何处理和发送请求的。
查阅相关资料
当在阅读源码的时候,对某一块代码功能实现不太清楚的时候,可以通过查阅相关资料来辅助阅读,包括但不限于以下几种通道:
官网书籍Github文章视频
问题驱动——不要为了看源码而看源码
首先我们要明确一点,看源码的目的是什么?
我个人的意见是,看源码是为了解决问题。开源项目的源代码并没有什么非常特殊的地方,也都是普通的代码。这些代码的数量级一般都挺大,如果想是从源码中学到东西,直接浏览整个Codebase无疑是大海捞针。
但如果是带着问题去看源码,比如想了解一下React的合成事件系统的原理,想了解React的setState前后发生了什么,或者想了解Webpack插件系统的原理。也有可能是遇到了一个bug,怀疑是框架/工具的问题。在这样的情况下,带着一个具体的目标去看源码,就会有的放矢。
看最新版的源码
之前看到一种说法,看源码要从项目的第一个commit开始看。如果是为了解决前文中对框架/工具产生的困惑,那自然要看当前项目中用到的框架/工具的版本。
如果是为了学习源码,我也建议看最新的源码。因为一个项目是在不断迭代和重构的。不同版本之间可能是一次完全的重写。比如Vue2.x和React16。重构导致了代码架构上的一些变化,Vue2.x引入了VritualDOM,Pull+Push的数据变化检测方式让整个代码的结构变的更清晰了,所以2.x的代码其实比1.x的更容易阅读。React16重写了Reconciler,引入了fiber这个概念,整个代码仓库结构也更清晰,所以更推荐阅读。
前置条件
看源码怎么看,当然不能一把梭了。
看源码之前需要对项目的原理有一个基本的了解。所谓原理就是,这个项目有哪些组成部分,为了达到最终的产出,要经过哪几步流程。这些流程里,业界主流的方案有哪几种。
比如前端View层框架,要渲染出UI,组件要经过mount、render等等步骤。数据驱动的前端框架,在mounted之后,就会进入一个循环,当用户交互触发组件数据变化时,会更新UI。其中数据的检测方式又有分Push和Pull两种方案。渲染UI可以是全量的字符串模板替换,也可以是基于VirtualDOM的差量DOM更新。
又比如前端的一些工具,Webpack和Babel这些工具都是基于插件的。基本的工作流程就是读取文件,解析代码成AST,调用插件去转换AST,最后生成代码。要了解Webpack的原理,就要知道Webpack基于一个叫tapable的模块系统。
那我们要如何了解这些呢?要了解这些,可以去各大网站和博客上的《XXX源码解析》系列。通过这些文章,我们可以对我们要看的框架/工具的原理有一个大致的了解。
本地build
不过最终我们还是要直接看源码。笔者真正看源码的第一步就是把项目的代码仓库clone到本地。然后按项目README上的构建指南,在本地build一下。
如果是前端框架,我们可以在HTML中里直接引入本地build出的umdbundle(记得用developmentbuild,不然会把代码压缩,可读性差),然后写一个简单的demo,demo里引入本地的build。如果是基于Nodejs的工具,我们可以用npmlink把这个工具的命令link到本地。也可以直接看项目的package.json的入口文件,直接用node运行那个文件。
这里要强调一下,大型的开源项目一般都会有一个ContributionGuide,目的是让想贡献代码的开发者更快上手。里面就有讲怎么在本地构建代码。
以React为例,React的ContributingGuide里就DevelopmentWorkflow这一节。里面有这么一段话:
Theeasiestwaytotryyourchangesistorunyarnbuildcore,dom–type=UMDandthenopenfixtures/packaging/babel-standalone/dev.html.Thisfilealreadyusesreact.development.jsfromthebuildfoldersoitwillpickupyourchanges.
所以React仓库中的fixtures/packaging/babel-standalone/dev.html就是一个方便的demo页。我们可以在这个页面快速查看我们在本地对代码的改动。
你可以尝试着在项目的入口文件中加入一句log,看看是不是可以在控制台/终端看到这句log。如果可以的话,恭喜你,你现在可以随便把玩这个项目了!
理清目录结构
在看具体的代码之前,我们需要理清项目的目录结构,这样我们才能更快的知道在哪里地方找相关功能的代码。
我们看看React的目录结构。React是一个monorepo。也就是一个仓库里包含了多个子仓库。我们在packages目录下可以看到很多单独的package:
在React16之后,React的代码分为ReactCore,Renderer和Reconciler三部分。这是因为React的设计让我们可以把负责映射数据到UI的Reconciler以及负责渲染VritualDOM到各个终端的Renderer和ReactCore分开。ReactCore包含了React的类定义和一些顶级API。大部分的渲染和View层diff的逻辑都在Reconciler和Renderer中。
Babel也是一个monorepo。Babel的核心代码是babel-core这个package,Babel开放了接口,让我们可以自定义Visitor,在AST转换时被调用。所以Babel的仓库中还包括了很多插件,真正实现语法转换的其实是这些插件,而不是babel-core本身。
Vuejs的代码比较典型,核心代码在src目录下,按功能模块划分。因为Vue也支持多平台渲染,所以把平台相关的代码都放到了platform文件夹下,core文件夹中是Vue的核心代码,compiler是Vue的模板编译器,把HTML风格的模板编译为renderfunction。
Webpack和Babel一样,可以说都是基于插件的系统。Webpack的主要源码在lib目录下,里面的webpack.js就是入口文件。
上面说了四个项目的目录结构,那我们遇到一个新的开源项目,应该怎么了解它的目录结构呢?
如果这个项目是一个monorepo,首先我们要找到核心的那个package,然后看里面的代码。
不是monorepo的话,一般来说,如果这个项目是一个CLI的工具,那bin目录下放的就是命令行界面相关的入口文件,lib或者src下面就是工具的核心代码。如果这个项目是一个前端View层框架,那目录结构就和Vue类似。
作为验证,大家可以看一下打包工具parcel和前端View层库moon的目录结构。目录结构这个东西往往是大同小异,多看几个项目就熟悉了。
debugger&&全局搜索大法
运行了本地的build,了解了目录结构,接下来我们就可以开始看源码了!之前说了,我们要以问题驱动,下面我就以React调用setState前后发生了什么这个问题作为例子。
我们可以在setState的地方打一个断点。首先我们要找到setState在什么地方。这个时候之前的准备工作就派上用处了。我们知道React的共有API在react这个package下面。我们就在那个package里面全局搜索。我们发现这个API定义在src/ReactBaseClasses.js这个文件里。
于是我们就在这里打一个断点:
ini复制代码Component.prototype.setState=function(partialState,callback){\ninvariant(\ntypeofpartialState===&39;||\ntypeofpartialState===&39;||\npartialState==null,\n&39;+\n&39;,\n);\ndebugger;\nthis.updater.enqueueSetState(this,partialState,callback,&39;);\n};\n
然后运行本地Reactbuild的demo页面,让组件触发setState,我们就可以在Devtool里看到断点了。
我们走进this.updater.enqueueSetState这个调用,就来到了ReactFiberClassComponent这个函数中的enqueueSetState,这里调用了enqueueUpdate和scheduleWork两个函数,如果要深入setState之后的流程,我们只需要再点击
走进这两个函数里看具体的代码就可以了。
如果想看setState之前发生了什么,我们只需要看Devtool右边的调用栈:
点击每一个frame就可以跳到对应的函数中,并且恢复当时的上下文。
结合一步一步的代码调试,我们可以看到框架的函数调用栈。对于每个重要的函数,我们可以在仓库里搜索到源码,进一步研究。
Node工具的调试方法也是相似的,我们可以在运行node命令时加上–inspect参数。具体可以看DebuggingNode.jswithChromeDevTools这篇博客。
其实大家都知道单步调试这种办法,但在哪里打断点才是最关键的。我们在熟悉框架的原理之后,就可以在框架的关键链路上打断点,比如前端View层框架的声明周期钩子和render方法,Node工具的插件函数,这些代码都是框架运行的必经之地,是不错的切入点。
如果是为了了解一个特定的问题,大家可以直接在自己觉得有问题的地方打断点。然后把源码运行起来,想办法让代码运行到那个地方。我们在断点可以看到局部变量等等信息,有助于定位问题。
来自开发团队的资源
其实开源项目的开发团队也都致力于让更多的人参与到项目中来,降低项目的门槛。所以我们在线上其实可以找到很多来自开发团队的资源。这些资源可以帮助我们去理解项目的原理。
关注核心开发者
每个项目都有一些核心开发者,比如React的DanAbramov,AndrewClark和SebastianMarkb?ge。Webpack的TobiasKoppers和SeanLarkin。Vue的EvanYou。我们可以在Twitter上关注他们,了解项目的动态。
关注官方博客和演讲视频
如果我们关注了上面的核心开发者,就会发现他们时常会发布一些和源码/项目原理有关的博客或者视频。
React的官方博客最近就有很多和项目开发有关的博客。
BehindtheScenes:ImprovingtheRepositoryInfrastructure这篇介绍的是React项目仓库的基础设施。SneakPeek:BeyondReact16
AndrewClark一开始就写了一篇介绍fiber架构的文档。DanAbramov最近在JSConf上对React未来的一些新特性的介绍-BeyondReact16。React博客中的SneakPeek:BeyondReact16也是对这次Talk的介绍。
EvanYou介绍前端框架数据变化侦测原理的Talk。Vue文档中也有ReactivityinDepth这样的介绍原理的章节。
SeanLarkin的Everythingisaplugin!Masteringwebpackfromtheinsideout介绍了Webpack的核心组件Tapable。
JamesKyle的HowtoBuildaCompiler可以让我们了解Babel转译代码的基本流程。
坚持
最后一点也是最核心的一点就是坚持。只有你长期坚持读源码,不停地思考,总结,不断提升自身技术的广度和深度,找到适合自己的阅读方式,阅读源码才会是越来越容易的一件事。
END,本文到此结束,如果可以帮助到大家,还望关注本站哦!
