2011-10-14 110 views
11

我的web應用程序以後端服務的默認impl運行。一個應該能夠實現接口並將jar放入plugins文件夾(不在apps classpath中)。一旦服務器重新啓動,想法就是將新的jar加載到類加載器中,並讓它參與依賴注入。我使用@Autowired使用Spring DI。新的插件服務impl將有@Primary註釋。所以給定兩個接口的impls,主要應該加載。Spring依賴注入和插件Jar

我得到的罐子裝入類加載器,可以手動調用implement執行。但我一直無法參與依賴注入,並且已經取代了默認的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

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生命週期中?

編輯: 簡而言之,我有一個戰爭文件,在戰爭中的插件目錄中有幾個插件罐。根據應用程序查看的配置文件中的值,當應用程序啓動時,我想加載該特定的插件jar並使用它運行應用程序。這樣,我可以將戰爭分發給任何人,並且他們可以根據配置值選擇運行哪個插件,而無需重新打包所有內容。這是我想解決的問題。

回答

7

這似乎是所有你需要的是打造春季ApplicationContext正常。我認爲這是可能的沒有類路徑混合。最重要的是Spring配置文件的位置在的類路徑中。因此,把你所有的插件罐放入WEB-INF/lib然後繼續閱讀。

讓我們從核心模塊開始。我們將通過位於classpath*:META-INF/spring/*-corecontext.xml的文件創建它ApplicationContext

現在,我們會讓所有的插件在其他地方有自己的配置文件。即'myplugin1'的配置位置如下:classpath*:META-INF/spring/*-myplugin1context.xml。並且anotherplugin將具有classpath*:META-INF/spring/*-anotherplugincontext.xml的配置。

你看到的是一個convension。如果你喜歡,你還可以使用subdirectiries:

  • 核心: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初始化參數傳遞)。比你需要構建配置位置的列表:

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

這樣你就可以輕鬆地管理是什麼,什麼是不加載到春天ApplicationContext

更新:

爲了使工作有一個比較隱蔽的假設,我做了,我現在將要解釋的。核心模塊的基本包和每個插件也應該是不相交。這是即:

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

這樣,每個模塊可以使用<context:componet-scan /> (在JavaConfig中相當於)輕鬆添加類路徑掃描它自己的類只有。核心模塊不應該包含包含掃描任何插件包的軟件包。 插件應擴展ApplicationContext的配置以將它們自己的包添加到類路徑掃描。

+0

謝謝。看起來像會起作用。但是我大量使用Spring Annotations,並且在插件罐中沒有任何spring xml。我想知道做註解驅動的依賴注入的做法與上面相同是多麼容易。 – Langali

+0

您應在每個插件中進行某種配置(請參閱我的擴展回答)。即使只有一個小的xml,只有一個插件包的'',它應該是插件**中的**,以及其他任何地方。這是插件,知道自己最好的,知道如何配置自己。核心模塊不應該意識到插件的存在 - 它只應該爲他們提供將自己的配置加入到ApplicationContext中的可能性。 – Roadrunner

2

如果重新啓動服務器,我不明白爲什麼你不能只將JAR添加到WEB-INF/lib和有它在CLASSPATH中。自定義類加載器和上下文監聽器的所有複雜情況都會消失,因爲您可以像在Spring控制下的任何其他類一樣對待它。

如果你做這種方式,因爲你不想打開或修改WAR,爲什麼不把它的服務器/ lib目錄?讓服務器類加載器選取它。這使所有插件類都可用於所有已部署的應用程序。

答案取決於separate/plugin目錄的重要性。如果它是解決方案的關鍵,並且無法將JAR添加到服務器的/ lib目錄,那就是這樣。我什麼都沒有。但我認爲至少重新考慮一下你必須確保它是實現你想要的唯一方法的解決方案是值得的。

+0

這總是一個選項。但是這個想法是在戰爭中有一些認證的插件(但不是在classpath中),並且用戶可以根據應用程序之外的配置值加載一個插件或另一個插件。這樣,普通用戶只需要關心配置屬性,而不是插件系統的內部。 – Langali