Lo primero que alguien podría pensar es ¿Porque quiero usar Spring con Dropwizard juntos? Bueno, en mi caso s porque la compañia en la cual estoy creando un nuevo proyecto en este momento ya tiene un grupo de librerias “core” con Dropwizard. Por ejemplo, Exception genericas, Exceptions Mapers, Filtros JaxRS, Healthchecks, un framework para autenticación y otras cosas más.
La verdad, no estoy interesado en re-escribir todas esas (ya creadas y bien testeadas) librerías pero en “sabor” Spring Framework. Y a la misma vez, me gustaría usar Spring Framework in la nueva aplicación y tomar ventaja de su Injección de Depencencias, Orientación a Aspectos (AOP), Spring Data, y en general simplificar el desarrollo.
Entonces, esa fue mi pregunta ¿Es posible integrar Spring Framework con Dropwizard? y la respuesta es, SI.
Injección de Dependencias en Dropwizard
Dropwizard por si solo no proporciona nuevas caracteristicas, en vez de eso, podemos decir que es un framework “glue-code”. Es una unión de diferentes tecnologías (muy bien integradas) como un WebServer, Restful API, Acceso a base de datos, métricas, healthchecks, para simplificar el desarrollo siguiendo cierta estructura en la programación.
Una de las cosas que no proporciona es un framework de injección de dependencias. Sin embargo, es muy común ver google GUICE como el “estandard” para ello, pero no está limitado a solamente usar GUICE. Por lo tanto somos capaces the utilizar otra framework de injección de dependencias como Spring DI. En vez de usar el bundle de GUICE, vamos a crear un Bundle para Spring que proporcionará todas los pasos básicos para comenzar a usar Spring DI en nuestra applicación.
Creando un Bundle de Spring Framework para Dropwizard
No voy a tomar crédito del siguiente código, esto está basado en el repositorio de GithHub: https://github.com/nhuray/dropwizard-spring
Desafortunadamente, el repositorio no ha tenido actualización desde 2014. Dropwizard ha liberado nueva versiones, y muchas de sus dependencias han sido actualizadas. Por ejemplo la versión de Jersey pasó de 1.x a 2.x. Esa es una razón porqué tomé ese código como referencia pero cambié unas pequeñas cosas para usarlos con las nuevas versiones.
public class SpringBundle<T extends Configuration> implements ConfiguredBundle<T> { private static final Logger LOG = LoggerFactory.getLogger(SpringBundle.class); public static final String CONFIGURATION_BEAN_NAME = "dw"; public static final String ENVIRONMENT_BEAN_NAME = "dwEnv"; private ConfigurableApplicationContext context; private boolean registerConfiguration; private boolean registerEnvironment; public SpringBundle(ConfigurableApplicationContext context) { this(context, false, false); } public SpringBundle(ConfigurableApplicationContext context, boolean registerConfiguration, boolean registerEnvironment) { if (registerConfiguration || registerEnvironment) { Preconditions.checkArgument(!context.isActive(), "Context must be not active in order to register configuration, environment or placeholder"); } this.context = context; this.registerConfiguration = registerConfiguration; this.registerEnvironment = registerEnvironment; } public void run(T configuration, Environment environment) throws Exception { if (this.registerConfiguration) { this.registerConfiguration(configuration, this.context); } if (this.registerEnvironment) { this.registerEnvironment(environment, this.context); } if (!this.context.isActive()) { this.context.refresh(); } this.registerManaged(environment, this.context); this.registerLifecycle(environment, this.context); this.registerServerLifecycleListeners(environment, this.context); this.registerTasks(environment, this.context); this.registerHealthChecks(environment, this.context); this.registerInjectableProviders(environment, this.context); this.registerProviders(environment, this.context); this.registerResources(environment, this.context); } public void initialize(Bootstrap<?> bootstrap) { } public ConfigurableApplicationContext getContext() { return this.context; } public void setRegisterConfiguration(boolean registerConfiguration) { this.registerConfiguration = registerConfiguration; } public void setRegisterEnvironment(boolean registerEnvironment) { this.registerEnvironment = registerEnvironment; } private void registerManaged(Environment environment, ConfigurableApplicationContext context) { Map<String, Managed> beansOfType = context.getBeansOfType(Managed.class); Iterator var4 = beansOfType.keySet().iterator(); while(var4.hasNext()) { String beanName = (String)var4.next(); Managed managed = (Managed)beansOfType.get(beanName); environment.lifecycle().manage(managed); LOG.info("Registering managed: " + managed.getClass().getName()); } } private void registerLifecycle(Environment environment, ConfigurableApplicationContext context) { Map<String, LifeCycle> beansOfType = context.getBeansOfType(LifeCycle.class); Iterator var4 = beansOfType.keySet().iterator(); while(var4.hasNext()) { String beanName = (String)var4.next(); if (!beanName.equals("dwEnv")) { LifeCycle lifeCycle = (LifeCycle)beansOfType.get(beanName); environment.lifecycle().manage(lifeCycle); LOG.info("Registering lifeCycle: " + lifeCycle.getClass().getName()); } } } private void registerServerLifecycleListeners(Environment environment, ConfigurableApplicationContext context) { Map<String, ServerLifecycleListener> beansOfType = context.getBeansOfType(ServerLifecycleListener.class); Iterator var4 = beansOfType.keySet().iterator(); while(var4.hasNext()) { String beanName = (String)var4.next(); if (!beanName.equals("dwEnv")) { ServerLifecycleListener serverLifecycleListener = (ServerLifecycleListener)beansOfType.get(beanName); environment.lifecycle().addServerLifecycleListener(serverLifecycleListener); LOG.info("Registering serverLifecycleListener: " + serverLifecycleListener.getClass().getName()); } } } private void registerTasks(Environment environment, ConfigurableApplicationContext context) { Map<String, Task> beansOfType = context.getBeansOfType(Task.class); Iterator var4 = beansOfType.keySet().iterator(); while(var4.hasNext()) { String beanName = (String)var4.next(); Task task = (Task)beansOfType.get(beanName); environment.admin().addTask(task); LOG.info("Registering task: " + task.getClass().getName()); } } private void registerHealthChecks(Environment environment, ConfigurableApplicationContext context) { Map<String, HealthCheck> beansOfType = context.getBeansOfType(HealthCheck.class); Iterator var4 = beansOfType.keySet().iterator(); while(var4.hasNext()) { String beanName = (String)var4.next(); HealthCheck healthCheck = (HealthCheck)beansOfType.get(beanName); environment.healthChecks().register(beanName, healthCheck); LOG.info("Registering healthCheck: " + healthCheck.getClass().getName()); } } private void registerInjectableProviders(Environment environment, ConfigurableApplicationContext context) { Map<String, InjectionResolver> beansOfType = context.getBeansOfType(InjectionResolver.class); Iterator var4 = beansOfType.keySet().iterator(); while(var4.hasNext()) { String beanName = (String)var4.next(); InjectionResolver injectableProvider = (InjectionResolver)beansOfType.get(beanName); environment.jersey().register(injectableProvider); LOG.info("Registering injectable provider: " + injectableProvider.getClass().getName()); } } private void registerProviders(Environment environment, ConfigurableApplicationContext context) { Map<String, Object> beansWithAnnotation = context.getBeansWithAnnotation(Provider.class); Iterator var4 = beansWithAnnotation.keySet().iterator(); while(var4.hasNext()) { String beanName = (String)var4.next(); Object provider = beansWithAnnotation.get(beanName); environment.jersey().register(provider); LOG.info("Registering provider : " + provider.getClass().getName()); } } private void registerResources(Environment environment, ConfigurableApplicationContext context) { Map<String, Object> beansWithAnnotation = context.getBeansWithAnnotation(Path.class); Iterator var4 = beansWithAnnotation.keySet().iterator(); while(var4.hasNext()) { String beanName = (String)var4.next(); Object resource = beansWithAnnotation.get(beanName); environment.jersey().register(resource); LOG.info("Registering resource : " + resource.getClass().getName()); } } private void registerConfiguration(T configuration, ConfigurableApplicationContext context) { ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); beanFactory.registerSingleton("dw", configuration); LOG.info("Registering Dropwizard Configuration under name : dw"); } private void registerEnvironment(Environment environment, ConfigurableApplicationContext context) { ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); beanFactory.registerSingleton("dwEnv", environment); LOG.info("Registering Dropwizard Environment under name : dwEnv"); } }
Cómo se usa?
Ahora que ya hemos creado el SpringBundle
, es tiempo de usarlo en nuestra aplicación.
public class HelloApp extends Service<HelloAppConfiguration> { public static void main(String[] args) throws Exception { new HelloApp().run(args); } @Override public void initialize(Bootstrap<HelloAppConfiguration> bootstrap) { // register configuration, environment and placeholder bootstrap.addBundle(new SpringBundle(applicationContext(), true, true, true)); } @Override public void run(HelloAppConfiguration configuration, Environment environment) throws Exception { // doing nothing } private ConfigurableApplicationContext applicationContext() throws BeansException { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.scan("my.package.with.springconfiguration"); return context; } }
Entonces, en la clase de inicio de Dropwizard, agregamos el “bundle” con el siguiente código:
bootstrap.addBundle(new SpringBundle(applicationContext(), true, true, true));
Y luego, en la instancia AnnotationConfigApplicationContext
(de spring) indicamos el paquete o clase donde queremos que Spring context escanee por nuestras configuraciones. Por ejemplo algo como esto:
package my.package.with.springconfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration @ComponentScan(basePackages = {}) public class MySpringConfiguration { }
En el código anterior, podemos definir otros paquetes para escenar en busca de componentes, repositorios, servicos de Spring, además de habiltar JPA, Spring Data, Spring Security y más.
Conclusión
Eso es todo, espero que más personas puedan tomar ventaje de esta integración en sus proyectos.