欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

springboot運(yùn)行時(shí)新增/更新外部接口的實(shí)現(xiàn)方法

 更新時(shí)間:2021年03月08日 09:46:33   作者:碼小D  
這篇文章主要介紹了springboot運(yùn)行時(shí)新增/更新外部接口的實(shí)現(xiàn)方法,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下

  最近有個需求:需要讓現(xiàn)有springboot項(xiàng)目可以加載外部的jar包實(shí)現(xiàn)新增、更新接口邏輯。本著拿來主義的思維網(wǎng)上找了半天沒有找到類似的東西,唯一有點(diǎn)相似的還是spring-loaded但是這個東西據(jù)我網(wǎng)上了解有如下缺點(diǎn):

  1、使用java agent啟動,個人傾向于直接使用pom依賴的方式

  2、不支持新增字段,新增方法,估計(jì)也不支持mybatis的xml加載那些吧,沒了解過

  3、只適合在開發(fā)環(huán)境IDE中使用,沒法生產(chǎn)使用

  無奈之下,我只能自己實(shí)現(xiàn)一個了,我需要實(shí)現(xiàn)的功能如下

  1、加載外部擴(kuò)展jar包中的新接口,多次加載需要能完全更新

  2、應(yīng)該能加載mybatis、mybatis-plus中放sql的xml文件

  3、應(yīng)該能加載@Mapper修飾的mybatis的接口資源

  4、需要能加載其它被spring管理的Bean資源

  5、需要能在加載完成后更新swagger文檔

  總而言之就是要實(shí)現(xiàn)一個能夠擴(kuò)展完整接口的容器,其實(shí)類似于熱加載也不同于熱加載,熱部署是監(jiān)控本地的class文件的改變,然后使用自動重啟或者重載,熱部署領(lǐng)域比較火的就是devtools和jrebel,前者使用自動重啟的方式,監(jiān)控你的classes改變了,然后使用反射調(diào)用你的main方法重啟一下,后者使用重載的方式,因?yàn)槭召M(fèi),具體原理也沒了解過,估計(jì)就是不重啟,只加載變過的class吧。而本文實(shí)現(xiàn)的是加載外部的jar包,這個jar包只要是個可訪問的URL資源就可以了。雖然和熱部署不一樣,但是從方案上可以借鑒,本文就是使用重載的方式,也就是只會更新擴(kuò)展包里的資源。

  先來一個自定義的模塊類加載器

package com.rdpaas.dynamic.core;


import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.AccessController;
import java.security.PrivilegedExceptionAction;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;


/**
 * 動態(tài)加載外部jar包的自定義類加載器
 * @author rongdi
 * @date 2021-03-06
 * @blog https://www.cnblogs.com/rongdi
 */
public class ModuleClassLoader extends URLClassLoader {

  private Logger logger = LoggerFactory.getLogger(ModuleClassLoader.class);

  private final static String CLASS_SUFFIX = ".class";

  private final static String XML_SUFFIX = ".xml";

  private final static String MAPPER_SUFFIX = "mapper/";

  //屬于本類加載器加載的jar包
  private JarFile jarFile;

  private Map<String, byte[]> classBytesMap = new HashMap<>();

  private Map<String, Class<?>> classesMap = new HashMap<>();

  private Map<String, byte[]> xmlBytesMap = new HashMap<>();

  public ModuleClassLoader(ClassLoader classLoader, URL... urls) {
    super(urls, classLoader);
    URL url = urls[0];
    String path = url.getPath();
    try {
      jarFile = new JarFile(path);
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  @Override
  protected Class<?> findClass(String name) throws ClassNotFoundException {
    byte[] buf = classBytesMap.get(name);
    if (buf == null) {
      return super.findClass(name);
    }
    if(classesMap.containsKey(name)) {
      return classesMap.get(name);
    }
    /**
     * 這里應(yīng)該算是騷操作了,我不知道市面上有沒有人這么做過,反正我是想了好久,遇到各種因?yàn)閟pring要生成代理對象
     * 在他自己的AppClassLoader找不到原對象導(dǎo)致的報(bào)錯,注意如果你限制你的擴(kuò)展包你不會有AOP觸碰到的類或者@Transactional這種
     * 會產(chǎn)生代理的類,那么其實(shí)你不用這么騷,直接在這里調(diào)用defineClass把字節(jié)碼裝載進(jìn)去就行了,不會有什么問題,最多也就是
     * 在加載mybatis的xml那里前后加三句話,
     * 1、獲取并使用一個變量保存當(dāng)前線程類加載器
     * 2、將自定義類加載器設(shè)置到當(dāng)前線程類加載器
     * 3、還原當(dāng)前線程類加載器為第一步保存的類加載器
     * 這樣之后mybatis那些xml里resultType,resultMap之類的需要訪問擴(kuò)展包的Class的就不會報(bào)錯了。
     * 不過直接用現(xiàn)在這種騷操作,更加一勞永逸,不會有mybatis的問題了
     */
    return loadClass(name,buf);
  }

  /**
   * 使用反射強(qiáng)行將類裝載的歸屬給當(dāng)前類加載器的父類加載器也就是AppClassLoader,如果報(bào)ClassNotFoundException
   * 則遞歸裝載
   * @param name
   * @param bytes
   * @return
   */
  private Class<?> loadClass(String name, byte[] bytes) throws ClassNotFoundException {

    Object[] args = new Object[]{name, bytes, 0, bytes.length};
    try {
      /**
       * 拿到當(dāng)前類加載器的parent加載器AppClassLoader
       */
      ClassLoader parent = this.getParent();
      /**
       * 首先要明確反射是萬能的,仿造org.springframework.cglib.core.ReflectUtils的寫法,強(qiáng)行獲取被保護(hù)
       * 的方法defineClass的對象,然后調(diào)用指定類加載器的加載字節(jié)碼方法,強(qiáng)行將加載歸屬塞給它,避免被spring的AOP或者@Transactional
       * 觸碰到的類需要生成代理對象,而在AppClassLoader下加載不到外部的擴(kuò)展類而報(bào)錯,所以這里強(qiáng)行將加載外部擴(kuò)展包的類的歸屬給
       * AppClassLoader,讓spring的cglib生成代理對象時(shí)可以加載到原對象
       */
      Method classLoaderDefineClass = (Method) AccessController.doPrivileged(new PrivilegedExceptionAction() {
        @Override
        public Object run() throws Exception {
          return ClassLoader.class.getDeclaredMethod("defineClass",
              String.class, byte[].class, Integer.TYPE, Integer.TYPE);
        }
      });
      if(!classLoaderDefineClass.isAccessible()) {
        classLoaderDefineClass.setAccessible(true);
      }
      return (Class<?>)classLoaderDefineClass.invoke(parent,args);
    } catch (Exception e) {
      if(e instanceof InvocationTargetException) {
        String message = ((InvocationTargetException) e).getTargetException().getCause().toString();
        /**
         * 無奈,明明ClassNotFoundException是個異常,非要拋個InvocationTargetException,導(dǎo)致
         * 我這里一個不太優(yōu)雅的判斷
         */
        if(message.startsWith("java.lang.ClassNotFoundException")) {
          String notClassName = message.split(":")[1];
          if(StringUtils.isEmpty(notClassName)) {
            throw new ClassNotFoundException(message);
          }
          notClassName = notClassName.trim();
          byte[] bytes1 = classBytesMap.get(notClassName);
          if(bytes1 == null) {
            throw new ClassNotFoundException(message);
          }
          /**
           * 遞歸裝載未找到的類
           */
          Class<?> notClass = loadClass(notClassName, bytes1);
          if(notClass == null) {
            throw new ClassNotFoundException(message);
          }
          classesMap.put(notClassName,notClass);
          return loadClass(name,bytes);
        }
      } else {
        logger.error("",e);
      }
    }
    return null;
  }

  public Map<String,byte[]> getXmlBytesMap() {
    return xmlBytesMap;
  }


  /**
   * 方法描述 初始化類加載器,保存字節(jié)碼
   */
  public Map<String, Class> load() {

    Map<String, Class> cacheClassMap = new HashMap<>();

    //解析jar包每一項(xiàng)
    Enumeration<JarEntry> en = jarFile.entries();
    InputStream input = null;
    try {
      while (en.hasMoreElements()) {
        JarEntry je = en.nextElement();
        String name = je.getName();
        //這里添加了路徑掃描限制
        if (name.endsWith(CLASS_SUFFIX)) {
          String className = name.replace(CLASS_SUFFIX, "").replaceAll("/", ".");
          input = jarFile.getInputStream(je);
          ByteArrayOutputStream baos = new ByteArrayOutputStream();
          int bufferSize = 4096;
          byte[] buffer = new byte[bufferSize];
          int bytesNumRead = 0;
          while ((bytesNumRead = input.read(buffer)) != -1) {
            baos.write(buffer, 0, bytesNumRead);
          }
          byte[] classBytes = baos.toByteArray();
          classBytesMap.put(className, classBytes);
        } else if(name.endsWith(XML_SUFFIX) && name.startsWith(MAPPER_SUFFIX)) {
          input = jarFile.getInputStream(je);
          ByteArrayOutputStream baos = new ByteArrayOutputStream();
          int bufferSize = 4096;
          byte[] buffer = new byte[bufferSize];
          int bytesNumRead = 0;
          while ((bytesNumRead = input.read(buffer)) != -1) {
            baos.write(buffer, 0, bytesNumRead);
          }
          byte[] xmlBytes = baos.toByteArray();
          xmlBytesMap.put(name, xmlBytes);
        }
      }
    } catch (IOException e) {
      logger.error("",e);
    } finally {
      if (input != null) {
        try {
          input.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }

    //將jar中的每一個class字節(jié)碼進(jìn)行Class載入
    for (Map.Entry<String, byte[]> entry : classBytesMap.entrySet()) {
      String key = entry.getKey();
      Class<?> aClass = null;
      try {
        aClass = loadClass(key);
      } catch (ClassNotFoundException e) {
        logger.error("",e);
      }
      cacheClassMap.put(key, aClass);
    }
    return cacheClassMap;

  }

  public Map<String, byte[]> getClassBytesMap() {
    return classBytesMap;
  }
}

 然后再來個加載mybatis的xml資源的類,本類解析xml部分是參考網(wǎng)上資料

package com.rdpaas.dynamic.core;

import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.builder.xml.XMLMapperEntityResolver;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.executor.keygen.SelectKeyGenerator;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.parsing.XNode;
import org.apache.ibatis.parsing.XPathParser;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.mapper.MapperFactoryBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.lang.reflect.Field;
import java.util.*;

/**
 * mybatis的mapper.xml和@Mapper加載類
 * @author rongdi
 * @date 2021-03-06
 * @blog https://www.cnblogs.com/rongdi
 */
public class MapperLoader {

  private Logger logger = LoggerFactory.getLogger(MapperLoader.class);

  private Configuration configuration;

  /**
   * 刷新外部mapper,包括文件和@Mapper修飾的接口
   * @param sqlSessionFactory
   * @param xmlBytesMap
   * @return
   */
  public Map<String,Object> refresh(SqlSessionFactory sqlSessionFactory, Map<String, byte[]> xmlBytesMap) {
    Configuration configuration = sqlSessionFactory.getConfiguration();
    this.configuration = configuration;

    /**
     * 這里用來區(qū)分mybatis-plus和mybatis,mybatis-plus的Configuration是繼承自mybatis的子類
     */
    boolean isSupper = configuration.getClass().getSuperclass() == Configuration.class;
    Map<String,Object> mapperMap = new HashMap<>();
    try {
      /**
       * 遍歷外部傳入的xml字節(jié)碼map
       */
      for(Map.Entry<String,byte[]> entry:xmlBytesMap.entrySet()) {
        String resource = entry.getKey();
        byte[] bytes = entry.getValue();
        /**
         * 使用反射強(qiáng)行拿出configuration中的loadedResources屬性
         */
        Field loadedResourcesField = isSupper
            ? configuration.getClass().getSuperclass().getDeclaredField("loadedResources")
            : configuration.getClass().getDeclaredField("loadedResources");
        loadedResourcesField.setAccessible(true);
        Set loadedResourcesSet = ((Set) loadedResourcesField.get(configuration));
        /**
         * 加載mybatis中的xml
         */
        XPathParser xPathParser = new XPathParser(new ByteArrayInputStream(bytes), true, configuration.getVariables(),
            new XMLMapperEntityResolver());
        /**
         * 解析mybatis的xml的根節(jié)點(diǎn),
         */
        XNode context = xPathParser.evalNode("/mapper");
        /**
         * 拿到namespace,namespace就是指Mapper接口的全限定名
         */
        String namespace = context.getStringAttribute("namespace");
        Field field = configuration.getMapperRegistry().getClass().getDeclaredField("knownMappers");
        field.setAccessible(true);

        /**
         * 拿到存放Mapper接口和對應(yīng)代理子類的映射map,
         */
        Map mapConfig = (Map) field.get(configuration.getMapperRegistry());
        /**
         * 拿到Mapper接口對應(yīng)的class對象
         */
        Class nsClass = Resources.classForName(namespace);

        /**
         * 先刪除各種
         */
        mapConfig.remove(nsClass);
        loadedResourcesSet.remove(resource);
        configuration.getCacheNames().remove(namespace);

        /**
         * 清掉namespace下各種緩存
         */
        cleanParameterMap(context.evalNodes("/mapper/parameterMap"), namespace);
        cleanResultMap(context.evalNodes("/mapper/resultMap"), namespace);
        cleanKeyGenerators(context.evalNodes("insert|update|select|delete"), namespace);
        cleanSqlElement(context.evalNodes("/mapper/sql"), namespace);

        /**
         * 加載并解析對應(yīng)xml
         */
        XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(new ByteArrayInputStream(bytes),
            sqlSessionFactory.getConfiguration(), resource,
            sqlSessionFactory.getConfiguration().getSqlFragments());
        xmlMapperBuilder.parse();

        /**
         * 構(gòu)造MapperFactoryBean,注意這里一定要傳入sqlSessionFactory,
         * 這塊邏輯通過debug源碼試驗(yàn)了很久
         */
        MapperFactoryBean mapperFactoryBean = new MapperFactoryBean(nsClass);
        mapperFactoryBean.setSqlSessionFactory(sqlSessionFactory);
        /**
         * 放入map,返回出去給ModuleApplication去加載
         */
        mapperMap.put(namespace,mapperFactoryBean);
        logger.info("refresh: '" + resource + "', success!");

      }
      return mapperMap;
    } catch (Exception e) {
      logger.error("refresh error",e.getMessage());
    } finally {
      ErrorContext.instance().reset();
    }
    return null;
  }

  /**
   * 清理parameterMap
   *
   * @param list
   * @param namespace
   */
  private void cleanParameterMap(List<XNode> list, String namespace) {
    for (XNode parameterMapNode : list) {
      String id = parameterMapNode.getStringAttribute("id");
      configuration.getParameterMaps().remove(namespace + "." + id);
    }
  }

  /**
   * 清理resultMap
   *
   * @param list
   * @param namespace
   */
  private void cleanResultMap(List<XNode> list, String namespace) {
    for (XNode resultMapNode : list) {
      String id = resultMapNode.getStringAttribute("id", resultMapNode.getValueBasedIdentifier());
      configuration.getResultMapNames().remove(id);
      configuration.getResultMapNames().remove(namespace + "." + id);
      clearResultMap(resultMapNode, namespace);
    }
  }

  private void clearResultMap(XNode xNode, String namespace) {
    for (XNode resultChild : xNode.getChildren()) {
      if ("association".equals(resultChild.getName()) || "collection".equals(resultChild.getName())
          || "case".equals(resultChild.getName())) {
        if (resultChild.getStringAttribute("select") == null) {
          configuration.getResultMapNames()
              .remove(resultChild.getStringAttribute("id", resultChild.getValueBasedIdentifier()));
          configuration.getResultMapNames().remove(namespace + "."
              + resultChild.getStringAttribute("id", resultChild.getValueBasedIdentifier()));
          if (resultChild.getChildren() != null && !resultChild.getChildren().isEmpty()) {
            clearResultMap(resultChild, namespace);
          }
        }
      }
    }
  }

  /**
   * 清理selectKey
   *
   * @param list
   * @param namespace
   */
  private void cleanKeyGenerators(List<XNode> list, String namespace) {
    for (XNode context : list) {
      String id = context.getStringAttribute("id");
      configuration.getKeyGeneratorNames().remove(id + SelectKeyGenerator.SELECT_KEY_SUFFIX);
      configuration.getKeyGeneratorNames().remove(namespace + "." + id + SelectKeyGenerator.SELECT_KEY_SUFFIX);

      Collection<MappedStatement> mappedStatements = configuration.getMappedStatements();
      List<MappedStatement> objects = new ArrayList<>();
      Iterator<MappedStatement> it = mappedStatements.iterator();
      while (it.hasNext()) {
        Object object = it.next();
        if (object instanceof MappedStatement) {
          MappedStatement mappedStatement = (MappedStatement) object;
          if (mappedStatement.getId().equals(namespace + "." + id)) {
            objects.add(mappedStatement);
          }
        }
      }
      mappedStatements.removeAll(objects);
    }
  }

  /**
   * 清理sql節(jié)點(diǎn)緩存
   *
   * @param list
   * @param namespace
   */
  private void cleanSqlElement(List<XNode> list, String namespace) {
    for (XNode context : list) {
      String id = context.getStringAttribute("id");
      configuration.getSqlFragments().remove(id);
      configuration.getSqlFragments().remove(namespace + "." + id);
    }
  }

}

  上面需要注意的是,處理好xml還需要將XXMapper接口也放入spring容器中,但是接口是沒辦法直接轉(zhuǎn)成spring的BeanDefinition的,因?yàn)榻涌跊]辦法實(shí)例化,而BeanDefinition作為對象的模板,肯定不允許接口直接放進(jìn)去,通過看mybatis-spring源碼,可以看出這些接口都會被封裝成MapperFactoryBean放入spring容器中實(shí)例化時(shí)就調(diào)用getObject方法生成Mapper的代理對象。下面就是將各種資源裝載spring容器的代碼了

package com.rdpaas.dynamic.core;

import com.rdpaas.dynamic.utils.ReflectUtil;
import com.rdpaas.dynamic.utils.SpringUtil;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.plugin.core.PluginRegistry;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.builders.ResponseMessageBuilder;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.service.ResponseMessage;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.DocumentationPlugin;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.spring.web.plugins.DocumentationPluginsBootstrapper;
import springfox.documentation.spring.web.plugins.DocumentationPluginsManager;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;

/**
 * 基于spring的應(yīng)用上下文提供一些工具方法
 * @author rongdi
 * @date 2021-03-06
 * @blog https://www.cnblogs.com/rongdi
 */
public class ModuleApplication {

  private final static String SINGLETON = "singleton";

  private final static String DYNAMIC_DOC_PACKAGE = "dynamic.swagger.doc.package";

  private Set<RequestMappingInfo> extMappingInfos = new HashSet<>();

  private ApplicationContext applicationContext;

  /**
   * 使用spring上下文拿到指定beanName的對象
   */
  public <T> T getBean(String beanName) {
    return (T) ((ConfigurableApplicationContext) applicationContext).getBeanFactory().getBean(beanName);
  }

  /**
   * 使用spring上下文拿到指定類型的對象
   */
  public <T> T getBean(Class<T> clazz) {
    return (T) ((ConfigurableApplicationContext) applicationContext).getBeanFactory().getBean(clazz);
  }

  /**
   * 加載一個外部擴(kuò)展jar,包括springmvc接口資源,mybatis的@mapper和mapper.xml和spring bean等資源
   * @param url jar url
   * @param applicationContext spring context
   * @param sqlSessionFactory mybatis的session工廠
   */
  public void reloadJar(URL url, ApplicationContext applicationContext,SqlSessionFactory sqlSessionFactory) throws Exception {
    this.applicationContext = applicationContext;
    URL[] urls = new URL[]{url};
    /**
     * 這里實(shí)際上是將spring的ApplicationContext的類加載器當(dāng)成parent傳給了自定義類加載器,很明自定義的子類加載器自己加載
     * 的類,parent類加載器直接是獲取不到的,所以在自定義類加載器做了特殊的騷操作
     */
    ModuleClassLoader moduleClassLoader = new ModuleClassLoader(applicationContext.getClassLoader(), urls);
    /**
     * 使用模塊類加載器加載url資源的jar包,直接返回類的全限定名和Class對象的映射,這些Class對象是
     * jar包里所有.class結(jié)尾的文件加載后的結(jié)果,同時(shí)mybatis的xml加載后,無奈的放入了
     * moduleClassLoader.getXmlBytesMap(),不是很優(yōu)雅
     */
    Map<String, Class> classMap = moduleClassLoader.load();

    MapperLoader mapperLoader = new MapperLoader();

    /**
     * 刷新mybatis的xml和Mapper接口資源,Mapper接口其實(shí)就是xml的namespace
     */
    Map<String, Object> extObjMap = mapperLoader.refresh(sqlSessionFactory, moduleClassLoader.getXmlBytesMap());
    /**
     * 將各種資源放入spring容器
     */
    registerBeans(applicationContext, classMap, extObjMap);
  }

  /**
   * 裝載bean到spring中
   *
   * @param applicationContext
   * @param cacheClassMap
   */
  public void registerBeans(ApplicationContext applicationContext, Map<String, Class> cacheClassMap,Map<String,Object> extObjMap) throws Exception {
    /**
     * 將applicationContext轉(zhuǎn)換為ConfigurableApplicationContext
     */
    ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) applicationContext;
    /**
     * 獲取bean工廠并轉(zhuǎn)換為DefaultListableBeanFactory
     */
    DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) configurableApplicationContext.getBeanFactory();

    /**
     * 有一些對象想給spring管理,則放入spring中,如mybatis的@Mapper修飾的接口的代理類
     */
    if(extObjMap != null && !extObjMap.isEmpty()) {
      extObjMap.forEach((beanName,obj) ->{
        /**
         * 如果已經(jīng)存在,則銷毀之后再注冊
         */
        if(defaultListableBeanFactory.containsSingleton(beanName)) {
          defaultListableBeanFactory.destroySingleton(beanName);
        }
        defaultListableBeanFactory.registerSingleton(beanName,obj);
      });
    }

    for (Map.Entry<String, Class> entry : cacheClassMap.entrySet()) {
      String className = entry.getKey();
      Class<?> clazz = entry.getValue();
      if (SpringUtil.isSpringBeanClass(clazz)) {
        //將變量首字母置小寫
        String beanName = StringUtils.uncapitalize(className);
        beanName = beanName.substring(beanName.lastIndexOf(".") + 1);
        beanName = StringUtils.uncapitalize(beanName);

        /**
         * 已經(jīng)在spring容器就刪了
         */
        if (defaultListableBeanFactory.containsBeanDefinition(beanName)) {
          defaultListableBeanFactory.removeBeanDefinition(beanName);
        }
        /**
         * 使用spring的BeanDefinitionBuilder將Class對象轉(zhuǎn)成BeanDefinition
         */
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
        BeanDefinition beanDefinition = beanDefinitionBuilder.getRawBeanDefinition();
        //設(shè)置當(dāng)前bean定義對象是單利的
        beanDefinition.setScope(SINGLETON);
        /**
         * 以指定beanName注冊上面生成的BeanDefinition
         */
        defaultListableBeanFactory.registerBeanDefinition(beanName, beanDefinition);
      }

    }

    /**
     * 刷新springmvc,讓新增的接口生效
     */
    refreshMVC((ConfigurableApplicationContext) applicationContext);

  }

  /**
   * 刷新springMVC,這里花了大量時(shí)間調(diào)試,找不到開放的方法,只能取個巧,在更新RequestMappingHandlerMapping前先記錄之前
   * 所有RequestMappingInfo,記得這里一定要copy一下,然后刷新后再記錄一次,計(jì)算出差量存放在成員變量Set中,然后每次開頭判斷
   * 差量那里是否有內(nèi)容,有就先unregiester掉
   */
  private void refreshMVC(ConfigurableApplicationContext applicationContext) throws Exception {


    Map<String, RequestMappingHandlerMapping> map = applicationContext.getBeanFactory().getBeansOfType(RequestMappingHandlerMapping.class);
    /**
     * 先拿到RequestMappingHandlerMapping對象
     */
    RequestMappingHandlerMapping mappingHandlerMapping = map.get("requestMappingHandlerMapping");

    /**
     * 重新注冊mapping前先判斷是否存在了,存在了就先unregister掉
     */
    if(!extMappingInfos.isEmpty()) {
      for(RequestMappingInfo requestMappingInfo:extMappingInfos) {
        mappingHandlerMapping.unregisterMapping(requestMappingInfo);
      }
    }

    /**
     * 獲取刷新前的RequestMappingInfo
     */
    Map<RequestMappingInfo, HandlerMethod> preMappingInfoHandlerMethodMap = mappingHandlerMapping.getHandlerMethods();
    /**
     * 這里注意一定要拿到拷貝,不然刷新后內(nèi)容就一致了,就沒有差量了
     */
    Set<RequestMappingInfo> preRequestMappingInfoSet = new HashSet(preMappingInfoHandlerMethodMap.keySet());

    /**
     * 這里是刷新springmvc上下文
     */
    applicationContext.getBeanFactory().getBeansOfType(RequestMappingHandlerMapping.class)
    .forEach((key,value) ->{
      value.afterPropertiesSet();
    });

    /**
     * 獲取刷新后的RequestMappingInfo
     */
    Map<RequestMappingInfo, HandlerMethod> afterMappingInfoHandlerMethodMap = mappingHandlerMapping.getHandlerMethods();
    Set<RequestMappingInfo> afterRequestMappingInfoSet = afterMappingInfoHandlerMethodMap.keySet();

    /**
     * 填充差量部分RequestMappingInfo
     */
    fillSurplusRequestMappingInfos(preRequestMappingInfoSet,afterRequestMappingInfoSet);

    /**
     * 這里真的是不講武德了,每次調(diào)用value.afterPropertiesSet();如下urlLookup都會產(chǎn)生重復(fù),暫時(shí)沒找到開放方法去掉重復(fù),這里重復(fù)會導(dǎo)致
     * 訪問的時(shí)候報(bào)錯Ambiguous handler methods mapped for
     * 目標(biāo)是去掉RequestMappingHandlerMapping -> RequestMappingInfoHandlerMapping -> AbstractHandlerMethodMapping
     * -> mappingRegistry -> urlLookup重復(fù)的RequestMappingInfo,這里的.getClass().getSuperclass().getSuperclass()相信會
     * 很懵逼,如果單獨(dú)通過getClass().getDeclaredMethod("getMappingRegistry",new Class[]{})是無論如何都拿不到父類的非public非
     * protected方法的,因?yàn)檫@個方法不屬于子類,只有父類才可以訪問到,只有你拿得到你才有資格不講武德的使用method.setAccessible(true)強(qiáng)行
     * 訪問
     */
    Method method = ReflectUtil.getMethod(mappingHandlerMapping,"getMappingRegistry",new Class[]{});
    method.setAccessible(true);
    Object mappingRegistryObj = method.invoke(mappingHandlerMapping,new Object[]{});
    Field field = mappingRegistryObj.getClass().getDeclaredField("urlLookup");
    field.setAccessible(true);
    MultiValueMap<String, RequestMappingInfo> multiValueMap = (MultiValueMap)field.get(mappingRegistryObj);
    multiValueMap.forEach((key,list) -> {
      clearMultyMapping(list);
    });

  }
 /**
   * 填充差量的RequestMappingInfo,因?yàn)橐呀?jīng)重寫過hashCode和equals方法所以可以直接用對象判斷是否存在
   * @param preRequestMappingInfoSet
   * @param afterRequestMappingInfoSet
   */
  private void fillSurplusRequestMappingInfos(Set<RequestMappingInfo> preRequestMappingInfoSet,Set<RequestMappingInfo> afterRequestMappingInfoSet) {
    for(RequestMappingInfo requestMappingInfo:afterRequestMappingInfoSet) {
      if(!preRequestMappingInfoSet.contains(requestMappingInfo)) {
        extMappingInfos.add(requestMappingInfo);
      }
    }
  }

  /**
   * 簡單的邏輯,刪除List里重復(fù)的RequestMappingInfo,已經(jīng)寫了toString,直接使用mappingInfo.toString()就可以區(qū)分重復(fù)了
   * @param mappingInfos
   */
  private void clearMultyMapping(List<RequestMappingInfo> mappingInfos) {
    Set<String> containsList = new HashSet<>();
    for(Iterator<RequestMappingInfo> iter = mappingInfos.iterator();iter.hasNext();) {
      RequestMappingInfo mappingInfo = iter.next();
      String flag = mappingInfo.toString();
      if(containsList.contains(flag)) {
        iter.remove();
      } else {
        containsList.add(flag);
      }
    }
  }

}

  上述有兩個地方很虐心,第一個就是刷新springmvc那里,提供的刷新springmvc上下文的方式不友好不說,刷新上下文后RequestMappingHandlerMapping -> RequestMappingInfoHandlerMapping -> AbstractHandlerMethodMapping -> mappingRegistry -> urlLookup屬性中會存在重復(fù)的路徑如下

  上述是我故意兩次加載同一個jar包后第二次走到刷新springmvc之后,可以看到擴(kuò)展包里的接口,由于unregister所以沒有發(fā)現(xiàn)重復(fù),那些重復(fù)的路徑都是本身服務(wù)的接口,由于沒有unregister所以出現(xiàn)了大把重復(fù),如果這個時(shí)候訪問重復(fù)的接口,會出現(xiàn)如下錯誤

java.lang.IllegalStateException: Ambiguous handler methods mapped for '/error':

  意思就是匹配到了多個相同的路徑解決方法有兩種,第一種就是所有RequestMappingInfo都先unregister再刷新,第二種就是我調(diào)試很久確認(rèn)就只有urlLookup會發(fā)生沖重復(fù),所以如下使用萬能的反射強(qiáng)行修改值,其實(shí)不要排斥使用反射,spring源碼中大量使用反射去強(qiáng)行調(diào)用方法,比如org.springframework.cglib.core.ReflectUtils類摘抄如下:

classLoaderDefineClass = (Method) AccessController.doPrivileged(new PrivilegedExceptionAction() {
  public Object run() throws Exception {
   return ClassLoader.class.getDeclaredMethod("defineClass",
      String.class, byte[].class, Integer.TYPE, Integer.TYPE, ProtectionDomain.class);
  }
});
classLoaderDefineClassMethod = classLoaderDefineClass;
// Classic option: protected ClassLoader.defineClass method
if (c == null && classLoaderDefineClassMethod != null) {
  if (protectionDomain == null) {
   protectionDomain = PROTECTION_DOMAIN;
  }
  Object[] args = new Object[]{className, b, 0, b.length, protectionDomain};
  try {
   if (!classLoaderDefineClassMethod.isAccessible()) {
     classLoaderDefineClassMethod.setAccessible(true);
   }
   c = (Class) classLoaderDefineClassMethod.invoke(loader, args);
  }
  catch (InvocationTargetException ex) {
   throw new CodeGenerationException(ex.getTargetException());
  }
  catch (Throwable ex) {
   // Fall through if setAccessible fails with InaccessibleObjectException on JDK 9+
   // (on the module path and/or with a JVM bootstrapped with --illegal-access=deny)
   if (!ex.getClass().getName().endsWith("InaccessibleObjectException")) {
     throw new CodeGenerationException(ex);
   }
  }
}

  如上可以看出來像spring這樣的名家也一樣也很不講武德,個人認(rèn)為反射本身就是用來給我們打破規(guī)則用的,只有打破規(guī)則才會有創(chuàng)新,所以大膽使用反射吧。只要不遇到final的屬性,反射是萬能的,哈哈!所以我使用反射強(qiáng)行刪除重復(fù)的代碼如下:

/**
     * 這里真的是不講武德了,每次調(diào)用value.afterPropertiesSet();如下urlLookup都會產(chǎn)生重復(fù),暫時(shí)沒找到開放方法去掉重復(fù),這里重復(fù)會導(dǎo)致
     * 訪問的時(shí)候報(bào)錯Ambiguous handler methods mapped for
     * 目標(biāo)是去掉RequestMappingHandlerMapping -> RequestMappingInfoHandlerMapping -> AbstractHandlerMethodMapping
     * -> mappingRegistry -> urlLookup重復(fù)的RequestMappingInfo,這里的.getClass().getSuperclass().getSuperclass()相信會
     * 很懵逼,如果單獨(dú)通過getClass().getDeclaredMethod("getMappingRegistry",new Class[]{})是無論如何都拿不到父類的非public非
     * protected方法的,因?yàn)檫@個方法不屬于子類,只有父類才可以訪問到,只有你拿得到你才有資格不講武德的使用method.setAccessible(true)強(qiáng)行
     * 訪問
     */
    Method method = ReflectUtil.getMethod(mappingHandlerMapping,"getMappingRegistry",new Class[]{});
    method.setAccessible(true);
    Object mappingRegistryObj = method.invoke(mappingHandlerMapping,new Object[]{});
    Field field = mappingRegistryObj.getClass().getDeclaredField("urlLookup");
    field.setAccessible(true);
    MultiValueMap<String, RequestMappingInfo> multiValueMap = (MultiValueMap)field.get(mappingRegistryObj);
    multiValueMap.forEach((key,list) -> {
      clearMultyMapping(list);
    });

   /**
   * 簡單的邏輯,刪除List里重復(fù)的RequestMappingInfo,已經(jīng)寫了toString,直接使用mappingInfo.toString()就可以區(qū)分重復(fù)了
   * @param mappingInfos
   */
  private void clearMultyMapping(List<RequestMappingInfo> mappingInfos) {
    Set<String> containsList = new HashSet<>();
    for(Iterator<RequestMappingInfo> iter = mappingInfos.iterator();iter.hasNext();) {
      RequestMappingInfo mappingInfo = iter.next();
      String flag = mappingInfo.toString();
      if(containsList.contains(flag)) {
        iter.remove();
      } else {
        containsList.add(flag);
      }
    }
  }

  還有個虐心的地方是刷新swagger文檔的地方,這個swagger只有需要做這個需求時(shí)才知道,他封裝的有多菜,根本沒有刷新相關(guān)的方法,也沒有可以控制的入口,真的是沒辦法。下面貼出我解決刷新swagger文檔的調(diào)試過程,使用過swagger2的朋友們都知道,要想在springboot集成swagger2主要需要編寫的配置代碼如下

@Configuration
@EnableSwagger2
public class SwaggerConfig {

  //swagger2的配置文件,這里可以配置swagger2的一些基本的內(nèi)容,比如掃描的包等等
  @Bean
  public Docket createRestApi() {
    List<ResponseMessage> responseMessageList = new ArrayList<>();
    responseMessageList.add(new ResponseMessageBuilder().code(200).message("成功").responseModel(new ModelRef("Payload")).build());
    Docket docket = new Docket(DocumentationType.SWAGGER_2)
        .globalResponseMessage(RequestMethod.GET,responseMessageList)
        .globalResponseMessage(RequestMethod.DELETE,responseMessageList)
        .globalResponseMessage(RequestMethod.POST,responseMessageList)
        .apiInfo(apiInfo()).select()
        //為當(dāng)前包路徑
        .apis(RequestHandlerSelectors.basePackage("com.xxx")).paths(PathSelectors.any()).build();
    return docket;
  }

  //構(gòu)建 api文檔的詳細(xì)信息函數(shù),注意這里的注解引用的是哪個
  private ApiInfo apiInfo() {
    return new ApiInfoBuilder()
        //頁面標(biāo)題
        .title("使用 Swagger2 構(gòu)建RESTful API")
        //創(chuàng)建人
        .contact(new Contact("rongdi", "https://www.cnblogs.com/rongdi", "495194630@qq.com"))
        //版本號
        .version("1.0")
        //描述
        .description("api管理").build();
  }

}

而訪問swagger的文檔請求的是如下接口/v2/api-docs

  通過調(diào)試可以找到swagger2就是通過實(shí)現(xiàn)了SmartLifecycle接口的DocumentationPluginsBootstrapper類,當(dāng)spring容器加載所有bean并完成初始化之后,會回調(diào)實(shí)現(xiàn)該接口的類(DocumentationPluginsBootstrapper)中對應(yīng)的方法start()方法,下面會介紹怎么找到這里的。

接著循環(huán)DocumentationPlugin集合去處理文檔

接著放入DocumentationCache中

然后再回到swagger接口的類那里,實(shí)際上就是從這個DocumentationCache里獲取到Documention

‘如果找不到解決問題的入口,我們至少可以找到訪問文檔的上面這個接口地址(出口),發(fā)現(xiàn)接口返回的文檔json內(nèi)容是從DocumentationCache里獲取,那么我們很明顯可以想到肯定有地方存放數(shù)據(jù)到這個DocumentationCache里,然后其實(shí)我們可以直接在addDocumentation方法里打個斷點(diǎn),然后看調(diào)試左側(cè)的運(yùn)行方法棧信息,就可以很明確的看到調(diào)用鏈路了

再回看我們接入swagger2的時(shí)候?qū)懙呐渲么a

//swagger2的配置文件,這里可以配置swagger2的一些基本的內(nèi)容,比如掃描的包等等
  @Bean
  public Docket createRestApi() {
    List<ResponseMessage> responseMessageList = new ArrayList<>();
    responseMessageList.add(new ResponseMessageBuilder().code(200).message("成功").responseModel(new ModelRef("Payload")).build());
    Docket docket = new Docket(DocumentationType.SWAGGER_2)
        .globalResponseMessage(RequestMethod.GET,responseMessageList)
        .globalResponseMessage(RequestMethod.DELETE,responseMessageList)
        .globalResponseMessage(RequestMethod.POST,responseMessageList)
        .apiInfo(apiInfo()).select()
        //為當(dāng)前包路徑
        .apis(RequestHandlerSelectors.basePackage("com.xxx")).paths(PathSelectors.any()).build();
    return docket;
  }

然后再看看下圖,應(yīng)該終于知道咋回事了吧,其實(shí)Docket對象我們僅僅需要關(guān)心的是basePackage,我們擴(kuò)展jar包大概率接口所在的包和現(xiàn)有包不一樣,所以我們需要新增一個Docket插件,并加入DocumentationPlugin集合,然后調(diào)用DocumentationPluginsBootstrapper的stop()方法清掉緩存,再調(diào)用start()再次開始解析

具體實(shí)現(xiàn)代碼如下

/**
   * 刷新springMVC,這里花了大量時(shí)間調(diào)試,找不到開放的方法,只能取個巧,在更新RequestMappingHandlerMapping前先記錄之前
   * 所有RequestMappingInfo,記得這里一定要copy一下,然后刷新后再記錄一次,計(jì)算出差量存放在成員變量Set中,然后每次開頭判斷
   * 差量那里是否有內(nèi)容,有就先unregiester掉
   */
  private void refreshMVC(ConfigurableApplicationContext applicationContext) throws Exception {


    Map<String, RequestMappingHandlerMapping> map = applicationContext.getBeanFactory().getBeansOfType(RequestMappingHandlerMapping.class);
    /**
     * 先拿到RequestMappingHandlerMapping對象
     */
    RequestMappingHandlerMapping mappingHandlerMapping = map.get("requestMappingHandlerMapping");

    /**
     * 重新注冊mapping前先判斷是否存在了,存在了就先unregister掉
     */
    if(!extMappingInfos.isEmpty()) {
      for(RequestMappingInfo requestMappingInfo:extMappingInfos) {
        mappingHandlerMapping.unregisterMapping(requestMappingInfo);
      }
    }

    /**
     * 獲取刷新前的RequestMappingInfo
     */
    Map<RequestMappingInfo, HandlerMethod> preMappingInfoHandlerMethodMap = mappingHandlerMapping.getHandlerMethods();
    /**
     * 這里注意一定要拿到拷貝,不然刷新后內(nèi)容就一致了,就沒有差量了
     */
    Set<RequestMappingInfo> preRequestMappingInfoSet = new HashSet(preMappingInfoHandlerMethodMap.keySet());

    /**
     * 這里是刷新springmvc上下文
     */
    applicationContext.getBeanFactory().getBeansOfType(RequestMappingHandlerMapping.class)
    .forEach((key,value) ->{
      value.afterPropertiesSet();
    });

    /**
     * 獲取刷新后的RequestMappingInfo
     */
    Map<RequestMappingInfo, HandlerMethod> afterMappingInfoHandlerMethodMap = mappingHandlerMapping.getHandlerMethods();
    Set<RequestMappingInfo> afterRequestMappingInfoSet = afterMappingInfoHandlerMethodMap.keySet();

    /**
     * 填充差量部分RequestMappingInfo
     */
    fillSurplusRequestMappingInfos(preRequestMappingInfoSet,afterRequestMappingInfoSet);

    /**
     * 這里真的是不講武德了,每次調(diào)用value.afterPropertiesSet();如下urlLookup都會產(chǎn)生重復(fù),暫時(shí)沒找到開放方法去掉重復(fù),這里重復(fù)會導(dǎo)致
     * 訪問的時(shí)候報(bào)錯Ambiguous handler methods mapped for
     * 目標(biāo)是去掉RequestMappingHandlerMapping -> RequestMappingInfoHandlerMapping -> AbstractHandlerMethodMapping
     * -> mappingRegistry -> urlLookup重復(fù)的RequestMappingInfo,這里的.getClass().getSuperclass().getSuperclass()相信會
     * 很懵逼,如果單獨(dú)通過getClass().getDeclaredMethod("getMappingRegistry",new Class[]{})是無論如何都拿不到父類的非public非
     * protected方法的,因?yàn)檫@個方法不屬于子類,只有父類才可以訪問到,只有你拿得到你才有資格不講武德的使用method.setAccessible(true)強(qiáng)行
     * 訪問
     */
    Method method = ReflectUtil.getMethod(mappingHandlerMapping,"getMappingRegistry",new Class[]{});
    method.setAccessible(true);
    Object mappingRegistryObj = method.invoke(mappingHandlerMapping,new Object[]{});
    Field field = mappingRegistryObj.getClass().getDeclaredField("urlLookup");
    field.setAccessible(true);
    MultiValueMap<String, RequestMappingInfo> multiValueMap = (MultiValueMap)field.get(mappingRegistryObj);
    multiValueMap.forEach((key,list) -> {
      clearMultyMapping(list);
    });

    /**
     * 刷新swagger文檔
     */
    refreshSwagger(applicationContext);
  }


  /**
   * 刷新swagger文檔
   * @param applicationContext
   * @throws Exception
   */
  private void refreshSwagger(ConfigurableApplicationContext applicationContext) throws Exception {
    /**
     * 獲取擴(kuò)展包swagger的地址接口掃描包,如果有配置則執(zhí)行文檔刷新操作
     */
    String extSwaggerDocPackage = applicationContext.getEnvironment().getProperty(DYNAMIC_DOC_PACKAGE);
    if (!StringUtils.isEmpty(extSwaggerDocPackage)) {
      /**
       * 拿到swagger解析文檔的入口類,真的不想這樣,主要是根本不提供刷新和重新加載文檔的方法,只能不講武德了
       */
      DocumentationPluginsBootstrapper bootstrapper = applicationContext.getBeanFactory().getBean(DocumentationPluginsBootstrapper.class);
      /**
       * 不管愿不愿意,強(qiáng)行拿到屬性得到documentationPluginsManager對象
       */
      Field field1 = bootstrapper.getClass().getDeclaredField("documentationPluginsManager");
      field1.setAccessible(true);
      DocumentationPluginsManager documentationPluginsManager = (DocumentationPluginsManager) field1.get(bootstrapper);

      /**
       * 繼續(xù)往下層拿documentationPlugins屬性
       */
      Field field2 = documentationPluginsManager.getClass().getDeclaredField("documentationPlugins");
      field2.setAccessible(true);
      PluginRegistry<DocumentationPlugin, DocumentationType> pluginRegistrys = (PluginRegistry<DocumentationPlugin, DocumentationType>) field2.get(documentationPluginsManager);
      /**
       * 拿到最關(guān)鍵的文檔插件集合,所有邏輯文檔解析邏輯都在插件中
       */
      List<DocumentationPlugin> dockets = pluginRegistrys.getPlugins();
      /**
       * 真的不能怪我,好端端,你還搞個不能修改的集合,強(qiáng)行往父類遞歸拿到unmodifiableList的list屬性
       */
      Field unModList = ReflectUtil.getField(dockets,"list");
      unModList.setAccessible(true);
      List<DocumentationPlugin> modifyerList = (List<DocumentationPlugin>) unModList.get(dockets);
      /**
       * 這下老實(shí)了吧,把自己的Docket加入進(jìn)去,這里的groupName為dynamic
       */
      modifyerList.add(createRestApi(extSwaggerDocPackage));
      /**
       * 清空罪魁禍?zhǔn)譊ocumentationCache緩存,不然就算再加載一次,獲取文檔還是從這個緩存中拿,不會完成更新
       */
      bootstrapper.stop();
      /**
       * 手動執(zhí)行重新解析swagger文檔
       */
      bootstrapper.start();
    }
  }

  public Docket createRestApi(String basePackage) {
    List<ResponseMessage> responseMessageList = new ArrayList<>();
    responseMessageList.add(new ResponseMessageBuilder().code(200).message("成功").responseModel(new ModelRef("Payload")).build());
    Docket docket = new Docket(DocumentationType.SWAGGER_2)
        .groupName("dynamic")
        .globalResponseMessage(RequestMethod.GET,responseMessageList)
        .globalResponseMessage(RequestMethod.DELETE,responseMessageList)
        .globalResponseMessage(RequestMethod.POST,responseMessageList)
        .apiInfo(apiInfo()).select()
        //為當(dāng)前包路徑
        .apis(RequestHandlerSelectors.basePackage(basePackage)).paths(PathSelectors.any()).build();
    return docket;
  }

  /**
   * 構(gòu)建api文檔的詳細(xì)信息函數(shù)
   */
  private ApiInfo apiInfo() {
    return new ApiInfoBuilder()
        //頁面標(biāo)題
        .title("SpringBoot動態(tài)擴(kuò)展")
        //創(chuàng)建人
        .contact(new Contact("rongdi", "https://www.cnblogs.com/rongdi", "495194630@qq.com"))
        //版本號
        .version("1.0")
        //描述
        .description("api管理").build();
  }

好了,下面給一下整個擴(kuò)展功能的入口吧

package com.rdpaas.dynamic.config;

import com.rdpaas.dynamic.core.ModuleApplication;
import org.apache.ibatis.session.SqlSessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.net.URL;

/**
 * 一切配置的入口
 * @author rongdi
 * @date 2021-03-06
 * @blog https://www.cnblogs.com/rongdi
 */
@Configuration
public class DynamicConfig implements ApplicationContextAware {

  private static final Logger logger = LoggerFactory.getLogger(DynamicConfig.class);

  @Autowired
  private SqlSessionFactory sqlSessionFactory;

  private ApplicationContext applicationContext;

  @Value("${dynamic.jar:/}")
  private String dynamicJar;

  @Bean
  public ModuleApplication moduleApplication() throws Exception {
    return new ModuleApplication();
  }

  @Override
  public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    this.applicationContext = applicationContext;
  }

  /**
   * 隨便找個事件ApplicationStartedEvent,用來reload外部的jar,其實(shí)直接在moduleApplication()方法也可以做
   * 這件事,但是為了驗(yàn)證容器初始化后再加載擴(kuò)展包還可以生效,所以故意放在了這里。
   * @return
   */
  @Bean
  @ConditionalOnProperty(prefix = "dynamic",name = "jar")
  public ApplicationListener applicationListener1() {
    return (ApplicationListener<ApplicationStartedEvent>) event -> {
      try {
        /**
         * 加載外部擴(kuò)展jar
         */
        moduleApplication().reloadJar(new URL(dynamicJar),applicationContext,sqlSessionFactory);
      } catch (Exception e) {
        logger.error("",e);
      }

    };
  }


}

再給個開關(guān)注解

package com.rdpaas.dynamic.anno;

import com.rdpaas.dynamic.config.DynamicConfig;
import org.springframework.context.annotation.Import;

import java.lang.annotation.*;

/**
 * 開啟動態(tài)擴(kuò)展的注解
 * @author rongdi
 * @date 2021-03-06
 * @blog https://www.cnblogs.com/rongdi
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({DynamicConfig.class})
public @interface EnableDynamic {
}

  好了,至此核心代碼和功能都分享完了,詳細(xì)源碼和使用說明見github:https://github.com/rongdi/springboot-dynamic

到此這篇關(guān)于springboot運(yùn)行時(shí)新增/更新外部接口的實(shí)現(xiàn)方法的文章就介紹到這了,更多相關(guān)springboot外部接口內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • java統(tǒng)計(jì)字符串中重復(fù)字符出現(xiàn)次數(shù)的方法

    java統(tǒng)計(jì)字符串中重復(fù)字符出現(xiàn)次數(shù)的方法

    這篇文章主要介紹了java統(tǒng)計(jì)字符串中重復(fù)字符出現(xiàn)次數(shù)的方法,涉及java針對字符串的遍歷與判斷相關(guān)操作技巧,需要的朋友可以參考下
    2016-08-08
  • Java基礎(chǔ)之位運(yùn)算知識總結(jié)

    Java基礎(chǔ)之位運(yùn)算知識總結(jié)

    最近接觸到了java位運(yùn)算,之前對位運(yùn)算的了解僅僅停留在表現(xiàn)結(jié)果上,乘2除以2,對背后的原理并不了解,現(xiàn)在學(xué)習(xí)記錄一下,需要的朋友可以參考下
    2021-05-05
  • 解決使用json-lib包實(shí)現(xiàn)xml轉(zhuǎn)json時(shí)空值被轉(zhuǎn)為空中括號的問題

    解決使用json-lib包實(shí)現(xiàn)xml轉(zhuǎn)json時(shí)空值被轉(zhuǎn)為空中括號的問題

    網(wǎng)上能查到的xml轉(zhuǎn)json的jar包大部分是net.sf.json-lib,但是JSON json =xmlSerializer.read(xml); 方法會出現(xiàn)將空值轉(zhuǎn)化為[]的問題,下面為大家提供兩種解決方法
    2018-03-03
  • Sentinel的熔斷降級、資源規(guī)則詳解與實(shí)例

    Sentinel的熔斷降級、資源規(guī)則詳解與實(shí)例

    這篇文章主要介紹了Sentinel的熔斷降級、資源規(guī)則詳解與實(shí)例,Sentinel是阿里巴巴開源的一款流量控制和熔斷降級的框架,它主要用于保護(hù)分布式系統(tǒng)中的服務(wù)穩(wěn)定性,Sentinel通過對服務(wù)進(jìn)行流量控制和熔斷降級,可以有效地保護(hù)系統(tǒng)的穩(wěn)定性,需要的朋友可以參考下
    2023-09-09
  • Spring?Web?MVC基礎(chǔ)理論概念

    Spring?Web?MVC基礎(chǔ)理論概念

    Spring?Web?MVC是基于Servlet?API構(gòu)建的原始Web框架,從?開始就包在Spring框架中,Spring?Web?MVC是一個Web框,本文給大家介紹Spring?Web?MVC基礎(chǔ)理論,感興趣的朋友一起看看吧
    2024-08-08
  • java 通過 SmbFile 類操作共享文件夾的示例

    java 通過 SmbFile 類操作共享文件夾的示例

    這篇文章主要介紹了java 通過 SmbFile 類操作共享文件夾,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-02-02
  • SpringBoot常用注解@RestControllerAdvice詳解

    SpringBoot常用注解@RestControllerAdvice詳解

    這篇文章主要介紹了SpringBoot常用注解@RestControllerAdvice詳解,@RestControllerAdvice是一個組合注解,由@ControllerAdvice、@ResponseBody組成,而@ControllerAdvice繼承了@Component,因此@RestControllerAdvice本質(zhì)上是個Component,需要的朋友可以參考下
    2024-01-01
  • maven工程中讀取resources中的資源文件

    maven工程中讀取resources中的資源文件

    Web項(xiàng)目中應(yīng)該經(jīng)常有這樣的需求,在maven項(xiàng)目的resources目錄下放一些文件,比如一些配置文件,資源文件等,本文主要介紹了maven工程中讀取resources中的資源文件,具有一定的參考價(jià)值,感興趣的可以了解一下
    2023-12-12
  • Java IO流 文件傳輸基礎(chǔ)

    Java IO流 文件傳輸基礎(chǔ)

    這篇文章主要介紹了Java IO流 文件傳輸基礎(chǔ)的相關(guān)資料,非常不錯,具有參考借鑒價(jià)值,需要的朋友可以參考下
    2016-07-07
  • 基于MyBatis的parameterType傳入?yún)?shù)類型

    基于MyBatis的parameterType傳入?yún)?shù)類型

    這篇文章主要介紹了基于MyBatis的parameterType傳入?yún)?shù)類型,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-09-09

最新評論