SpringBoot 插件化架構的4種實現方案

在復雜業務場景下,傳統的單體應用架構往往面臨著功能擴展困難、代碼耦合嚴重、迭代效率低下等問題。

插件化架構作為一種模塊化設計思想的延伸,能夠使系統具備更好的擴展性和靈活性,實現"熱插拔"式的功能擴展。

本文將介紹SpringBoot環境下實現插件化架構的4種實現方案。

方案一:基于Spring的條件注解實現

原理介紹

這種方案利用Spring提供的條件注解(如@Conditional@ConditionalOnProperty等)實現插件的動態加載。通過配置文件或環境變量控制哪些插件被激活,適合簡單的插件化需求。

實現步驟

  1. 1. 定義插件接口

  2. 2. 實現多個插件實現類

  3. 3. 使用條件注解控制插件加載

  4. 4. 在主應用中使用插件

代碼示例

1. 定義插件接口

public interface PaymentPlugin {String getName();boolean support(String payType);PaymentResult pay(PaymentRequest request);
}

2. 實現插件類

@Component
@ConditionalOnProperty(prefix = "plugins.payment", name = "alipay", havingValue = "true")
public class AlipayPlugin implements PaymentPlugin {@Overridepublic String getName() {return "alipay";}@Overridepublic boolean support(String payType) {return "alipay".equals(payType);}@Overridepublic PaymentResult pay(PaymentRequest request) {// 支付寶支付邏輯System.out.println("Processing Alipay payment");return new PaymentResult(true, "Alipay payment successful");}
}@Component
@ConditionalOnProperty(prefix = "plugins.payment", name = "wechat", havingValue = "true")
public class WechatPayPlugin implements PaymentPlugin {@Overridepublic String getName() {return "wechat";}@Overridepublic boolean support(String payType) {return "wechat".equals(payType);}@Overridepublic PaymentResult pay(PaymentRequest request) {// 微信支付邏輯System.out.println("Processing WeChat payment");return new PaymentResult(true, "WeChat payment successful");}
}

3. 插件管理器

@Component
public class PaymentPluginManager {private final List<PaymentPlugin> plugins;@Autowiredpublic PaymentPluginManager(List<PaymentPlugin> plugins) {this.plugins = plugins;}public PaymentPlugin getPlugin(String payType) {return plugins.stream().filter(plugin -> plugin.support(payType)).findFirst().orElseThrow(() -> new IllegalArgumentException("Unsupported payment type: " + payType));}public List<String> getSupportedPayments() {return plugins.stream().map(PaymentPlugin::getName).collect(Collectors.toList());}
}

4. 配置文件設置

plugins:payment:alipay: truewechat: truepaypal: false

5. 在服務中使用

@Service
public class PaymentService {private final PaymentPluginManager pluginManager;@Autowiredpublic PaymentService(PaymentPluginManager pluginManager) {this.pluginManager = pluginManager;}public PaymentResult processPayment(String payType, PaymentRequest request) {PaymentPlugin plugin = pluginManager.getPlugin(payType);return plugin.pay(request);}public List<String> getSupportedPaymentMethods() {return pluginManager.getSupportedPayments();}
}

優缺點分析

優點:

  • ? 實現簡單,無需額外的框架支持

  • ? 與Spring生態完全兼容

  • ? 啟動時即完成插件加載,性能穩定

缺點:

  • ? 不支持運行時動態加載/卸載插件

  • ? 所有插件代碼都需要在編譯時確定

  • ? 插件之間可能存在依賴沖突

適用場景

  • ? 功能模塊相對穩定,變化不頻繁的系統

  • ? 簡單的SaaS多租戶系統中不同租戶的功能定制

  • ? 不同部署環境需要不同功能模塊的場景

  • 關注公眾號:碼猿技術專欄,回復關鍵詞:1111 獲取阿里內部Java性能調優手冊!

方案二:基于SPI機制實現

原理介紹

SPI(Service Provider Interface)是Java提供的一種服務發現機制,允許第三方為系統提供實現。SpringBoot也提供了類似機制的擴展,可以利用它實現一種松耦合的插件化架構。

實現步驟

  1. 1. 定義插件接口和抽象類

  2. 2. 實現SPI配置

  3. 3. 創建插件實現類

  4. 4. 實現插件加載器

代碼示例

1. 定義插件接口

public interface ReportPlugin {String getType();boolean support(String reportType);byte[] generateReport(ReportRequest request);
}

2. 創建SPI配置文件

META-INF/services/目錄下創建與接口全限定名同名的文件,如:
META-INF/services/com.example.plugin.ReportPlugin

文件內容為實現類的全限定名:

com.example.plugin.impl.PdfReportPlugin
com.example.plugin.impl.ExcelReportPlugin
com.example.plugin.impl.HtmlReportPlugin

3. 實現插件類

public class PdfReportPlugin implements ReportPlugin {@Overridepublic String getType() {return "pdf";}@Overridepublic boolean support(String reportType) {return "pdf".equals(reportType);}@Overridepublic byte[] generateReport(ReportRequest request) {System.out.println("Generating PDF report");// PDF生成邏輯return "PDF Report Content".getBytes();}
}// 其他插件實現類類似

4. 插件加載器

@Component
public class SpiPluginLoader {private static final Logger logger = LoggerFactory.getLogger(SpiPluginLoader.class);private final Map<String, ReportPlugin> reportPlugins = new HashMap<>();@PostConstructpublic void loadPlugins() {ServiceLoader<ReportPlugin> serviceLoader = ServiceLoader.load(ReportPlugin.class);for (ReportPlugin plugin : serviceLoader) {logger.info("Loading report plugin: {}", plugin.getType());reportPlugins.put(plugin.getType(), plugin);}logger.info("Loaded {} report plugins", reportPlugins.size());}public ReportPlugin getReportPlugin(String type) {ReportPlugin plugin = reportPlugins.get(type);if (plugin == null) {throw new IllegalArgumentException("Unsupported report type: " + type);}return plugin;}public List<String> getSupportedReportTypes() {return new ArrayList<>(reportPlugins.keySet());}
}

5. 在服務中使用

@Service
public class ReportService {private final SpiPluginLoader pluginLoader;@Autowiredpublic ReportService(SpiPluginLoader pluginLoader) {this.pluginLoader = pluginLoader;}public byte[] generateReport(String reportType, ReportRequest request) {ReportPlugin plugin = pluginLoader.getReportPlugin(reportType);return plugin.generateReport(request);}public List<String> getSupportedReportTypes() {return pluginLoader.getSupportedReportTypes();}
}

優缺點分析

優點:

  • ? 標準的Java SPI機制,無需引入額外依賴

  • ? 插件實現與主程序解耦,便于第三方擴展

  • ? 配置簡單,只需添加配置文件

缺點:

  • ? 不支持運行時動態加載/卸載插件

  • ? 無法控制插件加載順序

適用場景

  • ? 需要支持第三方擴展的開源框架

  • ? 系統中的通用功能需要多種實現的場景

  • ? 插件之間無復雜依賴關系的系統

方案三:基于SpringBoot自動配置實現

原理介紹

SpringBoot的自動配置機制是實現插件化的另一種強大方式。通過創建獨立的starter模塊,每個插件可以自包含所有依賴和配置,實現"即插即用"。

實現步驟

  1. 1. 創建核心模塊定義插件接口

  2. 2. 為每個插件創建獨立的starter

  3. 3. 實現自動配置類

  4. 4. 在主應用中集成插件

代碼示例

1. 核心模塊接口定義

// plugin-core模塊
public interface StoragePlugin {String getType();boolean support(String storageType);String store(byte[] data, String path);byte[] retrieve(String path);
}

2. 插件實現模塊

// local-storage-plugin模塊
public class LocalStoragePlugin implements StoragePlugin {private final String rootPath;public LocalStoragePlugin(String rootPath) {this.rootPath = rootPath;}@Overridepublic String getType() {return "local";}@Overridepublic boolean support(String storageType) {return "local".equals(storageType);}@Overridepublic String store(byte[] data, String path) {// 本地存儲實現String fullPath = rootPath + "/" + path;System.out.println("Storing data to: " + fullPath);// 實際存儲邏輯return fullPath;}@Overridepublic byte[] retrieve(String path) {// 本地讀取實現System.out.println("Retrieving data from: " + path);// 實際讀取邏輯return "Local file content".getBytes();}
}

3. 自動配置類

@Configuration
@ConditionalOnProperty(prefix = "storage", name = "type", havingValue = "local")
@EnableConfigurationProperties(LocalStorageProperties.class)
public class LocalStorageAutoConfiguration {@Bean@ConditionalOnMissingBeanpublic StoragePlugin localStoragePlugin(LocalStorageProperties properties) {return new LocalStoragePlugin(properties.getRootPath());}
}@ConfigurationProperties(prefix = "storage.local")
public class LocalStorageProperties {private String rootPath = "/tmp/storage";// getter and setterpublic String getRootPath() {return rootPath;}public void setRootPath(String rootPath) {this.rootPath = rootPath;}
}

4. spring.factories配置

META-INF/spring.factories文件中添加:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.storage.local.LocalStorageAutoConfiguration

5. 類似地實現其他存儲插件

// s3-storage-plugin模塊
public class S3StoragePlugin implements StoragePlugin {// 實現亞馬遜S3存儲...
}@Configuration
@ConditionalOnProperty(prefix = "storage", name = "type", havingValue = "s3")
@EnableConfigurationProperties(S3StorageProperties.class)
public class S3StorageAutoConfiguration {@Bean@ConditionalOnMissingBeanpublic StoragePlugin s3StoragePlugin(S3StorageProperties properties) {return new S3StoragePlugin(properties.getAccessKey(),?properties.getSecretKey(),?properties.getBucket());}
}

6. 主應用使用插件

@Service
public class FileService {private final StoragePlugin storagePlugin;@Autowiredpublic FileService(StoragePlugin storagePlugin) {this.storagePlugin = storagePlugin;}public String saveFile(byte[] data, String path) {return storagePlugin.store(data, path);}public byte[] getFile(String path) {return storagePlugin.retrieve(path);}
}

7. 配置文件設置

storage:type: local ?# 可選值: local, s3, oss等local:root-path: /data/files

優缺點分析

優點:

  • ? 符合SpringBoot規范,易于集成

  • ? 插件可以包含完整的依賴和配置

  • ? 可通過配置動態切換插件

  • ? 插件可以訪問Spring上下文

缺點:

  • ? 需要重啟應用才能更換插件

  • ? 所有可能的插件需要預先定義

  • ? 多個插件同時存在可能引起依賴沖突

適用場景

  • ? 企業級應用中需要支持多種技術實現的場景

  • ? 不同部署環境使用不同技術棧的情況

  • ? 需要將復雜功能模塊化的大型應用

方案四:動態加載JAR實現

原理介紹

這種方案實現了真正的運行時動態加載插件,通過自定義ClassLoader加載外部JAR文件,實現插件的熱插拔。

實現步驟

  1. 1. 設計插件接口和擴展點

  2. 2. 實現插件加載器

  3. 3. 創建插件管理服務

  4. 4. 實現插件生命周期管理

代碼示例

1. 核心接口定義

// 插件接口
public interface Plugin {String getId();String getName();String getVersion();void initialize(PluginContext context);void start();void stop();
}// 插件上下文
public interface PluginContext {ApplicationContext getApplicationContext();ClassLoader getClassLoader();File getPluginDirectory();
}

2. 自定義類加載器

public class PluginClassLoader extends URLClassLoader {private final File pluginJarFile;public PluginClassLoader(File pluginJarFile, ClassLoader parent) throws MalformedURLException {super(new URL[]{pluginJarFile.toURI().toURL()}, parent);this.pluginJarFile = pluginJarFile;}public File getPluginJarFile() {return pluginJarFile;}
}

3. 插件加載器

@Component
public class JarPluginLoader {private static final Logger logger = LoggerFactory.getLogger(JarPluginLoader.class);@Value("${plugins.directory:/plugins}")private String pluginsDirectory;@Autowiredprivate ApplicationContext applicationContext;public Plugin loadPlugin(File jarFile) throws Exception {logger.info("Loading plugin from: {}", jarFile.getAbsolutePath());PluginClassLoader classLoader = new PluginClassLoader(jarFile, getClass().getClassLoader());// 查找plugin.properties文件URL pluginPropertiesUrl = classLoader.findResource("plugin.properties");if (pluginPropertiesUrl == null) {throw new IllegalArgumentException("Missing plugin.properties in plugin JAR");}Properties pluginProperties = new Properties();try (InputStream is = pluginPropertiesUrl.openStream()) {pluginProperties.load(is);}String mainClass = pluginProperties.getProperty("plugin.main-class");if (mainClass == null) {throw new IllegalArgumentException("Missing plugin.main-class in plugin.properties");}// 加載并實例化插件主類Class<?> pluginClass = classLoader.loadClass(mainClass);if (!Plugin.class.isAssignableFrom(pluginClass)) {throw new IllegalArgumentException("Plugin main class must implement Plugin interface");}Plugin plugin = (Plugin) pluginClass.getDeclaredConstructor().newInstance();// 創建插件上下文PluginContext context = new DefaultPluginContext(applicationContext, classLoader,?new File(pluginsDirectory, plugin.getId()));// 初始化插件plugin.initialize(context);return plugin;}// 簡單的插件上下文實現private static class DefaultPluginContext implements PluginContext {private final ApplicationContext applicationContext;private final ClassLoader classLoader;private final File pluginDirectory;public DefaultPluginContext(ApplicationContext applicationContext, ClassLoader classLoader,?File pluginDirectory) {this.applicationContext = applicationContext;this.classLoader = classLoader;this.pluginDirectory = pluginDirectory;if (!pluginDirectory.exists()) {pluginDirectory.mkdirs();}}@Overridepublic ApplicationContext getApplicationContext() {return applicationContext;}@Overridepublic ClassLoader getClassLoader() {return classLoader;}@Overridepublic File getPluginDirectory() {return pluginDirectory;}}
}

4. 插件管理服務

@Service
public class PluginManagerService {private static final Logger logger = LoggerFactory.getLogger(PluginManagerService.class);@Value("${plugins.directory:/plugins}")private String pluginsDirectory;@Autowiredprivate JarPluginLoader pluginLoader;private final Map<String, Plugin> loadedPlugins = new ConcurrentHashMap<>();private final Map<String, PluginClassLoader> pluginClassLoaders = new ConcurrentHashMap<>();@PostConstructpublic void init() {loadAllPlugins();}public void loadAllPlugins() {File directory = new File(pluginsDirectory);if (!directory.exists() || !directory.isDirectory()) {directory.mkdirs();return;}File[] jarFiles = directory.listFiles((dir, name) -> name.endsWith(".jar"));if (jarFiles != null) {for (File jarFile : jarFiles) {try {loadPlugin(jarFile);} catch (Exception e) {logger.error("Failed to load plugin: {}", jarFile.getName(), e);}}}}public Plugin loadPlugin(File jarFile) throws Exception {Plugin plugin = pluginLoader.loadPlugin(jarFile);String pluginId = plugin.getId();// 如果插件已加載,先停止并卸載if (loadedPlugins.containsKey(pluginId)) {unloadPlugin(pluginId);}// 啟動插件plugin.start();// 保存插件和類加載器loadedPlugins.put(pluginId, plugin);pluginClassLoaders.put(pluginId, (PluginClassLoader) plugin.getClass().getClassLoader());logger.info("Plugin loaded and started: {}", plugin.getName());return plugin;}public void unloadPlugin(String pluginId) {Plugin plugin = loadedPlugins.get(pluginId);if (plugin != null) {try {plugin.stop();logger.info("Plugin stopped: {}", plugin.getName());} catch (Exception e) {logger.error("Error stopping plugin: {}", plugin.getName(), e);}loadedPlugins.remove(pluginId);// 清理類加載器PluginClassLoader classLoader = pluginClassLoaders.remove(pluginId);if (classLoader != null) {try {classLoader.close();} catch (IOException e) {logger.error("Error closing plugin class loader", e);}}}}public List<PluginInfo> getLoadedPlugins() {return loadedPlugins.values().stream().map(plugin -> new PluginInfo(plugin.getId(), plugin.getName(), plugin.getVersion())).collect(Collectors.toList());}@Data@AllArgsConstructorpublic static class PluginInfo {private String id;private String name;private String version;}
}

5. 插件控制器

@RestController
@RequestMapping("/api/plugins")
public class PluginController {@Autowiredprivate PluginManagerService pluginManager;@GetMappingpublic List<PluginManagerService.PluginInfo> getPlugins() {return pluginManager.getLoadedPlugins();}@PostMapping("/upload")public ResponseEntity<String> uploadPlugin(@RequestParam("file") MultipartFile file) {if (file.isEmpty() || !file.getOriginalFilename().endsWith(".jar")) {return ResponseEntity.badRequest().body("Please upload a valid JAR file");}try {// 保存上傳的JAR文件File tempFile = File.createTempFile("plugin-", ".jar");file.transferTo(tempFile);// 加載插件Plugin plugin = pluginManager.loadPlugin(tempFile);return ResponseEntity.ok("Plugin uploaded and loaded: " + plugin.getName());} catch (Exception e) {return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Failed to load plugin: " + e.getMessage());}}@DeleteMapping("/{pluginId}")public ResponseEntity<String> unloadPlugin(@PathVariable String pluginId) {try {pluginManager.unloadPlugin(pluginId);return ResponseEntity.ok("Plugin unloaded: " + pluginId);} catch (Exception e) {return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Failed to unload plugin: " + e.getMessage());}}@PostMapping("/reload")public ResponseEntity<String> reloadAllPlugins() {try {pluginManager.loadAllPlugins();return ResponseEntity.ok("All plugins reloaded");} catch (Exception e) {return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Failed to reload plugins: " + e.getMessage());}}
}

6. 插件示例實現

// 在獨立項目中開發插件
public class ReportGeneratorPlugin implements Plugin {private PluginContext context;private boolean running = false;@Overridepublic String getId() {return "report-generator";}@Overridepublic String getName() {return "Report Generator Plugin";}@Overridepublic String getVersion() {return "1.0.0";}@Overridepublic void initialize(PluginContext context) {this.context = context;}@Overridepublic void start() {running = true;System.out.println("Report Generator Plugin started");// 注冊REST接口或服務try {ApplicationContext appContext = context.getApplicationContext();// 這里需要特殊處理來注冊新的Controller} catch (Exception e) {e.printStackTrace();}}@Overridepublic void stop() {running = false;System.out.println("Report Generator Plugin stopped");}// 插件特定功能public byte[] generateReport(String type, Map<String, Object> data) {// 報表生成邏輯return "Report Content".getBytes();}
}

7. 插件描述文件 (plugin.properties)

plugin.id=report-generator
plugin.name=Report Generator Plugin
plugin.version=1.0.0
plugin.main-class=com.example.plugin.report.ReportGeneratorPlugin
plugin.author=Your Name
plugin.description=A plugin for generating various types of reports

優缺點分析

優點:

  • ? 支持真正的運行時動態加載/卸載插件

  • ? 插件可以完全獨立開發和部署

  • ? 主應用無需重啟即可更新插件

缺點:

  • ? 實現復雜,需要處理類加載器和資源隔離問題

  • ? 可能存在內存泄漏風險

  • ? 插件與主應用的通信需要精心設計

  • ? 版本兼容性問題難以處理

適用場景

  • ? 需要在運行時動態更新功能的系統

  • ? 第三方開發者需要擴展的平臺

  • ? 插件開發和主應用開發由不同團隊負責的情況

  • ? 微內核架構的應用系統

方案對比

特性

條件注解

SPI機制

自動配置

動態JAR

實現復雜度

運行時加載

資源隔離

Spring集成

很好

一般

很好

一般

開發門檻

部署復雜度

適合規模

小型

小型

中型

中大型

總結

插件化架構不僅是一種技術選擇,更是一種系統設計思想。

通過將系統分解為核心框架和可插拔組件,我們能夠構建更加靈活、可維護和可擴展的應用系統,更好地應對不斷變化的業務需求。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/85248.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/85248.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/85248.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

VGG-19(Visual Geometry Group)模型

VGG-19 是由牛津大學視覺幾何組和 Google DeepMind 的研究人員在 2014 年提出的一個非常經典的深度卷積神經網絡模型。 一 核心結構 &#xff08;1&#xff09;深度&#xff1a; 模型名稱中的 "19" 指的是模型擁有 19 層帶有權重的層&#xff08;通常指&#xff1a;…

Windows11 鼠標卡死任務欄卡死 假死解決方法

最近很多朋友都有一個問題&#xff0c;就是Windows11電腦 在編輯文檔或者是切換窗口的時候出現任務欄假死&#xff0c;鼠標左右鍵失靈等現象&#xff0c;想了幾天解決方案今天吧最直接的方法教給大家 首發玖毅論壇 玖毅論壇https://www.webbbs.cn/ 第一步&#xff1a; 第一種…

BeikeShop - 一個開源、用戶友好的跨境電子商務平臺

BeikeShop - 一個開源、用戶友好的跨境電子商務平臺 BeikeShop 是全球領先的基于 Laravel 框架的開源電子商務平臺&#xff0c;專為國際貿易和跨境電子商務行業設計。 該系統是 100% 開源的&#xff01;它支持多語言、多幣種、支付、物流、會員管理等廣泛的實用功能&#xff0…

基于大模型的膽囊結石全周期診療方案研究報告

目錄 一、引言 1.1 研究背景與意義 1.2 研究目的與目標 1.3 研究方法與創新點 二、大模型預測膽囊結石的原理與技術基礎 2.1 大模型概述 2.2 用于膽囊結石預測的數據來源 2.3 模型構建與訓練 2.4 模型評估指標 三、術前風險預測與手術方案制定 3.1 術前評估指標與數…

[論文閱讀] 人工智能 | Gen-n-Val:利用代理技術革新計算機視覺數據生成

Gen-n-Val&#xff1a;利用代理技術革新計算機視覺數據生成 論文信息 article{huang2025gennval,title{Gen-n-Val: Agentic Image Data Generation and Validation},author{Huang, Jing-En and Fang, I-Sheng and Huang, Tzuhsuan and Wang, Chih-Yu and Chen, Jun-Cheng},jo…

【AI論文】ReasonMed:一個370K的多智能體生成數據集,用于推進醫療推理

摘要&#xff1a;盡管基于推理的大型語言模型&#xff08;LLM&#xff09;在數學和編程方面表現出色&#xff0c;但它們在知識密集型醫療問題回答方面的能力仍未得到充分探索。為解決這一問題&#xff0c;我們推出了ReasonMed&#xff0c;這是最大的醫療推理數據集&#xff0c;…

singlefligt使用方法和源碼解讀

singlefligt使用方法和源碼解讀 介紹 sync.once保證其整個生命周期內只調用一次&#xff1b;而singleflight則可以保證在一定范圍內其只調用一次。 背景|使用場景 應對緩存擊穿&#xff1a;加鎖可以解決這個問題&#xff0c;但是加鎖不太靈活&#xff08;不能控制訪問頻率之…

HTTP 協議的基本概念(請求/響應流程、狀態碼、Header、方法)問題解決方案大全

HTTP 協議的基本概念&#xff08;請求/響應流程、狀態碼、Header、方法&#xff09;問題解決方案大全 一. 摘要 HTTP 協議是 Web 開發的基石&#xff0c;但初學者往往只停留在 GET、POST 的層面&#xff0c;對重定向機制、緩存控制、請求體解析等概念缺乏深入理解&#xff0c;…

Python中常用的函數

以下是Python中常用的函數分類整理&#xff0c;涵蓋基礎操作、數據處理、文件操作、面向對象等場景&#xff0c;并附上示例說明&#xff1a; --- ### **一、基礎內置函數** | 函數 | 作用 | 示例 | |----…

【Windows】刪除鼠標右鍵多余菜單的方法

要刪除鼠標右鍵菜單中的多余菜單&#xff0c;如&#xff1a;“打開抖音壁紙”選項&#xff0c;通常需要通過修改注冊表或使用第三方工具來清理殘留的注冊表項。以下是詳細步驟&#xff08;操作注冊表前務必備份&#xff01;&#xff09;&#xff1a; 方法一&#xff1a;通過注冊…

【性能優化】啟用zram

性能優化 系統內存不足時&#xff0c;可以考慮啟動ZRAM功能&#xff08;壓縮內存&#xff09;。關于ZRAM的概念&#xff0c;可自行學習。這里記錄一下&#xff0c;啟用ZRAM的方式。 啟用ZRAM&#xff0c;可能會導致CPU升高&#xff0c;以及低內存時的惡性循環。是否啟用需要綜…

深度解析YOLOv8:CSPHet卷積結構如何實現極致輕量化

文章目錄 一、背景介紹1.1 YOLOv8的現狀1.2 降參數的必要性 二、相關技術介紹2.1 Dual思想2.2 HetConv 三、CSPHet結構設計3.1 CSP模塊的改進3.2 結合HetConv3.3 參數量的下降 四、CSPHet的代碼實現五、實驗結果六、總結與展望 在目標檢測領域&#xff0c;YOLO系列算法一直以其…

適配器模式demo

#include <QCoreApplication> #include <iostream>using namespace std;class XmCom { public:void ComByXm(){cout << "XM電源適配器只適用于小米筆記本電腦" << endl;} };class LxCom { public:virtual void ComByLx() 0;virtual ~LxCom…

數據處理考核要求-SQL測試的答案

在一個團隊中&#xff0c;有業務人員。如業務人員深入理解數據處理的內容&#xff0c;會大幅度增強相互配合的效率。 針對業務人員進行針對性培訓&#xff0c;還是比較容易掌握SQL的數據處理。類似與大學里面開的一門選修課。數據集選擇帆軟的Demo數據集。 業務人員學會SQL的…

第十七屆全國大學生數學競賽(數學類)初賽模擬試題

上周組委會發布了第十七屆全國大學生數學競賽通知&#xff0c;初賽暫定于2025年11月8日(星期六)上午9:00-11:30舉行&#xff0c;同時今年新增了個亮點&#xff0c;針對與數學類的同學&#xff0c;即&#xff1a; 為提升全國大學生數學競賽的含金量和公平性&#xff0c;并進一步…

解決: React Native iOS webview 空白頁

iOS react-native-webview 之前是正常的, 升級了 react-native / react-native-webview 等 之后, 就變成了空白頁. 通過下面的修改, 可以修復, 回到正常的狀態. 來源: https://github.com/react-native-webview/react-native-webview/issues/3697 diff --git a/node_modules/…

VMware安裝Ubuntu并實現root遠程登錄

前置信息 垃圾Ubuntu系統默認ssh、vim都沒有&#xff01;&#xff01;&#xff01; 已踩坑cnmUbuntu處于sb安全機制要求&#xff0c;默認是禁用root直接登錄的 1、修改root密碼 sudo -sH &#xff08;可以讓一個具有sudo權限的普通用戶進入 root&#xff09; 然后就是pas…

量化面試綠皮書:20. 正態生成

文中內容僅限技術學習與代碼實踐參考&#xff0c;市場存在不確定性&#xff0c;技術分析需謹慎驗證&#xff0c;不構成任何投資建議。 20. 正態生成 Q: 如何生成兩個標準正態分布&#xff08;N(0,1)&#xff09;的隨機變量&#xff0c;使它們之間的相關系數為p&#xff0c;假設…

Arduino入門教程:10、屏幕顯示

飛書文檔https://x509p6c8to.feishu.cn/docx/N45Pd0tA1oaC4CxUWZjc8Ekyn0b 屏幕應用場景 課程使用的SSD1306是一款128*64像素可以使用IIC驅動的OLED屏幕。 SSD1306 Oled顯示模塊共有4個引腳&#xff0c;標記為GND, VCC, SCL和SDA。這種Oled顯示模塊可以使用3.3V到5V輕松上電。…

華為云Flexus+DeepSeek征文|體驗華為云ModelArts快速搭建Dify-LLM應用開發平臺并創建自己dify釘釘群聊機器人

華為云FlexusDeepSeek征文&#xff5c;體驗華為云ModelArts快速搭建Dify-LLM應用開發平臺并創建自己dify釘釘群聊機器人 什么是華為云ModelArts 華為云ModelArts ModelArts是華為云提供的全流程AI開發平臺&#xff0c;覆蓋從數據準備到模型部署的全生命周期管理&#xff0c;幫…