pf4j-spring icon indicating copy to clipboard operation
pf4j-spring copied to clipboard

PluginClassLoader issue with loading duplicate spring classes

Open jamesmmchugh opened this issue 4 years ago • 6 comments

I have three projects consisting of:

  • A Shared-API project with interfaces for ExtensionPoints, only depending on PF4J (compile scope)
  • A Main-Application using Spring, PF4J and PF4J-Spring, and the Shared-API project above
  • A Plugin-Project that implements a SpringPlugin and tries to start it's own application context, with its own dependencies on Spring, PF4J (compile scope), PF4J-Spring (compile scope) and the shared-api (compile scope)

The Plugin-Project is built as a fat jar (exlcuding the shared API and PF4J classes), and is loaded by the Main-Application. The SpringPluginManager starts, but when it tries to execute the start() method of the plugin (which triggers the application context start, as per the examples) I get the following Exception:

loader org.pf4j.PluginClassLoader @7d3fb0ef wants to load interface org.springframework.context.ApplicationContext. A different interface with the same name was previously loaded by 'app'. (org.springframework.context.ApplicationContext is in unnamed module of loader 'app')

The exception makes sense, as both the Main-Application and the Plugin-Project have their own spring dependencies in their ClassPaths, but I thought the point of Plugins was that their class loading is isolated so that, for example, you could have differeing versions of the same dependency in the Plugin-Project and the Main-App without conflict? Am i doing something wrong / misunderstanding?

jamesmmchugh avatar Aug 21 '20 10:08 jamesmmchugh

@jamesmmchugh See documenttion about plugin ClassLoader. By default, PF4J uses child (plugin) first class loading strategy. I think that in your case you need to switch to parent (application) first class loading strategy. For introduction in problem, you can take a look on https://github.com/pf4j/pf4j/issues/392.

In the latest versions of PF4J was introduced ClassLoadingStrategy. For more information see https://github.com/pf4j/pf4j/pull/385.

decebals avatar Aug 21 '20 11:08 decebals

How is it meant to behave if your plugin uses the same class (but a different version) internally, from one being used and loaded by the application?

jamesmmchugh avatar Aug 21 '20 14:08 jamesmmchugh

Your case (error) is particular:

loader org.pf4j.PluginClassLoader @7d3fb0ef wants to load interface org.springframework.context.ApplicationContext. A different interface with the same name was previously loaded by 'app'. (org.springframework.context.ApplicationContext is in unnamed module of loader 'app')

I don't think that you want to use a different version of Spring (core/context) in app and plugins. I think that scope of the Spring library/dependency should be "provided" in plugins.

In general I say that it's normal to have different versions of the same library in multiple plugins but I don't think that this library could be Spring (core/context) because it is part of framework (pf4j-spring).

decebals avatar Aug 21 '20 14:08 decebals

I think I see the issue, because the SpringPlugin class belongs to the application's ClassLoader, and i'm implementing the createApplicationContext method for it, but i'm trying to start an application context with a different version of Spring (provided by the plugin's class loader).

I think for my use case I should actually avoid using pf4j-spring, as my plugin is an executable spring application also but I want the possibility for the version of spring to diverge between the plugin and the application (hence why I don't want Spring libraries to be provided by the application). I should do what spring-cloud-function-deployer does and implement the plugin as a standard plugin which starts the plugin spring application reflectively (i.e. https://github.com/spring-cloud/spring-cloud-function/blob/8dee0b94c70735b2c05731924650e89ad28eb526/spring-cloud-function-deployer/src/main/java/org/springframework/cloud/function/deployer/FunctionArchiveDeployer.java#L253)

Thanks for the advise.

jamesmmchugh avatar Aug 21 '20 14:08 jamesmmchugh

Is there any facility in PF4J already to handle spring-boot executable jars as plugins - given they are packaged differently from a standard JAR? If not perhaps this would be a nice extension in the future, and also starting the spring-boot executable plugin reflectively as per the example above.

jamesmmchugh avatar Aug 21 '20 14:08 jamesmmchugh

Take a look at https://github.com/pf4j/pf4j-spring/issues/48. Unfortunately I don't use Spring Boot in my projects, so I cannot help you. Maybe you can work together with @fabioformosa to a PR. Thanks!

decebals avatar Aug 21 '20 14:08 decebals