当前位置: 首页 > 知识库问答 >
问题:

动态加载的spring上下文

柴丰
2023-03-14

所以我有一个项目,在其中我使用Spring boot,并希望利用一个模块系统。我希望模块系统能够动态地重新加载。我让它几乎可以工作,但是@ComponentScan在模块中完全不工作。

有一个模块文件夹,包含启动时加载的jar文件,需要动态卸载、加载和重新加载。

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

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

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

我已经尝试将spring启动包与@SpringBootApplication一起使用(scanPackages={“package.name”}

@EnableJpaRepository@EntityScan

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

我试着使用。注册到核心上下文中,但它不起作用。无论如何,我不能真正使用它,因为据我所知,您可以取消注册配置。

这是我实际加载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 这是该模块的@配置:https://hastebin.com/lakemucifo.css

共有3个答案

邹玮
2023-03-14

不幸的是,您的代码链接都不工作,所以很难理解为什么模块的Components没有注册。

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

对你的应用程序来说,这可能是一个太大的改变,但我不会尝试编写自己的插件系统,而是尝试Java插件框架。这是一个相当典型的插件框架,它还有一个spring插件。

基本上,您可以编写插件,将它们构建为zip/jar,然后将它们放入plugins文件夹中。

司空玮
2023-03-14

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

锺离浩慨
2023-03-14

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

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

@Autowire
ConfigurationClassPostProcessor configurationClassPostProcessor;

configurationClassPostProcessor.processConfigBeanDefinitions(applicationContext.getDefaultListableBeanFactory());

你也可以看看我的回答:有没有办法扫描所有@组件,比如Spring应用程序上下文加载后的注释类

另一个解决方案:

要扫描基本包(如@ComponentScan),请使用

annotationConfigApplicationContext.scan(basePackageName).

它扫描基本包,并将@ComponentScan作为@ComponentScan注册@Component(etc)带注释的类。但是加载jar的类加载器必须与注释配置应用上下文#类加载器相同。因此,您必须将类Loader设置为:

annotationConfigApplicationContext.setClassLoader(yourClassLoader)

或者AnnotationConfigApplicationContext无法找到并注册类。

 类似资料:
  • 请帮帮我!!我到处找了很多,找到了任何有用的信息,或者我有,但我不能在这么短的时间内测试我在web上找到的所有东西,而且spring文档似乎只对普通情况和xml配置非常好,不适合我试图做的疯狂的事情,也不适合编程配置。

  • 问题内容: 我已经阅读了动态bean定义的更改。我在一个简单的代码示例中进行了尝试(请参见下面的代码),并且在不想停止服务器但添加/更改bean定义的情况下,它非常吸引人。 问题: 这样做安全吗(请参见下面的代码)? 我读过,借助or 或?可以在运行时实现bean定义更改。那么区别是什么呢? final static String header = “<?xml version="1.0" enc

  • 我读过动态bean定义更改。我在一个简单的代码示例中尝试了它(参见下面的代码),我发现在不想停止服务器而是添加/更改bean定义的情况下,它非常有吸引力。 问题: null 和都允许我在运行时更改bean定义。但有什么不同,利/弊?

  • 我使用Maven和surefire插件通过扩展AbstractTestNGSpringContextTests来运行TestNG测试,结果显示,即使上下文在类路径中也没有加载,并且测试在我的ide中正常工作,但在Maven中不行。 @contextConfiguration(locations=Array(“classpath*:/com/gottex/gottware/server/offlin

  • 问题内容: 我已经编写了一个PropertyUtils类(来自互联网),它将加载属性 而PropertiesUtil类如下所示 稍后,我可以通过调用PropertiesUtil.getProperty()方法来获取属性。 但是现在我要稍作修改,以便如果myApp.properties被用户修改/更改,则应再次加载 可能我需要FileWatcher类 但我的怀疑是 如何使用classpath:myA

  • 我已经写了一个PropertyUtils类(来自互联网),它将加载属性 PropertiesUtil类如下所示 稍后,我可以通过调用PropertiesUtil来获取属性。getProperty()方法。 但现在我想稍微修改一下,如果myApp。属性被用户修改/更改,应该重新加载 可能我需要FileWatcher类 但我的疑虑是 如何使用classpath创建File对象:myApp/myApp.