Hace tiempo vengo usando
Maven2 y
Eclipse con WTP. Para màes una muy buena combinación, porque ambos ofrecen muy interesantes opciones:
Maven2:
- Automatiza totalmente el proceso de building,y permite customizarlo a gusto.
- Maneja las dependencias del proyecto de una manera bastante eficiente.
- Se integra fácilmente con Ant, por lo tanto también cuenta con la flexibilidad y las innumerables opciones de éste.
- Facilita el proceso de integración continua, ejecutando los tests, reportes de cobertura, estilos de programación, etc.
Eclipse WTP:
- Permite ejecutar un container o appserver dentro de Eclipse, posibilitando asàel debug de aplicaciones, y el hot deploying, o sea no tener que rearrancar el container para ver los cambios hechos en una clase, un JSP u otro tipo de archivo. Se puede debuggear incluso un JSP.
- Tiene un buen editor JSP/HTML/XML con autocompletar, y la version 2.0 tiene incluso un editor gráfico HTML (aunque hace agua con css).
El problema es que los dos no se integran fácilmente. WTP necesita una estructura de directorio que es diferente a la de Maven. Además, si bien Maven descarga las dependencias automáticamente, las guarda en su propio repositorio local que no tiene nada que ver con WTP, y por lo tanto hay que copiar los .jar manualmente a donde WTP las pueda encontrar para cuando hace sus propios deploys internos.
Después de un par de dÃÂas de prueba y error, ya que la documentación en este sentido es escasa, encontré la manera de integrarlos 100%, con la ayuda del plugin de Elipse para Maven. Estos son los pasos a seguir, lo tengo probado y funciona muy bien. Asumo que tienen un mÃÂnimo conocimiento de Maven y de la estructura del pom.xml, sino les recomiendo que lean antes este breve tutorial. Las versiones que usé son Maven 2.0.7, puede que funcione con alguna versión anterior de la rama 2, pero definitivamente es necesario WTP 2.0 o superior.
- Partimos de un proyecto web que tiene una estructura de directorios casi igual a la que propone Maven:
.
|-- pom.xml
|-- WebContent
| |-- WEB-INF
| | `-- web.xml
| |-- index.jsp
| `-- jsp
| `-- websource.jsp
`-- src
`-- main
|-- java
| `-- com
| `-- example
| `-- projects
| `-- SampleAction.java
|-- test
| `-- com
| `-- example
| `-- projects
| `-- SampleActionTest.java
`-- resources
|-- log4j.properties
`-- hibernate.properties
- Si el proyecto ya está armado y no tiene esta estructura, bueno, o adaptan la estructura a esto, o se buscan otra manera de hacer la integración.

La diferencia con el standard de Maven, es que pusimos todos los archivos web en WebContent en vez de en src/main/webapp.
Lamentablemente el plugin de Maven para Eclipse (que crea automáticamente un proycto para Eclipse en base al pom.xml) no soporta WTP 2.0, asique no lo pude probar y no sé como será la integración. Esperemos que pronto soporten WTP 2.0.
- Este proyecto se puede migrar a Eclipse (si aún no lo está) haciendo File -> New -> Other -> Web -> Dynamic Web Project, desmarcando Use default e ingresando el directorio donde esta ubicado el proyecto. Lamentablemente los paths presentados no se pueden cambiar para nuestra conveniencia, ya que el campo no acepta valores que incluyan / , por lo tanto lo dejamos asày lo arreglaremos luego. Una vez abierto este proyecto en Eclipse, si queremos ejecutarlo en un container, o realizar un build con Maven, veremos que no funciona en ninguno de los dos casos por los siguiente motivos:
- Los nombres de los packages estan mal, ya que comienzan con main.java
- El web.xml en WebContent/WEB-INF/web.xml es uno creado por WTP, no el de nuestro proyecto, y Maven no encuentra ningúno de los archivos web (por ej. html, jsp, js, jpg, etc), ya que espera que estén en src/main/webapp.
- WTP no encuentra ninguno de los archivos de recursos que usualmente deben aparecer en el classpath (por ej. .properties, .xml, etc)
- Finalmente, WTP no encuentra ningún .jar externo, ya que estos estan en el repositorio
de Maven en ~/.m2/repo y WTP los espera en WebContent/WEB-INF/lib
Veamos entonces como solucionar cada uno de estos problemas.
- Para solucionar los packages, vamos a Java Build Path -> Source y removemos el único directorio de la lista. Luego seleccionamos Add Folder, y elegimos los directorios que contienen los archivos . java, o sea src/main/java y src/test/java. De esta manera, los packages quedan como corresponde.
- Para que el build funcione en Maven, vamos a decirle donde encontrar los archivos web. Para eso agregamos al pom.xml las siguientes lineas en la sección de plugins:
maven-war-plugin WebContent
Ahora deberÃÂa funcionar el build con Maven, lo podemos probar haciendo mvn clean install desde el directorio del proyecto.
- Para solucionar el tema de los recursos, haremos un truquito. Vamos a Java Build Path -> Source y seleccionamos Link Source… En Linked folder location ingresamos el path hacia nuestro directorio de resources src/main/resources. El Folder name puede ser cualquiera. Recomiendo en este paso, crear una variable PROJECT_HOME o algo asÃÂ, que apunte directo al root de nuestro proyecto, de manera de hacerlo portátil, si luego queremos mover el proyecto a otro directorio o PC, o dárselo a otra persona, bastará con cambiar esta variable para que el proyecto funcione en otra ubicación.
Esto hace que el directorio resources sea incluido en el classpath, y en el deploy interno vayan a quedar en WEB-INF/classes.
- Para que WTP vea los .jar, vamos a utilizar el plugin de Eclipse para Maven, que también además nos va a posibilitar correr Maven dentro de Eclipse, y facilitar la tarea de agregar dependencias.
Una vez instalado, veremos que el archivo pom.xml aparece decorado con una pequeña m en la esquina superior izquierda del ÃÂcono. Con el botón derecho clickeamos en el root del proyecto, y en el menú elegimos Maven2 -> Enable. Inmediatamente, esta acción agrega a nuestro proyecto una nueva biblioteca llamada Maven 2 Dependencies, que contiene todas las dependencias detalladas en el pom.xml. Esto es suficiente para que todas las dependencias estén en el classpath, por lo que el proyecto deberÃÂa compilar en Eclipse sin problemas. Sin embargo, WTP aún necesita que las dependencias estén en WebContent/WEB-INF/lib para que en el momento de hacer el deploy interno, estas esten en el lugar que el container las necesita.
Para lograr este último detalle, vamos a las properties del proyecto, y en J2EE Module Dependencies, seleccionamos la biblioteca Maven 2 Dependencies. De esta manera le decimos a WTP que incluya también las dependencias de Maven en WEB-INF/lib de su deploy interno.
Una vez seguidos todos estos pasos, deberÃÂa ser posible arrancar un container dentro de Eclipse (yo particularmente uso Tomcat 5.5 ó 6) y ejecutar nuestro proyecto como una webapp. Adicionalmente, podremos realizar builds con Maven también desde dentro de Eclipse seleccionando con el botón derecho en el root del proyecto y eligiendo el Run as… apropiado. Finalmente, para agregar una nueva dependencia, seleccionamos con el botón derecho el pom.xml, elegimos Maven2 -> Add Dependency y en el espacio para Query escribimos parte del nombre de la dependencia que buscamos. Casi inmediatamente aparecerá un árbol con los resultados encontrados, que debemos navegar hasta encontrar el paquete deseado, y simplemente eligéndolo, el .jar de descargará del repositorio central hacia nuestro repositorio local, se agregará la dependencia al pom, y también a la biblioteca de dependencias de Maven de nuestro proyecto, y por lo tanto también al deploy interno de WTP.
Nota: no elijan la opción del proyecto Maven2 -> Update Source Folders, no sé exactamente cuál es su objetivo, pero el efecto inmediato es que hace que el link que habÃÂamos creado hacia el directorio de recursos no sea mas un link, y entonces por algún motivo el WTP no lo incluye en su deploy interno.
Nota2: es posible que los recursos en el link no se deployeen automáticamente al modificarlos, si esto ocurre será suficiente con hacer un refresh (F5) del directorio linkeado.
Conclusiones
Tener Maven2 integrado con Eclipse WTP facilita mucho la tarea de todo el ciclo de desarrollo de una webapp.
Partiendo de esta base, se pueden luego agregar mas directorios de recursos, generar builds con cambios o bien con otras estructuras, generar diferentes builds en base al mismo código, etc. La documentación de Maven2 no es la gran cosa, pero con un poco de perseverancia de pueden obtener los resultados deseados.
I’ve been using Maven2 and Eclipse with WTP for a long time. To me they make a good match, because they offer very attractive options:
Maven2:
- Complete automation of the building process, and ad-hoc customization.
- Handles project dependencies in a rather efficient way.
- Easily integrated with Ant, so it has its flexibility and countless options.
- Eases the continuous integration process, by executing tests, coverage reports, programming styles, etc.
Eclipse WTP:
- Allows execution of a container or appserver inside Eclipse, so it’s possible to debug applications (even JSP’s) , and hot deploying, so there’s no need to restart the server every time a class, a JSP or other file changes.
- It has a good autocomplete JSP/HTML/XML editor. Version 2.0 even has a graphical HTML editor (although it has some problems with css).
The problem is they don’t integrate easily. WTP needs a specific directory structure which is different than Maven’s. Even though Maven downloads dependencies automatically, it stores them in its own local repository which has nothing to do with WTP, and therefore .jar files need to be manually copied where WTP will be able to find them to do its own internal deploys.
After a couple of days on trial and error (since documentation is non-existent), I’ve found the way to integrate them 100%, with help from Eclipse plugin for Maven. These are the steps to follow, I have tested it and it works great. I assume you have a basic knowledge of Maven and pom.xml structure, if not, I would recommend this brief tutorial. The versions I used were Maven 2.0.7 and WTP 2.0., it might work with previous 2.0x branch of Maven.
- Let’s start with a project with a directory structure almost as proposed by Maven:
.
|-- pom.xml
|-- WebContent
| |-- WEB-INF
| | `-- web.xml
| |-- index.jsp
| `-- jsp
| `-- websource.jsp
`-- src
`-- main
|-- java
| `-- com
| `-- example
| `-- projects
| `-- SampleAction.java
|-- test
| `-- com
| `-- example
| `-- projects
| `-- SampleActionTest.java
`-- resources
|-- log4j.properties
`-- hibernate.properties
If the project is already going with a different structure, well, you will need to adapt it, or find another way to make the integration work.
The difference with standard Maven is we placed all the web files in WebContent instead of src/main/webapp.
Unfortunately, Maven for Eclipse plugin (which automatically builds an Eclipse project based on pom.xml) currently doesn’t support WTP 2.0, so I couldn’t test it and I don’t know how integration would be. Hopefully they will support WTP 2.0 soon.
- This project can be migrated to Eclipse, (if it’s not yet) doing File -> New -> Other -> Web -> Dynamic Web Project, unchecking Use default and entering the directory where the project is. For some reason the paths cannot be changed to suits us, since strangely the field won’t accept values including “/”, so we’ll leave it as it is, and we’ll fix it later. Once the project is opened in Eclipse, if we want to run it in a container o make a build with maven, it won’t work because of the following reasons:
- Packages names are wrong, they start with main.java
- The web.xml at WebContent/WEB-INF/web.xml is one created automatically by WTP, not our project’s, and Maven can’t find none of the web files (.html, .jsp, .js, .jpg, etc.), since they’re supposed to be found at src/main/webapp.
- WTP can’t find any of the resource files usually found in the classpath (i.e. .properties, .xml, etc)
- Finally, WTP can’t find any external .jar, since these are in the Maven repository in ~/.m2/repo and WTP wants them on WebContent/WEB-INF/lib
Veamos entonces como solucionar cada uno de estos problemas.
- To fix packages, go to Java Build Path -> Source and remove the only directory on the list. Then select Add Folder, and choose the directories containing the .java files, namely src/main/java y src/test/java. This way, packages are fixed.
- To make the build work in Eclipse, let it know where to find the we files. Add the following lines to the pom.xml, in the plugins section:
maven-war-plugin WebContent
Now the Maven work should be working, we could try it by doing mvn clean install freom the project home directory.
- To fix the resources, let’s do a small trick. Go to Java Build Path -> Source and select Link Source… At Linked folder location enter the path to our resources directory src/main/resources. The Folder name ma be any. I’d suggest creating a variable PROJECT_HOME or something, pointing to our project root, and then extending the link folders from there, so it’ll be portable, if we later move this project to other location or PC, or give it to someone else, it’ll be as easy as changing this variable for the project to work from another location.
This will cause the resources directory will be included in the classpath, and the internal deploy will put it in WEB-INF/classes.
- To make WTP find the .jar, we’ll use Eclipse for Maven plugin, which will also allow us to run Maven from isnide Eclipse, and make it easy to add dependencies.
Once installed, the pom.xml file will appear decorated with a small m at the top left corner of its icon. Right-click on the project root, and choose Maven2 -> Enable from the menu. This will add a new library named Maven 2 Dependencies to our project, containing all the dependencies detailed in the pom.xml. This is enough for the whole lot of dependencies to be in the classpath , so the project should compile inside Eclipse with no problems now. However, WTP still needs the dependencies to be at WebContent/WEB-INF/lib to make the internal deploy.
For this last detail, go to project properties, and at J2EE Module Dependencies, select the library Maven 2 Dependencies. This way, we’re letting WTP know to include Maven dependencies at WEB-INF/lib in its internal depoly.
Once all these steps are done, should be possible to satart a container inside Tomcat (I used Tomcat 5.5 or 6) and run our project as a webapp. We should be able to make builds with Maven from inside Eclipse as well, right-clicking on the project root and choosing the appropriate Run as… Finally, to add a new dependency. right-click on pom.xml and choose Maven2 -> Add Dependency, and in the query field we write part of the name of the dependency. Almost immediately, a tree with the found results will appear, we simply move until we find the desired package and choose it. The dependency will be added to the pom, the .jar will be downloaded into our local repository, and added to the Maven dependency library, therefore to the WTP internal deploy.
Note: don’t choose the project option Maven2 -> Update Source Folders, I don’t know exactly what its purpose is, but the immediate effect is to make the link created to the resource folder no longer a link, so WTP will not include it in its internal deploy.
Note 2: sometimes the resources in the link don’t deploy automatically when modified. To force a deploy, it’s as easy as refresh (F5) the linked folder.
Conclusions
To have Maven2 integrated into Eclipse WTP eases the whole development cycle of a webapp.
Starting with this, more resource folders, different builds with changes or different structures, different builds based on the same basecode, etc., can be added later. Maven2 documentation was never that great, but with a bit of patience the desired objectives can be achieved.
Powered by ScribeFire.
A consecuencia de haber leido este post, me puse a pensar que fue lo último que hice tratando de buscar la mejor solución, y no la que hubiera usado sino me ponÃÂa a investigar 5 minutos.
Y encontré este simple caso:
Como decÃÂa en un post anterior, estuve haciendo un programador (scheduler) de reportes. El requerimiento era que fuera flexible y fácil de usar, ya que los mismos usuarios finales lo van a utilizar para programar los reportes. Para el caso de la periodicidad, obviamente no podia simplemente poner un campo donde ingresar una expresión Cron, porque entonces nadia iba a ser capáz de usarlo. Se me ocurrió entonces poner una serie de opciones, que el usuario podÃÂa elegir: Una vez, Diario, Semanal, Quincenal, Mensual, etc.
Para la parte de interface del usuario, ya habia decidido usar Stripes para todo lo nuevo (e ir lentamente migrando lo antiguo de Struts). Stripes se basa fuertemente en Java 5, y por lo tanto trae algo muy interesante que es un tag de JSP stripes:enumName() , que sirve para mostrar los valores de una enumeración. Esto está buenÃÂsimo, porque me hago un loop y muestro todos los valores de un Enum como radio buttons:
<c:forEach var=”freq” items=”<%= ReportFrequency.values() %>”>
<s:radio value=”${s:enumName(freq)}” name=”definition.frequency” id=”definition.frequency.${s:enumName(freq)}” class=”radio”/>
<s:label for=”definition.frequency.${s:enumName(freq)}”>${s:enumName(freq)}</s:label>
</c:forEach>
Lo mejor de todo esto, es que cuano el valor llega a mi Action de Stripes, me llega como un Enum del tipo ReportFrecuency (gracias al maravilloso binding de Stripes). El detalle acá, es que los Enum de Java 5 no son simples constantes, sino que también pueden tener comportamiento.
Mi Enum ReportFrecuency esta definido asi:
public enum ReportFrequency {
Once(TriggerUtils.makeImmediateTrigger(0, 0)),
Hourly(TriggerUtils.makeHourlyTrigger()),
Daily(TriggerUtils.makeHourlyTrigger(24)),
//etc
private Trigger trigger;
ReportFrequency(Trigger trigger) {
this.trigger = trigger;
}
public Trigger getTrigger() {
return trigger;
}
}
O sea que luego, lo único que tengo que hacer cuando creo el Trigger para el Scheduler, es:
Trigger trigger = definition.getFrequency().getTrigger();
FacilÃÂcimo. Y agregar una nueva frecuencia solo implica agregar un nuevo valor al Enum, no hay que tocar absolutamente nada más!.
Ahora, cómo hubiera hecho sino hubiera usado Enums y Stripes? Lo mas asqueroso que se me ocurre es todo hardcodeado, con los valores en strings repetidos en la vista y en el action, y una serie de ifs para elegi el Trigger. Algo un poco mejor podrÃÂa haber sido usar constantes static final String (pero igualmente hubiera tenido que escribir cada radiobutton individualmente), y después haber usado algún factory para obtener el Trigger correspondiente.
Creo yo que esta es la mejor solución posible (por lo menos a mi no se me ocurrió nada mejor), y además es extremádamente fácil de mantener. Las otras soluciones implican escribir mas, y son difÃÂciles de mantener, porque hay que cambiar varias cosas en varios lados, y mantener esos cambios sincronizados. Es cierto, quizá haya tardado un poco más en implementar esta solución porque tuve que investigar un poco sobre Enums y Stripes, pero el resultado es mucho mas robusto, agregar alguna frecuencia en el futuro es una pavada, y además aprendàalgo nuevo.
Y la satisfacción de haber hecho las cosas bien, es impagable.
Sunday November 26th 2006, 9:24 pm
Filed under:
Java,
Spring
En el post anterior contaba que habÃÂa reemplazado Picocontainer en mi aplicación con Spring (de paso lean este post que me causó bastante gracia por lo oportuno).
La verdad es que esperé demasiado para volar a Pico, deberÃÂa haberlo hecho antes. Pero creo que arrugué porque es un cambio grande, y estaba teniendo demasiada carga de trabajo como para dedicar tiempo en algo que no iba a resultar visible, ya que desde el punto de vista del usuario, nada cambia.
Lo que finalmente me llevó a decidir el cambio, fue lo siguiente: necesitaba usar Quartz Scheduler para programar la generación automática de unos reportes. Como debia quedar programado continuamente, necesitaba que los jobs del scheduler fueran persistentes. Quartz ofrece un jobstore que usa JDBC, pero el problema es que usa su propia conexión a la BD, con su propio pool de conexiones, en este caso DBCP, mientras que yo vengo usando c3p0 desde hace tiempo (ya que Hibernate 3 no soporta más DBCP). Entonces, si lo hubiera usado de esa manera, me hubiera quedado la aplicación con dos connection pools independientes, y encima diferentes. Inaceptable.
Habia dos soluciones: escribir mi propia implementación de JobStore, o aprovechar que Spring ya lo hizo. Y como si hay algo que no me gusta es reinventar la rueda, pues…
Como cualquier tarea complicada, es mejor separarla en partes mas manejables. Entonces, mi primer objetivo fue reemplazar a Pico solamente en la función de Dependency Injection, dejando para más adelante otras integraciones que ofrece Spring. Sin embargo, debido a que Pico estaba integrado con Struts, proveyendo DI a las actions de Struts, de alguna manera tenia que usar la integración de Spring con Struts. Para eso tenÃÂa dos opciones:
Usar ContextLoaderPlugin, un plugin de Struts que hace que los actions se manejen como beans de Spring, y que las dependencias se inyecten automáticamente, pero que tiene la enorme desventaja de tener que duplicar la definición de beans: además de definir las actions en struts-config.xml, tambien lo tengo que hacer en action-servlet.xml, para definirles las dependencias. Encima, yo usé wildcards en la definición de las actions para hacer mas chico el archivo, y esto no esta soportado de ninguna manera, lo que implicarÃÂa además, reescribir el struts-config.xml para quitar los wildcards. Ni en pedo. Antes dije que la integración de Pico con Struts era un poco mejor que la de Spring, y es justamente por esto, con agregar un servlet filter, Pico inyecta las dependencias directamente en las actions usando autowire, no hay que hacer más nada.
Usar ActionSupport, una clase de la que hay que hacer heredar todas las actions. Esta clase provée un método para obtener el application context, y de ahàlos beans necesarios, pero el caso es que al final, la inyección de dependencias la tengo que terminar haciendo yo… tampoco me convence.
Sin embargo, la clave estaba en este última clase, ActionSupport. Si ActionSupport sabe como obtener un appicationContext, deberÃÂa haber alguna manera de inyectar las dependencias automáticamente, usando autowiring.
En el libro de Rod Johnson, muestran una manera de hacerlo:
public class AutowiringActionSupport extends ActionSupport {
protected void onInit() {
getWebApplicationContext().getBeanFactory().autowireByType(this,
AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true);
}
}
ActionSupport tiene un método onInit() que se llama desde el constructor. Por lo tanto, si todas las action heredan de esta clase, cada vez que se instancie una de ellas, se ejecuta este código y se inyectan las dependecias. Perfecto, justo lo que necesitaba!. Sin embargo, se vé que este código fue escrito hace mucho, ya que no funciona en Spring 1.2.8 ni en 2.x . El método getBeanFactory() no existe (fue reemplazado por getAutowireCapableBeanFactory()), y autowireByType() tampoco, fue reemplazado por dos métodos, autowire() y autowireBeanProperties(). El primero no me sirve porque devuelve una nueva instancia del bean (y recuerden que yo estoy dentro del bean), y el segundo no permite hacer autowire por tipo, menos aún inyección en el constructor (ya que el bean ya esta instanciado). Pero lo que sàse puede hacer es esto:
protected void onInit() {
getWebApplicationContext().getAutowireCapableBeanFactory().
autowireBeanProperties(this, AutowireCapableBeanFactory.AUTOWIRE_BY_NAME, false);
}
Esto significa que estoy inyectando las dependencias en el objeto actual, que las voy a inyectar según el nombre (Spring busca properties en el bean que tengan el mismo nombre que el bean id), y el false del final significa que no voy a verificar si queda alguna alguna propiedad del bean sin setear.
En sÃÂntesis, para que esto funcione, tuve que hacer los siguientes pasos:
- Hacer que mi action padre herede de ActionSupport.
- Sobreescribir el método onInit() en mi clase padre.
- Cambiar el nombre de algunas properties de las actions, para que coincidan exáctamente con los id de los beans.
- Agregar los setters que me faltaban (recuerden que antes usaba exclusivamente constructor injection).
Esto fue relativamente fácil gracias a las opciones de refactoring y creación de código de Eclipse.
El resto fue simplemente terminar de armar el applicationContext.xml como para cualquier aplicación Spring, para lo que simplemente modifiqué los archivos xml de Pico.
Ahora ya tengo la aplicación con Spring en producción y andando sin problemas, algo de lo que estoy muy contento. El próximo paso será aprovechar las integración con Hibernate, para lo que pienso implementar un DAO genérico, como se explica en este artÃÂculo.
Saturday November 18th 2006, 1:29 pm
Filed under:
Java,
Spring
Hace mucho que querÃÂa escribir esto, pero por una razón u otra lo fui posponiendo, pero ahora que finalmente reemplacé Pico por Spring en la aplicación, creo que es un buen momento.
El proyecto en el que vengo trabajando hace mas de un año, usaba originalmente Picocontainer como container de dependency injection (DI). La razón, sin entrar mucho en detalles, fue que la persona que actuaba como arquitecto en ese momento, es uno de los commiters del proyecto Pico, y por lo tanto lo impuso a pesar que el resto del equipo se inclinaba por Spring, con el que ya tenÃÂamos experiencia previa (como comentario al margen, tampoco nos dejó usar Java 5…).
En primer lugar, aclaremos que Pico cumple su cometido impecablemente. Provee DI a una aplicación web sin problemas, provee constructor injection o setter injection (aunque esta última vaya uno a saber como se hace). Para poder externalizar la configuración a un XML externo, o bien a un script Groovy o Jyton, hay que usar Nanocontainer, otro paquete de los mismos autores. Hasta aquàtodo muy bien, pero la pregunta es, por qué un proyecto deberÃÂa usar Pico en lugar de Spring? Qué tiene mejor Pico que Spring, o que ofrece Pico que no pueda ofrecer Spring?
Veamos un poco en detalle:
Pico es muy “liviano”.
Es cierto, el tamaño de los .jar de Pico + Nano + Nanowar apenas pasa los 300Kb, mientras que el paquete completo de Spring son mas de 2Mb. Sin embargo, Spring también se distribuye en varios .jar separados y uno puede elegir deployar solo lo que uno va a usar. Pero en el peor de los casos, cuál es el problema para una aplicación que corre siempre en un mismo server? Quizá el deployment que hago una vez por semana tarde 20 seg. mas.
Pico provee constructor injection.
Si bien la gente de Spring promueve setter injection, a diferencia de la de Pico que promueve constructor injection (no me voy a explayar en esto, cada uno tiene sus méritos), no significa que que Spring no soporte constructor injection. De hecho, lo soporta mejor que Pico, paso a explicar:
Por default, Pico trabaja con autowire, lo que significa que automágicamente descubre que beans tiene que inyectar según el tipo de los beans y los tipos definidos en el constructor. Pico enfatiza mucho este aspecto, porque según ellos ayuda a mantener el XML de configuración en un tamaño reducido. Por ejemplo, si tenemos la siguiente clase:
package com.sarcobjects.persistence.hibernate;
public class HibernateGamePersister extends HibernatePersisterSupport implements GamePersister, Serializable {
public HibernateGamePersister(SessionFactory factory) {
super(factory);
}
…
}
El constructor necesita una instancia de la clase SessionFactory. Entonces, este fragmento de XML es suficiente para configurarlo:
<component-implementation class=’com.sarcobjects.persistence.hibernate.SessionFactory’ />
<component-implementation class=’com.sarcobjects.persistence.hibernate.HibernateGamePersister’/>
Pico descubre que el tipo que necesita inyectar es SessionFactory, lo busca en el XML y si lo encuentra lo inyecta.
El equivalente en Spring serÃÂa:
<bean id=”gamePersister” class=’com.sarcobjects.persistence.hibernate.HibernateGamePersister’ autowire=”constructor”/>
La diferencia es que le tengo que agregar autowire=”constructor” porque para Spring el default es setter injection.
Hasta acá todo muy lindo, pero el problema aparece cuando le tengo que pasar además, por ejemplo, una constante, o bien el archivo de configuración contiene mas de una definición del mismo tipo. Ahàya pierdo todo lo automágico y no me queda mas remedio que explÃÂcitamente definir que quiero inyectar. Por ejemplo:
<component-implementation class=”com.sarcobjects.web.admin.dashboard.QuartzDashboardScheduler”>
<parameter key=”service” />
<parameter key=”factory” />
<parameter key=”gameStatus” />
<parameter>
<int>10</int>
</parameter>
</component-implementation>
Aquànecesito pasarle un valor directamente desde la configuración, pero si le paso solamente el parámetro int, Pico no sabe de donde sacar los otros parámetros, y por lo tanto tengo que definirlos explÃÂcitamente. La misma configuración en Spring serÃÂa:
<bean class=”com.sarcobjects.web.admin.dashboard.QuartzDashboardScheduler” autowire=”constructor”>
<constructor-arg index=”3″ value=”6″ />
</bean>
Sólo tengo que incluir el parámetro que Spring no puede deducir. No es que Spring sea mas inteligente, pero la configuración nos permite mezclar parámetros implÃÂcitos con explÃÂcitos, con el objeto de mantener el tamaño del XML al mÃÂnimo.
Pico soporta diferentes scopes para los beans: application, session y request.
Esto era cierto hace un año, pero ahora Spring 2 también soporta session y request, además del clásico application. Sin embargo, en esta aplicación jamás usamos otros scope que no fuera application.
Pico se integra con Struts y WebWorks, con Hibernate y tiene soporte para AOP.
La integración con Struts esta bien, hasta quizá sea levemente superior a la de Spring. La integración con Hibernate no pasa de soportar un SessionFactory, y la de AOP no tengo idea, quiza si hubiera documentación…
Spring obviamente se integra con estos y muchos mas Frameworks, y de una manera mucho mas completa.
En sÃÂntesis, Spring puede hacer todo lo que Pico hace. Sin embargo, si hacemos la pregunta al revés, puede hacer Pico todo lo que Spring hace?
Definitivamente, no. Para empezar, la documentación. Nosotros usamos Pico en un nivel aceptable porque tenÃÂamos a uno de los autores en el equipo. Pero no veo cómo alguien que quiera usar Pico pueda sacarlo andando razonablemente con la documentación que se ofrece en el sitio. En contraposición, Spring tiene una muy buena documentación, varios libros, y miles de desarrolladores en todo el mundo que nos pueden ayudar.
No voy a enumerar todas las facilidades que además aporta Spring (se pueden encontrar por toda la web) para desarrollar una aplicación, pero por ejemplo, el soporte para demarcación declarativa de transacciones me parece buenÃÂsimo. De hecho, la arquitectura impuesta por este arquitecto no permite tener transacciones que abarquen mas de una tabla! O sea que básicamente nuestra aplicación no es transaccional.
En fin, cuándo se deberÃÂa usar Pico? Quizá en aplicaciones chicas, puntuales, que no necesiten integrarse con demasiados otros frameworks, que necesite ser distribuida a muchos puntos.
Pero en aplicaciones mediana a grandes, transaccionales, con integración a otros frameworks, con acceso a web services, y que pueda escalar., no me cabe duda que Spring es una solución muy recomedable.
En el próximo post voy a contar lo que tuve que hacer para volar Pico e instalar Spring en la aplicación.
Update 26/11: Olvidé comentar que el SpringIDE para Eclipse, facilita todo aún mas al manejar los archivos .xml de Spring, con el autocompletado de elementos, y nombres de clases. Permite clickear en un bean y abrir la clase respectiva en el editor. Nos avisa si hay una referencia a un bean que no existe, o si falta algun setter o constructor para poder inyectar todas las dependencias. Y como si esto fuera poco, además nos genera un gráfico con el mapa de los beans, aunque claro, esto solo funciona cuando las dependencias estan marcadas explÃÂcitamente, y no con autowire (aunque podrÃÂa, supongo que una próxima versión lo incluira).
Thursday June 01st 2006, 4:27 pm
Filed under:
Java
Leí esto y recordé que quería escribir un poco sobre este tema.
Hay gente que le da mas importancia al test coverage que a los tests en si.
Te dicen, el coverage tiene que estar al 100% (o al 90% o al procentaje que sea). La cuestión es que de esa forma, uno termina perdiendo el tiempo escribiendo tests para código trivial, como por ejemplo los getters y los setters. Esto es peligroso, porque el desarrollador lazy (y todos somos algo lazy), va a empezar a hacer trampa. Es perfectamente posible (y facil) escribir tests "dummy", que en realidad no hagan nada, pero recorran el código. Eso hace que el test coverage salga fenómeno, todos felices, pero en realidad los tests no sirven para nada.
Que yo sepa no existe una herramienta que de alguna manera indique la calidad de los tests, creo que eso seria bastante mas útil que el test coverage. Con esto no quiere decir que el test coverage no tiene su utilidad, porque puede haber casos donde nos pasemos por alto alguna parte donde los test sean importantes, y el test coverage nos avisa, y por eso tiene que estar en el proceso de building.
Concretamente, a lo que voy es que no tiene mucho sentido decir "la aplicación tiene un 100% de test coverage", porque eso realmente no me dice nada, ni me asegura que la calidad del producto sea alta.
Una de las cosas que se criticaba de Hibernate 2 era que sus excepciones (HibernateException para ser específico) eran checked, y por lo tanto no habia mas remedio que catchearlas en todos los métodos que las tiraran, o bien tirarlas explícitamente. Spring presentó una solución a esta situación, ya que si uno usaba el HibernateTemplate provisto con Spring, HibernateException era reemplazada excepciones unchecked, y por lo tanto no era necesario catchearlas en todos lados. Mas aún, HibernateException era reemplazada por toda una jerarquía de excepciones, que permitian tener un conocimiento mas acertado del error ocurrido en la base de datos. Encima de todo, esta jerarquía era la misma para cualquier BD, y para cualquier método de acceso a BD soportado por Spring, ya sea Hibernate, JDBC, JDO, iBatis o TopLink. Asi teníamos por ejemplo DataIntegrityViolationException (cuando intentamos insertar o updatear un registro que viola alguna constraint), BadSqlGrammarException (cuando tratamos de ejecutar algún SQL inválido), etc.
Esto a mi entender tenia dos beneficios: por un lado, no es necesario catchear ni tirar las excepciones en la capa de acesso a datos, se puede hacer en una capa mas alta, y por otro, puedo tener una idea mas acertada de lo que realmente pasó en la BD, todo esto independientemente de la BD o el mecanismo de acceso a la misma que este usando, y sin crear una dependencia a ésta.
Con Hibernate 3, la cosa cambió un poco. Si bien es perfectamente posible seguir usando el HibernateTemplate de la misma manera, Hibernate 3 ofrece por sí mismo algunas cosas mas.
Para empezar, HibernateException es ahora unchecked y por lo tanto ya no es necesario catcharla en la capa de acceso a datos. Esto que a priori puede parecer una gran ventaja, quizá no lo sea tanto, ya que si dejamos que HibernateException suba por todas las capas y la catcheamos quizá cerca de la vista, estamos introduciendo una dependencia con Hibernate en todas las capas. Puede que esto no nos importe en algún caso, pero si queremos que nuestra aplicación tenga todas sus capas bien diferenciadas e independientes como para que en el futuro podamos reemplazar alguna sin mayores problemas, esto es incorrecto.
Por otro lado, Hibernate 3 agregó también una jerarquía de excepciones que dependen de HibernateException. Tenemos por ejemplo, ConstraintViolationException (cuando intentamos insertar o updatear un registro que viola alguna constraint), JDBCConnectionException (si perdimos la conexión a la BD), etc
De esta manera, es posible manejar las excepciones de una manera similar a Spring. Otra novedad de Hibernate 3, pero que escapa un poco al alcance de este artículo, es SessionFactory.getCurrentSession(), que permite mantener una session abierta a lo largo de la aplicación y usarla cuando la necesitemos, sin necesidad de abrirla y cerrarla manualmente cada vez.
Todo esto implica que con Hibernate 3 es posible tener nuestros DAOs como simples POJOs, sin necesariemente tener que usar Spring. Por supuesto que todavia estamos dejamos de lado algunas ventajas, como por ejemplo manejo de transacciones en forma declarativa, pero es una alternativa.
Por ejemplo, en mi proyecto actual no usamos Spring (no porque yo no quiera ni porque no lo considere bueno, sino por otras razones que ya comentaré en otro post), y dentro de mis Hibernate DAOs, el approach que use para manejar las excepciones es algo así:
public void saveCustomers(Customer[] customers) throws PersistenceException {
String msg = null;
try {
msg = getCustomerNames();
saveOrUpdate(openSession(), customers);
} catch (ConstraintViolationException cve) {
throw new PersistenceException("persister.customer.save.constraint.exception", new Object[] {msg, cve.getConstraintName()}, cve);
} catch (HibernateException e) {
throw new PersistenceException("persister.customer.save.failed", new Object[] {msg}, e);
}
}
Yo particularmente prefiero que las excepciones de Hibernate no salgan de la implementación de los DAOs y por lo tanto, las reemplazo con PersistenceException (que forma parte de una jerarquia de excepciones propia de mi aplicación), pero les pongo el mensaje que yo quiero, de manera de informarle el error al usuario de la manera mas amigable y acertada posible. Noten que ConstraintViolationException provee un método getConstraintName(), que devuelve el nombre de la constraint violada. Si en la base de datos uno se toma el trabajo de ponerle nombres lo suficientemente descriptivos a las constraint, el usuario va a terminar viendo una descripción bastante correcta de cual fue el problema, y hasta quizá pueda resolverlo por si mismo, o en el peor de los casos un administrador no va a tener que andar buscando en enormes logs cual fue el problema para poder resolverlo.
En una capa superior de mi aplicación:
private String saveCustomer(StrutsCustomerForm form) {
Customer customer = null;
try {
validateCustomerForm(form);
customer = toCustomer(form);
customerDAO.saveCustomers(new Customer[] {customer});
} catch (Exception e) {
addMessageException(e);
saveMessages();
return ERROR;
}
return SUCCESS;
}
Aquí simplemente cacheo Exception, porque sé que todas las otras capas de mi aplicación ya reemplazaron las excepciones respectivas por alguna de mi jerarquia propia y le pusieron un mensaje (que dicho sea de paso, es un key de un resource bundle, ya que mi jerarquia de excepciones sobreescribe getLocalizedMessage() para soportar i18n), que es lo que termina en la pantalla del usuario.
Wednesday March 15th 2006, 3:45 pm
Filed under:
Java
Este es un problema que aparentemente le ocurre a bastante gente, incluyéndome.
La mayorÃÂa de los artÃÂculos sobre JSTL dicen que hay que poner el siguiente tag para poder usar los tags de JSTL con expresiones EL:
< %@ taglib uri="http://java.sun.com/jstl/core" prefix="c"%>
Pero al escribir en el JSP algo asi como:
<c:if test="${param.isPopup}" >
nos encontramos conque el JSP no se muestra, y aparece una linda excepción:
According to TLD or attribute directive in tag file, attribute test does not accept any expressions
Lo qué? No era que se podian usar las expresiones EL? Con esto uno se puede romper la cabeza varias horas, hasta que en algún lugar alguien sugiere usar:
< %@ taglib uri="http://java.sun.com/jstl/core_rt" prefix="c"%>
Entonces todo funciona bien, pero cómo puede ser si ese uri es para run time expressions, las antiguas y limitadas expresiones de JSP?
La respuesta a esto yace en Tomcat. La version 5.x soporta JSP 2.0, que incluye soporte para expresiones EL. Entonces, al usar la version EL de los tags JSTL, las expresiones se intentan evaluar dos veces: una por Tomcat, y otra por los tags. La versión RT no intenta evaluar la expresión EL que ya evaluó Tomcat, y por lo tanto funciona.
La solución correcta? Usar el uri de la versión 1.1 de JSTL, que es este:
< %@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
Corolario:
No olvidemos que como Tomcat evalúa las expresiones EL directamente, éstas se pueden escribir en el código HTML sin necesidad de usar el tag <c:out>, por ejemplo:
<h1>${form.pageTitle}</h1>
Monday February 27th 2006, 2:15 pm
Filed under:
Java
Este es un artÃculo que escribà para el blog de mi amigo Niko.
Pueden leerlo acá: http://nikofactory.blogspot.com/2006/02/hibernate-annotations.html
Friday February 24th 2006, 7:13 pm
Filed under:
Java
Todos estos años desarrollando Web Applications, usando Tomcat y Eclipse (primero con el plugin Sysdeo, mas recientemente con el WTP). De esta manera puedo correr el Tomcat dentro de Eclipse, arrancarlo, pararlo y hacer debug como quiero, todo muy cómodo. Lo único malo era que cada vez que modificaba algo en una clase, tenia que reiniciar el contexto (Eclipse lo hace automáticamente, asique no me parecia tan malo), lo que llevaba unos segundos, mas lo que tardaba en iniciar Spring y/o Hibernate, mas reiniciar la aplicación desde el login otra vez. Todo esto no llevaba mas de un minuto, pero multiplicado por varias veces al dia, mas lo que uno puede distraerse mientras espera (a ver si agregaron algun update en LIA? Ufa, no.), me hacia perder un tiempito.
Hete aquÃ, que muchas veces cuando Eclipse restarteaba el contexto en Tomcat, aparecÃa una ventana con el mensaje Hot code replace failed, al que nunca le presté demasiada atención. Pero hoy, por alguna razón que desconozco (posiblemente por puro azar), una neurona se me conectó con otra, y relacioné Hot Code Replace con modificacion de mis clases, y me pregunté: por qué corcho tengo que restartear el contexto cada vez que modifico algo?
Me puse a investigar, y lo primero que encontré fue la opción Auto Reload en la configuración de Web Modules. La puse en false, y probé. Cambié algo en una clase, la grabé, la publiqué manualmente a Tomcat, recargué la página y funcionó!!! Pero en ese momento Eclipse me restarteó el contexto nuevamente.
Ahi me dà cuenta que el problema esta en Eclipse, que fuerza el restarteo del contexto sin importarle un cazzo de nada en cuanto detecta un cambio. Googleando un poco, encontré esto que me dió la pauta que WTP no hacia las cosas del todo bien. También và que habia salido la versión 1.0.1 de WTP, asique actualicé. Volvà a probar y ahora funciona bien (Eclipse no restartea el contexto), aunque no estoy seguro si fue por la 1.0.1 o alguna otra razón (como por ejemplo reiniciar Eclipse luego de haber cambiado la configuracion del Web Module).
De paso aprendà que el hot code replace funciona con limitaciones, por ejemplo, no soporta que se agreguen o quiten métodos de una clase, ni que se renombren. Pero para muchos casos es una bendición.
Asique he encontrado la luz, a partir de ahora todo sera mucho mejor, mi productividad aumentará de manera exponencial, la gente me sonreirá por la calle y probablemente me voy a ganar el Euromillions. Qué lindo!
Wednesday September 01st 2004, 10:57 pm
Filed under:
General,
Java
Hoy empec� en Globant. Mi comienzo (y el de otras tres personas), coincidi� con la inauguraci�n de un nuevo piso de oficinas, que se agrega al que ya tenian, efectivamente duplicando el total de m2 de oficinas que la empresa posee.
Las oficinas estan muy bien, son muy c�modas, tengo un escritorio muy amplio en L, y un monitor LG Flatron de 17″ (fundamental!!!).
Nos recibieron muy bien, hubo medialunas y frutas para el desayuno. Para el mediod�a hubo un �gape por la inauguraci�n de las nuevas oficinas, con sanguchitos, gaseosas y masitas.
Mas tarde habl� con una inglesa, que probablemente sea mi project manager, pero a�n no estoy asignado a un proyecto.
Estuvimos instalando nuestras PC (por suerte tenemos WinXP, y con total libertad para instalar y desinstalar lo que querramos).
Los tel�fonos IP son una maravilla, con un nro. de interno nos conectamos a Londres, y tambi�n podemos hablar a cualquier parte, con un headset conectado a la placa de audio. Si nos dejan un mensaje en la casilla de voz, el sistema nos manda un mail con el mensaje attachado en un .wav.
En fin, creo que todo va a ir bien, los mantengo al tanto.