Динамически загруженные пружинные контексты - PullRequest
2 голосов
/ 22 июня 2019

Итак, у меня есть проект, в котором я использую загрузку Spring, и хочу использовать систему модулей.Я хочу, чтобы система модулей была динамически перезагружаемой.У меня это почти работает, но @ComponentScan полностью не работает в модулях.

Существует папка модуля, содержащая файлы JAR, которые загружаются при запуске, и их необходимо динамически выгружать, загружать и перезагружать.

Модули создаются через AnnotationConfigApplicationContext, загрузчик классов контекста устанавливается на загрузчик классов ядра, а класс @Configuration, предоставляемый методом в интерфейсе модуля, регистрируется через context.register.

Объявления @Bean в @Configuration работают для autowire, и все классы загружаются правильно.Проблема в том, что @ComponentScan не работает.Я даже пытался аннотировать класс с @Component, у которого нет поля или чего-либо еще, и он выдает ошибку автопровода, как будто это не в контексте.Это не было бы таким большим делом, если бы это не было проблемой с моими репозиториями.

Если есть способ, которым я могу вручную запускать компонентное сканирование без аннотаций, я в порядке, выполняя эту дополнительную работу,

Я пытался использовать весенний загрузочный пакет с @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;
}

Есть бин, который является пустымкласс с @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 ]

1 голос
/ 01 июля 2019

Если я правильно понимаю, вы пытаетесь загрузить jar и зарегистрироваться как bean-компонент после загрузки ApplicationContext.Но ComponentScan сканирует @Component подобно аннотации, когда контекст приложения загружается не после.Поэтому вы должны регистрировать классы программно (как вы делаете context.register(..)).

А после классов регистров возможно у вас есть @Configuration аннотированный класс, и у него есть @Bean аннотированные методы.Чтобы зарегистрировать @Bean аннотированных методов, вы можете запустить

@Autowire
ConfigurationClassPostProcessor configurationClassPostProcessor;

и

configurationClassPostProcessor.processConfigBeanDefinitions(applicationContext.getDefaultListableBeanFactory());

Также вы можете посмотреть мой ответ: Есть ли способ отсканировать все @Componentнапример, аннотированный класс после Spring ApplicationContext Load

Другое решение :

Для сканирования базовых пакетов (например, @ComponentScan) используйте

annotationConfigApplicationContext.scan(basePackageName).

Он сканирует базовые пакеты и регистрирует аннотированные классы @Component (и т. Д.), Как это делает @ComponentScan.Но classLoader, который загружает jar, должен совпадать с annotationConfigApplicationContext # classLoader.Поэтому вы должны установить classLoader с помощью:

annotationConfigApplicationContext.setClassLoader(yourClassLoader)

, иначе AnnotationConfigApplicationContext не сможет найти и зарегистрировать классы.

0 голосов
/ 04 июля 2019

К сожалению, ни одна из ваших кодовых ссылок не работает, поэтому немного сложно понять, почему Components для Модуля не регистрируется.

Но я знаю, что Spring становится трудным, когда вы этого не делаете.пусть он управляет жизненным циклом bean-компонента.

Это может быть слишком большим изменением для вашего приложения, но вместо того, чтобы пытаться написать свою собственную систему плагинов, но я бы попробовал Plugin Framework для Java .Это довольно типичный фреймворк для плагинов, и у него есть весенний плагин .

По сути, вы пишете свои плагины, собираете их как zip / jar и помещаете в папку plugins.

0 голосов
/ 02 июля 2019

Есть ли вероятность, что класс @SpringBootApplication находится в корневом пакете одного из базовых пакетов @ComponentScan?По какой-то странной причине, по которой я все еще ищу объяснение, я переместил свой класс SpringBootApplication из com.comp.app в com.comp.app.xyx, загрузил все компоненты из com.comp.app. *, Которые были расположены в других зависимостях.,

...