Spring 依赖注入和插件 Jar

发布于 2024-12-09 16:42:42 字数 1872 浏览 0 评论 0原文

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

我将 jar 加载到类加载器中,并且可以手动调用 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 加载了插件 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 仍然作为服务注入。知道如何让插件 jar 参与 spring 的 DI 生命周期吗?

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

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

各自安好 2024-12-16 16:42:43

看起来您所需要的只是正确创建 Spring ApplicationContext 。我认为没有类路径混合是可能的。最重要的是 Spring 配置文件在类路径中的位置。因此,将所有插件 jar 放入 WEB-INF/lib 中并继续阅读。

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

现在我们将使所有插件在其他地方拥有它们的配置文件。即“myplugin1”的配置位置如下:classpath*:META-INF/spring/*-myplugin1context.xmlanotherplugin 的配置位于 classpath*:META-INF/spring/*-anotherplugincontext.xml

您看到的是约定。如果您愿意,也可以使用子目录:

  • core: classpath*:META-INF/spring/core/*.xml
  • myplugin1: classpath*:META-INF/spring/myplugin1/*。 xml
  • anotherplugin: 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 中加载的内容和未加载的内容。

更新:

为了使其发挥作用,我还做了一个隐藏的假设,现在我将对此进行解释。核心模块和每个插件的基础包也应该不相交。即:

  • com.mycompany.myapp.core
  • com.mycompany.myapp.myplugin1
  • com.mycompany.myapp.anotherplugin

这样每个模块都可以使用 (在JavaConfig 中的等效项)可以轻松地添加类路径扫描仅针对其自己的类。核心模块不应包含任何插件包的任何包扫描。 插件应该扩展 ApplicationContext 的配置,以将自己的包添加到类路径扫描中。

It seems like all You need is to create the Spring ApplicationContext properly. 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 into WEB-INF/lib and read on.

Let's start with the core module. We'll make it to create it's ApplicationContext from files located at classpath*: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. And anotherplugin will have the configs at classpath*:META-INF/spring/*-anotherplugincontext.xml.

What You see is a convension. You can also use subdirectiries if You like:

  • core: classpath*:META-INF/spring/core/*.xml
  • myplugin1: classpath*:META-INF/spring/myplugin1/*.xml
  • anotherplugin: classpath*:META-INF/spring/anotherplugin/*.xml

What matters is that the locations have to be disjoint.

All that remains is to pass the right locations to the ApplicationContext creator. For web applications the right place for this would be to extend the ContextLoaderListener and override the method customizeContext(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:

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()]));

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.:

  • com.mycompany.myapp.core
  • com.mycompany.myapp.myplugin1
  • com.mycompany.myapp.anotherplugin

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 of ApplicationContext to add their own packages to classpath scanning.

小女人ら 2024-12-16 16:42:43

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

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

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

If you restart the server, I see no reason why you can't just add the JAR to the WEB-INF/lib and have it in the CLASSPATH. All the complication of a custom class loader and context listener goes away, because you treat it just like any other class under Spring's control.

If you do it this way because you don't want to open or modify a WAR, why not put it in the server /lib directory? Let the server class loader pick it up. This makes all plugin classes available to all deployed apps.

The answer depends on how important the separate /plugin directory is. If it's key to the solution, and you can't add the JAR to the server's /lib directory, then that's that. I've got nothing. But I think it'd be worthwhile to at least revisit the solution you have to make sure that it's the only way to accomplish what you want.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文