动态加载的Spring上下文

时间:2019-06-22 03:45:28

标签: java spring module classloader hierarchy

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

有一个模块文件夹,其中包含jar文件,这些jar文件在启动时会加载,并且需要动态卸载,加载和重新加载。

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

@Configuration中的@Bean声明适用于自动装配,并且所有类均正确加载。问题是@ComponentScan不起作用。我什至尝试使用@Component注释类,该类没有字段或任何内容,并且会引发自动装配错误,就像不在上下文中一样。如果这不是我的存储库的问题,那也没什么大不了的。

如果有一种方法,我可以手动运行没有注释的componentcan,我可以做一些额外的工作。

我尝试将spring boot软件包与@SpringBootApplication(scanPackages = {“ package.name”}

一起使用

@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在起作用,我知道它是在春季管理的,但是@ComponentScan不起作用。

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

更多代码:

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

这是以下模块之一的示例:https://hastebin.com/umujiqepob.java 这是该模块的@configuration:https://hastebin.com/lakemucifo.css

3 个答案:

答案 0 :(得分: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必须与注解ConfigApplicationContext#classLoader相同。因此,您必须使用以下方式设置classLoader:

annotationConfigApplicationContext.setClassLoader(yourClassLoader)

否则AnnotationConfigApplicationContext无法找到和注册类。

答案 1 :(得分:0)

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

答案 2 :(得分:0)

很遗憾,您的代码链接均无效,因此很难理解为什么模块的Components未注册。

但是我确实知道,如果不让Spring管理bean的生命周期,Spring就会变得困难。

对您的应用进行的更改可能太大了,但是与其尝试编写自己的插件系统,还不如尝试Plugin Framework for Java。这是一个非常典型的插件框架,并且还有一个spring plugin

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