Manual de Java

 
 
 

Mapa Web

 
borde   borde
Portada arrow Lista de Códigos Java arrow Ficheros de configuración en aplicaciones Java, un enfoque sencillo

 

Ficheros de configuración en aplicaciones Java, un enfoque sencillo Imprimir E-mail
Detalles de calidad


  A la hora de desarrollar un aplicación siempre nos encontramos con constates y valores por defecto. Cablearlos en el código es un grave error. Cada modificación implicaría una recompilación del código. Puede que en el jurásico. Cuando las computadoras se programaban con tarjetas perforadas, eso fuese aceptable; hoy en día no.

  Bromas aparte, un buen código no puede permitirse esos lujos y se hace imprescindible utilizar mecanismos que nos permitan modificar la configuración de nuestros programas de manera cómoda y efectiva. Tampoco es cuestión de hacer nada complejo: basta con usar un fichero de configuración.

  En este artículo aprenderemos como podemos usar ficheros de configuración de manera sencilla en nuestras aplicaciones Java.

Arte vs. ingeniería


  Mucha gente – entre los cuales me incluyo – proviene del mundo de la programación a medio-bajo nivel, donde se siente por lo general un rechazo hacia las APIs de programación, escudándose en tópicos como: “es más lento”, “no es exactamente lo que necesito”, etc.

  Lo cierto es que hoy en día el uso de APIs de programación, salvo casos excepcionales, es el modo ideal para desarrollar. Intentar realizar tareas complejas basándose en las posibilidades básicas del lenguaje que estemos usando, aunque factible, suele ser una gran pérdida de tiempo. La programación actual tiene más de ingeniería que de arte; seamos ingenieros y usemos soluciones que ya funcionan para crear otras nuevas.

  Supongo que todavía habrá gente que no crea lo que aquí lee, así que haremos un experimento práctico. Intentaremos crear un sistema para acceder a variables de configuración. Primero lo haremos a mano, después usaremos una clase del extenso API de Java directamente y por último, basándonos en lo anterior, afinaremos esta solución. Espero que al llegar al final del artículo, el lector se decante sin dudarlo por la última opción.

Haciéndolo a mano


  Supongamos que tenemos un fichero de configuración sencillo que contiene pares del tipo clave=valor, donde almacenamos constantes relevantes a nuestra aplicación. Un fichero como este:


depuracion=True
alto=480
ancho=640


  Ahora debemos crear un código que nos permita acceder a sus contenidos en cualquier parte de la aplicación. Por tanto, debemos leer los pares y guardarlos en una estructura en memoria que permita un acceso rápido. Para nuestro ejemplo realizamos una sencilla clase con un método main donde experimentaremos.


import java.util.HashMap;
import java.util.StringTokenizer;
import java.io.BufferedReader;
import 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 debería representar mayor problema, unos cuantos castings, algún 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 más libre deberíamos hacer una interpretación más exhaustiva del fichero. Un claro ejemplo de esto es que si añadimos líneas en blanco al final del fichero – meros retornos de carro – necesitaríamos modificar el código.
    3. Como programadores de esta clase, sabemos para que vale cada parámetro del fichero de configuración, pero un usuario lo tiene difícil. Algún comentario no vendría mal.

  ¡Vaya, vaya! Esto empieza a complicarse.


El API al rescate


  Francamente, yo no estoy dispuesto a tirar líneas de código para hacer algo que otros ya han hecho y que está a mi disposición como programador Java.

  El API de Java contiene la clase Properties que nos permite hacer todo lo que queremos, que es crear ficheros de configuración complejos y manejarlos con facilidad. Tanto la estructura de la clase como la sintaxis de los ficheros de propiedades están comentadas en la documentación 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 código como nuestra versión 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 almacenándolo como un conjunto de pares clave-valor. - evidentemente, la extensión del fichero no es obligatoriamente .properties aunque nunca está de más para identificarlos rápidamente - La clase Properties es una subclase de HashTable lo que asegura un acceso eficiente usando técnicas de dispersión.

  Una vez más realizamos unas pruebas, pero esta vez accedemos a los valores mediante los métodos de acceso de Properties.


-- listing properties --
depuracion=True
alto=480
ancho=640
480
true
null


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


Algo más realista


  Las bases ya están sentadas, ahora es cuestión de ponerse a jugar un poco y buscar una manera interesante de usar lo que acabamos de aprender, solucionando los pequeños detalles que nos hemos ido dejando por el camino. Para ello os propongo un pequeño código compuesto de tres clases que veremos a continuación.

  La primera es una excepción personalizada, que nos va a permitir manejar el caso en el que el parámetro 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 estática AlmacenPropiedades para gestionar los valores de configuración. Es deseable que está clase sea de fácil acceso en cualquier parte de nuestra aplicación. Podíamos haber optado también por usar un singleton - siguiendo el patrón de diseño clásico de la instancia única -.

  La idea es añadir 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 métodos 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 método de acceso a las claves realiza el casting a String y además gestiona el caso en que no se encuentra la clave, lanzando nuestra excepción 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 excepción. 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 sería que fuese otro parámetro configurable... ¡vaya!, parece que hemos llegado a un callejón 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 configuración.

  Con esta aproximación, solo cableamos el nombre del fichero - algo aceptable - y añadimos 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 actúa en este caso el cargador de clases se escapa de los objetivos de este artículo, pero recomiendo al lector interesado una visita a la documentación 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 métodos sincronizados.
    2. Si tenemos varios cargadores de clases debemos recordar que el concepto de instancia única no existe, por tanto, no podemos suponer sin más que en dos puntos de nuestra aplicación no se estén usando dos clases distintas, con lo que podemos tener problemas de consistencia. Esto es clásico 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 líneas arriba, espero que a estas alturas el lector ya se haya decantado por algo similar a la última opción 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 soñar con cosas mucho más complejas, como usar XML o acceder a un directorio activo a través de una red, pero esa, amigos, es otra historia.

David Barral
http://www.elrincondelprogramador.com/default.asp?pag=articulos/leer.asp&id=30

 

 
Portada
Capítulos del Manual de Java
Introducción a Java
Origen de Java
Características de Java
Instalación del JSDK
Conceptos Basicos
Programación
Control de Flujo
Clases
Variables y Métodos de Instancia
Alcance de Objetos y Reciclado de Memoria
Herencia
Control de Acceso
Variables y Métodos Estáticos
this y super
Clases Abstractas
Interfaces
Métodos Nativos
Paquetes
Referencias
Referencias y Arrays
Referencias y Listas
Una mínima aplicación
Compilación y Ejecución de Hola Mundo
Un Applet Básico
La Clase Math
La Clase Character
La Clase Float
La Clase Double
La Clase Integer
La Clase Long
La Clase Boolean
La Clase String
La Clase StringBuffer
Uso de Conversiones
Manejo de Excepciones
Generar Excepciones
Excepciones Predefinidas
Crear Excepciones
Capturar Excepciones
Propagación de Excepciones
Entrada/Salida Estándar
Ficheros
Streams de Entrada
Streams de Salida
Ficheros de Acceso Aleatorio
Practicar en línea
Lista de prácticas en línea
FAQ
Preguntas frecuentes
Códigos Java
Lista de Códigos Java
Foros
Foros Java
Otros Manuales
Manuales de otros lenguajes
 
   
 
 
Alojamiento web en Hostalia