【问题标题】:Dynamically loaded spring contexts动态加载的弹簧上下文
【发布时间】:2019-06-22 03:45:28
【问题描述】:

所以我有一个项目,我正在使用 Spring Boot,并且想要使用模块系统。我希望模块系统可以动态重新加载。我几乎可以使用它,但 @ComponentScan 在模块中完全不起作用。

有一个module文件夹,里面有jar文件,启动时加载,需要动态卸载,加载,重新加载。

模块是通过 AnnotationConfigApplicationContext 创建的,上下文的类加载器设置为核心的类加载器,模块接口中的方法提供的 @Configuration 类通过 context.register 注册。

@Configuration 中的@Bean 声明适用于自动装配,并且所有类都正确加载。问题是 @ComponentScan 不起作用。我什至尝试用 @Component 注释一个没有字段或任何东西的类,它会抛出一个自动装配错误,就像它不在上下文中一样。如果这不是我的存储库的问题,这将不是什么大问题。

如果有一种方法我可以在没有注释的情况下手动运行组件扫描,我可以做一些额外的工作。

我已经尝试使用带有 @SpringBootApplication(scanPackages = {"package.name"}

的 spring boot 包

@EnableJpaRepository @EntityScan

都在适当的地方,并且有正确的参数。

我尝试将 .register 与不起作用的核心上下文一起使用。无论如何,我不能真正使用它,因为据我所知,您可以取消注册配置。

这是我实际加载 jar 文件的代码

    public Module loadModule(Path path) throws Exception {
    if (!path.toFile().exists()) {
        throw new Exception(String.format("Could not find module at %s", path.toAbsolutePath()));
    }

    JarFile file = new JarFile(path.toFile());
    ZipEntry moduleJson = file.getEntry("module.json");
    System.out.println(path.toUri().toURL());

    if (moduleJson == null) {
        throw new Exception(String.format("Could not find module json at %s", path.toAbsolutePath()));
    }

    String moduleJsonSTR = CharStreams
            .toString(new InputStreamReader(file.getInputStream(moduleJson), Charsets.UTF_8));
    Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
            .setPrettyPrinting().create();

    PluginConfig config = gson.fromJson(moduleJsonSTR,   PluginConfig.class);


    URLClassLoader classLoader = new URLClassLoader(new URL[] { path.toUri().toURL() },
            getClass().getClassLoader());
    Class mainClass = classLoader.loadClass(config.getMain());

    Enumeration<JarEntry> entries = file.entries();

    while (entries.hasMoreElements()) {
        JarEntry clazz = entries.nextElement();

        if (clazz.getName().equals(mainClass.getName())) {
            System.out.println("FOUND MAIN AND SKIPPING");
            continue;
        }

        if (clazz.isDirectory() || !clazz.getName().endsWith(".class")) {
            continue;
        }
        String className = clazz.getName().substring(0, clazz.getName().length() - 6);
        className = className.replace('/', '.');
        classLoader.loadClass(className);
    }
    Module module = (Module) mainClass.newInstance();
    System.out.println(module.getConfigurationClass().getName());
    module.setName(config.getName());
    file.close();
    classLoader.close();
    return module;
}

这是我初始化上下文的代码

public AnnotationConfigApplicationContext initializeContext() {
    SpringConfig cfg = getSpringConfig();
    this.context = new AnnotationConfigApplicationContext();
    this.context.setClassLoader(Core.class.getClassLoader());
    this.context.refresh();
    this.context.register(getConfigurationClass());
    Map<String, Object> props = context.getEnvironment().getSystemProperties();
    cfg.getProperties().forEach((key, value) -> props.put(key, value));
    return context;
}

有一个 bean 是一个空白类,@Component 被自动装配到一个上下文自动装配的类中,没有问题。所以我知道 autowire 正在工作,我知道它是 spring 管理的,但 @ComponentScan 不工作。

我想让 ComponentScan 工作,或者想办法以编程方式手动添加组件。

更多代码:

核心插件: 这是我的模块类:https://hastebin.com/potayuqali.java 这是加载所述模块的控制器:https://hastebin.com/ipegaqojiv.java 示例模块:

这是其中一个模块的示例:https://hastebin.com/umujiqepob.java 这是该模块的@configuration:https://hastebin.com/lakemucifo.css

【问题讨论】:

标签: java spring module classloader hierarchy


【解决方案1】:

如果我理解正确,您尝试在 ApplicationContext 加载后加载 jar 并注册为 bean。但是 ComponentScan 会在应用程序上下文加载不之后扫描 @Component 之类的注释。所以你应该以编程方式注册类(就像你做的context.register(..)一样)。

在注册类之后,也许你有 @Configuration 带注释的类,它有一个 @Bean 带注释的方法。要注册 @Bean 带注释的方法,您可以运行

@Autowire
ConfigurationClassPostProcessor configurationClassPostProcessor;

configurationClassPostProcessor.processConfigBeanDefinitions(applicationContext.getDefaultListableBeanFactory());

你也可以看看我的回答: Is There Any Way To Scan All @Component like Annotated Class After Spring ApplicationContext Load

另一种解决方案

扫描基础包(如@ComponentScan)使用

annotationConfigApplicationContext.scan(basePackageName).

它扫描基础包并像@ComponentScan 一样注册@Component(etc) 注释类。但是加载jar的classLoader必须和annotationConfigApplicationContext#classLoader一致。所以你必须设置classLoader:

annotationConfigApplicationContext.setClassLoader(yourClassLoader)

否则 AnnotationConfigApplicationContext 找不到和注册类。

【讨论】:

  • 我实际上是从 spring 应用程序加载 jars,每个 JAR 部署自己的上下文。这是加载jar的控制器代码:github.com/Redmancometh/StormCore/blob/master/src/main/java/org/…这是主插件接口:github.com/Redmancometh/StormCore/blob/master/src/main/java/org/…
  • 我无法理解“每个 JAR 部署自己的上下文”是什么意思,但我更新了我的帖子。
  • 因为每个罐子都有自己的上下文。如果您查看 StormPlugin 类,您可以看到在每个插件加载时调用的 initializeContext 插件。每个插件都是它自己的 SpringApplication,并且有它自己的 ApplicationContext。所以它不是在 ApplicationContext 加载之后,因为我不想将它添加到 StormCore 上下文中。如果有意义的话,我希望每个插件都建立自己的上下文。
【解决方案2】:

@SpringBootApplication 类是否存在于 @ComponentScan 的 basePackage 之一的根包中?出于某种奇怪的原因,我仍在寻找解释,将我的 SpringBootApplication 类从 com.comp.app 移动到 com.comp.app.xyx,从 com.comp.app.* 加载了位于其他依赖项中的所有组件.

【讨论】:

  • 遗憾的是没有。它不是一个 Spring Boot 应用程序,它只是一个使用 @Configuration 的普通旧 Spring 应用程序。对于 ComponentScan,我什至尝试指定 baseClasses 无济于事。
【解决方案3】:

很遗憾,您的代码链接都不起作用,所以有点难以理解为什么模块的 Components 没有注册。

但我知道,如果不让 Spring 管理 bean 生命周期,Spring 会变得很困难。

这可能对您的应用程序进行了太大的更改,但我不会尝试编写自己的插件系统,而是尝试Plugin Framework for Java。这是一个相当典型的插件框架,它也有一个spring plugin

基本上你编写插件,将它们构建为 zip/jar 并将它们放入 plugins 文件夹中。

【讨论】:

猜你喜欢
  • 2020-01-10
  • 2018-05-06
  • 1970-01-01
  • 1970-01-01
  • 2012-08-04
  • 1970-01-01
  • 2016-09-02
  • 2015-04-05
  • 2015-10-22
相关资源
最近更新 更多