I have web application running with a default impl of a backend service. One should be able to implement the interface and drop the jar into the plugins folder (which is not in the apps classpath). Once the server is restarted, the idea is to load the new jar into the classloader, and have it take part in dependency injection. I am using Spring DI using @Autowired. The new plugin service impl will have @Primary annotation. So given two impls of the interface, the primary should be loaded.
I got the jar loaded into the classloader and can invoke the impl manually. But I haven’t been able to get to to participate in the Dependency Injection, and have it replace the default impl.
Here’s a simplified example:
@Controller
public class MyController {
@Autowired
Service service;
}
//default.jar
@Service
DefaultService implements Service {
public void print() {
System.out.println("printing DefaultService.print()");
}
}
//plugin.jar not in classpath yet
@Service
@Primary
MyNewService implements Service {
public void print() {
System.out.println("printing MyNewService.print()");
}
}
//For lack of better place, I loaded the plugin jar from the ContextListener
public class PluginContextLoaderListener extends org.springframework.web.context.ContextLoaderListener {
@Override
protected void customizeContext(ServletContext servletContext,
ConfigurableWebApplicationContext wac) {
System.out.println("Init Plugin");
PluginManager pluginManager = PluginManagerFactory.createPluginManager("plugins");
pluginManager.init();
//Prints the MyNewService.print() method
Service service = (Service) pluginManager.getService("service");
service.print();
}
}
<listener>
<listener-class>com.plugin.PluginContextLoaderListener</listener-class>
</listener>
Even after I have loaded the jar into the classloader, DefaultService is still being injected as service. Any idea how I get the plugin jar to participate into the spring’s DI lifecycle?
Edited:
To put it simply, I have a war file that has a few plugin jars in a plugins directory inside the war. Based on a value from a configuration file that the app looks at, when the app is started, I want to load that particular plugin jar and run the application with it. That way, I can distribute the war to anyone, and they can choose which plugin to run based on a config value without having to to repackage everything. This is the problem I am trying to solve.
It seems like all You need is to create the Spring
ApplicationContextproperly. I think it’s possible without classpath mingling. What matters most are the locations of the Spring configuration files within the classpath. So put all Your plugin jar’s intoWEB-INF/liband read on.Let’s start with the core module. We’ll make it to create it’s
ApplicationContextfrom files located atclasspath*:META-INF/spring/*-corecontext.xml.Now we’ll make all plugins to have their config files elsewhere. I.e. ‘myplugin1’ will have its config location like this:
classpath*:META-INF/spring/*-myplugin1context.xml. Andanotherpluginwill have the configs atclasspath*:META-INF/spring/*-anotherplugincontext.xml.What You see is a convension. You can also use subdirectiries if You like:
classpath*:META-INF/spring/core/*.xmlclasspath*:META-INF/spring/myplugin1/*.xmlclasspath*:META-INF/spring/anotherplugin/*.xmlWhat matters is that the locations have to be disjoint.
All that remains is to pass the right locations to the
ApplicationContextcreator. For web applications the right place for this would be to extend theContextLoaderListenerand override the methodcustomizeContext(ServletContext, ConfigurableWebApplicationContext).All that remains is to read Your config file (its location can be passed as servlet init parameter). Than You need to construct the list of config locations:
This way You can easily manage what is and what is not loaded into Spring
ApplicationContext.Update:
To make it work there’s one more hidden assumption I made that I’m about to explain now. The base package of the core module and each plugin should also be disjoint. That is i.e.:
This way each module can use
<context:componet-scan />(on equivalent in JavaConfig) easily to add classpath scanning for it’s own classes only. The core module should not contain any package scanning of any plugin packages. The plugins should extend configuration ofApplicationContextto add their own packages to classpath scanning.