1

We want to make a plugin-type main program based on spring. The main program can load other Spring jars and non-spring jars as a plugin. Each plugin is based on IPlugin, And the plugin's 'IPlugin' class same as the main program's 'IPlugin' class. We make the non-spring plugin work by 'URLClassLoader', But the way not for the spring plugin. In the 'TestPlugin' project, the implementation named of 'PluginTest' and execute 'SpringApplication.run(PluginTest.class, args);' in function 'init(String[])'. 'ClassNotFoundException' occurred for load class 'PluginTest'(cause of spring jars structure).

String pluginClassName =  "com.example.demo.PluginTest";
c = newClassLoader.loadClass(pluginClassName);

So, We replace the pom 'plugins' section like following:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-assembly-plugin</artifactId>
    <version>3.1.0</version>
    <configuration>
        <descriptorRefs>
            <descriptorRef>jar-with-dependencies</descriptorRef>
        </descriptorRefs>
        <finalName>${project.artifactId}-${project.version}</finalName>
        <appendAssemblyId>false</appendAssemblyId>
        <attach>false</attach>
        <archive>
            <manifest>
                <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
            </manifest>
        </archive>
    </configuration>
    <executions>
        <execution>
            <id>make-assembly</id>
            <phase>package</phase>
            <goals>
                <goal>single</goal>
            </goals>
        </execution>
    </executions>
</plugin>

But, We got another error 'Caused by: java.lang.NoClassDefFoundError: com/zaxxer/hikari/HikariConfig' (JDBC used in test project). I don't know how to properly load other spring jars at run time.

Whole codes of plugin loader:

@Component
public class Initializer implements ApplicationRunner
{
    @Override
    public void run(ApplicationArguments args)
    {
        String jarPath = "e:/tmp/demo-0.0.1-SNAPSHOT.jar";
        File file = new File(jarPath);
        IPlugin p;

        try
        {

            ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
            URLClassLoader newClassLoader = new URLClassLoader(new URL[]{file.toURI().toURL()}, oldClassLoader);
            Thread.currentThread().setContextClassLoader(newClassLoader);

            String pluginClassName =  "com.example.demo.PluginTest";
            Class<?> c = newClassLoader.loadClass(pluginClassName);
            Object pluginTest = c.newInstance();
            p = (IPlugin)pluginTest;
            p.init(new String[]{
                    "--spring.config.location=e:/tmp/application.properties"
            });
        }
        catch (Exception e)
        {
            System.out.println(e);
            e.printStackTrace();
        }
    }
}

Thanks!

pcsutar
  • 1,715
  • 2
  • 9
  • 14
Tancen
  • 13
  • 3
  • Have you considered Spring DM? https://docs.spring.io/spring-osgi/docs/current/reference/html/ – tgdavies Dec 27 '21 at 05:21
  • The Spring team has stopped maintaining Spring DM. https://stackoverflow.com/questions/26958343/spring-dynamic-modules-is-it-alive-project – Tancen Dec 27 '21 at 06:42
  • I've asked a [similar question](https://stackoverflow.com/q/65859403/112968) in the past. It might be relevant or useful to you. – knittl Dec 27 '21 at 20:32

1 Answers1

0

In short - if your requirement is to load external jars at runtime and extend your spring application context with that - then this is not possible for a couple of reasons:

  • The Spring Application Context is built at application startup by scanning the Classpath, instantiating all beans and wiring them to each other (plus - doing a lot of fancy stuff in addition). Once setup, that application context is mostly static and will not be reevaluated.
  • It's possible to change a whole lot in Spring via configuration as code (e.g. how database transactions work or enabling/disabling certain spring features). Therefore, modifying that configuration at runtime, would be extremely hard as all possible combinations of changes would need to be considered - and back-integrated into the existing context. In addition, there's also potential error cases that can't be resolved while the application is running (e.g. consider you're introducing a circular dependency. that would normally prevent the application from even starting up - but now the application is already started - what should happen?)
  • There are massive security and stability issues with dynamically loading an executing code from external jars at runtime.
  • Even if Spring would be able to take care of all these things, there'd still be the problem that the application would need to be implemented dynamically. (e.g. do not cache references to other beans or information locally. also consider that beans may just not be there yet)

In short, Spring is not designed for that kind of dynamicity. If that is what you really need, consider application platforms that are more suitable for that sort of requirement. OSGI (Spring DM was built on OSGI) might be a solution, but be warned that there is a gigantic complexity and overhead involved in building OSGI applications that this platform requires from an application developer in order to solve the challenges mentioned above.

I would instead really recommend, to evaluate if you can work with a model, that can live without dynamic code loading as this makes things a lot easier. For instance, Spring has a very usable auto configuration system built in, that requires absolutely minimal overhead. What's necessary though is that your libraries are present in the classpath at runtime. For more details, you can read the documentation here.

Matthias
  • 2,622
  • 1
  • 18
  • 29