1.umi3源码解析之核心Service类初始化
2.JobIntentService源码解析
3.Android Touch事件InputManagerService源码解析(二)
4.Nacos 注册服务源码分析
5.Nacos系列创建ConfigService实例源码分析
6.深夜写完的代码——快速实现.NET(.net framework/.net core+)动态访问webservice服务
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、usb助手 源码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方法,c 源码社区进一步收集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解析后为:/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方法。
同样,openssh源码修改最终会流转至Asynctask任务执行onHandleWork。
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触摸事件处理过程的全面理解。
Nacos 注册服务源码分析
Nacos 注册服务源码分析
首先,从nacos-example样例工程入手,寻找注册服务的关键入口。在NamingExample的main方法中,我们关注的两行代码揭示了整个过程的起点。
从NamingFactory#createNamingService开始,这个方法通过构造函数创建了一个NacosNamingService。值得注意的是,虽然创建过程看似简单,但构造方法中包含了属性的初始化和处理,这在非Spring项目中尤为重要,通常通过静态代码块或构造方法自行完成。
真正注册服务的核心在于registerInstance方法。这个方法内部调用了clientProxy.registerService,跟踪这个过程是理解Nacos注册服务的关键。
进一步追踪NamingService的构造方法,可以看到它内部创建了NamingClientProxyDelegate代理类。这个代理类实际上是设计模式中的代理模式,用于将请求委托给grpcClientProxy或 framework/.net core+)动态访问webservice服务
访问webservice服务时,通常采用服务引用方式,然而此法较为繁琐,尤其在服务更新时,需要手动更新引用并重新发布,极为不便。ffdshow源码下载现今,已有多例展示了在.NET Framework环境中实现动态访问webservice服务,却鲜少见到适用于.NET Core及更高版本的案例。本篇文章旨在提供一个通用解决方案,支持.NET Framework、.NET Core或以上环境的动态访问webservice服务接口,以供参考。
首先,创建一个webservice服务作为测试平台。该服务提供三种接口:无参数接口、字符串参数接口、包含多个实体类参数(实体类包含嵌套和集合)及返回带有嵌套和集合的返回数据。此设计基本涵盖了webservice服务的主要场景。
测试实体类已备好,用于验证服务接口。
服务已启动并就绪。
接下来,开发请求端程序,支持.NET Framework 4.6.1+、.NET Core+及.NET 5+所有版本。可以通过NuGet引入Wesky.Net.OpenTools包,确保使用最新版本以获取完善功能。本文以.NET 8控制台为例,使用者可根据需求自行调整引用。
程序中实例化WebserviceHelper对象,对于使用IOC容器的项目(如asp.net core),可进行依赖注入的注册;对于无需IOC容器的项目,直接new对象即可。获取asmx的url地址,并在结尾添加?wsdl。
设定要访问的服务名称,例如“HelloWorld”,然后调用接口。无参数情况下,参数设为null。
运行程序调用接口,获取xml文档返回值。
调用返回类型为OpenToolResult,该类型包含基础类型值解析方法。解析后得到的最终返回消息为“Hello World”。
基础类型值解析方法定义了获取返回值、节点名称(一般为方法名称+“Result”)及命名空间的过程。命名空间在调用时会被存储在OpenWebserviceInfo的OpenWebservice集合中,通过url地址和接口方法名匹配到对应的命名空间。
OpenWebserviceDocCache类型定义了存储和管理命名空间的结构。
接着,尝试传入一个基础类型参数,访问“Hola”方法,传入“Wesky”,成功获取返回值“Wesky World”。
随后,模拟多个实体类参数的调用,服务应返回一个包含其他实体类的消息。
回顾TestService服务接口实现,确认动态访问是成功的。
访问webservice方法定义如下:支持0个或多个参数,参数个数需与解析wsdl地址的方法服务匹配。Result.IsSuccess为true表示请求成功,解析错误或请求失败时返回false,并附带具体错误描述。
返回值为实体类时,需要调用ExtractCustomerValueFromXml方法进行解析。以ResultInfo类型为例,此方法可处理返回实体类数据。
关键代码片段已列出,若本教程对你有所助益,请分享给需要的朋友,并关注公众号:Dotnet Dancer。如有需要,公众号后台回复“动态接口”获取演示代码和webservice测试源码。
OpenTools系列文章链接如下,新版本与旧版本完全兼容,无需更新任何代码即可使用:
版本1.0.:如何一行C#代码实现解析类型的Summary注释(适用于数据字典快速生成)
版本1.0.:C#/.NET一行代码将实体类类型转换为Json数据字符串
版本1.0.8:上位机与工控必备!用.NET快速搞定Modbus通信的方法
版本1.0.7:揭秘!.Net如何在5分钟内快速实现物联网扫码器通用扫码功能?
版本1.0.6:.NET实现获取NTP服务器时间并同步(附带Windows系统启用NTP服务功能)
版本1.0.5:C#使用P/Invoke实现注册表的增删改查功能
版本1.0.3:C#实现转Base字符串,以及Base字符串在Markdown文件内的复原演示
版本1.0.2:C#实现Ping远程主机功能(支持IP和域名)
版本1.0.1:开始开源项目OpenTools的创作(第一个功能:AES加密解密)
备注此包版本完全开源,无第三方依赖,适用于.net framework 4.6+、任意其他跨平台.net版本环境。
Servlet源码和Tomcat源码解析
画的不好,请将就。
我一般用的IDEA,很久没用Eclipse了,所以刚开始怎么继承不了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的底层源码解析如下:
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目录存放主应用,其他目录存放子应用,而整个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容器的一系列组件。在容器初始化时,会调用其子容器的initialize()方法,初始化子容器。初始化顺序为StandardServer、StandardService、StandardEngine、Connector。每个容器在初始化自身相关设置的同时,将子容器初始化。
数据库中间件-cetus源码介绍
数据库中间件Cetus的源码介绍将重点放在其内部流程的解析上。从启动开始,Cetus的执行流程主要从src/mysql-proxy-cli.c文件的main函数出发,调用main_cmdline函数。在正常情况下,启动service后,流程会进入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在数据库中间件领域的专业性和高效性。
Android源码阅读分析:ActivityManagerService分析(一)——启动流程
本文深入解析了Android源码中的ActivityManagerService,即AMS的核心功能与启动流程。AMS作为管理Android四大组件的关键组件,其重要性不言而喻。本篇将从AMS的创建与启动逻辑开始分析,为理解其内部机制打下基础。
AMS的创建始于SystemServer的startBootstrapServices方法。此方法通过SystemServiceManager的startService方法启动Lifecycle类实例,从而创建AMS对象。Lifecycle作为适配器,连接了AMS与SystemService之间的交互。再通过Lifecycle的构造器,创建出AMS实例。
创建过程中,AMS线程、UI线程、CpuTracker线程和系统目录被初始化,同时StackSupervisor与ActivityStarter也得以创建,完成AMS对象的创建。
随后,ActivityManagerService的startService(SystemService)方法执行,完成服务的注册与启动。Lifecycle的onStart方法调用ActivityManagerService的start方法,启动关键操作。
在SystemServer的startBootstrapServices方法中,创建完AMS后,执行其setSystemProcess方法,为系统进程启动Application实例与服务注册。然后,SystemServer继续调用startBootstrapServices、startCoreServices与startOtherServices方法,启动更多系统服务与持久化进程,完成桌面Activity的启动与广播发布。
文中总结了AMS创建与启动的关键步骤,并预告后续文章将深入探讨AMS的具体使用、对四大组件的管理以及内存管理等内容。通过本篇解析,读者能更直观地理解Android系统中AMS的核心功能与作用。
2024-11-29 00:59
2024-11-29 00:53
2024-11-29 00:31
2024-11-29 00:10
2024-11-28 23:08
2024-11-28 23:00