pf4j / pf4j-spring Goto Github PK
View Code? Open in Web Editor NEWPlugin Framework for Spring (PF4J - Spring Framework integration)
License: Apache License 2.0
Plugin Framework for Spring (PF4J - Spring Framework integration)
License: Apache License 2.0
I tested the following calls:
pluginManager.startPlugin(PLUGIN_ID);
pluginManager.getExtensions(PLUGIN_ID);
pluginManager.stopPlugin(PLUGIN_ID);
pluginManager.startPlugin(PLUGIN_ID);
pluginManager.getExtensions(PLUGIN_ID);
pluginManager.stopPlugin(PLUGIN_ID);
The secons call to getExtensions
results in an Exception: java.lang.IllegalStateException: org.springframework.context.annotation.AnnotationConfigApplicationContext@4a6facb0 has been closed already
I figured out the following workaround in SpringPlugin
@Override
public void stop() {
// close applicationContext
if ((applicationContext != null) && (applicationContext instanceof ConfigurableApplicationContext)) {
((ConfigurableApplicationContext) applicationContext).close();
**applicationContext = null;**
}
}
By adding applicationContext = null
a new application context is created when restarting the plugin. It should work but I am not 100% sure that there are no side effects so maybe you could have a quick check?
It's not possible to define JPA entities in a plugin.
Here's the plugins entity: https://github.com/keering/keering/blob/master/plugins/widgets/chat/src/main/java/com/github/keering/plugins/widgets/chat/model/jpa/ChatHistory.java
And this is how I set entity scanning: https://github.com/keering/keering/blob/master/app/src/main/java/com/github/keering/app/configuration/PluginConfiguration.java#L29-L40
Debug log is only picking up these classes:
2019-01-03 12:07:19.740 DEBUG 13507 --- [ main] o.hibernate.jpa.internal.util.LogHelper : PersistenceUnitInfo [
name: default
persistence provider classname: null
classloader: org.springframework.boot.loader.LaunchedURLClassLoader@5f341870
excludeUnlistedClasses: true
JTA datasource: null
Non JTA datasource: HikariDataSource (HikariPool-1)
Transaction type: RESOURCE_LOCAL
PU root URL: file:/home/danny/workspace/keering/build/app-1.0-SNAPSHOT.jar
Shared Cache Mode: UNSPECIFIED
Validation Mode: AUTO
Jar files URLs []
Managed classes names [
com.github.keering.app.model.jpa.UserConfiguration$UserConfigurationId
com.github.keering.app.model.jpa.UserConfiguration
com.github.keering.api.model.jpa.User
com.github.keering.api.model.jpa.Role]
Mapping files names []
Properties []
I guess it's due to different class loaders for main app and plugins.
You can start the project by running: $ ./gradlew build && cd build && java -jar app-1.0-SNAPSHOT.jar
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'greetings': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private java.util.List org.pf4j.demo.Greetings.greetings; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [org.pf4j.demo.api.Greeting] found for dependency [collection of org.pf4j.demo.api.Greeting]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:292)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1185)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:537)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:475)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:304)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:300)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:195)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:703)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:760)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:482)
at org.springframework.context.annotation.AnnotationConfigApplicationContext.<init>(AnnotationConfigApplicationContext.java:84)
at org.pf4j.demo.Boot.main(Boot.java:35)
Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: private java.util.List org.pf4j.demo.Greetings.greetings; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [org.pf4j.demo.api.Greeting] found for dependency [collection of org.pf4j.demo.api.Greeting]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:508)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:87)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:289)
... 12 more
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [org.pf4j.demo.api.Greeting] found for dependency [collection of org.pf4j.demo.api.Greeting]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoSuchBeanDefinitionException(DefaultListableBeanFactory.java:1103)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:915)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:858)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:480)
... 14 more
I was able to get the pf4j-spring configured with Spring boot and the plugins are working fine.
I am trying to get the properties configured in application.properties into one of the plugins and the properties are not getting resolved.
Is this possible? Or is there a way a do it?
Thx.
i am not aware of how to add dependency in plugin!
i am facing issue while adding third party jar "apache poi" in plugin, i have added maven dependency in pom file, i am able to build plugin jar file but getting ClassnotFound exception at run time
Moved from pf4j/pf4j#126.
Hello, i hope i can get some help.
I have tested successfully in my Spring Boot web application, loading plugins and getting my extensions from the plugin packaged in a jar like in your documentation shows in this way:
FileSystem sistemaFicheros = FileSystems.getDefault();
PluginManager pluginManager = new SpringPluginManager(sistemaFicheros.getPath("/home/vbravo/Escritorio/plugins"));
pluginManager.loadPlugins();
List<Converter> converters = pluginManager.getExtensions(Converter.class);
for (Converter zipToPdfConversion : converters) {
zipToPdfConversion.getConversionType();
}
But i don't see any example of loading this plugin via @Autowired (in Spring Boot) like you say in the documentation:
Ready, your extension is available in your application via PluginManager or Spring Autowire.
I don't understand very well what you mean exactly with Autowired
, Do you mean i could load the plugin in this way as attribute of a class in a Spring Boot context?
@Autowired
Converter zipToPdfConversion;
Where Converter zipToPdfConversion is implemented in the same form of your documentation:
public class HelloPlugin extends SpringPlugin {
public HelloPlugin(PluginWrapper wrapper) {
super(wrapper);
}
@Override
public void start() {
System.out.println("HelloPlugin.start()");
}
@Override
public void stop() {
System.out.println("HelloPlugin.stop()");
super.stop(); // to close applicationContext
}
@Override
protected ApplicationContext createApplicationContext() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.setClassLoader(getWrapper().getPluginClassLoader());
applicationContext.register(SpringConfiguration.class);
applicationContext.refresh();
return applicationContext;
}
@Extension
public static class HelloGreeting implements Greeting {
@Autowired
private MessageProvider messageProvider;
@Override
public String getGreeting() {
// return "Hello";
// complicate a little bit the code
return messageProvider.getMessage();
}
}
}
Can you show me some example to see how to do that Autowired of a plugin class (extending SpringPlugin) thru Spring Boot?
The idea is to load the plugin without package the plugin in a jar, i have just the plugin in the source code. I want the two ways, load from a jar and load without packaged in jar (is this possible?).
Thank you, greetings.
I need a demo to use pf4j-spring together with Spring Boot. i.e. integrate pf4j-spring into a spring boot application.
From AbstractPluginManager.loadPlugins() its called AbstractPluginManager.loadPluginFromPath() that recognise and marks plugins as DISABLED (I'm only adding the disabled.txt in the plugins folder). But later AbstractPluginManager.resolvePlugins() mark the same plugin as RESOLVED. Is there something I'm missing?
I'm not able to autowire an Extension in an Extension: https://github.com/keering/keering/blob/master/plugins/widgets/chat/src/main/java/com/github/keering/plugins/widgets/chat/service/impl/ChatHistoryServiceImpl.java#L16
You can start the project by running: $ ./gradlew build && cd build && java -jar app-1.0-SNAPSHOT.jar
com/devsecops/center/rest/Dpcrest is the package that invokes Maven dependencies in the plug-in. I tried to invoke this dependency in other plug-ins and add plug-ins in the same way without any problems, which puzzled me.
java.lang.IllegalStateException: Failed to introspect Class [com.devsecops.plugin.service.impl.MainServiceImpl] from ClassLoader [org.pf4j.PluginClassLoader@44351f3f]
at org.springframework.util.ReflectionUtils.getDeclaredMethods(ReflectionUtils.java:481)
at org.springframework.util.ReflectionUtils.doWithLocalMethods(ReflectionUtils.java:321)
at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.buildResourceMetadata(CommonAnnotationBeanPostProcessor.java:405)
at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.findResourceMetadata(CommonAnnotationBeanPostProcessor.java:363)
at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessProperties(CommonAnnotationBeanPostProcessor.java:332)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1422)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireBean(AbstractAutowireCapableBeanFactory.java:318)
at org.pf4j.spring.SpringExtensionFactory.create(SpringExtensionFactory.java:60)
at org.pf4j.ExtensionWrapper.getExtension(ExtensionWrapper.java:37)
at org.pf4j.AbstractPluginManager.getExtensions(AbstractPluginManager.java:572)
at com.devsecops.center.utils.PluginManagerUtil.getExtensions(PluginManagerUtil.java:67)
at com.devsecops.center.utils.PluginManagerUtil.instanceConfig(PluginManagerUtil.java:155)
at com.devsecops.center.utils.PluginManagerUtil.installPlugin(PluginManagerUtil.java:143)
at com.devsecops.center.api.v1.SystemAPI.uploadPlugin(SystemAPI.java:51)
at sun.reflect.GeneratedMethodAccessor101.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:888)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:793)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:660)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:367)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:860)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1598)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.NoClassDefFoundError: com/devsecops/center/rest/Dpcrest
at java.lang.Class.getDeclaredMethods0(Native Method)
at java.lang.Class.privateGetDeclaredMethods(Class.java:2701)
at java.lang.Class.getDeclaredMethods(Class.java:1975)
at org.springframework.util.ReflectionUtils.getDeclaredMethods(ReflectionUtils.java:463)
... 62 more
Caused by: java.lang.ClassNotFoundException: com.devsecops.center.rest.Dpcrest
at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at org.pf4j.PluginClassLoader.loadClass(PluginClassLoader.java:126)
... 66 more
public class WelcomePlugin extends SpringPlugin {
public WelcomePlugin(PluginWrapper wrapper) {
super(wrapper);
}
@Override
public void stop() {
super.stop();
}
@Override
public void start() throws PluginException {
super.start();
}
@Override
protected ApplicationContext createApplicationContext() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.setClassLoader(getWrapper().getPluginClassLoader());
applicationContext.register(PluginConfigurtion.class);
applicationContext.refresh();
return applicationContext;
}
}
Hi,
first of all: great framework, great possibilities to override all factory methods for custom needs.
However I encountered a "problem" using pf4j-spring. "Problem", because one could workaround this with ease, but this breaks with conventions.
Background
Dependency injection can be achieved by
These are in fact not specific to spring, they're just all ways Java gives us to deal with dependent classes.
Although they seem to be equally qualified, they're not. The convention is to use them in certian situations:
Relation to pf4j-spring
And here we come back to pf4j-spring. Currently pf4j-spring only supports the latter two (setter and field injection) as it calls the default constructor of the extension class right before checking the autowire
flag (see code below).
This missing support forces user of this framework to break with conventions and not use constructor injection for mandatory dependencies in classes that implement ExtensionPoint.
The easy way
As a user of pf4j-spring one can avoid using constructor injection for mandatory dependencies. This will break with mentioned convention, but works.
The convention following way
As a user of pf4j-spring one can override <T> T create(Class<T> extensionClass)
in anonymous or subclass to support the desired behaviour (did I mention the great extensibility possibilities?). As a down side of this approach each user has to come up with an own solution.
Anyways, it is my opinion that following the convention should be provided by this framework by default.
No, I will provide a pull request.
Sneak peek
As a sneak peek the PR rewrites SpringExtensionFactory.java
(but it retains the current overall structure and extensibility) and uses springs beanFactory.autowire(extensionClass, AUTOWIRE_CONSTRUCTOR, ...)
to solve the mentioned problem.
It will also slightly change the HelloPlugin of demo plugin2 in order to demonstrate the new possibiliy.
Stay tuned for updates.
Best regards
M Schrรถer
In combination with the PF4J-update project, I am using pf4j spring for Spring Boot.
I am trying to create a REST-API for plugin management in Spring Boot, where plugins can be installed, uninstalled, enabled and disabled at runtime.
I have been able to successfully load a plugin & start from the repository at runtime. For these calls, I have been using pluginManager.disablePlugin("hello-plugin")
& pluginManager.enablePlugin("hello-plugin")
.
Before the enablePlugin
& after executing disablePlugin
of the "hello-plugin" I am able to call the
PluginManager.getExtensionClasses(Greeting.class)
there's is no problem.
Then I try to re-enable the plugin again with the enablePlugin("hello-plugin") method. It all works, however when I want to access the "hello-plugin" with the pluginmanager.getExtensionClasses() I get the following error:
java.lang.IllegalStateException: org.springframework.context.annotation.AnnotationConfigApplicationContext@6df20ade has been closed already
With this object PluginState state = pluginManager.startPlugin(mPluginId)
I can see that my "hello-plugin" has started, but I am not able to access it via the getExtension methods from the pluginManager.
This is my SpringConfiguration:
@Bean
public SpringPluginManager pluginManager() {
return new SpringPluginManager() {
@Override
protected PluginClasspath createPluginClasspath() {
return isDevelopment() ? new GradleDevelopmentPluginClasspath() : new DefaultPluginClasspath();
}
};
}
@Bean
@DependsOn("pluginManager")
public UpdateManager updateManager() {
return new UpdateManager(mPluginManager);
}
Currently, what works for me to "enable" a plugin after disabling the plugin is to uninstall the plugin and then re-install the plugin. But that maybe not be the best option as you may want to save the current state of the plugin.
Please help me apply this logic so that pf4j can be used for mvc,
so that the extension can be accessed as a controller in the application host, then the extension must be registered into ((ConfigurableBeanFactory) beanFactory) .registerSingleton (extension.getClass (). getName (), extension);
i successfully accessed the controller extension into the application host, but failed to call the service or context brought by the extension, so the extension I injected into the host could not process the database in the plugin.
my api
public interface ControllerInterface extends ExtensionPoint {
List<Object> controller();
}
my plugin
public class PayrollPlugin extends SpringPlugin {
public RestPlugin(PluginWrapper wrapper) {
super(wrapper);
}
@Override
.......
.......
@Override
protected ApplicationContext createApplicationContext() {
ApplicationContext rootContext = ((SpringPluginManager) getWrapper().getPluginManager()).getApplicationContext();
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.setClassLoader(getWrapper().getPluginClassLoader());
applicationContext.setParent(rootContext); /* with this i try to hope to be able to access the bean application host datasource via the ApplicationContext host from the ApplicationContext.getParent () plugin */
applicationContext.register(PluginConfiguration.class);
applicationContext.refresh();
return applicationContext;
}
}
my controller
@Slf4j
@RestController
@RequestMapping("/plugin.id.payroll")
public class PayrollExtensionController {
@Autowired
private ApplicationContext pluginContext; /* i try to access the ApplicationContext plugin but return null */
@GetMapping("/generate")
public ResponseEntity<String> generate(){
/* i can't execute the service that I defined in this plugin */
return ResponseEntity.ok().body("Hallow from generate");
}
}
my extension
@Slf4j
@Extension
public class ControllerPlugin implements ControllerInterface {
@Override
public List<Object> controller() {
return Arrays.asList(new PayrollExtensionController(), new PieceExtensionController());
}
}
my plugin config on host application
@Slf4j
public class ControllerBean {
@Autowired(required = false)
private ControllerInterface controllerInterface;
public void printController() {
log.info("Found {} extensions for extension '{}'", controllerInterface.controller().size(), ControllerInterface.class.getName());
}
}
@Bean
@DependsOn("pluginManager")
public ControllerBean controllerBean() {
addController(pluginManager());
return new ControllerBean();
}
private void addController(PluginManager pluginManager) {
pluginManager.getExtensions(ControllerInterface.class)
.forEach(controllerInterface -> {
log.info("Extension {} loaded", controllerInterface.getClass().getName());
controllerInterface.controller().forEach(object -> {
log.info("Registered controller {}", object.getClass().getName());
((ConfigurableBeanFactory)beanFactory).registerSingleton(object.getClass().getName(), object);
});
});
}
is there a solution to my problem? or pf4j can be modified to create an @ControllerExtention
annotation
I'm trying to load plugins to base Spring Boot application. But I cannot figure out how to specify the location of plugins. Becouse SpringPluginManager doesn't have constructor with Path argument like DefaultPluginManager.
Here is link to my project repository https://github.com/hudrogen/parent-spring-pf4j
It is based on the demo project.
Not compatible with pf4j-update, because different basics libraries (pf4j-spring uses pf4j: 2.5.0 but pf4j-update uses pf4j: 3.0.1) as a result when run error: Caused by: java.lang.ClassNotFoundException: org.pf4j .PluginRuntimeException
I have three projects consisting of:
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?
I tried to run your demo application with the development mode (-Dpf4j.mode=development) in Eclipse. I realized that the extension files get not generated in the "target/classes/META-INF" folder although I have activated on the plugin projects the Java compiler option "Enable Annotation Processing".
If I run the Maven build to produce the jar files then the extension files are packed inside the jar files. So I assume it is a problem of the Eclipse processing? I am using Eclipse v2019-03 and Java 1.8 and version 0.5.0 of your project
Please see the log statements when I start your demo "boot" application
log.txt
Apologies if this is not the right avenue for this question.
The current release of pf4j-spring
available on Maven central is version 0.5.0 which depends on pf4j version 2.x. I see that the latest HEAD uses 3.0.1. May I know when a new release will be pushed so we can start using it please?
Ideally we'd like to upgrade to using pf4j 3.2.0 (which is the latest as of today), but even 3.0.1 would be nice.
If there's anything blocking that needs to be fixed before a release, please let me know. We'll help if it's something we can take up.
Thanks for all your work.
Relying on the demo application I created project where main module based on the Spring Boot. Main module runs plugin's extension. Extensions realised some API. Generally, my project is like a bunch of pluin1 and app from demo project. Now it works like demo project, but throught Spring Boot.
My project is a web application.
I want to realise this case:
I create Spring Controller in my plugin, annote it with @RestController
, map requests with @RequestMapping("/pluginControllerPath")
From base module I load this plugin and start it.
After running my aplication I expect to see not empty response after request to the http://hostname/pluginControllerPath
. But now I get 404 Not Found.
In total, the task is sharing beans between plugin and base application. Plugin should be able to autowire base application's beans. Plugin's classes marked with @RestController
must work as http controller, after they are loaded into the base application.
Hi,
I try to use pf4j update with pf4j spring in my app ....
as describe in previous issue https://github.com/pf4j/pf4j-spring/issues/31, i build locally the latest pf4j spring dependency.
I work with:
When i try to get an updateManager using
UpdateManager updateManager = new UpdateManager(pluginManager);
and i got an class not found execption as pluginException is not found. But all dependencies are correctly added. May be i forget something, but in previous issue, someone encountered a classNotFoundException (other class) and i would like to know if you are able to reproduce this issue.
Here logs about this exception:
2019-07-24 19:21:43.518 INFO 9740 --- [ main] com..nx_plugin_in.InPlugin : in plugin class successfully started
2019-07-24 19:21:43.694 INFO 9740 --- [ main] com..demo.NxAgentApplication : Started NxAgentApplication in 1.565 seconds (JVM running for 2.369)
2019-07-24 19:21:43.695 INFO 9740 --- [ main] com..demo.NxAgentApplication : in custom start ....
2019-07-24 19:21:43.735 INFO 9740 --- [ main] ConditionEvaluationReportLoggingListener :
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2019-07-24 19:21:43.741 ERROR 9740 --- [ main] o.s.boot.SpringApplication : Application run failed
java.lang.NoClassDefFoundError: org/pf4j/PluginException
at com..demo.NxAgentApplication$1.run(NxAgentApplication.java:84) ~[classes!/:0.0.1-SNAPSHOT]
at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:770) [spring-boot-2.1.6.RELEASE.jar!/:2.1.6.RELEASE]
at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:760) [spring-boot-2.1.6.RELEASE.jar!/:2.1.6.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:318) [spring-boot-2.1.6.RELEASE.jar!/:2.1.6.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1213) [spring-boot-2.1.6.RELEASE.jar!/:2.1.6.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1202) [spring-boot-2.1.6.RELEASE.jar!/:2.1.6.RELEASE]
at com.********.demo.NxAgentApplication.main(NxAgentApplication.java:35) [classes!/:0.0.1-SNAPSHOT]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_211]
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) ~[na:1.8.0_211]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) ~[na:1.8.0_211]
at java.lang.reflect.Method.invoke(Unknown Source) ~[na:1.8.0_211]
at org.springframework.boot.loader.MainMethodRunner.run(MainMethodRunner.java:47) [nx-agent-0.0.1-SNAPSHOT.jar:0.0.1-SNAPSHOT]
at org.springframework.boot.loader.Launcher.launch(Launcher.java:86) [nx-agent-0.0.1-SNAPSHOT.jar:0.0.1-SNAPSHOT]
at org.springframework.boot.loader.Launcher.launch(Launcher.java:50) [nx-agent-0.0.1-SNAPSHOT.jar:0.0.1-SNAPSHOT]
at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:51) [nx-agent-0.0.1-SNAPSHOT.jar:0.0.1-SNAPSHOT]
Caused by: java.lang.ClassNotFoundException: org.pf4j.PluginException
at java.net.URLClassLoader.findClass(Unknown Source) ~[na:1.8.0_211]
at java.lang.ClassLoader.loadClass(Unknown Source) ~[na:1.8.0_211]
at org.springframework.boot.loader.LaunchedURLClassLoader.loadClass(LaunchedURLClassLoader.java:92) ~[nx-agent-0.0.1-SNAPSHOT.jar:0.0.1-SNAPSHOT]
at java.lang.ClassLoader.loadClass(Unknown Source) ~[na:1.8.0_211]
... 15 common frames omitted
When running spring boot application with spring-boot-maven-plugin the plugin manager can find the plugin but it cannot find the extension.idx. Simply running java -jar from command line works without the problem.
This causes that you are unable to use spring-boot-maven-plugin meaning that your development process is slowed down.
Thanks for the good work. PF4J is exactly what I was searching for a long time ๐
Currently, the last available version of pf4j-spring
is 0.6.0. The sonatype repository does not contain the snapshort version (https://oss.sonatype.org/#nexus-search;gav~org.pf4j~pf4j-spring~~~). Thus, to use the snapshot we have to clone the repository and install the version locally.
Is it planned to deplay the snapshot version 0.7.0-SNAPSHOT
?
Hi,
I'm trying to import plugins on-runtime,
I began without on-runtime plugin loading, base on the pf4j-spring demo I manage to create plugins and if I place the plugin file into /plugins before creating the Spring ApplicationContext everything is fine, I can access each of my mapped routes.
To implement on-runtime plugin loading, when I detect a new plugin I load them and then call the following mรฉthod:
public void registerMvcEndpoints(PluginManager pm) {
pm.getExtensions(PluginInterface.class).stream()
.flatMap(g -> g.mvcControllers().stream())
.forEach(r -> {
if (!beanAlreadyRegistered.contains(r.getClass().getName())){
((ConfigurableBeanFactory) beanFactory).registerSingleton(r.getClass().getName(), r);
beanAlreadyRegistered.add(r.getClass().getName());
}
});
applicationContext
.getBeansOfType(RequestMappingHandlerMapping.class)
.forEach((k, v) -> {
v.afterPropertiesSet();
});
But when I try to access a route, even without adding a plugin I get IllegalStateException: Ambiguous handler methods mapped for ...
That is because the routes are registered a second time, so because of that I try to use unregisterMapping() to clean the mapping by adding the following code:
RequestMappingHandlerMapping requestMappingHandlerMapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
Map<RequestMappingInfo, HandlerMethod> handlerMethods = requestMappingHandlerMapping.getHandlerMethods();
for (RequestMappingInfo key : handlerMethods.keySet()) {
requestMappingHandlerMapping.unregisterMapping(key);
}
But I get this exception:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'pluginEndpoints' defined in class path resource [fr/polytech/al3/PolyVilleActive/PluginConfig.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [int]: Factory method 'pluginEndpoints' threw exception; nested exception is java.util.ConcurrentModificationException
at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:656) ~[spring-beans-5.2.5.RELEASE.jar!/:5.2.5.RELEASE]
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:636) ~[spring-beans-5.2.5.RELEASE.jar!/:5.2.5.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1338) ~[spring-beans-5.2.5.RELEASE.jar!/:5.2.5.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1177) ~[spring-beans-5.2.5.RELEASE.jar!/:5.2.5.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:557) ~[spring-beans-5.2.5.RELEASE.jar!/:5.2.5.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517) ~[spring-beans-5.2.5.RELEASE.jar!/:5.2.5.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323) ~[spring-beans-5.2.5.RELEASE.jar!/:5.2.5.RELEASE]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) ~[spring-beans-5.2.5.RELEASE.jar!/:5.2.5.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:321) ~[spring-beans-5.2.5.RELEASE.jar!/:5.2.5.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) ~[spring-beans-5.2.5.RELEASE.jar!/:5.2.5.RELEASE]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:882) ~[spring-beans-5.2.5.RELEASE.jar!/:5.2.5.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:878) ~[spring-context-5.2.5.RELEASE.jar!/:5.2.5.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:550) ~[spring-context-5.2.5.RELEASE.jar!/:5.2.5.RELEASE]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:141) ~[spring-boot-2.2.6.RELEASE.jar!/:2.2.6.RELEASE]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:747) ~[spring-boot-2.2.6.RELEASE.jar!/:2.2.6.RELEASE]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397) ~[spring-boot-2.2.6.RELEASE.jar!/:2.2.6.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:315) ~[spring-boot-2.2.6.RELEASE.jar!/:2.2.6.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1226) ~[spring-boot-2.2.6.RELEASE.jar!/:2.2.6.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1215) ~[spring-boot-2.2.6.RELEASE.jar!/:2.2.6.RELEASE]
at fr.polytech.al3.PolyVilleActive.SpringPluginContainerApplication.main(SpringPluginContainerApplication.java:29) ~[classes!/:0.0.1-SNAPSHOT]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
at org.springframework.boot.loader.MainMethodRunner.run(MainMethodRunner.java:48) ~[spring-plugin-container-0.0.1-SNAPSHOT.jar:0.0.1-SNAPSHOT]
at org.springframework.boot.loader.Launcher.launch(Launcher.java:87) ~[spring-plugin-container-0.0.1-SNAPSHOT.jar:0.0.1-SNAPSHOT]
at org.springframework.boot.loader.Launcher.launch(Launcher.java:51) ~[spring-plugin-container-0.0.1-SNAPSHOT.jar:0.0.1-SNAPSHOT]
at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:52) ~[spring-plugin-container-0.0.1-SNAPSHOT.jar:0.0.1-SNAPSHOT]
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [int]: Factory method 'pluginEndpoints' threw exception; nested exception is java.util.ConcurrentModificationException
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185) ~[spring-beans-5.2.5.RELEASE.jar!/:5.2.5.RELEASE]
at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:651) ~[spring-beans-5.2.5.RELEASE.jar!/:5.2.5.RELEASE]
... 27 common frames omitted
Caused by: java.util.ConcurrentModificationException: null
at java.base/java.util.LinkedHashMap$LinkedHashIterator.nextNode(LinkedHashMap.java:719) ~[na:na]
at java.base/java.util.LinkedHashMap$LinkedKeyIterator.next(LinkedHashMap.java:741) ~[na:na]
at java.base/java.util.Collections$UnmodifiableCollection$1.next(Collections.java:1047) ~[na:na]
at fr.polytech.al3.PolyVilleActive.PluginConfig.registerMvcEndpoints(PluginConfig.java:81) ~[classes!/:0.0.1-SNAPSHOT]
at fr.polytech.al3.PolyVilleActive.PluginConfig.pluginEndpoints(PluginConfig.java:54) ~[classes!/:0.0.1-SNAPSHOT]
at fr.polytech.al3.PolyVilleActive.PluginConfig$$EnhancerBySpringCGLIB$$7f069b53.CGLIB$pluginEndpoints$2(<generated>) ~[classes!/:0.0.1-SNAPSHOT]
at fr.polytech.al3.PolyVilleActive.PluginConfig$$EnhancerBySpringCGLIB$$7f069b53$$FastClassBySpringCGLIB$$c0fdb9ae.invoke(<generated>) ~[classes!/:0.0.1-SNAPSHOT]
at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:244) ~[spring-core-5.2.5.RELEASE.jar!/:5.2.5.RELEASE]
at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:331) ~[spring-context-5.2.5.RELEASE.jar!/:5.2.5.RELEASE]
at fr.polytech.al3.PolyVilleActive.PluginConfig$$EnhancerBySpringCGLIB$$7f069b53.pluginEndpoints(<generated>) ~[classes!/:0.0.1-SNAPSHOT]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154) ~[spring-beans-5.2.5.RELEASE.jar!/:5.2.5.RELEASE]
... 28 common frames omitted
Hi,
I have been playing with this framework and managed to get an initial project working which is a spring application which makes use of plugins through the pf4j framework. The git repo is here: https://github.com/michaelruocco/spring-boot-plugin-example.
What I would now like to do is to make use of spring within the plugins themselves. I have had a go at adding this following this element of the demo: https://github.com/pf4j/pf4j-spring/tree/master/demo/plugins/plugin2/src/main/java/org/pf4j/demo/hello, however I am having some issues.
The changes are pushed to a branch here: https://github.com/michaelruocco/spring-boot-plugin-example/tree/spring-enabled-plugin. I can see that the problem is that the ChannelIdProvider bean is not being injected into the extension but I am not sure why. I am also confused as to why the .log message from BidvAliasLoaderPlugin are not being printed out on startup. I suspect that I am doing something wrong but I am not sure what. Any help you can provide would be much appreciated.
Thanks.
Hey,
is there any option to load new added plugins on demand, without the needing to deploy the app again?
Greetings!
AbstractEventManager.whichPlugin()
attempts to determine which plugin owns an extension class by comparing their classloaders. Under certain curcumstances (Such as running in IntelliJ) the classloader used will not contain the package but the parent classloader will.
I Overrode the whichPlugin() method in SpringPluginManager and the problem is resolved:
import org.pf4j.PluginWrapper;
import org.pf4j.spring.SpringPluginManager;
public class SpringPluginManagerExt extends SpringPluginManager {
@Override
public PluginWrapper whichPlugin(Class<?> clazz) {
ClassLoader classLoader = clazz.getClassLoader();
for (PluginWrapper plugin : getResolvedPlugins()) {
if (plugin.getPluginClassLoader() == classLoader || plugin.getPluginClassLoader().getParent() == classLoader) {
return plugin;
}
}
return null;
}
}
This method is probably not ideal... the PluginClassloader should properly fill out the packages map and then use that or some other mechanism to determine ownership...
I try to build Plugin1 and Plugin 2 from pf4j-spring demo project. I use pf4j-spring ver. 0.4.0
I dropped pf4j-spring module from project, and use pf4j-spring as maven dependency from maven repository.
So, the project has structure like this:
parent
-api
-app
-plugins
--plugin1
--plugin2
It's stack trace of maven install
[ERROR] COMPILATION ERROR :
[INFO] -------------------------------------------------------------
[ERROR] error: C:\Users\salimgaraev\Desktop\demo-master\plugins\plugin1\target\classes\META-INF\extensions.idx
[INFO] 1 error
[INFO] -------------------------------------------------------------
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.327 s
[INFO] Finished at: 2018-07-03T12:53:43+03:00
[INFO] Final Memory: 18M/74M
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:2.3.2:compile (default-compile) on project pf4j-spring-demo-plugin1: Compilation failure
[ERROR] error: C:\Users\salimgaraev\Desktop\demo-master\plugins\plugin1\target\classes\META-INF\extensions.idx
[ERROR] -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException
While maven try to compile module, it generates extension.idx with data inside
I think problem is with extension, because when I comment extension method in Plugin class, module compiles without errors.
Hi,
Could you take a look at this questions?
Question: How to update the โListโ bean while some plugins are unloaded/deleted?
public class Greetings {
@Autowired
private List<Greeting> greetings;
public void printGreetings() {
System.out.println(String
.format("Found %d extensions for extension point '%s'", greetings.size(),
Greeting.class.getName()));
for (Greeting greeting : greetings) {
System.out.println(">>> " + greeting.greeting());
}
}
}
Found 3 extensions for extension point 'api.Greeting'
>>> custom
>>> Plugin2Greeting
>>> Plugin1Greeting
@GetMapping("uninstall/{pluginId}")
public Flux<String> uninstall(@PathVariable String pluginId) {
pluginManager.stopPlugin(pluginId);
pluginManager.unloadPlugin(pluginId);
return Flux.just("ok");
}
So how to update the bean 'List' during runtime, or any samples illustrate how to integrate with pf4j-update?
Thanks.
Hi Decebals,
I've questions about plugins and extensions. As describe in documentations, a plugin enable some feature for managins plugins. So, i extend plugin (in api), implement it on each jar and enable custom method that could be called by app or other plugin later (using custom Type/interface declared in api). But there are extensions and as describe in documentation, an extension is a specific behaviour of an application (or for a plugin). I think custom method that are created in my plugin could be an extension ... for me twice are possible but i didn't see pro and con of two scenario ... And i don't know if it's a good idea to add custom method or specific behaviour (like external request for example) in the plugin instead of using extensions
@Configuration
public class SpringConfiguration {
@Bean
public MessageProvider messageProvider() {
return new HelloMessageProvider();
}
@Bean
public SpringPluginManager pluginManager() {
return new SpringPluginManager();
}
@Bean
@DependsOn("pluginManager")
public Greetings greetings() {
return new Greetings();
}
}
public static void main(String[] args) {
// print logo
printLogo();
// retrieves the spring application context
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfiguration.class);
MessageProvider messageProvider = applicationContext.getBean(MessageProvider.class);
// retrieves automatically the extensions for the Greeting.class extension point
Greetings greetings = applicationContext.getBean(Greetings.class);
greetings.printGreetings();
// stop plugins
PluginManager pluginManager = applicationContext.getBean(PluginManager.class);
/*
// retrieves manually the extensions for the Greeting.class extension point
List<Greeting> greetings = pluginManager.getExtensions(Greeting.class);
System.out.println("greetings.size() = " + greetings.size());
*/
pluginManager.stopPlugins();
}
However the MessageProvider is null ใ Help plz
Hi Decebals,
I am facing the same issue, can you please help on this how to add a dependency for third-party jar..
Also added dependency in assambly.xml
file:
<includes>
<include>org.pf4j:*</include>
<include>com.googlecode.json-simple:*</include>
</includes>
Originally posted by @MohankumarD in #19 (comment)
SpringPluginManager pluginManager = new SpringPluginManager(new File("plugins").toPath());
DemoImpl implA = pluginManager.getExtension(DemoImpl.class,"xxxx").get(0);
implA.setMessage("hello");
DemoImpl impB = pluginManager.getExtension(DemoImpl.class,"xxxx").get(0);
String message = implB.getMessage(); // I want an unassigned message
SpringExtensionFactory
is written in extensionClass.newInstance()
, But after the experiment, the message I get is still hello.I have no idea.
Have a problem when using SpringPluginManager in a SpringBoot project with a system extension defined. Inside the system extension I have annotated a dependency using the Spring '@Autowired' annotation like how you would define for a SpringPlugin's plugin extension. However, upon running the code, the dependency inside the system extension was not injected because the SpringExtensionFactory's create method only does so for SpringPlugin's plugin extension only.
Below is the extract of the SpringExtensionFactory create method and my suggested modifications
@Override
public <T> T create(Class<T> extensionClass) {
T extension = createWithoutSpring(extensionClass);
if (autowire && extension != null) {
// test for SpringBean
PluginWrapper pluginWrapper = pluginManager.whichPlugin(extensionClass);
if (pluginWrapper != null) { // is plugin extension
Plugin plugin = pluginWrapper.getPlugin();
if (plugin instanceof SpringPlugin) {
// autowire
ApplicationContext pluginContext = ((SpringPlugin) plugin).getApplicationContext();
pluginContext.getAutowireCapableBeanFactory().autowireBean(extension);
}
} else if (this.pluginManager instanced SpringPluginManager) { // is system extension and plugin manager is SpringPluginManager
SpringPluginManager springPluginManager = (SpringPluginManager) this.pluginManager;
ApplicationContext pluginContext = springPluginManager.getAppplicationContext();
pluginContext.getAutowireCapableBeanFactory().autowireBean(extension);
}
}
return extension;
}
I've got a more representative demo Spring PF4J demo here:
https://github.com/claymccoy/Pf4jSpringGreetingDemo
It works fine, even when I uncomment the single greeting endpoint.
https://github.com/claymccoy/Pf4jSpringGreetingDemo/blob/master/src/main/java/org/pf4j/demo/GreetingsController.java#L25
But with the single greeting endpoint uncommented, if I add more greeting extensions via plugins it fails. That is not surprising. However, I would expect it to work if one of the extensions is marked with the Primary Spring annotation. But I still get this:
***************************
APPLICATION FAILED TO START
***************************
Description:
Field greeting in org.pf4j.demo.GreetingsController required a single bean, but 2 were found:
- org.pf4j.demo.WhazzupGreeting: a programmatically registered singleton - com.claymccoy.pf4jdemo.bjj.BjjGreeting: a programmatically registered singleton
Action:
Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed
> Task :bootRun FAILED
Hi decebals,
I'm trying to enable communication between two plugins. For example, i would like, when there is an incomming message on my plugin 1 to notify plugin2 and finally plugin2 to give a response to plugin1.
When i would like send a message to plugin2, i was stuck as i don't know or i haven't any known method of plugin2 in my plugin1.
So, may be i've to extend the plugin class to create my custom plugin and extends it on all plugin i need to enable communication by implementing two methods:
This is what i try to achieve:
public class Main extends Plugin implements MainManager {
private static final Logger LOGGER = LoggerFactory.getLogger(Core.class);
private SpringPluginManager pm;
private String message;
List<PluginWrapper> plugins;
public Main(PluginWrapper wrapper) {
super(wrapper);
LOGGER.info("Core instanciation");
message = "Incoming message";
pm = (SpringPluginManager) wrapper.getPluginManager();
}
@Override
public void start() throws PluginException {
super.start();
LOGGER.info("Core succesfully started");
plugins = pm.getPlugins();
//plugins.forEach(p->LOGGER.info("some id " + p.getPluginId()));
fromOut(message);
}
public void fromOut(String message) {
LOGGER.info("incoming message => " + message);
/*send data to in plugin*/
plugins.forEach(p -> {
if (p.getPluginId().equals("in-plugin")) {
// HERE NOTIFY PLUGIN2
p.notifyIncomingEvent(message);
}
});
}
}
For you is it the good way ?
Spring Framework in Version 5 is out since 2017 and should be used by pf4j-spring to be up-to-date and support having newer features in a pf4j-spring app.
Hi,
I have downloaded the demo app and imported it into Ecplipse. Then I executed the Boot class using the VM parameter -Dpf4j.mode=development.
The exception bellow appears when the application starts (the first line in the log):
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'greetings': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private java.util.List org.pf4j.demo.Greetings.greetings; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [org.pf4j.demo.api.Greeting] found for dependency [collection of org.pf4j.demo.api.Greeting]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
Then I can see the two plugins being started:
WelcomePlugin.start()
WELCOMEPLUGIN
2018-03-18 23:44:40,778 DEBUG org.pf4j.AbstractPluginManager - Fire 'PluginStateEvent [plugin=welcome-plugin, newState=STARTED, oldState=CREATED]' to 'org.pf4j.DefaultExtensionFinder@731f8236'
2018-03-18 23:44:40,779 INFO org.pf4j.AbstractPluginManager - Start plugin '[email protected]'
2018-03-18 23:44:40,779 DEBUG org.pf4j.DefaultExtensionFactory - Create instance for plugin 'org.pf4j.demo.hello.HelloPlugin'
HelloPlugin.start()
And then I see log traces saying that no extensions were found, so the greeting messages are not being printed.
Do you have an idea of what I could be forgetting?
Once I get it working I'll test Service, Controller and RestController, I guess I will be able to use it with the information provided in other issues.
Thank you very much.
See pf4j/pf4j#171
Hello,
I'm making a spring boot application using Wicket and PF4J. I want to use spring services in my plugins. These services are not using services outside the plugin class loader. In my plugin, I can have wicket panels. The panel class can use the Spring service.
Here the class of the plugin:
public class MyPlugin extends SpringPlugin implements MyPluginConfiguration {
public MyPlugin(PluginWrapper wrapper) {
super(wrapper);
}
@Override
protected ApplicationContext createApplicationContext() {
final AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.setClassLoader(getWrapper().getPluginClassLoader());
applicationContext.register(SpringConfiguration.class);
applicationContext.refresh();
return applicationContext;
}
@Override
public Panel getConfigurationPanel(String wicketId) {
return new ConfigurationPanel(wicketId);
}
}
Its interface:
public interface MyPluginConfiguration {
Panel getConfigurationPanel(String wicketId);
}
The SpringConfiguration class:
@Configuration
public class SpringConfiguration {
@Bean
public LabelService getLabelService() {
return new LabelService();
}
}
The configuration panel:
public class ConfigurationPanel extends Panel {
@SpringBean
private LabelService labelService;
public ConfigurationPanel(String id) {
super(id);
add(new Label("text", labelService.getLabel()));
}
}
And the service:
@Service
public class LabelService {
public String getLabel() {
return "Hello World from the plugin.";
}
}
To display the configuration, i'm using this page in my application:
public class PluginTestPage extends WebPage {
@SpringBean
private PluginManager pluginManager;
public PluginTestPage() {
final PluginWrapper pluginWrapper = pluginManager.getPlugin("example");
if (pluginWrapper.getPlugin() instanceof MyPluginConfiguration) {
final MyPluginConfiguration pluginConfiguration = (MyPluginConfiguration) pluginWrapper.getPlugin();
add(pluginConfiguration.getConfigurationPanel("configuration-panel"));
} else {
add(new Label("configuration-panel", "Can't get the configuration panel"));
}
}
}
When I'm asking the PluginTestPage, I have this error:
org.apache.wicket.WicketRuntimeException: Can't instantiate page using constructor 'public com.example.pages.PluginTestPage()'. An exception has been thrown during construction!
at org.apache.wicket.session.DefaultPageFactory.newPage(DefaultPageFactory.java:194) ~[wicket-core-9.0.0.jar:9.0.0]
at org.apache.wicket.session.DefaultPageFactory.newPage(DefaultPageFactory.java:67) ~[wicket-core-9.0.0.jar:9.0.0]
at org.apache.wicket.DefaultMapperContext.newPageInstance(DefaultMapperContext.java:90) ~[wicket-core-9.0.0.jar:9.0.0]
at org.apache.wicket.core.request.handler.PageProvider$Provision.getPage(PageProvider.java:369) ~[wicket-core-9.0.0.jar:9.0.0]
at org.apache.wicket.core.request.handler.PageProvider.getPageInstance(PageProvider.java:170) ~[wicket-core-9.0.0.jar:9.0.0]
at org.apache.wicket.request.handler.render.PageRenderer.getPage(PageRenderer.java:78) ~[wicket-core-9.0.0.jar:9.0.0]
at org.apache.wicket.request.handler.render.WebPageRenderer.respond(WebPageRenderer.java:231) ~[wicket-core-9.0.0.jar:9.0.0]
at org.apache.wicket.core.request.handler.RenderPageRequestHandler.respond(RenderPageRequestHandler.java:202) ~[wicket-core-9.0.0.jar:9.0.0]
at org.apache.wicket.request.cycle.RequestCycle$HandlerExecutor.respond(RequestCycle.java:917) ~[wicket-core-9.0.0.jar:9.0.0]
at org.apache.wicket.request.RequestHandlerExecutor.execute(RequestHandlerExecutor.java:63) ~[wicket-request-9.0.0.jar:9.0.0]
at org.apache.wicket.request.cycle.RequestCycle.execute(RequestCycle.java:283) ~[wicket-core-9.0.0.jar:9.0.0]
at org.apache.wicket.request.cycle.RequestCycle.processRequest(RequestCycle.java:254) ~[wicket-core-9.0.0.jar:9.0.0]
at org.apache.wicket.request.cycle.RequestCycle.processRequestAndDetach(RequestCycle.java:222) ~[wicket-core-9.0.0.jar:9.0.0]
at org.apache.wicket.protocol.http.WicketFilter.processRequestCycle(WicketFilter.java:276) ~[wicket-core-9.0.0.jar:9.0.0]
at org.apache.wicket.protocol.http.WicketFilter.processRequest(WicketFilter.java:207) ~[wicket-core-9.0.0.jar:9.0.0]
at org.apache.wicket.protocol.http.WicketFilter.doFilter(WicketFilter.java:300) ~[wicket-core-9.0.0.jar:9.0.0]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:373) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1589) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) ~[na:na]
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) ~[na:na]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
at java.base/java.lang.Thread.run(Thread.java:834) ~[na:na]
Caused by: java.lang.reflect.InvocationTargetException: null
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) ~[na:na]
at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:490) ~[na:na]
at org.apache.wicket.session.DefaultPageFactory.newPage(DefaultPageFactory.java:175) ~[wicket-core-9.0.0.jar:9.0.0]
... 33 common frames omitted
Caused by: java.lang.IllegalStateException: Concrete bean could not be received from the application context for class: com.example.pf4j_wicket_spring.services.LabelService.
at org.apache.wicket.spring.SpringBeanLocator.lookupSpringBean(SpringBeanLocator.java:277) ~[wicket-spring-9.0.0.jar:9.0.0]
at org.apache.wicket.spring.SpringBeanLocator.locateProxyTarget(SpringBeanLocator.java:198) ~[wicket-spring-9.0.0.jar:9.0.0]
at org.apache.wicket.spring.injection.annot.AnnotProxyFieldValueFactory.getFieldValue(AnnotProxyFieldValueFactory.java:150) ~[wicket-spring-9.0.0.jar:9.0.0]
at org.apache.wicket.injection.Injector.inject(Injector.java:111) ~[wicket-ioc-9.0.0.jar:9.0.0]
at org.apache.wicket.spring.injection.annot.SpringComponentInjector.inject(SpringComponentInjector.java:124) ~[wicket-spring-9.0.0.jar:9.0.0]
at org.apache.wicket.spring.injection.annot.SpringComponentInjector.onInstantiation(SpringComponentInjector.java:130) ~[wicket-spring-9.0.0.jar:9.0.0]
at org.apache.wicket.application.ComponentInstantiationListenerCollection$1.notify(ComponentInstantiationListenerCollection.java:38) ~[wicket-core-9.0.0.jar:9.0.0]
at org.apache.wicket.application.ComponentInstantiationListenerCollection$1.notify(ComponentInstantiationListenerCollection.java:34) ~[wicket-core-9.0.0.jar:9.0.0]
at org.apache.wicket.util.listener.ListenerCollection.notify(ListenerCollection.java:80) ~[wicket-util-9.0.0.jar:9.0.0]
at org.apache.wicket.application.ComponentInstantiationListenerCollection.onInstantiation(ComponentInstantiationListenerCollection.java:33) ~[wicket-core-9.0.0.jar:9.0.0]
at org.apache.wicket.Component.<init>(Component.java:682) ~[wicket-core-9.0.0.jar:9.0.0]
at org.apache.wicket.MarkupContainer.<init>(MarkupContainer.java:185) ~[wicket-core-9.0.0.jar:9.0.0]
at org.apache.wicket.markup.html.WebMarkupContainer.<init>(WebMarkupContainer.java:53) ~[wicket-core-9.0.0.jar:9.0.0]
at org.apache.wicket.markup.html.WebMarkupContainer.<init>(WebMarkupContainer.java:45) ~[wicket-core-9.0.0.jar:9.0.0]
at org.apache.wicket.markup.html.panel.Panel.<init>(Panel.java:67) ~[wicket-core-9.0.0.jar:9.0.0]
at com.example.pf4j_wicket_spring.panels.ConfigurationPanel.<init>(ConfigurationPanel.java:25) ~[na:na]
at com.example.pf4j_wicket_spring.MyPlugin.getConfigurationPanel(MyPlugin.java:40) ~[na:na]
at com.example.pages.PluginTestPage.<init>(PluginTestPage.java:30) ~[classes/:na]
... 38 common frames omitted
Is it a mistake with my settings ?
Regards,
Stef
Hi, I found that it can not get the annotation when loading the classes from the plugins folder, so it can not inject the annotation to the BeanDefinition. I debug the ExtensionsInjector class, and found that.
I want to use the spring mvc with this project. but it can not work. Is this a bug or am I using the wrong way?
I've tested loading a plugin developed with spring boot.
Normally, a spring boot project (it could be the plugin) is built with spring-boot-maven-plugin
that create a spring boot executable jar, that is a fat jar that contains classes + libs.
The structure of a spring boot executable jar is different than a standard jar, look at this doc page: https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-executable-jar-format.html#executable-jar-jar-file-structure
So plugin classes (and extension.idx) are placed, into the jar, at this folder: BOOT-INF/classes
.
Is there a way for pf4j to search classes not in the root of jar but in a subfolder of it?
Hey,
I want to extend an class that needs a other scope then the singleton. How can I specify the bean scope for the @Extension
annotated class?
I want to use your library with Spring and Vaadin 8. All works fine! But now I want to test to get SpringView
annotated classes loaded out of the plugin. And there I have problems with the scopes of the bean that will be created by your library. The SpringView
cannot be a singleton.
Is there any option to set the singleton explicitly?
Greetings!
When we register the configuration files in plugin - only the beans created in config files are being eligble for autowiring but not the components declared in the plugin application - is there any way to register all the components created as well?
i am able to integrate spring plugin with my main application context as parent child, controller and service and repository annotations also discovered, when i select from any table that i can see select query log in logger
But when i do operation like insert then it is not saving data to table also query is not printed in logger.
Does anyone found a proper way to include @component (@controller / @service) in a plugin ?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.