亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

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

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

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

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

加載jar的技術(shù)棧

  • springboot 2.2.6.RELEASE
  • mybatis-plus 3.4.1

實現(xiàn)加載

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

加載分為兩大步

第一步需要將對應(yīng)的jar中的class文件加載進當前運行內(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中類的加載機制,大體上spring在掃描到@Component注解的類時,會根據(jù)其class生成對應(yīng)的BeanDefinition,然后在將其注冊在BeanDefinitionRegistry(這是個接口,最終由DefaultListableBeanFactory實現(xiàn))。當其備引用注入實例時即getBean時被實例化并被注冊到DefaultSingletonBeanRegistry中。后續(xù)單例都將由DefaultSingletonBeanRegistry所管理。

controller加載

controller的加載機制

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è)置當前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)類實例化再加載進去就可以了,不需要什么特殊的處理,所以這里就不貼代碼了,加載同controller的第一步

mapper加載

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

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

注冊別名

mybatis對于別名的管理是存在SqlSessionFactory的Configuration(這個對象很重要,mybatis加載的資源之類的都在這個對象中管理)對象的TypeAliasRegistry中。TypeAliasRegistry是使用HashMap來維護別名的,這里我們直接調(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中維護的,我們只要通過反射取得這些對象然后修改就可以了,代碼如下

/**
     * 解析加載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進去就好,如果是單例需要被spring管理的則registerBeanDefinition就可以了

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

相關(guān)文章

  • 自己手寫Mybatis通用batchInsert問題

    自己手寫Mybatis通用batchInsert問題

    這篇文章主要介紹了自己手寫Mybatis通用batchInsert問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-11-11
  • maven項目在實踐中的構(gòu)建管理之路的方法

    maven項目在實踐中的構(gòu)建管理之路的方法

    這篇文章主要介紹了maven項目在實踐中的構(gòu)建管理之路的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2019-05-05
  • Java中Swagger框架的使用詳解

    Java中Swagger框架的使用詳解

    這篇文章主要介紹了Java框架Swagger的使用詳解,在開發(fā)期間接口會因業(yè)務(wù)的變更頻繁而變動,如果需要實時更新接口文檔,這是一個費時費力的工作,Swagger應(yīng)運而生,他可以輕松的整合進框架并通過一系列注解生成強大的API文檔,需要的朋友可以參考下
    2023-08-08
  • Java正則表達式的語法及示例解析

    Java正則表達式的語法及示例解析

    這篇文章主要介紹了Java正則表達式的語法及示例解析,需要的朋友可以參考下
    2017-07-07
  • SpringCloud?Ribbon負載均衡流程分析

    SpringCloud?Ribbon負載均衡流程分析

    在Eureka注冊中心中我們在添加完@LoadBalanced注解,即可實現(xiàn)負載均衡功能,現(xiàn)在一起探索一下負載均衡的原理(Ribbon),感興趣的朋友一起看看吧
    2024-03-03
  • Java 發(fā)送http請求(get、post)的示例

    Java 發(fā)送http請求(get、post)的示例

    這篇文章主要介紹了Java 發(fā)送http請求的示例,幫助大家更好的理解和使用Java,感興趣的朋友可以了解下
    2020-10-10
  • Java?CountDownLatch線程同步源碼硬核解析

    Java?CountDownLatch線程同步源碼硬核解析

    對于并發(fā)執(zhí)行,Java中的CountDownLatch是一個重要的類。為了更好的理解CountDownLatch這個類,本文將通過例子和源碼帶領(lǐng)大家深入解析這個類的原理,感興趣的可以學(xué)習(xí)一下
    2023-01-01
  • IntelliJ?IDEA?2023.2最新版激活方法及驗證ja-netfilter配置是否成功

    IntelliJ?IDEA?2023.2最新版激活方法及驗證ja-netfilter配置是否成功

    隨著2023.2版本的發(fā)布,用戶們渴望了解如何激活這個最新版的IDE,本文將介紹三種可行的激活方案,包括許可證服務(wù)器、許可證代碼和idea?vmoptions配置,幫助讀者成功激活并充分利用IDEA的功能,感興趣的朋友參考下吧
    2023-08-08
  • Java 使用getClass().getResourceAsStream()方法獲取資源

    Java 使用getClass().getResourceAsStream()方法獲取資源

    這篇文章主要介紹了Java 使用getClass().getResourceAsStream()方法獲取資源的相關(guān)資料,這里主要講解哪種方式可以獲取到文件資源,需要的朋友可以參考下
    2017-07-07
  • 使用Scala生成隨機數(shù)的方法示例

    使用Scala生成隨機數(shù)的方法示例

    這篇文章主要介紹了使用Scala生成隨機數(shù)的方法示例,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-06-06

最新評論