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

SpringBoot實現(xiàn)動態(tài)加載外部Jar流程詳解

 更新時間:2023年05月20日 11:48:08   作者:加班狂魔  
這篇文章主要介紹了SpringBoot動態(tài)加載外部Jar的流程,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧

背景及實現(xiàn)思路

想要設(shè)計一個stater,可以方便加載一個可以完整運行的springboot單體jar包,為了在已執(zhí)行的服務(wù)上面快速的擴(kuò)展功能而不需要重啟整個服務(wù),又或者低代碼平臺生成代碼之后可以快速預(yù)覽。

加載jar的技術(shù)棧

  • springboot 2.2.6.RELEASE
  • mybatis-plus 3.4.1

實現(xiàn)加載

想要完成類加載要熟悉spring中類加載機(jī)制,以及java中classloader的雙親委派機(jī)制。

加載分為兩大步

第一步需要將對應(yīng)的jar中的class文件加載進(jìn)當(dāng)前運行內(nèi)存中,第二步則是將對應(yīng)的bean注冊到spring,交由spring管理。

load class

load class主要使用jdk中URLClassLoader工具類,但是這里要注意一點,構(gòu)建classloader時,構(gòu)造函數(shù)可以指定父類加載器,如果指定之后,java才會將兩個classloader加載的同一個class視作類型一致,如果不指定會出現(xiàn) com.demo.A can not cast to com.demo.A這樣的情況。

但是我這里依舊沒有指定父類加載器,原因如下:

  • 我要加載的jar都是可以獨立運行的,沒有必須要依賴別的工程的文件
  • 我需要可以卸載掉,如果制定了父類加載器,那么會到這這個classloader不能回收,那么該加載器就一直在內(nèi)存中。

加載jar的代碼

/**
     * 加載jar包
     *
     * @param jarPath     jar路徑
     * @param packageName 掃面代碼的路徑
     * @return
     */
    public boolean loadJar(String jarPath, String packageName) {
        try {
            File file = FileUtil.file(jarPath);
            URLClassLoader classloader = new URLClassLoader(new URL[]{file.toURI().toURL()}, this.applicationContext.getClassLoader());
            JarFile jarFile = new JarFile(file);
            // 獲取jar包下所有的classes
            String pkgPath = packageName.replace(".", "/");
            Enumeration<JarEntry> entries = jarFile.entries();
            Class<?> clazz = null;
            List<JarEntry> xmlJarEntry = new ArrayList<>();
            List<String> loadedAliasClasses = new ArrayList<>();
            List<String> otherClasses = new ArrayList<>();
            // 首先加載model
            while (entries.hasMoreElements()) {
                JarEntry jarEntry = entries.nextElement();
                String entryName = jarEntry.getName();
                if (entryName.charAt(0) == '/') {
                    entryName = entryName.substring(1);
                }
                if (entryName.endsWith("Mapper.xml")) {
                    xmlJarEntry.add(jarEntry);
                } else {
                    if (jarEntry.isDirectory() || !entryName.contains(pkgPath) || !entryName.endsWith(".class")) {
                        continue;
                    }
                    String className = entryName.substring(0, entryName.length() - 6);
                    otherClasses.add(className.replace("/", "."));
                    log.info("load class : " + className.replace("/", "."));
                    // 將變量首字母置小寫
                    String beanName = StringUtils.uncapitalize(className);
                    if (beanName.contains(LoaderConstant.MODEL)) {
                        // 加載所有的class
                        clazz = classloader.loadClass(className.replace("/", "."));
                        SqlSessionFactory sqlSessionFactory = applicationContext.getBean(SqlSessionFactory.class);
                        sqlSessionFactory.getConfiguration().getTypeAliasRegistry().registerAlias(beanName.replace("/", "."), clazz);
                        loadedAliasClasses.add(beanName.replace("/", ".").toLowerCase());
                        doMap.put(className.replace("/", "."), clazz);
                    }
                }
            }
            // 再加載其他class
            for (String otherClass : otherClasses) {
                // 加載所有的class
                clazz = classloader.loadClass(otherClass.replace("/", "."));
                log.info("load class : " + otherClass.replace("/", "."));
                // 將變量首字母置小寫
                String beanName = StringUtils.uncapitalize(otherClass);
                if (beanName.endsWith(LoaderConstant.MAPPER)) {
                    mapperMap.put(beanName, clazz);
                } else if (beanName.endsWith(LoaderConstant.CONTROLLER)) {
                    controllerMap.put(beanName, clazz);
                } else if (beanName.endsWith(LoaderConstant.SERVICE_IMPL)) {
                    serviceImplMap.put(beanName, clazz);
                } else if (beanName.endsWith(LoaderConstant.SERVICE)) {
                    serviceMap.put(beanName, clazz);
                }
            }
            // 加載所有XML
            for (JarEntry jarEntry : xmlJarEntry) {
                SqlSessionFactory sqlSessionFactory = applicationContext.getBean(SqlSessionFactory.class);
                mybatisXMLLoader.xmlReload(sqlSessionFactory, jarFile, jarEntry, jarEntry.getName());
            }
            Jar jar = new Jar();
            jar.setName(jarPath);
            jar.setJarFile(jarFile);
            jar.setLoader(classloader);
            jar.setLoadedAliasClasses(loadedAliasClasses);
            // 開始加載bean
            registerBean(jar);
            registry.registerJar(jarPath, jar);
        } catch (Exception e) {
            log.error(e.getLocalizedMessage());
            return false;
        }
        return true;
    }

通常bean注冊過程

想要實現(xiàn)熱加載,一定得了解在spring中類的加載機(jī)制,大體上spring在掃描到@Component注解的類時,會根據(jù)其class生成對應(yīng)的BeanDefinition,然后在將其注冊在BeanDefinitionRegistry(這是個接口,最終由DefaultListableBeanFactory實現(xiàn))。當(dāng)其備引用注入實例時即getBean時被實例化并被注冊到DefaultSingletonBeanRegistry中。后續(xù)單例都將由DefaultSingletonBeanRegistry所管理。

controller加載

controller的加載機(jī)制

controller所特殊的是,spring會將其注冊到RequestMappingHandlerMapping中。所以想要熱加載controller 就需要三步。

  • 生成并注冊BeanDefinition
  • 生成并注冊實例注冊
  • RequestMappingHandlerMapping

代碼如下

// 獲取bean工廠并轉(zhuǎn)換為DefaultListableBeanFactory
        DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) ((ConfigurableApplicationContext)
                applicationContext).getBeanFactory();
        // 定義BeanDefinition
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
        GenericBeanDefinition beanDefinition = (GenericBeanDefinition) beanDefinitionBuilder.getRawBeanDefinition();
        //設(shè)置當(dāng)前bean定義對象是單利的
        beanDefinition.setScope("singleton");
        // 將變量首字母置小寫
        beanName = StringUtils.uncapitalize(beanName);
        // 將構(gòu)建的BeanDefinition交由Spring管理
        beanFactory.registerBeanDefinition(beanName, beanDefinition);
        // 手動構(gòu)建實例,并注入base service 防止卸載之后不再生成
        Object obj = clazz.newInstance();
        beanFactory.registerSingleton(beanName, obj);
        log.info("register Singleton :" + beanName);
        final RequestMappingHandlerMapping requestMappingHandlerMapping =
                    applicationContext.getBean(RequestMappingHandlerMapping.class);
        if (requestMappingHandlerMapping != null) {
                String handler = beanName;
                Object controller = null;
                try {
                    controller = applicationContext.getBean(handler);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                if (controller == null) {
                    return beanName;
                }
                // 注冊Controller
                Method method = requestMappingHandlerMapping.getClass().getSuperclass().getSuperclass().
                        getDeclaredMethod("detectHandlerMethods", Object.class);
                // 將private改為可使用
                method.setAccessible(true);
                method.invoke(requestMappingHandlerMapping, handler);
        }

關(guān)于IOC

其實只要注冊BeanDefinition之后,你getBean的時候spring會自動幫你完成@Autowired @Resouce 以及構(gòu)造方法的注入,這里我自己完成實例化是想完成一些業(yè)務(wù)上的處理,如自定義注入一些代理類。

關(guān)于AOP

這樣寫有一個弊端就是無法使用AOP,因為AOP是在getBean的時候三層緩存中完成代理的生成的,這里如果你要用這種方式注入可以參考spring源碼,構(gòu)建出來代理類再注入

service加載

service加載我這里直接將service對應(yīng)的實現(xiàn)類實例化再加載進(jìn)去就可以了,不需要什么特殊的處理,所以這里就不貼代碼了,加載同controller的第一步

mapper加載

mapper的加載時最復(fù)雜的一部分,首先針mapper有兩種,一種是純Mapper接口文件的加載,一種是xml文件的加載。并且你需要分析本身Mybatis是如何加載的,這樣才能完整的降mapper加載到內(nèi)存中。這里我將步驟分解為以下幾步

  • 注冊別名(主要是為了XML使用)
  • 解析XML文件
  • 解析Mapper接口,注冊mapper并注冊

注冊別名

mybatis對于別名的管理是存在SqlSessionFactory的Configuration(這個對象很重要,mybatis加載的資源之類的都在這個對象中管理)對象的TypeAliasRegistry中。TypeAliasRegistry是使用HashMap來維護(hù)別名的,這里我們直接調(diào)用registerAliases方法就好

SqlSessionFactory sqlSessionFactory = applicationContext.getBean(SqlSessionFactory.class);
sqlSessionFactory.getConfiguration().getTypeAliasRegistry().registerAlias(beanName.replace("/", "."), clazz);

解析XML文件

解析XML文件其實比較簡單只要調(diào)用XMLMapperBuilder來解析就好了,XMLMapperBuilder.parse方法會解析XML文件并注冊resultMaps、sqlFragments、mappedStatements。但是這里需要注意一點,那就是你解析的時候需要判斷一下把之前加載的數(shù)據(jù)需要刪除掉,同理resultMaps、sqlFragments、mappedStatements這些數(shù)據(jù)都是在SqlSessionFactory的Configuration中維護(hù)的,我們只要通過反射取得這些對象然后修改就可以了,代碼如下

/**
     * 解析加載XML
     *
     * @param sqlSessionFactory
     * @param jarFile jar對象
     * @param jarEntry jar包中的XML對象
     * @param name XML名稱
     * @throws IOException
     * @throws NoSuchFieldException
     * @throws IllegalAccessException
     */
    public void xmlReload(SqlSessionFactory sqlSessionFactory, JarFile jarFile, JarEntry jarEntry, String name) throws IOException, NoSuchFieldException, IllegalAccessException {
        // 2. 取得Configuration
        Configuration targetConfiguration = sqlSessionFactory.getConfiguration();
        Class<?> aClass = targetConfiguration.getClass();
        if (targetConfiguration.getClass().getSimpleName().equals("MybatisConfiguration")) {
            aClass = Configuration.class;
        }
        Set<String> loadedResources = (Set<String>) ObjectUtil.getFieldValue(targetConfiguration, aClass, "loadedResources");
        loadedResources.remove(name);
        // 3. 去掉之前加載的數(shù)據(jù)
        Map<String, ResultMap> resultMaps = (Map<String, ResultMap>) ObjectUtil.getFieldValue(targetConfiguration, aClass, "resultMaps");
        Map<String, XNode> sqlFragmentsMaps = (Map<String, XNode>) ObjectUtil.getFieldValue(targetConfiguration, aClass, "sqlFragments");
        Map<String, MappedStatement> mappedStatementMaps = (Map<String, MappedStatement>) ObjectUtil.getFieldValue(targetConfiguration, aClass, "mappedStatements");
        XPathParser parser = new XPathParser(jarFile.getInputStream(jarEntry), true, targetConfiguration.getVariables(), new XMLMapperEntityResolver());
        XNode mapperXNode = parser.evalNode("/mapper");
        List<XNode> resultMapNodes = mapperXNode.evalNodes("/mapper/resultMap");
        String namespace = mapperXNode.getStringAttribute("namespace");
        for (XNode xNode : resultMapNodes) {
            String id = xNode.getStringAttribute("id", xNode.getValueBasedIdentifier());
            resultMaps.remove(namespace + "." + id);
        }
        List<XNode> sqlNodes = mapperXNode.evalNodes("/mapper/sql");
        for (XNode sqlNode : sqlNodes) {
            String id = sqlNode.getStringAttribute("id", sqlNode.getValueBasedIdentifier());
            sqlFragmentsMaps.remove(namespace + "." + id);
        }
        List<XNode> msNodes = mapperXNode.evalNodes("select|insert|update|delete");
        for (XNode msNode : msNodes) {
            String id = msNode.getStringAttribute("id", msNode.getValueBasedIdentifier());
            mappedStatementMaps.remove(namespace + "." + id);
        }
        try {
            // 4. 重新加載和解析被修改的 xml 文件
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(jarFile.getInputStream(jarEntry),
                    targetConfiguration, name, targetConfiguration.getSqlFragments());
            xmlMapperBuilder.parse();
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        log.info("Parsed mapper file: '" + name + "'");
    }

其他類記載

其他類加載就比較簡單了,直接使用classloader將這些類load進(jìn)去就好,如果是單例需要被spring管理的則registerBeanDefinition就可以了

到此這篇關(guān)于SpringBoot實現(xiàn)動態(tài)加載外部Jar流程詳解的文章就介紹到這了,更多相關(guān)SpringBoot動態(tài)加載外部Jar內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Springboot使用pdfbox提取PDF圖片的代碼示例

    Springboot使用pdfbox提取PDF圖片的代碼示例

    PDFBox是一個用于創(chuàng)建和處理PDF文檔的Java庫,它可以使用Java代碼創(chuàng)建、讀取、修改和提取PDF文檔中的內(nèi)容,本文就給大家介紹Springboot如何使用pdfbox提取PDF圖片,感興趣的同學(xué)可以借鑒參考
    2023-06-06
  • IntelliJ IDEA 關(guān)閉多余項目的操作方法

    IntelliJ IDEA 關(guān)閉多余項目的操作方法

    這篇文章主要介紹了IntelliJ IDEA 關(guān)閉多余項目的操作方法,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-04-04
  • Java中使用instanceof判斷對象類型的示例

    Java中使用instanceof判斷對象類型的示例

    在List<Object>中遍歷Object時,先判斷類型,再定向轉(zhuǎn)換,本文給大家介紹Java中使用instanceof判斷對象類型,感興趣的朋友跟隨小編一起看看吧
    2023-08-08
  • JavaWeb中轉(zhuǎn)發(fā)與重定向的區(qū)別小結(jié)

    JavaWeb中轉(zhuǎn)發(fā)與重定向的區(qū)別小結(jié)

    轉(zhuǎn)發(fā)和重定向是JavaWeb中常用的兩種頁面跳轉(zhuǎn)方式,它們在實現(xiàn)上有一些區(qū)別,本文主要介紹了JavaWeb中轉(zhuǎn)發(fā)與重定向的區(qū)別小結(jié),具有一定的參考價值,感興趣的可以了解一下
    2023-10-10
  • SpringBoot關(guān)于自定義注解實現(xiàn)接口冪等性方式

    SpringBoot關(guān)于自定義注解實現(xiàn)接口冪等性方式

    這篇文章主要介紹了SpringBoot關(guān)于自定義注解實現(xiàn)接口冪等性方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-11-11
  • Java利用位運算實現(xiàn)加減運算詳解

    Java利用位運算實現(xiàn)加減運算詳解

    這篇文章主要為大家介紹了如何使用位運算來實現(xiàn)加減功能,也就是在整個運算過程中不能出現(xiàn)加減符號。文中的示例代碼講解詳細(xì),感興趣的可以了解一下
    2022-12-12
  • SpringBoot任務(wù)之詳解郵件任務(wù)

    SpringBoot任務(wù)之詳解郵件任務(wù)

    今天給大家整理的文章是SpringBoot郵件任務(wù)的相關(guān)知識點,文中有非常詳細(xì)的介紹及代碼示例,對正在學(xué)習(xí)SpringBoot任務(wù)的小伙伴們很有幫助,需要的朋友可以參考下
    2021-06-06
  • Java 中模仿源碼自定義ArrayList

    Java 中模仿源碼自定義ArrayList

    這篇文章主要介紹了Java 中模仿源碼自定義ArrayList的相關(guān)資料,需要的朋友可以參考下
    2017-03-03
  • java如何讓帶T的時間格式化

    java如何讓帶T的時間格式化

    這篇文章主要介紹了java如何讓帶T的時間格式化問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-01-01
  • Spring整合Redis完整實例代碼

    Spring整合Redis完整實例代碼

    這篇文章主要介紹了Spring整合Redis完整實例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-04-04

最新評論

资溪县| 清原| 蕉岭县| 湄潭县| 昂仁县| 古丈县| 石景山区| 蕲春县| 资兴市| 大同县| 石台县| 锦屏县| 江安县| 犍为县| 金湖县| 曲周县| 临夏县| 义马市| 志丹县| 共和县| 克拉玛依市| 巴彦淖尔市| 石家庄市| 柞水县| 永春县| 扶沟县| 神农架林区| 建昌县| 永安市| 西平县| 漳平市| 济南市| 福贡县| 当雄县| 遂平县| 新绛县| 定兴县| 玉门市| 甘洛县| 上栗县| 霍城县|