我的Web应用程序运行时带有后端服务的默认impl。一个人应该能够实现接口并将jar放入plugins文件夹(不在apps类路径中)。重新启动服务器之后,我们的想法是将新jar加载到类加载器中,并让它参与依赖注入。我正在使用@Autowired的Spring DI。新的插件服务impl将具有@Primary注释。因此,给定接口的两个impl,应该加载primary。
我将jar加载到类加载器中并可以手动调用impl。但我无法参与依赖注入,并将其替换为默认impl。
这是一个简化的例子:
@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()");
}
}
//由于缺少更好的地方,我从ContextListener
加载了插件jarpublic 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>
即使我将jar加载到类加载器中,DefaultService仍然作为服务注入。知道如何让插件罐参与弹簧的DI生命周期吗?
编辑: 简单来说,我有一个war文件,在war里面的plugins目录中有一些插件jar。基于应用程序查看的配置文件中的值,当应用程序启动时,我想加载该特定的插件jar并使用它运行应用程序。这样,我可以将战争分发给任何人,他们可以根据配置值选择运行哪个插件,而无需重新打包所有内容。这是我试图解决的问题。
答案 0 :(得分:7)
您似乎只需要正确创建Spring ApplicationContext
。我认为可能没有类路径混合。最重要的是Spring配置文件在类路径中的位置。所以将你的所有插件jar放入WEB-INF/lib
并继续阅读。
让我们从核心模块开始。我们将从位于ApplicationContext
的文件中创建classpath*:META-INF/spring/*-corecontext.xml
。
现在我们将使所有插件将其配置文件放在其他位置。即'myplugin1'的配置位置如下:classpath*:META-INF/spring/*-myplugin1context.xml
。并且anotherplugin
会在classpath*:META-INF/spring/*-anotherplugincontext.xml
处获得配置。
你看到的是一个对话。如果您愿意,也可以使用子目录:
classpath*:META-INF/spring/core/*.xml
classpath*:META-INF/spring/myplugin1/*.xml
classpath*:META-INF/spring/anotherplugin/*.xml
重要的是,地点必须不相交。
剩下的就是将正确的位置传递给ApplicationContext
创建者。对于Web应用程序,正确的位置是扩展ContextLoaderListener
并覆盖方法customizeContext(ServletContext, ConfigurableWebApplicationContext)
。
剩下的就是读取您的配置文件(它的位置可以作为servlet init参数传递)。比你需要构建配置位置列表:
String locationPrefix = "classpath*:META-INF/spring/";
String locationSiffix = "/*.xml";
List<String> configLocations = new ArrayList<String>();
configLocations.add(locationPrefix + "core" + locationSiffix);
List<String> pluginsTurnedOn = getPluginsTurnedOnFromConfiguration();
for (String pluginName : pluginsTurnedOn) {
configLocations.add(locationPrefix + pluginName + locationSiffix);
}
applicationContext.setConfigLocations(configLocations.toArray(new String[configLocations.size()]));
这样您就可以轻松管理Spring ApplicationContext
中的内容和内容。
<强>更新强>
为了让它发挥作用,我做了一个更隐藏的假设,我现在要解释。核心模块和每个插件的基础包也应该不相交。那就是:
这样,每个模块都可以轻松地使用<context:componet-scan />
(在JavaConfig中等效)来为它自己的类添加类路径扫描。核心模块不应包含任何插件包的任何包扫描。 插件应扩展ApplicationContext
的配置,以将自己的包添加到类路径扫描中。
答案 1 :(得分:2)
如果重新启动服务器,我认为没有理由不能将JAR添加到WEB-INF / lib并将其保存在CLASSPATH中。自定义类加载器和上下文监听器的所有复杂性都消失了,因为你就像在Spring控制下的任何其他类一样对待它。
如果你这样做是因为你不想打开或修改WAR,为什么不把它放在server / lib目录下呢?让服务器类加载器接收它。这使得所有已部署的应用程序都可以使用所有插件类。
答案取决于单独/插件目录的重要性。如果它是解决方案的关键,并且您无法将JAR添加到服务器的/ lib目录,那就是那个。我什么都没有。但我认为至少要重新审视你必须确保它是实现你想要的唯一方法的解决方案是值得的。