最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Spring Boot Pf4j模塊化開(kāi)發(fā)方案設(shè)計(jì)過(guò)程解析

 更新時(shí)間:2025年12月29日 09:17:26   作者:Jeffcky  
本文介紹了基于SpringPf4j實(shí)現(xiàn)Java版本模塊化開(kāi)發(fā)的設(shè)計(jì)方案,包括插件化基礎(chǔ)設(shè)施的二次封裝、Spring控制器動(dòng)態(tài)注冊(cè)、SpringDoc-OpenApi集成等,通過(guò)這些步驟,實(shí)現(xiàn)插件的動(dòng)態(tài)加載和Swagger接口的動(dòng)態(tài)展示,感興趣的朋友跟隨小編一起看看吧

前言

上一篇文章還是2年前,一是工作太忙,二是人也變得懶散,好多新東西僅止于腦海里面的印象,未能深入,不成體系,最近主要花了些時(shí)間實(shí)現(xiàn)Java版本的模塊化,同時(shí)也要重點(diǎn)兼顧小伙伴們從.NET Core移植模塊的成本,所以需要全盤考慮的東西會(huì)更加實(shí)際,好在有些Java底子加上AI的出現(xiàn),實(shí)現(xiàn)的過(guò)程相對(duì)會(huì)容易一些,最近對(duì)AGI提起興趣,接下來(lái)應(yīng)該會(huì)重點(diǎn)學(xué)習(xí)這方面的應(yīng)用開(kāi)發(fā)再來(lái)和大家分享,好了,話不多說(shuō),接下來(lái)的系列文章會(huì)講講Java版本的模塊化,和大家一起探討探討,或許有更好的一些建議,我能學(xué)習(xí)到更多。

Spring Pf4j實(shí)現(xiàn)效果

我們選擇【https://github.com/pf4j/pf4j】作為Java模塊化的基礎(chǔ)設(shè)施,雖然官方作者提供了pf4j-spring的版本基礎(chǔ)使用,但能力太弱(主要作者對(duì)spring boot好像不是非常熟悉,并沒(méi)有任何貶低意思,在相關(guān)issue作者也做出了表明),尤其是我們還要考慮.NET Core模塊的移植,所以不能完全開(kāi)箱即用,所以我對(duì)其進(jìn)行二次封裝。二次封裝為Spring版本,注意這里我說(shuō)的是封裝為Spring,而不是SpringBoot,因?yàn)镾pringBoot是Web應(yīng)用,而Spring提供了SpringBoot的基礎(chǔ)能力,所以我們只需要引入Spring基礎(chǔ)包即可,萬(wàn)萬(wàn)不可將SpringBoot全家桶引入到模塊化基礎(chǔ)設(shè)施,這點(diǎn)考慮非常重要。最終插件只需要繼承封裝的插件類即可

插件開(kāi)發(fā)者可重寫beforeApplicationContextRefresh和afterApplicationContextReady,熟悉.NET Core開(kāi)發(fā)的伙伴們應(yīng)該能猜到等同于ConfigureServices和Configure方法,在before方法里可自定義手動(dòng)注冊(cè)相關(guān)bean(當(dāng)然常見(jiàn)的component和bean等注解會(huì)自動(dòng)注冊(cè)),而after則是上下文刷新完成后可做業(yè)務(wù)上的初始化工作

Spring Pf4j上下文

每個(gè)插件有獨(dú)立的上下文,所以在啟動(dòng)插件時(shí)需創(chuàng)建插件上下文,完成創(chuàng)建插件上下文分為4個(gè)步驟,一是初始化上下文,二是提供上述抽象開(kāi)發(fā)者可重寫的手動(dòng)注冊(cè),三是刷新插件上下文,四是上述插件利用上下文進(jìn)行相關(guān)業(yè)務(wù)初始化操作

private ApplicationContext createApplicationContext() {
        long startTs = System.currentTimeMillis();
        // Step 1: Pre-create application context
        log.info("Initializing base context for plugin '{}'", pluginId);
        long preCreateStart = System.currentTimeMillis();
        AnnotationConfigApplicationContext annotationContext = preCreateApplicationContext();
        log.info("Initialized base context for plugin '{}' in {} ms",
                pluginId, System.currentTimeMillis() - preCreateStart);
        // Step 2: Customize context before refresh
        log.info("Customizing context configuration for plugin '{}'", pluginId);
        long handleStart = System.currentTimeMillis();
        AnnotationConfigApplicationContext context = beforeApplicationContextRefresh(annotationContext);
        log.info("Customized context configuration for plugin '{}' in {} ms",
                pluginId, System.currentTimeMillis() - handleStart);
        if (context == null) {
            context = annotationContext;
        }
        // Step 3: Refresh the context (load beans, etc.)
        log.info("Refreshing Spring context for plugin '{}'", pluginId);
        long postCreateStart = System.currentTimeMillis();
        postCreateApplicationContext(context);
        log.info("Refreshed Spring context for plugin '{}' in {} ms",
                pluginId, System.currentTimeMillis() - postCreateStart);
        // Step 4: Post-refresh custom logic
        log.info("Executing post-refresh logic for plugin '{}'", pluginId);
        long customStart = System.currentTimeMillis();
        afterApplicationContextReady(context);
        log.info("Completed post-refresh logic for plugin '{}' in {} ms",
                pluginId, System.currentTimeMillis() - customStart);
        // Total time
        log.info("Plugin '{}' context fully initialized in {} ms",
                pluginId, System.currentTimeMillis() - startTs);
        return context;
    }

整個(gè)步驟最重要的屬于初始化插件的上下文,這里貼一下偽代碼

Spring控制器動(dòng)態(tài)注冊(cè)

控制器的動(dòng)態(tài)注冊(cè)必然是等插件上下文刷新完成后去通過(guò)插件上下文獲取控制器bean,同時(shí)基于控制器的請(qǐng)求處理映射為RequestMappingHandlerMapping,所以我們需要實(shí)現(xiàn)自定義的請(qǐng)求處理映射,這里我們暫時(shí)只需考慮控制器及其方法的動(dòng)態(tài)注冊(cè)

public class GJPluginRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
    private static final Logger log = LoggerFactory.getLogger(GJPluginRequestMappingHandlerMapping.class);
    @Override
    public void detectHandlerMethods(@NotNull Object controller) {
        super.detectHandlerMethods(controller);
    }
}

我們將上述自定義請(qǐng)求映射處理作為bean注冊(cè)到主應(yīng)用,然后在插件上下文創(chuàng)建完成后,獲取注冊(cè)到主應(yīng)用的自定義請(qǐng)求處理映射,傳入插件,偽代碼如下:

GJPluginLifecycle registerController() {
        GJPluginRequestMappingHandlerMapping pluginRequestMappingHandlerMapping = plugin.getMainApplicationContext()
        .getBean("pluginRequestMappingHandlerMapping", GJPluginRequestMappingHandlerMapping.class);
        pluginRequestMappingHandlerMapping.registerControllers(plugin);
        return this;
    }

插件上下文獲取控制器bean,并將插件控制器bean注冊(cè)到主應(yīng)用上下文以及控制器方法注冊(cè)到自定義的請(qǐng)求處理映射中

public Set<Object> getControllerBeans(GJPlugin springBootPlugin) {
        ApplicationContext applicationContext = springBootPlugin.getApplicationContext();
        Set<Object> beans = new LinkedHashSet<>();
        Map<String, Object> controllerBeans = applicationContext.getBeansWithAnnotation(Controller.class);
        Map<String, Object> restControllerBeans = applicationContext.getBeansWithAnnotation(RestController.class);
        beans.addAll(controllerBeans.values());
        beans.addAll(restControllerBeans.values());
        if (log.isTraceEnabled()) {
            List<String> names = beans.stream()
                    .map(b -> b.getClass().getSimpleName())
                    .collect(Collectors.toList());
            log.debug("Scanned {} controller beans: {}", beans.size(), names);
        }
        return beans;
    }

我們?cè)賮?lái)遍歷插件中所有控制器列表,進(jìn)行動(dòng)態(tài)注冊(cè)即可

SpringDoc-OpenApi

上述為整個(gè)模塊化或者插件化的設(shè)計(jì)方案,我們首先需要實(shí)現(xiàn)的第一個(gè)則是Swagger,將所有插件接口列表能夠在主應(yīng)用啟動(dòng)完成后在swagger頁(yè)面里呈現(xiàn)出來(lái),但我們插件控制器為動(dòng)態(tài)注冊(cè),那么這里如何設(shè)計(jì)呢,我們一步步來(lái)。首先是在主應(yīng)用引入openapi的包

 <dependency>
        <groupId>org.springdoc</groupId>
        <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
 </dependency>

上述只是主應(yīng)用定義的控制器已被呈現(xiàn),但要使得動(dòng)態(tài)注冊(cè)的插件控制器在主應(yīng)用啟動(dòng)后也能在swagger中呈現(xiàn)出來(lái),我們還需要完成3個(gè)步驟,一是在插件基礎(chǔ)設(shè)施中引入openapi,插件化基礎(chǔ)設(shè)施盡可能輕量,無(wú)需引入springdoc-openapi-starter-webmvc-ui,建議引入springdoc-openapi-starter-common包即可,如此插件只需對(duì)控制器等等打上標(biāo)簽,其他應(yīng)該都用不到。二是插件注冊(cè)時(shí)需要構(gòu)建插件控制器的GroupedOpenApi(即每個(gè)插件對(duì)應(yīng)一個(gè)GroupedOpenApi),并將其注冊(cè)到主應(yīng)用上下文,三是主應(yīng)用需要支持動(dòng)態(tài)注冊(cè)多GroupedOpenApi。我們重點(diǎn)關(guān)注步驟2和步驟3,在主應(yīng)用yml配置文件中對(duì)spring-doc的相關(guān)配置過(guò)于簡(jiǎn)單此處忽略不講,為了實(shí)現(xiàn)多模塊的動(dòng)態(tài)注冊(cè),需要使用springdoc-OpenApi的多GroupedOpenApi延遲注冊(cè),如下為通用方案

@Configuration
public class SpringDocOpenApiCfg {
    @Bean
    MultipleOpenApiWebMvcResource multipleOpenApiResource(List<GroupedOpenApi> groupedOpenApis,
                                                          ObjectFactory<OpenAPIService> defaultOpenAPIBuilder,
                                                          AbstractRequestService requestBuilder,
                                                          GenericResponseService responseBuilder,
                                                          OperationService operationParser,
                                                          SpringDocConfigProperties springDocConfigProperties,
                                                          SpringDocProviders springDocProviders,
                                                          SpringDocCustomizers springDocCustomizers) {
        return new MultipleOpenApiWebMvcResource(groupedOpenApis,
                defaultOpenAPIBuilder, requestBuilder,
                responseBuilder, operationParser,
                springDocConfigProperties,
                springDocProviders,
                springDocCustomizers);
    }
}

 我們封裝插件的注冊(cè)GroupedOpenApi邏輯,如下:

public class GJPluginOpenApiInfo {
    /**
     * 獲取插件Swagger分組名稱(插件ID即為組名)
     */
    public String getGroupName;
    public String getGroupName() {
        return getGroupName;
    }
    public void setGroupName(String getGroupName) {
        this.getGroupName = getGroupName;
    }
    /**
     * 獲取插件Controller所在包
     */
    private List<String> getControllerPackages;
    public void setControllerPackages(List<String> getControllerPackages) {
        this.getControllerPackages = getControllerPackages;
    }
    public List<String> getControllerPackages() {
        return getControllerPackages;
    }
}
public class GJPluginOpenApiConfig {
    public static final String PLUGIN_SWAGGER_BEAN_PREFIX = "pluginGroupedOpenApi-";
    public static void registerPluginOpenApiBeans(GJPlugin springBootPlugin, GJPluginOpenApiInfo pluginSwaggerInfo) {
        String groupName = pluginSwaggerInfo.getGroupName();
        groupName = groupName.trim().toLowerCase();
        if (groupName.trim().isEmpty()) {
            return;
        }
        String beanName = PLUGIN_SWAGGER_BEAN_PREFIX + groupName;
        String finalGroupName = groupName;
        GroupedOpenApi groupedOpenApi = GroupedOpenApi.builder()
                .group(finalGroupName.trim())
                .displayName(finalGroupName.trim())
                .packagesToScan(pluginSwaggerInfo.getControllerPackages().toArray(new String[0]))
                .build();
        springBootPlugin.registerBeanToMainContext(beanName, groupedOpenApi);
    }
}

在上述我們遍歷控制器列表動(dòng)態(tài)注冊(cè)控制器時(shí),此時(shí)調(diào)用上述封裝注冊(cè)插件的GroupedOpenApi,代碼如下:

我們搞一個(gè)Demo插件控制器,看能不能在swagger界面中呈現(xiàn)出來(lái)

此時(shí)我們發(fā)現(xiàn)插件GroupedOpenApi有了,但插件接口列表沒(méi)有呈現(xiàn),同時(shí)主應(yīng)用的接口列表悄無(wú)聲息已無(wú),于是乎開(kāi)始自定義OpenApiResource調(diào)試等等系列操作,底層最后在構(gòu)建計(jì)算接口列表等等時(shí)有一個(gè)方法引起重要關(guān)注

上述嚴(yán)格判斷插件控制器方法的bean到底是不是屬于對(duì)應(yīng)的控制器,于是我們回過(guò)頭去看我們動(dòng)態(tài)注冊(cè)控制器的bean和將控制器的方法注冊(cè)到請(qǐng)求處理映射的邏輯,如下再重點(diǎn)標(biāo)識(shí)一下,以免小伙伴們忘記了

未曾注意到這一細(xì)節(jié),我們發(fā)現(xiàn)了問(wèn)題,注冊(cè)控制器到主應(yīng)用上下文的bean用的控制器名稱,而將控制器方法的注冊(cè)傳入的是控制器對(duì)象而不是簡(jiǎn)單的控制器名稱,所以獲取到的方法控制器bean則是控制器的hash值,而控制器的bean實(shí)際是字符串,所以傳入方法的控制器也修改為控制器的名稱

總結(jié)

如上基于pf4j二次封裝的整個(gè)設(shè)計(jì)思路,其中還涉及一些細(xì)節(jié)并未詳細(xì)展開(kāi),細(xì)節(jié)主要是對(duì)pf4j底層實(shí)現(xiàn)的深入了解,然后在封裝以及安全等等上做出了進(jìn)一步的打磨,若有需要了解的小伙伴們,可在評(píng)論留言,我們可一起碰撞碰撞思路,本文暫到此為止,感謝閱讀。

到此這篇關(guān)于Spring Boot Pf4j模塊化開(kāi)發(fā)設(shè)計(jì)方案的文章就介紹到這了,更多相關(guān)Spring Boot Pf4j模塊化開(kāi)發(fā)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • java IP地址網(wǎng)段計(jì)算的示例代碼

    java IP地址網(wǎng)段計(jì)算的示例代碼

    這篇文章主要介紹了java IP地址網(wǎng)段計(jì)算的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-03-03
  • Java從ftp服務(wù)器上傳與下載文件的實(shí)現(xiàn)

    Java從ftp服務(wù)器上傳與下載文件的實(shí)現(xiàn)

    這篇文章主要給大家介紹了關(guān)于Java從ftp服務(wù)器上傳與下載文件的實(shí)現(xiàn)方法,最近項(xiàng)目中需要實(shí)現(xiàn)將文件先存放到ftp上,需要的時(shí)候再?gòu)膄tp上下載,做的過(guò)程中碰到了問(wèn)題,所以這里總結(jié)下,需要的朋友可以參考下
    2023-08-08
  • Java之操作Redis案例講解

    Java之操作Redis案例講解

    這篇文章主要介紹了Java之操作Redis案例講解,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-08-08
  • 一文盤點(diǎn)Java創(chuàng)建實(shí)例對(duì)象的方式

    一文盤點(diǎn)Java創(chuàng)建實(shí)例對(duì)象的方式

    Java對(duì)象是通過(guò)加載、鏈接、初始化三大步驟來(lái)完成對(duì)象的創(chuàng)建及初始化,那么接下來(lái)就說(shuō)一下Java創(chuàng)建實(shí)例對(duì)象的方式有哪幾種,文中并通過(guò)代碼示例講解的非常詳細(xì),需要的朋友可以參考下
    2025-02-02
  • Java金額大小寫的轉(zhuǎn)換方法

    Java金額大小寫的轉(zhuǎn)換方法

    這篇文章主要為大家詳細(xì)介紹了Java金額大小寫的轉(zhuǎn)換方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-04-04
  • JAVA提高第七篇 類加載器解析

    JAVA提高第七篇 類加載器解析

    這篇文章主要為大家詳細(xì)介紹了JAVA提高第七篇類加載器的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-10-10
  • Java Swing組件布局管理器之FlowLayout(流式布局)入門教程

    Java Swing組件布局管理器之FlowLayout(流式布局)入門教程

    這篇文章主要介紹了Java Swing組件布局管理器之FlowLayout(流式布局),結(jié)合實(shí)例形式分析了Swing組件布局管理器FlowLayout流式布局的常用方法及相關(guān)使用技巧,需要的朋友可以參考下
    2017-11-11
  • Java計(jì)算交集,差集,并集的方法示例

    Java計(jì)算交集,差集,并集的方法示例

    這篇文章主要介紹了Java計(jì)算交集,差集,并集的方法,結(jié)合實(shí)例形式簡(jiǎn)單分析了java集合運(yùn)算的簡(jiǎn)單操作技巧,需要的朋友可以參考下
    2017-10-10
  • 詳解Java如何在Array和List之間進(jìn)行轉(zhuǎn)換

    詳解Java如何在Array和List之間進(jìn)行轉(zhuǎn)換

    這篇文章主要為大家介紹了詳解Java如何在Array和List之間進(jìn)行轉(zhuǎn)換的方法示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-05-05
  • mybatis條件語(yǔ)句中帶數(shù)組參數(shù)的處理

    mybatis條件語(yǔ)句中帶數(shù)組參數(shù)的處理

    這篇文章主要介紹了mybatis條件語(yǔ)句中帶數(shù)組參數(shù)的處理方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-09-09

最新評(píng)論

界首市| 高州市| 方城县| 万载县| 香河县| 南投市| 富蕴县| 海丰县| 普安县| 宾阳县| 格尔木市| 莎车县| 广东省| 定边县| 黄大仙区| 平定县| 镇宁| 五华县| 盐池县| 银川市| 南开区| 泗洪县| 克什克腾旗| 中卫市| 浑源县| 厦门市| 徐水县| 金乡县| 瑞丽市| 宝鸡市| 神农架林区| 阿城市| 彝良县| 邯郸市| 施秉县| 渭南市| 镇远县| 东安县| 武威市| 都江堰市| 津市市|