Bitsmi Blog
OCP7 11 – Hilos (03) – Mecanismos básicos en la plataforma Java
En este artículo se exponen los mecanismos básicos que proporciona la plataforma estándar de Java para la implementación de tareas concurrentes detallada para su versión 7.
Se tratarán los siguientes conceptos:
- Implementación de tareas mediante hilos de ejecución
- Gestión del ciclo de vida de los hilos de ejecución mediante API
OCP7 08 – Excepciones (II)
Extensión de los tipos de excepción estándar
En la medida de lo posible se deberá hacer uso de la jerarquía de excepciones provistas por la JDK. En caso de querer crear nuevos tipos, se deberán tener en cuenta los siguientes puntos:
- Extender de
RuntimeException
o una de sus subclases en caso de definir un tipo de error no recuperable (Excepción no comprobada). - Extender de
Exception
o una de sus subclases (a excepción de RuntimeException) en caso de definir un tipo de error recuperable (Excepción comprobada) que debe ser explícitamente declarado y capturado. - No extender directamente de la clase
Throwable
, ya que la mayoría de tratamientos de errores mediante bloquestry/catch
se hace como mínimo a nivel deException
. En estos casos, las excepciones derivadas directamente deThrowable
no serían capturadas por estos bloques lo que podría provocar efectos no previstos.
Tratamiento y propagación de excepciones
Se puede encontrar más información aquí.
Propagación entre métodos
Cuándo se gestionan las excepciones, hay ocasiones que se desea relanzar una excepción que está siendo gestionada. Un programador novicio cree que el código siguiente puede hacer esto:
public class EjemploExceptionRethrow {
public static void demoRethrow()throws IOException {
try {
// Se fuerza el lanzamiento de una IOException cómo ejemplo,
// Normalmente la excepción es disparada por la ejecución de código.
throw new IOException(“Error”);
}
catch(Exception exception) {
/* Se trata la excepción y se relanza */
throw exception;
}
}
public static void main(String[] args) {
try {
demoRethrow();
} catch (IOException exception) {
System.err.println(exception.getMessage());
}
}
}
El código anterior no compilará correctamente en caso de hacerse con versiones de Java anteriores a 7 ya que el método demoRethrow
explicitamente especifica en su firma que la excepción
lanzada es de tipo IOException
, mientras que el bloque catch
declara la excepción capturada y relanzada de tipo Exception
.
En este caso el compilador no es capaz de inferir el tipo real de la excepción. En caso de la compilación del código se realice sobre la versión 7 o posterior,
el código anterior será válido y compilará correctamente. En este caso, el compilador sí es capaz de inferir el tipo final al que pertenece de la excepción relanzada analizando las excepciones
lanzadas dentro del bloque try
.
El siguiente ejemplo muestra otro modo de gestionar la excepción y propagarla.
public class EjemploAntiguoExceptionRethrow {
public static demoRethrow() {
try {
throw new IOException("Error");
}
catch(IOException exception) {
/*
* Se trata la excepción y se relanza
*/
throw new RuntimeException(exception);
}
}
public static void main(String[] args) {
try {
demoRethrow();
} catch (RuntimeException exception) {
System.err.println(exception.getCause().getMessage());
}
}
}
El problema con el código anterior es que realmente no está relanzando la excepción original. La está encapsulando con otra excepción, lo que implica que el código del catch
del main
necesita gestionar
la excepción con la que se ha encapsulado el catch
original.
Captura de excepciones mediante bloque try/catch/finally
Java 7 permite capturar múltiples excepciones en un mismo bloque catch
. Este mecanismo es llamado multicatch.
try {
// Ejecución que puede generar uno de los errores catch
} catch(SQLException e) {
System.out.println(e);
} catch(IOException e) {
System.out.println(e);
} catch(Exception e) {
System.out.println(e);
}
A partir de Java 7 se puede implementar el multicatch.
try {
// Ejecución que puede generar uno de los errores catch
} catch(SQLException | IOException e) {
System.out.println(e);
} catch(Exception e) {
System.out.println(e);
}
A destacar la utilización del carácter pipe |
para separar los nombres de las clases. El carácter pipe |
entre los nombres de las excepciones es el mecanismo
cómo se declaran las múltiples excepciones a ser capturadas por el mismo catch
.
Para un mayor detalle en el siguiente enlace se puede encontrar más información sobre la implementación del multicatch.
Bloque try with resources
Implementación de la interfaz Closeable
. Para evitar que recursos como ficheros, comunicaciones con la BBDD u otros servicios queden en un estado indeterminado tras un error,
Java cuenta con mecanismos para evitar este tipo de situaciones, uno de los cuáles es el try-with-resources, Similar al try
pero con las siguientes diferencias:
- Diferencia de que entre paréntesis se declaran aquellos recursos que se desean proteger.
- Un recurso siempre debe ser cerrado después de que termine el programa.
- Un recurso es cualquier objeto que implemente la clase
java.lang.AutoCloseable
.
Los recursos de este bloque de código deben implementar la interfaz java.io.Closeable
(que hereda de AutoCloseable
– se recomienda mejor utilizar este objeto).
Utilizando este bloque de código el programador no tiene que preocuparse de cerrar los recursos utilizados dentro del try-with-resources.
Una de las ventajas de Autocloseable
consiste en poder llamar varias veces a su método close()
obteniendo siempre el mismo resultado.
Las excepciones generadas durante el proceso de cierre de un recurso son totalmente ignoradas.
Es importante indicar que el orden del cierre de recursos es el opuesto al orden de apertura de esos recursos: el primer recurso en abrirse es el último recurso en cerrarse.
Ejemplo para definir un nuevo objeto try-with-resources
package TryWithResources;
public class NewResource implements AutoCloseable{
String closingMessage;
public NewResource(String closingMessage) {
this.closingMessage = closingMessage;
}
public void doSomeWork(String work) throws ExceptionA{
System.out.println(work);
throw new ExceptionA("Exception thrown while doing some work");
}
@Override
public void close() throws ExceptionB{
System.out.println(closingMessage);
throw new ExceptionB("Exception thrown while closing");
}
public void doSomeWork(NewResource res) throws ExceptionA{
res.doSomeWork("Wow res getting res to do work");
}
}
Ejemplo de su utilización en la ejecución de un programa
package TryWithResources;
public class TryWithRes {
public static void main(String[] args) {
try(NewResource res = new NewResource("Res1 closing")) {
res.doSomeWork("Listening to podcast");
} catch(Exception e) {
System.out.println("Exception: "+e.getMessage()
+" Thrown by: "+e.getClass().getSimpleName());
}
}
}
Ejemplo cuándo existen recursos anidados ambos derivando de AutoCloseable
.
package TryWithResources;
public class TryWithResV2 {
public static void main(String[] args) {
try (NewResource res = new NewResource("Res1 closing");
NewResource res2 = new NewResource("Res2 closing")) {
try (NewResource nestedRes = new NewResource("Nestedres closing")) {
nestedRes.doSomeWork(res2);
}
} catch (Exception e) {
System.out.println("Exception: " + e.getMessage()
+ " Thrown by: " + e.getClass().getSimpleName());
}
}
}
Nótese el uso del carácter » ; » cómo separador en la declaración de los recursos dentro del bloque try-with-resources.
Tratamiento de excepciones no capturadas
En el procesado de hilos (Threads) puede producirse que su ejecución termine de forma abrupta debido a que se ha producido una excepción y ésta no ha sido capturada.
Java dispone a partir de la versión 5 de la Interface UncaughtExceptionHandler
perteneciente al paquete java.lang. Esta interface es invocada cuándo un hilo (Thread)
termina abruptamente su ejecución debido a una excepción no capturada.
El siguiente código de ejemplo muestra la utilización de esta Interface.
public class MultiplexUncaughtExceptionHandler implements UncaughtExceptionHandler {
private final UncaughtExceptionHandler[] handlers;
public MultiplexUncaughtExceptionHandler(UncaughtExceptionHandler... handlers) {
super();
this.handlers = Arrays.copyOf(handlers, handlers.length);
}
public void uncaughtException(Thread t, Throwable e) {
for (UncaughtExceptionHandler handler : handlers) {
try {
handler.uncaughtException(t, e);
} catch (Throwable th) {
th.printStackTrace();
}
}
}
}
Se puede encontrar más información en este enlace oficial de Oracle.
OCP7 13 – Localización
Las especificaciones para el uso de recursos en formato de fichero de propiedades a través de la API ResourceBundle de Java definen unas reglas para la nomenclatura y ubicación de dichos ficheros que hay que seguir de forma que la JVM pueda localizar y recuperar los recursos definidos en ellos. No obstante, dicha API proporciona mecanismos que permiten personalizar este proceso. En este POST se explica cómo hacerlo.
Spring Boot – Configuración del contenedor subyacente
Una de las principales características del framework Spring Boot es permite la ejecución de aplicaciones web sin necesidad de usar servidores de aplicaciones externos donde desplegarlas. Esto lo consigue mediante el uso de un contenedor incrustado en la misma aplicación (Tomcat, Jetty o Undertow). Aunque el framework configura por defecto este contenedor con valores válidos en muchos de los casos, a veces las peculiaridades de las aplicaciones hacen que se tengan que modificar ciertos parámetros de la configuración, como por ejemplo el soporte para HTTPS o el tamaño máximo peticiones de las peticiones, por poner algunos ejemplos. En este post se explicará la manera de personalizar estos valores.
SQL – Encapsulado de sentencias complejas
En muchas ocasiones el acceso a un conjunto de datos almacenado en base de datos requiere de una consulta especialmente compleja. En los casos en que se debe permitir acceso a sistemas externos a estos datos, la solución ideal pasa por implementar una capa de negocio intermedia entre la aplicación consumidora de la información y la base de datos, sea mediante servicios web, un data-grid u otra componente que permita abstraer la complejidad de la obtención de los datos para facilitarla al sistema externo. Hay casos en que esto no es posible y por requerimientos de arquitectura en el sistema, las aplicaciones externas deben acceder directamente a la base de datos para obtener la información. En estos supuestos, es buena idea implementar una interfaz para la obtención de los datos, una API que permita crear una caja negra sobre la implementación real de la obtención de los datos. Con ello se consigue: Las modificación de las estructuras subyacentes de la base de datos no afectan a la integración con el sistema externo al mantenerse la interfaz de obtención de datos. Permite mantener el control de cómo se obtienen los datos, lo que minimiza la posibilidad de errores al estar centralizada en una única implementación. Ante la aparición de errores, se minimiza el tiempo necesario para la corrección, ya que estaría localizada en un único punto: La implementación de la interfaz. En este post se propone un posible mecanismo para construir estas interfaces de acceso aplicado a bases de datos Oracle.
SQL – Expresiones de Tablas Comunes (CTE)
Las Expresiones de Tabla Común, en inglés Common Table Expressions (CTE), se pueden definir cómo la especificación de un conjunto de resultados temporales que se obtienen
a través de una subconsulta determinada. El ámbito de aplicación de este conjunto de datos queda restringido a una ejecución concreta de la instrucción SQL
en la que se encuentra definida (SELECT
, INSERT
, UPDATE
o DELETE
), momento a partir del cual, dichos resultados son eliminados del contexto de ejecución.
Dicho de una manera un tanto simplista y haciendo un símil con conceptos aplicables a la programación imperativa, sería cómo la definición de una subrutina local,
en el sentido que permite definir un código, en este caso una consulta que devuelve un conjunto de resultados determinados, asignarlo a un identificador determinado
y usarlo cómo referencia dentro de otras partes de la consulta principal.
Algunas de las ventajas que proporciona su utilización son:
- Permite evitar la reevaluación de una subconsulta que se ejecute múltiples veces dentro de la consulta principal
- Simplifica la escritura y legibilidad de consultas complejas en las que se realicen definiciones de tablas derivadas
(definidas dentro de la clausula
FROM
cómo una subconsulta) en uno o múltiples niveles - Permite substituir la definición de vistas globales cuando sólo están restringidas a una sola consulta, ayudando a no ensuciar el espacio global de nombres con definiciones innecesarias
- Permite realizar operaciones de agrupación o condicionales sobre datos derivados de operaciones escalares o no deterministas. En este caso se podrá definir el cálculo de las operaciones requeridas dentro de la consulta CTE y posteriormente realizar las agrupaciones necesarias en la consulta principal o en otra CTE como si de valores de una tabla corriente se tratara
Aunque forma parte del estándard SQL-99, su implementación por parte de los motores de base de datos que soportan este estándar es opcional:
- Oracle: Soportado a partir de la versión 9i r2
- MS SQL Server: Soportado a partir de la versión 2008
- PostgreSQL: Soportado a partir de la versión 8.4
- SQLLite: Soportado a partir de la versión 3.8.3
- IBM DB2: Soportado
- MySQL y su derivada MariaDB no lo soportan aún
ResourceBundle – Localización de recursos
La API estándar de Java provee de mecanismos para la localización de recursos (mensajes de texto, URLs a imágenes u otros recursos…) mediante la utilización de la clase ResourceBundle
.
La forma más conocida y habitual de usarlos es a través de ficheros de propiedades, en los que se especifican los recursos a utilizar para cada localización en forma de clave – valor,
pero existen otras posibilidades, como por ejemplo la utilización de clases java.
OCP7 14 – Patrones de dseño en Java (Singleton, Factory y DAO)
Gestión de dependencias Maven mediante la librería Aether
Aether es una librería Java que permite integrar en cualquier aplicación Java el mecanismo de resolución de dependencias de Maven. Se trata de una forma mucho más simple de hacerlo que integrar la distribución completa de Maven o incrustar Plexus dentro de la aplicación.
La API de Aether provee funcionalidades para:
- Definir y gestionar de un repositorio local de artefactos.
- Recuperar artefactos desde múltiples repositorios remotos para su consumo local.
- Publicar artefactos locales en múltiples repositorios remotos.
- Resolver las dependencias transitivas de los artefactos.
- Inspeccionar el grafo de dependencias de un artefacto.
En este post se exponen ejemplos concretos de implementaciones para las funcionalidades anteriormente mencionadas.
Orden de ejecución de los métodos de un test de JUnit4
Aunque quizá JUnit4 sea el framework de testing más extendido en el ecosistema Java, adolece de ciertas limitaciones de fábrica que según como se mire son difíciles de explicar. Una de ellas para mi gusto es la dificultad de poder marcar el orden de ejecución de los métodos de una clase de test de forma sencilla. Entiendo que mirándolo de una forma purista cada uno de los métodos de un test case debe ser independiente y su ejecución no se debería ver afectada por el resto, pero en determinados casos es de mucha ayuda poder marcar el orden de ejecución, como por ejemplo poder probar la conexión a una fuente de datos antes de obtener los datos.
En este articulo se pretende exponer diferentes alternativas para dar respuesta a este caso de uso.