弹簧依赖注入和插件罐

2022-09-04 22:14:20

我有使用后端服务的默认 impl 运行的 Web 应用程序。应该能够实现接口并将jar放入插件文件夹(不在apps类路径中)。重新启动服务器后,我们的想法是将新jar加载到类加载器中,并让它参与依赖注入。我正在使用弹簧DI使用@Autowired。新的插件服务 impl 将具有@Primary注释。因此,给定接口的两个 impls,应加载主接口。

我把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()");
   } 
}

由于缺乏更好的地方,我从IntextListener加载了插件jar。

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>

即使我已经将jar加载到类加载器中,DefaultService仍然被注入为服务。任何想法,我如何让插件罐参与弹簧的DI生命周期?

编辑:简单地说,我有一个war文件,在war的插件目录中有几个插件jar。根据应用程序查看的配置文件中的值,当应用程序启动时,我想加载该特定的插件jar并使用它运行应用程序。这样,我可以将战争分发给任何人,他们可以根据配置值选择要运行的插件,而不必重新打包所有内容。这就是我试图解决的问题。


答案 1

似乎您所需要的只是正确地创建春天。我认为这在没有类路径混合的情况下是可能的。最重要的是Spring配置文件在类路径的位置。因此,请将所有插件jar放入并继续阅读。ApplicationContextWEB-INF/lib

让我们从核心模块开始。我们将从 位于 的文件创建它。ApplicationContextclasspath*:META-INF/spring/*-corecontext.xml

现在,我们将使所有插件在其他地方都有其配置文件。即'myplugin1'将具有如下配置位置:.并将在 处进行配置。classpath*:META-INF/spring/*-myplugin1context.xmlanotherpluginclasspath*:META-INF/spring/*-anotherplugincontext.xml

你所看到的是一个召集。如果您愿意,也可以使用子方向:

  • 核心:classpath*:META-INF/spring/core/*.xml
  • myplugin1:classpath*:META-INF/spring/myplugin1/*.xml
  • 另一种普鲁金:classpath*:META-INF/spring/anotherplugin/*.xml

重要的是,这些位置必须是脱节的

剩下的就是将正确的位置传递给创建者。对于Web应用程序,正确的位置是扩展并覆盖方法。ApplicationContextContextLoaderListenercustomizeContext(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

更新:

为了使它起作用,我又做了一个隐藏的假设,我现在要解释。核心模块和每个插件的基本包也应该是不相交的。即:

  • com.mycompany.myapp.core
  • com.mycompany.myapp.myplugin1
  • com.mycompany.myapp.otherplugin

这样,每个模块都可以轻松地使用(在 JavaConfig 中的等效项上)为其自己的类添加类路径扫描。核心模块不应包含任何插件包的任何包扫描。插件应扩展 的配置,以将自己的包添加到类路径扫描中。<context:componet-scan />ApplicationContext


答案 2

如果你重新启动服务器,我看不出你有什么理由不能将JAR添加到WEB-INF/lib中,并将其放在CLASSPATH中。自定义类加载器和上下文侦听器的所有复杂性都消失了,因为您可以像在Spring控制下的任何其他类一样对待它。

如果您这样做是因为您不想打开或修改 WAR,为什么不将其放在服务器 /lib 目录中呢?让服务器类装入器拾取它。这使得所有插件类都可用于所有已部署的应用程序。

答案取决于单独的 /plugin 目录的重要性。如果它是解决方案的关键,并且您无法将 JAR 添加到服务器的 /lib 目录,那么就是这样。我什么都没有。但我认为,至少重新审视你必须确保它是实现你想要的事情的唯一方法的解决方案是值得的。


推荐