El dilema

Cuando se están diseñando aplicaciones Java que serán desplegadas en un Servlet Container, Web Profile o full JavaEE Application server, se puede llegar a la necesidad de utilizar dependencias (.jars) que no son parte de la especificación estandard y por consiguiente entrar en el dilema: 
  1. Que el empaquetado (.war) incluya dicha(s) dependencia(s). Comúnmente utilizando un sistema gestor de dependencias como por ejemplo maven y su scope: compile.
  2. Que la(s) dependencia(s) se encuentre(n) en el container donde la aplicación será desplegada. En maven utilizando el scope: provided para evitar que el gestor de dependencia incluya los jars dentro del empaquetado.

Seleccionando una solución

Seleccionando para el resto de este artículo la opción número 2 y tomando como ejemplo el Servlet Container más famoso de mundo, Apache Tomcat, a continuación listo algunas de las ventajas y desventajas de dicho enfoque:

Ventajas:
  • Optimización de espacio de disco duro en el sistema host de la instalación de Tomcat
  • Estandarización en los ambientes de deployment
  • Empaquetados (.war) con menor tamaño que contribuyen a reducir tiempos en pipelines de Integración y Entrega continua.
  • Útil cuando lo que se promueve entre ambientes es el artefacto (.war) y no el código fuente.
Desventajas:
  • La instalación de tomcat empieza a convertirse en una personalizada y no estándar.
  • Requiere que tanto los ambientes locales de desarrollo como los diferentes ambientes de producción estén sincronizados con la última versión personalizada de Tomcat.
  • Cambios en una dependencia es replicable para todas las aplicaciones desplegadas en la instancia de Tomcat y si estas poseen diferentes arquitecturas se tendrá que tomar una decisión de no realizar el cambio o desplegar las aplicaciones incompatibles en otra instancia de tomcat. el resultado final será una nueva instancia personalizada.

Class Loaders 101 en apache Tomcat

Acorde a la documentación oficial tomcat provee 3 jerarquías de Class Loaders:
  • Bootstrap: Carga las clases básicas de ejecución provistas en los jars de la JRE que utiliza la instancia de Tomcat.
  • System: Se inicializa a partir del contenido de la variable de ambiente classpath.
  • Common: Carga clases adicionales que normalmente se encuentran en jars ubicados en: $CATALINA_HOME/lib
  • Webapp: Para cada aplicación desplegada tomcat crea un class loader que carga clases ubicadas en /WEB-INF/classes  y clases dentro de jars ubicados en /WEB-INF/lib
La figura 1 describe la jerarquía:
      Bootstrap
|
System
|
Common
/
Webapp1 Webapp2 ...
Figura 1.
La Figura 2 describe el orden por default de class loaders que una aplicación desplegada enTomcat 9 utiliza en orden ascendente:
                 Bootstrap (1)
|
System (4)
|
Common (5)
|
WebappX
/
/WEB-INF/classes (2) /WEB-INF/lib/*.jar (3)
Figura 2.

Iniciando a implementar la solución

Un primer enfoque que en muchos tutoriales realizan con los JDBC Drivers es posicionar los jars en la carpeta: $CATALINA_HOME/lib. Esto hará que el class loader Common incluya dichas dependencias en el orden y jerarquías especificados en las figura 2.

Optimizando la solución

Por simple lógica se pude observar en la Figura 1. que tanto System como Common  ponen a disposición de las aplicaciones las clases que cada uno carga, sin embargo la recomendación es que no utilicemos Common (por ende la ubicación $CATALINA_HOME/lib) para exponer clases de jars que queremos compartir entre las aplicaciones desplegadas en la instancia de Tomcat.
Un enfoque recomendado es posicionar las clases y/o dependencias necesarias en los directorios:
$CATALINA_HOME/shared/lib y
$CATALINA_HOME/shared/classes

Quedando como último paso indicarle a tomcat que tome en consideración las rutas anteriormente descritas para que este nuevo class loader (shared) sea tomando en consideración luego de common. Para realizar esto debemos de editar el archivo: 
$CATALINA_HOME/conf/catalina.properties
y dentro del archivo especificar las rutas en:
shared.loader=»${catalina.base}/shared/classes»,»${catalina.base}/shared/lib/*.jar»

Un error que se ha estado reportando mucho en los talleres, Hackatones y demás actividades de iniciación a Java EE 7 es el derivado al intentar anotaciones del paquete javax.enterprise cuando se realiza un proyecto en Netbeans  8.01 con Glassfish 4.1. A continuación se detalle el Problema, el efecto y las posibles soluciones:


Ambiente:
Java JDK 1.7.0_45
Netbeans 8.0.1
Glassfish 4.1

Proyecto creado:

El Problema:
Cuando se desea utilizar la anotación de CDI 1.1 SessionScoped (habiendo indicado en la creación del proyecto que la biblioteca son dadas por el Servidor, en este caso Glassfish 4.1), Netbeans no resuelve javax.enterprise.context.SessionScoped.

La Solución V1:
Dado que Netbeans solamente reconoce javax.faces.bean.SessionScoped, pero debido a que esa no es la anotación que queremos usar y que el objetivo de este post no es comparar javax.faces.bean.SessionScoped con javax.enterprise.context.SessionScoped, la solución (al momento de escribir este post) es agregar en las propiedades del proyecto la biblioteca referente a JavaEE 7 dado que por alguna razón Netbeans no lo resuelve en Glassfish 4.1.

La Solución V2:
Susitutir Glassfish 4.1 por Glassfish 4.0 e indicar a Netbeans que utilice está anterior versión de GlassFish.

La solución V3:
Crear un proyecto en donde las dependencias sean utilizadas bajo un Project Object Model, en otras palabras: Utilizar Maven. Esta es mi solución cinco estrellas ;).

El 8 parece ser un número importante en la comunidad Java estos días… Dando secuencia a las encuestas publicadas por el JCP, ya están disponibles los resultados de la encuesta comunitaria de Java para darle forma al próximo lanzamiento de Java EE. Lo más interesante de estos resultados es que de nuevo se invita a […]