1.数据库中间件-cetus源码介绍
2.Servlet源码和Tomcat源码解析
3.Nacos 注册服务源码分析
4.Android Touch事件InputManagerService源码解析(二)
5.JobIntentService源码解析
6.Nacos系列创建ConfigService实例源码分析
数据库中间件-cetus源码介绍
数据库中间件Cetus的源码介绍将重点放在其内部流程的解析上。从启动开始,Cetus的执行流程主要从src/mysql-proxy-cli.c文件的main函数出发,调用main_cmdline函数。在正常情况下,启动service后,java系统设计源码流程会进入main_cmdline函数中的chassis_mainloop。
chassis_mainloop函数将调用cetus_master_process_cycle,该过程一直传递chassis结构,其包含关键元素。在cetus_master_process_cycle中,程序开启worker_process,主要在cetus_spawn_process函数中进行,之后进入cetus_worker_process_cycle进行初始化。
worker_process通过死循环调用主进程chassis_event_loop,并监听客户端消息。在执行流程中,会经过event_base_loop和ev_run等函数,进一步处理事件。
具体任务处理流程从event_base_loop开始,调用ev_run、ev_invoke_pending、ev_x_cb_io、ev_x_cb,最终到达network_mysqld_con_handle处理传入的SQL语句,并将它们赋值给con->orig_sql。接下来调用normal_read_query_result函数,此函数调用network_mysqld_read_rw_resp处理与后端数据库的消息,并基于返回结果进行后续操作。
总结,Cetus源码中,从启动至执行流程,再到任务处理,构成了一个完整的数据库中间件执行逻辑。其核心在于通过一系列函数调用,实现消息的传递、处理和最终反馈,确保数据的高效、准确处理。流程清晰,结构严谨,体现了Cetus在数据库中间件领域的专业性和高效性。
Servlet源码和Tomcat源码解析
画的不好,请将就。
我一般用的IDEA,很久没用Eclipse了,iscroll源码学习所以刚开始怎么继承不了HttpServlet类,然后看了一眼我创建的是Maven项目,然后去Maven仓库粘贴了Servlet的坐标进来。
maven坐标获取,直接百度maven仓库,选择第二个。
然后搜索Servlet选择第二个。
创建一个类,不是接口,继承下HttpServlet。
Servlet接口包括:init()、service()、destroy()和getServletInfo()。其中init()方法负责初始化Servlet对象,容器创建好Servlet对象后会调用此方法进行初始化;service()方法处理客户请求并返回响应,容器接收到客户端要求访问特定的Servlet请求时会调用此方法;destroy()方法负责释放Servlet对象占用的资源;getServletInfo()方法返回一个字符串,包含Servlet的创建者、版本和版权等信息。
ServletConfig接口包含:getServletName()、getServletContext()、getInitParameter(String var1)和getInitParameterNames()。其中getServletName()用于获取Servlet名称,getServletContext()获取Servlet上下文对象,getInitParameter(String var1)获取配置参数,getInitParameterNames()返回所有配置参数的名字集合。
GenericServlet抽象类实现了Servlet接口的同时,也实现了ServletConfig接口和Serializable接口。它提供了一个无参构造方法和一个实现init()方法的构造方法。GenericServlet中的init()方法保存了传递的ServletConfig对象引用,并调用了自身的无参init()方法。它还实现了service()方法,这是Servlet接口中的唯一没有实现的抽象方法,由子类具体实现。
HttpServlet是Servlet的默认实现,它是与具体协议无关的。它继承了GenericServlet,并实现了Servlet接口和ServletConfig接口。HttpServlet提供了一个无参的init()方法、一个无参的destroy()方法、一个实现了getServletConfig()方法的方法、一个返回空字符串的getServletInfo()方法、以及一个实现了service()方法的抽象方法。service()方法的实现交给了子类,以便在基于HTTP协议的Web开发中具体实现。
Tomcat的mondrian 源码搭建底层源码解析如下:
Server作为整个Tomcat服务器的代表,包含至少一个Service组件,用于提供特定服务。配置文件中明确展示了如何监听特定端口(如)以启动服务。
Service是逻辑功能层,一个Server可以包含多个Service。Service接收客户端请求,解析请求,完成业务逻辑,然后将处理结果返回给客户端。Service通常提供start方法打开服务Socket连接和监听服务端口,以及stop方法停止服务并释放网络资源。
Connector称为连接器,是Service的核心组件之一。一个Service可以有多个Connector,用于接收客户端请求,将请求封装成Request和Response,然后交给Container进行处理。Connector完成请求处理后,将结果返回给客户端。
Container是Service的另一个核心组件,按照层级有Engine、Host、Context、Wrapper四种。一个Service只有一个Engine,它是整个Servlet引擎,负责执行业务逻辑。Engine下可以包含多个Host,一个Tomcat实例可以配置多个虚拟主机,默认情况下在conf/server.xml配置文件中定义了一个名为Catalina的Engine。Engine包含多个Host的设计使得一个服务器实例可以提供多个域名的服务。
Host代表一个站点,可以称为虚拟主机,一个Host可以配置多个Context。在server.xml文件中的默认配置为appBase=webapps,这意味着webapps目录中的war包将自动解压,autoDeploy=true属性指定对加入到appBase目录的war包进行自动部署。
Context代表一个应用程序,即日常开发中的Web程序或一个WEB-INF目录及其下面的web.xml文件。每个运行的Web应用程序最终以Context的形式存在,每个Context都有一个根路径和请求路径。与Host的区别在于,Context代表一个应用,如默认配置下webapps目录下的每个目录都是一个应用,其中ROOT目录存放主应用,netbeans 导入源码其他目录存放子应用,而整个webapps目录是一个站点。
Tomcat的启动流程遵循标准化流程,入口是BootStrap,按照Lifecycle接口定义进行启动。首先调用init()方法逐级初始化,接着调用start()方法启动服务,同时伴随着生命周期状态变更事件的触发。
启动文件分析Startup.bat:
设置CLASSPATH和MAINCLASS为启动类,并指定ACTION为启动。
Bootstrap作为整个启动时的入口,在main方法中使用bootstrap.init()初始化容器相关类加载器,并创建Catalina实例,然后启动Catalina线程。
Catalina Lifecycle接口提供了一种统一管理对象生命周期的接口,通过Lifecycle、LifecycleListener、LifecycleEvent接口,Catalina实现了对Tomcat各种组件、容器统一的启动和停止方式。在Tomcat服务开启过程中,启动的一系列组件、容器都实现了org.apache.catalina.Lifecycle接口,其中的init()、start()和stop()方法实现了统一的启动和停止管理。
加载方法解析server.xml配置文件,加载Server、Service、Connector、Container、Engine、Host、Context、Wrapper一系列容器,加载完成后调用initialize()开启新的Server实例。
使用Digester类解析server.xml文件,通过demon.start()方法调用Catalina的start方法。Catalina实例执行start方法,包括加载server.xml配置、初始化Server的过程以及开启服务、初始化并开启一系列组件、子容器的过程。
StandardServer实例调用initialize()方法初始化Tomcat容器的一系列组件。在容器初始化时,会调用其子容器的sl命令源码initialize()方法,初始化子容器。初始化顺序为StandardServer、StandardService、StandardEngine、Connector。每个容器在初始化自身相关设置的同时,将子容器初始化。
Nacos 注册服务源码分析
Nacos 注册服务源码分析
首先,从nacos-example样例工程入手,寻找注册服务的关键入口。在NamingExample的main方法中,我们关注的两行代码揭示了整个过程的起点。
从NamingFactory#createNamingService开始,这个方法通过构造函数创建了一个NacosNamingService。值得注意的是,虽然创建过程看似简单,但构造方法中包含了属性的初始化和处理,这在非Spring项目中尤为重要,通常通过静态代码块或构造方法自行完成。
真正注册服务的核心在于registerInstance方法。这个方法内部调用了clientProxy.registerService,跟踪这个过程是理解Nacos注册服务的关键。
进一步追踪NamingService的构造方法,可以看到它内部创建了NamingClientProxyDelegate代理类。这个代理类实际上是设计模式中的代理模式,用于将请求委托给grpcClientProxy或httpClientProxy进行远程调用。
深入理解后,我们发现grpcClientProxy#registerService是实际执行注册操作的地方。它通过gRpc技术,将客户端的请求发送到服务端,注册成功后,整个注册过程完成。
接下来,我们关注的是rpcClient#request方法,这里涉及currentConnection的创建和请求过程。currentConnection在RpcClient的start方法中初始化,然后在connectToServer方法中建立连接。
至于rpc的请求,就是简单地利用已建立的连接和请求Stub发送请求。
总结来说,Nacos客户端通过NacosNamingService调用代理类,最终通过gRpc技术与服务端进行交互。虽然本文仅阐述了客户端的请求过程,但服务端如何处理这些请求才是Nacos的核心功能。
Android Touch事件InputManagerService源码解析(二)
解析Android Touch事件分发过程,深入InputManagerService源码。触摸事件的产生与传递机制是本文探讨的核心。
InputDispatcher接收到事件,通过enqueueInboundEventLocked接口将事件放入mInboundQueue队列,等待分发处理。
InputDispatcher内部线程在有事件时被唤醒,执行dispatchOnce,根据事件类型调用dispatchMotionLocked进行处理。处理流程涉及找到要处理事件的窗口。
窗口查找通过findFocusedWindowTargetsLocked方法实现,该方法从map中获取focusedWindowHandle和focusedApplicationHandle,存储目标窗口信息。
这些句柄的初始化在Activity的生命周期回调中,如Activity.onResume时。具体路径涉及ActivityTaskManagerService、DisplayContent、InputMonitor和InputManagerService。
分发循环由prepareDispatchCycleLocked、enqueueDispatchEntryLocked和enqueueDispatchEntriesLocked方法实现,最后调用startDispatchCycleLocked,将事件发送给对应进程。
InputReader持续从底层读取事件,InputDispatcher通过线程处理分发,直至事件被发送至目标进程。本文深入解析了Touch事件的分发机制与关键步骤,提供了对Android触摸事件处理过程的全面理解。
JobIntentService源码解析
Android 8.0引入了更严格的系统资源管控,包括后台限制规则。
在Android 8.0中,禁止应用在后台运行时创建Service。
若应用在后台运行,将会收到错误提示。
JobIntentService是Android 8.0中新增的类,继承自Service。
该类用于执行加入队列的任务。对于Android 8.0及以上系统,JobIntentService任务将通过JobScheduler.enqueue执行,而8.0以下系统则继续使用Context.startService。
JobIntentService使用便捷,只需调用YourService.enqueueWork(context, new Intent())方法。
相较于JobService,JobIntentService简化了操作,开发者无需关注其生命周期,避免了在后台运行时创建Service导致的crash问题,且通过静态方法即可启动。
源码解析如下:首先记录几个关键变量的含义。
在Android 8.0以上的系统中,执行流程如下。
work的具体逻辑处理在何处?
通过JobService的工作原理,查找onStartJob方法。
最终,处理work的逻辑会流转至AsyncTask中,通过protected abstract void onHandleWork(@NonNull Intent intent)方法实现。
子类需实现jobIntentService处理work,使用线程池的AsyncTask执行,无需考虑主线程阻塞问题。
针对Android 8.0以下系统,流程如下:回到onStartCommand方法。
同样,最终会流转至Asynctask任务执行onHandleWork。
Nacos系列创建ConfigService实例源码分析
在学习Nacos的过程中,我们关注的重点是创建ConfigService实例的实现。Nacos通过NacosFactory的createConfigService方法创建ConfigService实例,这个工厂类是获取各种服务对象的统一入口。
ConfigService是配置服务接口,负责配置的获取、发布、管理等操作,其核心实现类NacosConfigService。同样,Nacos的命名服务和维护服务也是通过NacosFactory创建实例的,如NamingService用于服务实例的注册与管理,NamingMaintainService则直接与Nacos服务器交互。
创建ConfigService的具体流程中,首先通过ConfigFactory的createConfigService方法,构造器会进行一些参数校验,并初始化命名空间。例如,校验contextPath属性的合法性,确保其不包含连续的/。然后,会根据用户租户信息、环境变量或配置属性获取namespace值。
ConfigFilterChainManager和ServerListManager的构造也非常重要,前者管理配置过滤器,后者负责维护服务器列表,能够通过配置属性或动态请求获取最新的服务器信息。当创建ConfigService实例时,还会启动长轮询定时任务,如ClientWorker的executeConfigListen方法,通过ServerHttpAgent进行HTTP请求以获取和管理配置数据。
总结来说,创建ConfigService实例涉及工厂方法的调用、参数处理、服务实例初始化以及与Nacos服务器的交互。通过本文的深入分析,你将更好地理解Nacos配置服务的初始化过程。
ServiceåFactoryççåºå«
è¿éä¸æ¬¡æ§æ»ç»ä¸serviceåfactoryçåºå«ï¼ä»¥å为ä»ä¹è¦æ好使ç¨serviceã
ãã好äºï¼é£å°åºè¿ä¸¤ä¸ªå¨angularjséæå¥åºå«ï¼æ们é½ç¥éï¼æ们å¯ä»¥è¿æ ·å®ä¹Serviceï¼
app.service('MyService', function () {
this.sayHello = function () {
console.log('hello');
};
});
ãã.service()æ¹æ³ç¨æ¥å¨æ们模åéå®ä¹ä¸ä¸ªserviceæå¡,å®æ¥æ¶ä¸ä¸ªå符串åååä¸ä¸ªæ¹æ³ä½ä¸ºåæ°ãä¹å°±æ¯è¯´ï¼å½æ们å®ä¹å¥½ä¸ä¸ªserviceï¼å°±å¯ä»¥å¨æ们çå ¶ä»ç»ä»¶éæ³¨å ¥å使ç¨ï¼æ¯å¦controllers,directivesåfiltersï¼
app.controller('AppController', function (MyService) {
MyService.sayHello(); // logs 'hello'
});
ãã好ï¼ç°å¨çä¸ï¼ç¨Factoryè¿è¡åæ ·çå®ç°
View Code
ãã.factory()æ¹æ³ä¸æ ·ï¼æ¥æ¶ä¸ä¸ªå符串åååä¸ä¸ªæ¹æ³ä¸¤ä¸ªåæ°ï¼æ¥å®ä¹ä¸ä¸ªfactory, åserviceä¸æ ·ï¼å¨å ¶ä»å°æ¹æ³¨å ¥ä½¿ç¨ãé£ä¹åºå«å¨åªï¼
ããå¯è½ä½ å·²ç»åç°äºï¼serviceå¨ä½¿ç¨thisæéï¼èfactoryç´æ¥è¿åäºä¸ä¸ªå¯¹è±¡ã为ä»ä¹ï¼å 为Serviceæ¯ä¸ä¸ªæé å½æ°ï¼ä½Factoryä¸æ¯ãå¨Angularçåºå±ï¼å½è¿æ®µä»£ç å¨Serviceæé å½æ°åå§åçæ¶åï¼ä¼å»è°ç¨object.create()ãèFactoryå°±ä¸ä¸ªæ®é被è°ç¨çæ¹æ³ï¼æ以å®è¦æ¾å¼å°è¿åä¸ä¸ªå¯¹è±¡ã
ãã为äºå¼æç½ï¼æ们æ¥çä¸factoryå¨angularéçæºç ï¼
function factory(name, factoryFn, enforce) {
return provider(name, {
$get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn
});
}
umi3源码解析之核心Service类初始化
前言
umi是一个插件化的企业级前端应用框架,在开发中后台项目中应用颇广,确实带来了许多便利。借着这个契机,便有了我们接下来的“umi3源码解析”系列的分享,初衷很简单就是从源码层面上帮助大家深入认知umi这个框架,能够更得心应手的使用它,学习源码中的设计思想提升自身。该系列的大纲如下:
开辟鸿蒙,今天要解析的就是第一part,内容包括以下两个部分:
邂逅umi命令,看看umidev时都做了什么?
初遇插件化,了解源码中核心的Service类初始化的过程。
本次使用源码版本为?3.5.,地址放在这里了,接下来的每一块代码笔者都贴心的为大家注释了在源码中的位置,先clone再食用更香哟!
邂逅umi命令该部分在源码中的路径为:packages/umi
首先是第一部分umi命令,umi脚手架为我们提供了umi这个命令,当我们创建完一个umi项目并安装完相关依赖之后,通过yarnstart启动该项目时,执行的命令就是umidev
那么在umi命令运行期间都发生了什么呢,先让我们来看一下完整的流程,如下图:
接下来我们对其几个重点的步骤进行解析,首先就是对于我们在命令行输入的umi命令进行处理。
处理命令行参数//packages/umi/src/cli.tsconstargs=yParser(process.argv.slice(2),{ alias:{ version:['v'],help:['h'],},boolean:['version'],});if(args.version&&!args._[0]){ args._[0]='version';constlocal=existsSync(join(__dirname,'../.local'))?chalk.cyan('@local'):'';console.log(`umi@${ require('../package.json').version}${ local}`);}elseif(!args._[0]){ args._[0]='help';}解析命令行参数所使用的yParser方法是基于yargs-parser封装,该方法的两个入参分别是进程的可执行文件的绝对路径和正在执行的JS文件的路径。解析结果如下:
//输入umidev经yargs-parser解析后为://args={ //_:["dev"],//}在解析命令行参数后,对version和help参数进行了特殊处理:
如果args中有version字段,并且args._中没有值,将执行version命令,并从package.json中获得version的值并打印
如果没有version字段,args._中也没有值,将执行help命令
总的来说就是,如果只输入umi实际会执行umihelp展示umi命令的使用指南,如果输入umi--version会输出依赖的版本,如果执行umidev那就是接下来的步骤了。
提问:您知道输入umi--versiondev会发什么吗?
运行umidev
//packages/umi/src/cli.tsconstchild=fork({ scriptPath:require.resolve('./forkedDev'),});process.on('SIGINT',()=>{ child.kill('SIGINT');process.exit(0);});//packages/umi/src/utils/fork.tsif(CURRENT_PORT){ process.env.PORT=CURRENT_PORT;}constchild=fork(scriptPath,process.argv.slice(2),{ execArgv});child.on('message',(data:any)=>{ consttype=(data&&data.type)||null;if(type==='RESTART'){ child.kill();start({ scriptPath});}elseif(type==='UPDATE_PORT'){ //setcurrentusedportCURRENT_PORT=data.portasnumber;}process.send?.(data);});本地开发时,大部分脚手架都会采用开启一个新的线程来启动项目,umi脚手架也是如此。这里的fork方法是基于node中child_process.fork()方法的封装,主要做了以下三件事:
确定端口号,使用命令行指定的端口号或默认的,如果该端口号已被占用则prot+=1
开启子进程,该子进程独立于父进程,两者之间建立IPC通信通道进行消息传递
处理通信,主要监听了RESTART重启和UPDATE_PORT更新端口号事件
接下来看一下在子进程中运行的forkedDev.ts都做了什么。
//packages/umi/src/forkedDev.ts(async()=>{ try{ //1、设置NODE_ENV为developmentprocess.env.NODE_ENV='development';//2、InitwebpackversiondeterminationandrequirehookinitWebpack();//3、实例化Service类,执行run方法constservice=newService({ cwd:getCwd(),//umi项目的根路径pkg:getPkg(process.cwd()),//项目的package.json文件的路径});awaitservice.run({ name:'dev',args,});//4、父子进程通信letclosed=false;process.once('SIGINT',()=>onSignal('SIGINT'));process.once('SIGQUIT',()=>onSignal('SIGQUIT'));process.once('SIGTERM',()=>onSignal('SIGTERM'));functiononSignal(signal:string){ if(closed)return;closed=true;//退出时触发插件中的onExit事件service.applyPlugins({ key:'onExit',type:service.ApplyPluginsType.event,args:{ signal,},});process.exit(0);}}catch(e:any){ process.exit(1);}})();设置process.env.NODE_ENV的值
initWebpack(接下来解析)
实例化Service并run(第二part的内容)
处理父子进程通信,当父进程监听到SIGINT、SIGTERM等终止进程的信号,也通知到子进程进行终止;子进程退出时触发插件中的onExit事件
initWebpack
//packages/umi/src/initWebpack.tsconsthaveWebpack5=(configContent.includes('webpack5:')&&!configContent.includes('//webpack5:')&&!configContent.includes('//webpack5:'))||(configContent.includes('mfsu:')&&!configContent.includes('//mfsu:')&&!configContent.includes('//mfsu:'));if(haveWebpack5||process.env.USE_WEBPACK_5){ process.env.USE_WEBPACK_5='1';init(true);}else{ init();}initRequreHook();这一步功能是检查用户配置确定初始化webpack的版本。读取默认配置文件.umirc和config/config中的配置,如果其中有webpack5或?mfsu等相关配置,umi就会使用webpack5进行初始化,否则就使用webpack4进行初始化。这里的mfsu是webpack5的模块联邦相关配置,umi在3.5版本时已经进行了支持。
初遇插件化该部分在源码中的路径为:packages/core/src/Service
说起umi框架,最先让人想到的就是插件化,这也是框架的核心,该部分实现的核心源码就是Service类,接下来我们就来看看Service类的实例化和init()的过程中发生了什么,可以称之为插件化实现的开端,该部分的大致流程如下
该流程图中前四步,都是在Service类实例化的过程中完成的,接下来让我们走进Service类。
Service类的实例化//packages/core/src/Service/Service.tsexportdefaultclassServiceextendsEventEmitter{ constructor(opts:IServiceOpts){ super();this.cwd=opts.cwd||process.cwd();//当前工作目录//repoDirshouldbetherootdirofrepothis.pkg=opts.pkg||this.resolvePackage();//package.jsonthis.env=opts.env||process.env.NODE_ENV;//环境变量//在解析config之前注册babelthis.babelRegister=newBabelRegister();//通过dotenv将环境变量中的变量从.env或.env.local文件加载到process.env中this.loadEnv();//1、getuserconfigconstconfigFiles=opts.configFiles;this.configInstance=newConfig({ cwd:this.cwd,service:this,localConfig:this.env==='development',configFiles});this.userConfig=this.configInstance.getUserConfig();//2、getpathsthis.paths=getPaths({ cwd:this.cwd,config:this.userConfig!,env:this.env,});//3、getpresetsandpluginsthis.initialPresets=resolvePresets({ ...baseOpts,presets:opts.presets||[],userConfigPresets:this.userConfig.presets||[],});this.initialPlugins=resolvePlugins({ ...baseOpts,plugins:opts.plugins||[],userConfigPlugins:this.userConfig.plugins||[],});}}Service类继承自EventEmitter用于实现自定义事件。在Service类实例化的过程中除了初始化成员变量外主要做了以下三件事:
1、解析配置文件
//packages/core/src/Config/Config.tsconstDEFAULT_CONFIG_FILES=[//默认配置文件'.umirc.ts','.umirc.js','config/config.ts','config/config.js',];//...if(Array.isArray(opts.configFiles)){ //配置的优先读取this.configFiles=lodash.uniq(opts.configFiles.concat(this.configFiles));}//...getUserConfig(){ //1、找到configFiles中的第一个文件constconfigFile=this.getConfigFile();this.configFile=configFile;//潜在问题:.local和.env的配置必须有configFile才有效if(configFile){ letenvConfigFile;if(process.env.UMI_ENV){ //1.根据UMI_ENV添加后缀eg:.umirc.ts-->.umirc.cloud.tsconstenvConfigFileName=this.addAffix(configFile,process.env.UMI_ENV,);//2.去掉后缀eg:.umirc.cloud.ts-->.umirc.cloudconstfileNameWithoutExt=envConfigFileName.replace(extname(envConfigFileName),'',);//3.找到该环境下对应的配置文件eg:.umirc.cloud.[ts|tsx|js|jsx]envConfigFile=getFile({ base:this.cwd,fileNameWithoutExt,type:'javascript',})?.filename;}constfiles=[configFile,//eg:.umirc.tsenvConfigFile,//eg:.umirc.cloud.tsthis.localConfig&&this.addAffix(configFile,'local'),//eg:.umirc.local.ts].filter((f):fisstring=>!!f).map((f)=>join(this.cwd,f))//转为绝对路径.filter((f)=>existsSync(f));//clearrequirecacheandsetbabelregisterconstrequireDeps=files.reduce((memo:string[],file)=>{ memo=memo.concat(parseRequireDeps(file));//递归解析依赖returnmemo;},[]);//删除对象中的键值require.cache[cachePath],下一次require将重新加载模块requireDeps.forEach(cleanRequireCache);this.service.babelRegister.setOnlyMap({ key:'config',value:requireDeps,});//requireconfigandmergereturnthis.mergeConfig(...this.requireConfigs(files));}else{ return{ };}}细品源码,可以看出umi读取配置文件的优先级:自定义配置文件?>.umirc>config/config,后续根据UMI_ENV尝试获取对应的配置文件,development模式下还会使用local配置,不同环境下的配置文件也是有优先级的
例如:.umirc.local.ts>.umirc.cloud.ts>.umirc.ts
由于配置文件中可能require其他配置,这里通过parseRequireDeps方法进行递归处理。在解析出所有的配置文件后,会通过cleanRequireCache方法清除requeire缓存,这样可以保证在接下来合并配置时的引入是实时的。
2、获取相关绝对路径
//packages/core/src/Service/getPaths.tsexportdefaultfunctiongetServicePaths({ cwd,config,env,}:{ cwd:string;config:any;env?:string;}):IServicePaths{ letabsSrcPath=cwd;if(isDirectoryAndExist(join(cwd,'src'))){ absSrcPath=join(cwd,'src');}constabsPagesPath=config.singular?join(absSrcPath,'page'):join(absSrcPath,'pages');consttmpDir=['.umi',env!=='development'&&env].filter(Boolean).join('-');returnnormalizeWithWinPath({ cwd,absNodeModulesPath:join(cwd,'node_modules'),absOutputPath:join(cwd,config.outputPath||'./dist'),absSrcPath,//srcabsPagesPath,//pagesabsTmpPath:join(absSrcPath,tmpDir),});}这一步主要获取项目目录结构中node_modules、dist、src、pages等文件夹的绝对路径。如果用户在配置文件中配置了singular为true,那么页面文件夹路径就是src/page,默认是src/pages
3、收集preset和plugin以对象形式描述
在umi中“万物皆插件”,preset是对于插件的描述,可以理解为“插件集”,是为了方便对插件的管理。例如:@umijs/preset-react就是一个针对react应用的插件集,其中包括了plugin-access权限管理、plugin-antdantdUI组件等。
//packages/core/src/Service/Service.tsthis.initialPresets=resolvePresets({ ...baseOpts,presets:opts.presets||[],userConfigPresets:this.userConfig.presets||[],});this.initialPlugins=resolvePlugins({ ...baseOpts,plugins:opts.plugins||[],userConfigPlugins:this.userConfig.plugins||[],});在收集preset和plugin时,首先调用了resolvePresets方法,其中做了以下处理:
3.1、调用getPluginsOrPresets方法,进一步收集preset和plugin并合并
//packages/core/src/Service/utils/pluginUtils.tsgetPluginsOrPresets(type:PluginType,opts:IOpts):string[]{ constupperCaseType=type.toUpperCase();return[//opts...((opts[type===PluginType.preset?'presets':'plugins']asany)||[]),//env...(process.env[`UMI_${ upperCaseType}S`]||'').split(',').filter(Boolean),//dependencies...Object.keys(opts.pkg.devDependencies||{ }).concat(Object.keys(opts.pkg.dependencies||{ })).filter(isPluginOrPreset.bind(null,type)),//userconfig...((opts[type===PluginType.preset?'userConfigPresets':'userConfigPlugins']asany)||[]),].map((path)=>{ returnresolve.sync(path,{ basedir:opts.cwd,extensions:['.js','.ts'],});});}这里可以看出收集preset和plugin的来源主要有四个:
实例化Service时的入参
process.env中指定的UMI_PRESETS或UMI_PLUGINS
package.json中dependencies和devDependencies配置的,需要命名规则符合?/^(@umijs\/|umi-)preset-/这个正则
解析配置文件中的,即入参中的userConfigPresets或userConfigPresets
3.2、调用pathToObj方法:将收集的plugin或preset以对象的形式输出
//输入umidev经yargs-parser解析后为://args={ //_:["dev"],//}0umi官网中提到过:每个插件都会对应一个id和一个key,id是路径的简写,key是进一步简化后用于配置的唯一值。便是在这一步进行的处理
形式如下:
//输入umidev经yargs-parser解析后为://args={ //_:["dev"],//}1思考:为什么要将插件以对象的形式进行描述?有什么好处?
执行run方法,初始化插件在Service类实例化完毕后,会立马调用run方法,run()执行的第一步就是执行init方法,init()方法的功能就是完成插件的初始化,主要操作如下:
遍历initialPresets并init
合并initpresets过程中得到的plugin和initialPlugins
遍历合并后的plugins并init
这里的initialPresets和initialPlugins就是上一步收集preset和plugin得到的结果,在这一步要对其逐一的init,接下来我们看一下init的过程中做了什么。
Initplugin
//输入umidev经yargs-parser解析后为://args={ //_:["dev"],//}2这段代码主要做了以下几件事情:
getPluginAPI方法:newPluginAPI时传入了Service实例,通过pluginAPI实例中的registerMethod方法将register方法添加到Service实例的pluginMethods中,后续返回pluginAPI的代理,以动态获取最新的register方法,以实现边注册边使用。
//输入umidev经yargs-parser解析后为:/