Egg.js 源码分析-项目启动

2023-12-18 16:58
文章标签 分析 源码 启动 项目 js egg

本文主要是介绍Egg.js 源码分析-项目启动,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

前端时间抽出时间针对Koa2源码进行了简单的学习,koa 源码是一个很简单的库, 针对分析过程, 想手把手的实现一个类型koa 的框架,其代码, 根据一步步的完善实现一个简单版本的Koa, 每一个步骤一个Branch , 如: stpe-1, 对应的是我想实现第一步的代码, 代码仅供自己简单的学习,很多地方不完善,只是想体验下Koa 的思想。下面几点是我对Koa 的简单理解:

  • 所有的NodeJS 框架最基本的核心就是通过原生库http or https启动一个后端服务http.createServer(this.serverCallBack()).listen(...arg), 然后所有的请求都会进入serverCallBack方法, 然后我们可以通过拦截,在这个方法中处理不同的请求
  • Koa 是一个洋葱模型, 其是基于中间件来实现的.通过use来添加一个中间件, koa-router其实就是一个koa的中间件,我们的所有的请求都会将所有的中间件都执行一遍,洋葱模型如下图所示

 

 

 

 

 

 

上面是我对Koa 源码分析的一些简单的理解, 后面我会将对Koa 的理解,进一步的记录下来。 Koa 是一个很小巧灵活的框架, 不像Express, Express 已经集成了很多的功能, 很多功能不再需要第三方的框架,比如说路由功能, Koa 需要引用第三方的库koa-router 来实现路由等。但是express 则不需要,下面是Koa 和Express, 两个实现一个简单的功能的Demo , 我们可以比较下其使用方式:

// Express
const express = require('express')
const app = express()app.get('/', function (req, res) {res.send('Hello World!')
})app.listen(3000, function () {console.log('Example app listening on port 3000!')
})
复制代码
// Koa 
var Koa = require('koa');
// 引用第三方路由库
var Router = require('koa-router');var app = new Koa();
var router = new Router();
router.get('/', (ctx, next) => {// ctx.router available
});
// 应用中间件: router
app.use(router.routes()).use(router.allowedMethods());
app.listen(3000);
复制代码

哈哈,我们上面说了很多的废话(文字表达能力问题), 其实我是想分析下,怎么基于Koa 框架去应用, eggjs就是基于Koa 框架基础上实现的一个框架, 我们下面来具体分析下eggjs 框架。

Eggjs 基本使用

我们根据快速入门, 可以很快搭建一个Egg 项目框架,

$ npm i egg-init -g
$ egg-init egg-example --type=simple
$ cd egg-example
$ npm i
复制代码

我们可以用npm run dev 快速启动项目.然后打开localhost:7001,就可以看到页面输出:

hi, egg.

说明我们项目初始化已经完成,而且已经启动成功。我们现在可以学习下egg项目生成的相关代码。其代码文件结构如下:

 

 

 

分析整个文件结构,找了整个项目都没有发现app.js之类的入口文件(我一般学习一个新的框架,都会从入口文件着手), 发现app 文件夹下面的应该对项目很重要的代码:

1, controller文件夹,我们从字面理解,应该是控制层的文件,其中有一个home.js 代码如下:

'use strict';const Controller = require('egg').Controller;class HomeController extends Controller {async index() {this.ctx.body = 'hi, egg';}
}module.exports = HomeController;复制代码

这个类继承了egg 的Controller 类, 暂时还没有发现这个项目哪个地方有引用这个Controller 类?

2, 一个router.js 文件, 从字面意义上我们可以理解其为一个路由的文件,其代码如下:

'use strict';/*** @param {Egg.Application} app - egg application*/
module.exports = app => {const { router, controller } = app;router.get('/', controller.home.index);
};复制代码

这个文件暴露了一个方法, 从目前来猜测应该就是路由的一些配置, 但是找遍整个项目也没有发现,哪个地方引用了这个方法, router.get('/', controller.home.index);, 但是从这个get 方法的第二个参数, 其似乎指向的是Controller 里面的home.js 文件index 方法,我们可以尝试修改下home.js 中的this.ctx.body = 'hi, egg -> hello world!';, 然后重新运行npm run dev, 发现页面输出是hi, egg -> hello world!, 看来controller.home.index这个指向的是home.js 里的index 方法无疑了, 但是controller.home.index这个index 方法绑定的是在一个controller对象上,什么时候绑定的呢?

我们接下来带着如下疑问来学些eggjs :

  • 没有类似的app.js 入口文件,运行npm run dev 如何启动一个项目(启动server, 监听端口, 添加中间件)?
  • 我们打开页面http://localhost:7001/,怎么去通过router.js 去查找路由的,然后调用对应的回调函数?
  • Controller 是如何绑定到app 上面的controller 对象上的?

eggjs 启动

我们先查看一开始用egg-init命令创建的项目的package.json 文件,查看scripts,里面有一系列的命令,如下图:

 

 

我们可以通过npm run start来启动程序, 但是其中有一个命令debug, 我们可以可以通过npm run debug命令来调试eggjs 程序, 其对用的命令是egg-bin debug, 所以我们整个入口就是这个命令,我们下面来具体分析下egg-bin debug是如何工作的.

 

egg-bin

egg-bin 中的start-cluster文件, 调用了eggjs 的入口方法:require(options.framework).startCluster(options); 其中options.framework指向的就是一个绝对路径D:\private\your_project_name\node_modules\egg(也就是egg 模块), 直接执行D:\private\your_project_name\node_modules\egg\index.js暴露出来的exports.startCluster = require('egg-cluster').startCluster;startCluster方法。 下面我们就来分析egg-cluster 模块。

egg-cluster

egg-cluster 的项目结构如下, 其中有两个主要的文件: master.js, app_worker.js两个文件,

 

 

 

master.js是跟nodejs的多线程有关,我们先跳过这一块,直接研究app_worker.js文件,学习eggjs 的启动过程。下面我们就是app_worker.js 执行的主要步骤。

  1. const Application = require(options.framework).Application; , 引入eggjs 模块, optons.framework 指向的就是D:\private\your_project_name\node_modules\egg
  2. const app = new Application(options);(创建一个 egg 实例)
  3. app.ready(startServer);调用egg 对象的** ready ** 方法,其startServer 是一个回调函数,其功能是调用nodejs 原生模块http or httpscreateServer 创建一个nodejs 服务(server = require('http').createServer(app.callback());, 我们后续会深入分析这个方法)。

上面三个步骤, 已经启动了一个nodejs 服务, 监听了端口。也就是已经解决了我们的第一个疑问:

没有类似的app.js 入口文件,运行npm run dev 如何启动一个项目(启动server, 监听端口, 添加中间件)?

上面其实我们还是只是分析了eggjs启动的基本流程, 还没有涉及eggjs 的核心功能库,也就是** egg ** 和** egg-core** 两个库,但是我们上面已经初实例化了一个eggjs 的对象const app = new Application(options);, 下面我们就从这个入口文件来分析eggjs 的核心模块。

egg & egg-core

egg 和egg-core 模块下面有几个核心的类,如下:

Application(egg/lib/applicaton.js) -----> EggApplication(egg/lib/egg.js) -----> EggCore(egg-core/lib/egg.js) -----> KoaApplication(koa)

从上面的关系可以,eggjs 是基于koa的基础上进行扩展的,所以我们从基类的构造函数开始进行分析(因为new Application 会从继类开始的构造函数开始执行)。

EggCore(egg-core/lib/egg.js)

我们将构造函数进行精简,代码如下

 

 

从上图可知,构造函数就是初始化了很多基础的属性,其中有两个属性很重要:

 

  1. this.lifecycle负责整个eggjs 实例的生命周期,我们后续会深入分析整个生命周期
  1. this.loader(egg-core/lib/loader/egg_loader.js)解决了eggjs 为什么在服务启动后,会自动加载,将项目路径下的router.js, controller/**.js, 以及service/**.js绑定到 app 实例上, 我们接下来会重点分析这个loader.

EggApplication(egg/lib/egg.js)

我们将构造函数进行精简,代码如下

 

 

 

这个构造函数同样也是初始化了很多基础的属性, 但是其中有调用EggCore 构造函数初始化的loaderloadConfig() 方法, 这个方法顾名思义就是去加载配置,其指向的是: egg/lib/loader/app_worker_loader .js 的方法loadConfig , 这个方法,如下:

  loadConfig() {this.loadPlugin();super.loadConfig();}复制代码

其会加载所有的Plugin ,然后就加载所有的Config.

this.loadPlugin() 指向的是egg-core/lib/loader/mixin/plgin.js的方法loadPlugin, 其会加载三种plugin:

  • const appPlugins = this.readPluginConfigs(path.join(this.options.baseDir, 'config/plugin.default'));,应用配置的plugin , 也就是your-project-name/config/plugin.js, 也就是每个应用需要配置的特殊的插件
  • const eggPluginConfigPaths = this.eggPaths.map(eggPath => path.join(eggPath, 'config/plugin.default'));, 也就是从eggjs 框架配置的插件, 其路径是在egg/config/plugin.js, 也就是框架自带的插件
  • process.env.EGG_PLUGINS 第三种, 是启动项目是,命令行带参数EGG_PLUGINS的插件, 应该使用不广。

最后将所有的plugin 挂在在app实例上this.plugins = enablePlugins;,。(后续会学习怎么这些plugin 怎么工作的。)

接下来会执行super.loadConfig()方法, 其指向的是egg-core/lib/loader/mixin/config.jsloadConfig()方法, 其同样会加载四种config:

  • const appConfig = this._preloadAppConfig();, 应用配置的config , 也就是每个应用的特殊配置,其会加载两个配置:
  const names = ['config.default',`config.${this.serverEnv}`,];
复制代码

第一个一定会加载对应的config.default配置, 也就是your-project-name/config/config.default.js,跟运行环境没有关系的配置, 其次会加载跟运行环境有关的配置,如: config.prod.js, config.test.js, config.local.js, config.unittest.js

  • 会去加载所有的plugin 插件目录
   if (this.orderPlugins) {for (const plugin of this.orderPlugins) {dirs.push({path: plugin.path,type: 'plugin',});}}
复制代码
  • 会去加载egg 项目目录, 也就是egg/config 目录
    for (const eggPath of this.eggPaths) {dirs.push({path: eggPath,type: 'framework',});}
复制代码
  • 回去加载应用项目的目录, 也就是也就是your-project-name/config

最后将合并的config 挂载在app 实例上this.config = target;

我们可以打开egg/config/config.default.js文件,可以查看下,默认的都有什么配置,其中一个配置如下:

  config.cluster = {listen: {path: '',port: 7001,hostname: '',},};
复制代码

很明显,这应该是一个对server 启动的配置,我们暂且可以这样猜测。

我们上面有分析在egg-cluster/lib/app_worker.js中,我们初始化app 后,我们有调用app.ready(startServer);方法,我们可以猜测startServer方法就是启动nodejs server 的地方。

startServer方法中,初始化了一个http server server = require('http').createServer(app.callback());, 然后我们给listen server.listen(...args);;, 这样算是node js 的server 启动起来了, 我们可以查看下,我可以查看args 的参数:

  const args = [ port ];if (listenConfig.hostname) args.push(listenConfig.hostname);debug('listen options %s', args);server.listen(...args);
复制代码

这里给args 添加了prot 端口参数, 我们可以跳转到prot定义的地方:

const app = new Application(options);
const clusterConfig = app.config.cluster || /* istanbul ignore next */ {};
const listenConfig = clusterConfig.listen || /* istanbul ignore next */ {};
const port = options.port = options.port || listenConfig.port;
复制代码

我们可以看到port 最终来源于: app.config.cluster.listen.port,从这里我们得知, eggjs 的config 的使用方式。

问题:

如果我们不想在eggjs 项目启动时,默认打开的端口不是7001 ,我们改怎么操作呢?

我们应该有如下两种方式:

  1. 在执行npm run debug 命令时,添加相应的参数
  2. 我们可以在我们项目的config/config.default.js 中添加配置,将默认的给覆盖掉,如:

module.exports = appInfo => {const config = exports = {};// use for cookie sign key, should change to your own and keep securityconfig.keys = appInfo.name + '_1541735701381_1116';// add your config hereconfig.middleware = [];config.cluster = {listen: {path: '',port: 7788,hostname: '',},};return config;
};复制代码

如上,我们再次启动项目的时候,打开的端口就是: 7788了。

思考:

我们已经知道可以在config 中进行相应的配置了, 我们还有什么其他的应用在config 上面呢?

我们知道在不同的运行环境下,会加载不同的配置,那如果我们在开发的时候,调用api 的路径是: http://dev.api.com, 但是在上线的时候,我们调用的app的路径是: http://prod.api.com, 我们就可以在 config.prod.js中配置 apiURL:http://prod.api.com, 在config.local.js配置: apiURL:http://prod.api.com

然后我们在我们调用API的地方通过 app.apiURL就可以。

Application(egg/lib/application.js)

Application(egg/lib/applicaton.js) -----> EggApplication(egg/lib/egg.js) -----> EggCore(egg-core/lib/egg.js) -----> KoaApplication(koa)

我们已经将上述的两个核心的类: EggApplication(egg/lib/egg.js) -----> EggCore(egg-core/lib/egg.js), 我们现在来分析最上层的类: Application(egg/lib/applicaton.js)。

我们还是从构造函数入手,我们发现了一行很重要的代码this.loader.load();其指向的是: app_worker_loader.js(egg/lib/loader/app_worker_loader.js)的load 方法, 其实现如下:

  load() {// app > plugin > corethis.loadApplicationExtend();this.loadRequestExtend();this.loadResponseExtend();this.loadContextExtend();this.loadHelperExtend();// app > pluginthis.loadCustomApp();// app > pluginthis.loadService();// app > plugin > corethis.loadMiddleware();// appthis.loadController();// appthis.loadRouter(); // Dependent on controllers}
复制代码

从这个方法可知,加载了一大批的配置,我们可以进行一一的分析:

this.loadApplicationExtend();

这个方法会去给应用加载很多的扩展方法, 其加载的路径是: app\extend\application.js, 会将对应的对象挂载在app 应用上。 (使用方法可以参考egg-jsonp/app/extend/applicaton.js 或者egg-session/app/extend/application.js)

this.loadResponseExtend(); this.loadResponseExtend(); this.loadContextExtend(); this.loadHelperExtend();,

this.loadApplicationExtend();加载的方式是一样的,只是对应的名称分别是: request.js, response.js, helper.js, context.js

this.loadCustomApp();

定制化应用, 加载的文件是对应项目下的app.js (your_project_name/app.js), 其具体的代码实现如下: (egg-core/lib/loader/mixin/custom.js)

  [LOAD_BOOT_HOOK](fileName) {this.timing.start(`Load ${fileName}.js`);for (const unit of this.getLoadUnits()) { const bootFilePath = this.resolveModule(path.join(unit.path, fileName));if (!bootFilePath) {continue;}const bootHook = this.requireFile(bootFilePath);// bootHook 是加载的文件if (is.class(bootHook)) {// if is boot class, add to lifecyclethis.lifecycle.addBootHook(bootHook);} else if (is.function(bootHook)) {// if is boot function, wrap to class// for compatibilitythis.lifecycle.addFunctionAsBootHook(bootHook);} else {this.options.logger.warn('[egg-loader] %s must exports a boot class', bootFilePath);}}// init bootsthis.lifecycle.init();this.timing.end(`Load ${fileName}.js`);},
复制代码

从上可知** bootHook** 对应的就是加载的文件,从上面的if else可知, app.js 必须暴露出来的是一个class 或者是一个function ,然后调用this.lifecycle.addFunctionAsBootHook(bootHook);, 其代码如下:

  addFunctionAsBootHook(hook) {assert(this[INIT] === false, 'do not add hook when lifecycle has been initialized');// app.js is export as a funciton// call this function in configDidLoadthis[BOOT_HOOKS].push(class Hook {constructor(app) {this.app = app;}configDidLoad() {hook(this.app);}});}
复制代码

将对应的hook push 到this.lifecycle 的BOOT_HOOKS 数组中, 并且包装成了一个类, 且在configDidLoad 调用对应的hook.然后调用了this.lifecycle.init();去初始化生命周期:

  init() {assert(this[INIT] === false, 'lifecycle have been init');this[INIT] = true;this[BOOTS] = this[BOOT_HOOKS].map(t => new t(this.app));this[REGISTER_BEFORE_CLOSE]();}
复制代码

这个init 方法做了三件事情:

  • 将lifecycle 的INIT 状态标记为: true
  • 将BOOT_HOOKS 对应的类, 实例化一个对象,保存在BOOTS
  • 调用REGISTER_BEFORE_CLOSE方法,其中会调用我们的hook 的beforeClose 方法。

this.loadCustomApp(); 方法如下:

  loadCustomApp() {this[LOAD_BOOT_HOOK]('app');this.lifecycle.triggerConfigWillLoad();},
复制代码

所以接下执行this.lifecycle.triggerConfigWillLoad();

  triggerConfigWillLoad() {for (const boot of this[BOOTS]) {if (boot.configWillLoad) {boot.configWillLoad();}}this.triggerConfigDidLoad();}triggerConfigDidLoad() {for (const boot of this[BOOTS]) {if (boot.configDidLoad) {boot.configDidLoad();}}this.triggerDidLoad();}
复制代码

其中boot.configDidLoad(); 就是我们app.js 定义的hook, 被加工成的Hook 类:

class Hook {constructor(app) {this.app = app;}configDidLoad() {hook(this.app);}}
复制代码

然后就将app.js 与eggjs 关联起来了。

this.loadService();

查找的your_project_name/app/service/.js, 然后将文件名称作为一个作为属性,挂载在context**上下文上,然后将对应的js 文件,暴露的方法赋值在这个属性上, 比如说我们在如下路径下: your_project_name/app/service/home.js, 其代码如下:

'use strict';// app/service/home.js
const Service = require('egg').Service;class HomeService extends Service {async find() {// const user = await this.ctx.db.query('select * from user where uid = ?', uid);const user = [{name: 'Ivan Fan',age: 18,},];return user;}
}module.exports = HomeService;
复制代码

我们在其他的地方就可以通过: this.ctx.service.home.find()方法调用service里面的方法了,如在controller 中调用:

'use strict';
const Controller = require('egg').Controller;
class HomeController extends Controller {async index() {// this.ctx.body = 'hi, egg';this.ctx.body = await this.ctx.service.home.find();}
}
module.exports = HomeController;
复制代码

this.loadMiddleware();

这个方法用来加载中间件,我们后面会单独来分析中间件

this.loadController();

这个方法是去加载controller , 其代码如下:

  loadController(opt) {this.timing.start('Load Controller');opt = Object.assign({caseStyle: 'lower',directory: path.join(this.options.baseDir, 'app/controller'),initializer: (obj, opt) => {// return class if it exports a function// ```js// module.exports = app => {//   return class HomeController extends app.Controller {};// }// ```if (is.function(obj) && !is.generatorFunction(obj) && !is.class(obj) && !is.asyncFunction(obj)) {obj = obj(this.app);}if (is.class(obj)) {obj.prototype.pathName = opt.pathName;obj.prototype.fullPath = opt.path;return wrapClass(obj);}if (is.object(obj)) {return wrapObject(obj, opt.path);}// support generatorFunction for forward compatbilityif (is.generatorFunction(obj) || is.asyncFunction(obj)) {return wrapObject({ 'module.exports': obj }, opt.path)['module.exports'];}return obj;},}, opt);const controllerBase = opt.directory;this.loadToApp(controllerBase, 'controller', opt);this.options.logger.info('[egg:loader] Controller loaded: %s', controllerBase);this.timing.end('Load Controller');},
复制代码

其加载的路径是: app/controller 下面的js 文件。然后将对应文件的名称挂载在app.controller上面,然后就可以通过如下方式,调用controller下面js 暴露的方法:

module.exports = app => {const { router, controller } = app;router.get('/', controller.home.index);
};
复制代码

上面也就是解决了我们一开始的疑问三:

  • Controller 是如何绑定到app 上面的controller 对象上的?

this.loadRouter();

这个方法,顾名思义就是去加载router, 其代码如下:

  loadRouter() {this.timing.start('Load Router');// 加载 router.jsthis.loadFile(this.resolveModule(path.join(this.options.baseDir, 'app/router')));this.timing.end('Load Router');},
复制代码

只会加载对应项目下的app/router.js, 也就是路由应该只有一个入口文件.如下Demo:

'use strict';/*** @param {Egg.Application} app - egg application*/
module.exports = app => {const { router, controller } = app;router.get('/', controller.home.index);
};复制代码

如上代码实现路由。但是我们只是给对应的路由添加了方法, 但是如何去监听路由变化,然后调用不同的方法呢? 这个涉及到koa 中间件的使用方法,我们后续会单独分析中间件, 以及koa-router

总结

  1. egg 的核心模块包括Application(egg/lib/applicaton.js) -----> EggApplication(egg/lib/egg.js) -----> EggCore(egg-core/lib/egg.js) -----> KoaApplication(koa)
  2. eggjs 会通过loadConfig() 去加载配置文件
  loadConfig() {this.loadPlugin();super.loadConfig();}
复制代码
  1. 会通过load() 方法去加载一系列相关配置
  load() {// app > plugin > corethis.loadApplicationExtend();this.loadRequestExtend();this.loadResponseExtend();this.loadContextExtend();this.loadHelperExtend();// app > pluginthis.loadCustomApp();// app > pluginthis.loadService();// app > plugin > corethis.loadMiddleware();// appthis.loadController();// appthis.loadRouter(); // Dependent on controllers}
复制代码

计划

  1. 梳理Eggjs的基本使用方法
  2. 中间件middleware的使用
  3. 路由router 的使用原理
  4. egg-cluster的分析
  5. egg 生命周期的分析


作者:bluebrid
链接:https://juejin.im/post/5be92cc95188251fd925d49b
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

这篇关于Egg.js 源码分析-项目启动的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/509137

相关文章

Redis主从/哨兵机制原理分析

《Redis主从/哨兵机制原理分析》本文介绍了Redis的主从复制和哨兵机制,主从复制实现了数据的热备份和负载均衡,而哨兵机制可以监控Redis集群,实现自动故障转移,哨兵机制通过监控、下线、选举和故... 目录一、主从复制1.1 什么是主从复制1.2 主从复制的作用1.3 主从复制原理1.3.1 全量复制

Python 中 requests 与 aiohttp 在实际项目中的选择策略详解

《Python中requests与aiohttp在实际项目中的选择策略详解》本文主要介绍了Python爬虫开发中常用的两个库requests和aiohttp的使用方法及其区别,通过实际项目案... 目录一、requests 库二、aiohttp 库三、requests 和 aiohttp 的比较四、requ

SpringBoot项目启动后自动加载系统配置的多种实现方式

《SpringBoot项目启动后自动加载系统配置的多种实现方式》:本文主要介绍SpringBoot项目启动后自动加载系统配置的多种实现方式,并通过代码示例讲解的非常详细,对大家的学习或工作有一定的... 目录1. 使用 CommandLineRunner实现方式:2. 使用 ApplicationRunne

Redis主从复制的原理分析

《Redis主从复制的原理分析》Redis主从复制通过将数据镜像到多个从节点,实现高可用性和扩展性,主从复制包括初次全量同步和增量同步两个阶段,为优化复制性能,可以采用AOF持久化、调整复制超时时间、... 目录Redis主从复制的原理主从复制概述配置主从复制数据同步过程复制一致性与延迟故障转移机制监控与维

使用IntelliJ IDEA创建简单的Java Web项目完整步骤

《使用IntelliJIDEA创建简单的JavaWeb项目完整步骤》:本文主要介绍如何使用IntelliJIDEA创建一个简单的JavaWeb项目,实现登录、注册和查看用户列表功能,使用Se... 目录前置准备项目功能实现步骤1. 创建项目2. 配置 Tomcat3. 项目文件结构4. 创建数据库和表5.

Python项目打包部署到服务器的实现

《Python项目打包部署到服务器的实现》本文主要介绍了PyCharm和Ubuntu服务器部署Python项目,包括打包、上传、安装和设置自启动服务的步骤,具有一定的参考价值,感兴趣的可以了解一下... 目录一、准备工作二、项目打包三、部署到服务器四、设置服务自启动一、准备工作开发环境:本文以PyChar

多模块的springboot项目发布指定模块的脚本方式

《多模块的springboot项目发布指定模块的脚本方式》该文章主要介绍了如何在多模块的SpringBoot项目中发布指定模块的脚本,作者原先的脚本会清理并编译所有模块,导致发布时间过长,通过简化脚本... 目录多模块的springboot项目发布指定模块的脚本1、不计成本地全部发布2、指定模块发布总结多模

SpringBoot项目删除Bean或者不加载Bean的问题解决

《SpringBoot项目删除Bean或者不加载Bean的问题解决》文章介绍了在SpringBoot项目中如何使用@ComponentScan注解和自定义过滤器实现不加载某些Bean的方法,本文通过实... 使用@ComponentScan注解中的@ComponentScan.Filter标记不加载。@C

Redis连接失败:客户端IP不在白名单中的问题分析与解决方案

《Redis连接失败:客户端IP不在白名单中的问题分析与解决方案》在现代分布式系统中,Redis作为一种高性能的内存数据库,被广泛应用于缓存、消息队列、会话存储等场景,然而,在实际使用过程中,我们可能... 目录一、问题背景二、错误分析1. 错误信息解读2. 根本原因三、解决方案1. 将客户端IP添加到Re

Java汇编源码如何查看环境搭建

《Java汇编源码如何查看环境搭建》:本文主要介绍如何在IntelliJIDEA开发环境中搭建字节码和汇编环境,以便更好地进行代码调优和JVM学习,首先,介绍了如何配置IntelliJIDEA以方... 目录一、简介二、在IDEA开发环境中搭建汇编环境2.1 在IDEA中搭建字节码查看环境2.1.1 搭建步