You are on page 1of 9

Ficheros de configuracin en aplicaciones Java, un enfoque sencillo

Detalles de calidad

A la hora de desarrollar un aplicacin siempre nos encontramos con constates y valores por defecto. Cablearlos en el cdigo es un grave error. Cada modificacin implicara una recompilacin del cdigo. Puede que en el jursico. Cuando las computadoras se programaban con tarjetas perforadas, eso fuese aceptable; hoy en da no. Bromas aparte, un buen cdigo no puede permitirse esos lujos y se hace imprescindible utilizar mecanismos que nos permitan modificar la configuracin de nuestros programas de manera cmoda y efectiva. Tampoco es cuestin de hacer nada complejo: basta con usar un fichero de configuracin. En este artculo aprenderemos como podemos usar ficheros de configuracin de manera sencilla en nuestras aplicaciones Java.
Arte vs. ingeniera

Mucha gente entre los cuales me incluyo proviene del mundo de la programacin a medio-bajo nivel, donde se siente por lo general un rechazo hacia las APIs de programacin, escudndose en tpicos como: es ms lento, no es exactamente lo que necesito, etc. Lo cierto es que hoy en da el uso de APIs de programacin, salvo casos excepcionales, es el modo ideal para desarrollar. Intentar realizar tareas complejas basndose en las posibilidades bsicas del lenguaje que estemos usando, aunque factible, suele ser una gran prdida de tiempo. La programacin actual tiene ms de ingeniera que de arte; seamos ingenieros y usemos soluciones que ya funcionan para crear otras nuevas. Supongo que todava habr gente que no crea lo que aqu lee, as que haremos un experimento prctico. Intentaremos crear un sistema para acceder a variables de configuracin. Primero lo haremos a mano, despus usaremos una clase del extenso API de Java directamente y por ltimo, basndonos en lo anterior, afinaremos esta solucin. Espero que al llegar al final del artculo, el lector se decante sin dudarlo por la ltima opcin.

Hacindolo a mano

Supongamos que tenemos un fichero de configuracin sencillo que contiene pares del tipo clave=valor, donde almacenamos constantes relevantes a nuestra aplicacin. Un fichero como este:
depuracion=True

alto=480 ancho=640

Ahora debemos crear un cdigo que nos permita acceder a sus contenidos en cualquier parte de la aplicacin. Por tanto, debemos leer los pares y guardarlos en una estructura en memoria que permita un acceso rpido. Para nuestro ejemplo realizamos una sencilla clase con un mtodo main donde experimentaremos.
import import import import java.util.HashMap; java.util.StringTokenizer; java.io.BufferedReader; java.io.FileReader;

public class Main { public static void main(String[] args) { String FICHERO_CONFIGURACION = "Configuracion.conf"; HashMap propiedades; try { propiedades = new HashMap(); BufferedReader br = new BufferedReader( new FileReader(FICHERO_CONFIGURACION)); String s; while ((s = br.readLine())!= null) { StringTokenizer st = new StringTokenizer(s, "="); propiedades.put(st.nextToken(),st.nextToken()); } br.close(); System.out.println(propiedades); /* Buscamos algunos valores */ int alto = Integer.parseInt( (String)propiedades.get("alto")); boolean depuracion = Boolean.valueOf( (String)propiedades.get("depuracion")).booleanValue(); System.out.println(alto); System.out.println(depuracion); System.out.println( (String)propiedades.get("DevuelveNull")); } catch (Exception e) { /* Manejo de excepciones */ } } }

Parece sencillo, leemos secuencialmente el fichero y usando un StringTokenizer

que usa el igual como separador - almacenamos los pares clave valor obtenidos en un mapa hash, que nos permite un acceso eficiente a los valores. Ahora probemos a recuperar valores. Esto no debera representar mayor problema, unos cuantos castings, algn parsing y listo. La salida del programa por consola es la siguiente:
{depuracion=True, alto=480, ancho=640} 480 true null

Aunque todo parece funcionar se observan varios problemas. 1. Si solicitamos un valor que no existe, obtenemos un null. Esto puede provocar problemas null pointer exceptions as que debemos tener cuidado y comprobar que el valor no es null antes de utilizarlo tan alegremente. 2. La sintaxis del fichero debe ser muy estricta. No es lo mismo "alto=480" que "alto =480", los espacios importan. Si queremos tener una sintaxis ms libre deberamos hacer una interpretacin ms exhaustiva del fichero. Un claro ejemplo de esto es que si aadimos lneas en blanco al final del fichero meros retornos de carro necesitaramos modificar el cdigo. 3. Como programadores de esta clase, sabemos para que vale cada parmetro del fichero de configuracin, pero un usuario lo tiene difcil. Algn comentario no vendra mal. Vaya, vaya! Esto empieza a complicarse.

El API al rescate

Francamente, yo no estoy dispuesto a tirar lneas de cdigo para hacer algo que otros ya han hecho y que est a mi disposicin como programador Java. El API de Java contiene la clase Properties que nos permite hacer todo lo que queremos, que es crear ficheros de configuracin complejos y manejarlos con facilidad. Tanto la estructura de la clase como la sintaxis de los ficheros de propiedades estn comentadas en la documentacin del API y puede ser consultada online. Un fichero .properties nos permite, sin grandes alardes, hacer casi todo lo que deseamos mientras respetemos el formato clave = valor (o valores). Pero lo veremos mejor con un ejemplo:
############################################ # # Fichero de configuracion #

############################################ # Parametro 1 depuracion = True # Otros parametros alto = 480 ancho = 640

Hagamos ahora un cdigo como nuestra versin a mano pero usando la clase
Properties. import java.util.Properties; import java.io.FileInputStream; public class Main { public static void main(String[] args) { String FICHERO_CONFIGURACION = "Configuracion.properties"; Properties propiedades; /* Carga del fichero de propiedades */ try { FileInputStream f = new FileInputStream(FICHERO_CONFIGURACION); propiedades = new Properties(); propiedades.load(f); f.close(); /* Imprimimos los pares clave = valor */ propiedades.list(System.out); /* Buscamos algunos valores */ int alto = Integer.parseInt( propiedades.getProperty("alto")); boolean depuracion = Boolean.valueOf( propiedades.getProperty("depuracion")).booleanValue(); System.out.println(alto); System.out.println(depuracion); System.out.println( propiedades.getProperty("DevuelveNull")); } catch (Exception e) { /* Manejo de excepciones */ } } }

Para leer los datos del fichero solo necesitamos crear un InputStream. Este ser usado por la clase Properties para leer y parsear el fichero almacenndolo como un conjunto de pares clave-valor. - evidentemente, la extensin del fichero no es obligatoriamente .properties aunque nunca est de ms para identificarlos rpidamente - La clase Properties es una subclase de HashTable lo que asegura un acceso eficiente usando tcnicas de dispersin. Una vez ms realizamos unas pruebas, pero esta vez accedemos a los valores mediante los mtodos de acceso de Properties.
-- listing properties -depuracion=True alto=480 ancho=640 480 true null

La diferencia ms apreciable, es sin duda que Properties ya sabe que trabaja con por lo que no es necesario realizar castings. Sin embargo, seguimos encontrando el problema del null. La solucin ahora est en nuestras manos.
Strings,

Algo ms realista

Las bases ya estn sentadas, ahora es cuestin de ponerse a jugar un poco y buscar una manera interesante de usar lo que acabamos de aprender, solucionando los pequeos detalles que nos hemos ido dejando por el camino. Para ello os propongo un pequeo cdigo compuesto de tres clases que veremos a continuacin. La primera es una excepcin personalizada, que nos va a permitir manejar el caso en el que el parmetro buscado est ausente.
public class FaltaPropiedadException extends Exception { private String nombreParametro; public FaltaPropiedadException(String nombreParametro) { super("Falta parametro de configuracion: '" + nombreParametro + "'"); this.nombreParametro = nombreParametro; } public String getNombreParametro() { return nombreParametro; } }

Ahora crearemos una clase esttica AlmacenPropiedades para gestionar los valores de configuracin. Es deseable que est clase sea de fcil acceso en cualquier parte de nuestra aplicacin. Podamos haber optado tambin por usar un singleton - siguiendo el patrn de diseo clsico de la instancia nica -. La idea es aadir una capa alrededor de las properties para controlar el acceso que se hace a ellas: a. Preferimos usar un HashMap en vez de Properties, que es una HashTable, por motivos de eficiencia, ya que los mtodos de acceso de una HashTable son sincronizados es decir, soportan concurrencia-. Como se trata de valores de solo lectura esto es innecesario. Sin embargo, seguimos necesitamos la clase Properties para interpretar el fichero. b. El mtodo de acceso a las claves realiza el casting a String y adems gestiona el caso en que no se encuentra la clave, lanzando nuestra excepcin personalizada para que sean las capas superiores las que se ocupen de las acciones a tomar ante el fallo.
import java.util.Properties; import java.util.HashMap; import java.io.FileInputStream; public class AlmacenPropiedades { private static final String CONFIGURATION_FILE = "Configuracion.properties"; private static HashMap propiedades; /* Bloque de inicializacion */ static { try { FileInputStream f = new FileInputStream(CONFIGURATION_FILE); Properties propiedadesTemporales = new Properties(); propiedadesTemporales.load(f); f.close(); propiedades = new HashMap(propiedadesTemporales); /* Imprimimos los pares clave = valor */ System.out.println(propiedades); } catch (Exception e) { /* Manejo de excepciones */ } } private AlmacenPropiedades() { }

public static String getPropiedad(String nombre) throws FaltaPropiedadException { String valor = (String) propiedades.get(nombre); if (valor == null) throw new FaltaPropiedadException(nombre); return valor; } }

Por ltimo creamos una clase Main para realizar las pruebas de siempre. Como nica diferencia, vamos a gestionar la excepcin. Por ejemplo, en este caso, la mostramos por consola y damos un valor por defecto a la variable.
public class Main { public static void main(String[] args) { String devuelveNull; try { /* Buscamos algunos valores */ int alto = Integer.parseInt( AlmacenPropiedades.getPropiedad("alto")); boolean depuracion = Boolean.valueOf( AlmacenPropiedades.getPropiedad("depuracion")). booleanValue(); System.out.println(alto); System.out.println(depuracion); devuelveNull = AlmacenPropiedades. getPropiedad("DevuelveNull"); } catch (FaltaPropiedadException e) { System.out.println(e); devuelveNull = "Valor por defecto"; } } }

Una vez compilado y ejecutado obtenemos la siguiente salida, como era de esperar:
{depuracion=True, alto=480, ancho=640} 480 true FaltaPropiedadException: Falta parametro de configuracion: 'DevuelveNull'

Una ltima mejora

El lector observador habr notado que el uso de un FileInputStream, para cargar el fichero de propiedades, nos obliga a cablear la ruta del mismo - absoluta o relativa - en la propia clase como un static String. Esto es incomodo si pensamos cambiar esa ruta, ya que nos obliga a modificar dicha cadena y a recompilar. Lo ideal sera que fuese otro parmetro configurable... vaya!, parece que hemos llegado a un callejn sin salida. Afortunadamente, hay una puerta por donde podemos escapar del problema. Esta puerta es el cargador de clases. Gracias al uso del mismo y a la variable de entorno CLASSPATH podemos implementar un mecanismo flexible y eficaz para localizar el fichero de configuracin. Con esta aproximacin, solo cableamos el nombre del fichero - algo aceptable - y aadimos al CLASSPATH el directorio que lo contiene. Ahora ser el cargador de clases el que se ocupe de encontrarlo. Para ello modificaremos el bloque static de AlmacenPropiedades.
static { try { Class almacenPropiedadesClass = AlmacenPropiedades.class; ClassLoader classLoader = almacenPropiedadesClass.getClassLoader(); InputStream inputStream = classLoader.getResourceAsStream(CONFIGURATION_FILE); Properties propiedadesTemporales = new Properties(); propiedadesTemporales.load(inputStream); inputStream.close(); propiedades = new HashMap(propiedadesTemporales); /* Imprimimos los pares clave = valor */ System.out.println(propiedades); } catch (Exception e) { /* Manejo de excepciones */ } }

Entender como acta en este caso el cargador de clases se escapa de los objetivos de este artculo, pero recomiendo al lector interesado una visita a la documentacin del API de Java. Y con esto terminamos.
Un detalle final: escribiendo valores

Aunque no se ha mencionado, existe la posibilidad de modificar las propiedades y

volcarlas a fichero. Sin embargo, hay que tener cuidado con la escritura de valores. 1. Si nos encontramos en un entorno multihilo, debemos controlar la concurrencia mediante mtodos sincronizados. 2. Si tenemos varios cargadores de clases debemos recordar que el concepto de instancia nica no existe, por tanto, no podemos suponer sin ms que en dos puntos de nuestra aplicacin no se estn usando dos clases distintas, con lo que podemos tener problemas de consistencia. Esto es clsico en aplicaciones web corriendo sobre servidores de aplicaciones que usan varios cargadores de clases como Jakarta Tomcat -. En este caso se buscan otras soluciones.

Despedida

Como ya dije, muchas lneas arriba, espero que a estas alturas el lector ya se haya decantado por algo similar a la ltima opcin que planteamos. Las posibilidades son muchas, pero la base es siempre la misma. Ahora todo depende del buen hacer de cada uno. Esta claro que se puede soar con cosas mucho ms complejas, como usar XML o acceder a un directorio activo a travs de una red, pero esa, amigos, es otra historia

You might also like