2011-10-14 22 views
11

Tengo una aplicación web ejecutándose con una impl predeterminada de un servicio de back-end. Uno debería ser capaz de implementar la interfaz y soltar el jar en la carpeta de complementos (que no está en la ruta de clase de las aplicaciones). Una vez que se reinicia el servidor, la idea es cargar el nuevo jar en el cargador de clases y hacer que participe en la inyección de dependencias. Estoy usando Spring DI usando @Autowired. La nueva impl del servicio de complemento tendrá la anotación @Primary. Entonces, dados dos impls de la interfaz, la primaria debería estar cargada.Spring Dependency Injection and Plugin Jar

Tengo el jar cargado en el cargador de clases y puedo invocar el impl manualmente. Pero no he podido llegar a participar en Dependency Injection, y he sustituido la impl predeterminada.

Aquí está un ejemplo simplificado:

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

// Por falta de un lugar mejor, he cargado el frasco plugin de la 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> 

incluso después de haber cargado el frasco en el cargador de clases, DefaultService todavía se está inyectando como servicio. ¿Alguna idea de cómo obtengo el plugin jar para participar en el ciclo de vida DI de la primavera?

Editado: Para decirlo simplemente, tengo un archivo de guerra que tiene algunos plugins en un directorio de complementos dentro de la guerra. En función de un valor de un archivo de configuración que la aplicación revisa, cuando se inicia la aplicación, quiero cargar ese archivo de complemento particular y ejecutar la aplicación con él. De esa forma, puedo distribuir la guerra a cualquier persona, y ellos pueden elegir qué plugin ejecutar en función de un valor de configuración sin tener que volver a empaquetar todo. Este es el problema que intento resolver.

Respuesta

7

Parece que todo lo que necesita es crear el Spring ApplicationContext correctamente. Creo que es posible sin classpath mingling. Lo que más importa son las ubicaciones de los archivos de configuración de Spring dentro de classpath. Así que ponga todos los archivos jar de su complemento en WEB-INF/lib y siga leyendo.

Comencemos con el módulo central. Lo haremos para crear su ApplicationContext desde archivos ubicados en classpath*:META-INF/spring/*-corecontext.xml.

Ahora haremos que todos los complementos tengan sus archivos de configuración en otro lugar. Es decir. 'myplugin1' tendrá su ubicación de configuración así: classpath*:META-INF/spring/*-myplugin1context.xml. Y anotherplugin tendrá las configuraciones en classpath*:META-INF/spring/*-anotherplugincontext.xml.

Lo que ves es a convension.También puede utilizar subdirectiries si quieres:

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

Lo que importa es que los lugares tienen que ser disjuntos.

Lo único que queda es pasar las ubicaciones correctas al creador ApplicationContext. Para las aplicaciones web, el lugar correcto para esto sería extender el ContextLoaderListener y anular el método customizeContext(ServletContext, ConfigurableWebApplicationContext).

Lo único que queda es leer su archivo de configuración (su ubicación se puede pasar como parámetro de servlet init). De lo que necesita para construir la lista de ubicaciones de configuración:

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

De esta manera, podrá gestionar fácilmente lo que es y lo que no está cargado en la primavera ApplicationContext.

Actualización:

para que funcione hay uno más supuesto oculto hice que estoy a punto de explicar ahora. El paquete base del módulo central y cada complemento también debe ser disjunto. Esa es decir:

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

De esta manera cada módulo puede utilizar <context:componet-scan /> (en el equivalente en JavaConfig) para agregar fácilmente el escaneo classpath para sus propias clases solo. El módulo central no debe contener ningún paquete de análisis de paquetes de complementos. Los complementos deben ampliar la configuración de ApplicationContext para agregar sus propios paquetes al escaneo classpath.

+0

Gracias. Parece que eso funcionará. Pero estoy usando anotaciones de primavera en gran medida, y no tengo ningún xml de primavera en los archivos jar de complementos. Me pregunto qué tan fácil será hacer lo mismo que arriba para la inyección de dependencia guiada por anotación. – Langali

+0

Debe tener algún tipo de configuración en cada uno de sus complementos (@ver mi respuesta extendida). Incluso si solo se trata de un pequeño archivo XML con solo un ' 'del paquete del complemento, debería estar ** en el complemento ** y en ningún otro lugar. Es el complemento que mejor se conoce y sabe cómo configurarse. El módulo central no debería estar al tanto de la existencia de complementos, solo debería brindarles la posibilidad de unir sus propias configuraciones a 'ApplicationContext'. – Roadrunner

2

Si reinicia el servidor, no veo ninguna razón por la que no pueda simplemente agregar el JAR al WEB-INF/lib y tenerlo en CLASSPATH. Toda la complicación de un cargador de clases personalizado y escucha de contexto desaparece, porque la trata como cualquier otra clase bajo el control de Spring.

Si lo hace de esta manera porque no desea abrir o modificar un WAR, ¿por qué no lo coloca en el directorio server/lib? Deje que el cargador de clases del servidor lo recoja. Esto hace que todas las clases de complementos estén disponibles para todas las aplicaciones implementadas.

La respuesta depende de la importancia del directorio separado/complemento. Si es clave para la solución, y no puede agregar el JAR al directorio/lib del servidor, eso es todo. No tengo nada. Pero creo que valdría la pena revisar al menos la solución que tienes para asegurarte de que es la única manera de lograr lo que deseas.

+0

Esa siempre es una opción. Pero la idea era tener algunos complementos certificados dentro de la guerra (pero no en classpath), y los usuarios podían cargar un plugin u otro basado en un valor de configuración fuera de la aplicación. De esta forma, un usuario promedio solo debería preocuparse por la propiedad config, y no por las funciones internas del sistema de complementos. – Langali