Ingeniería de Software

Practica 2: Automatización de construcción, cargado tardío y manejo de excepciones

En la práctica pasada estuvimos viendo las diferentes herramientas provistas por el Java Development Kit que nos permiten compilar, generar documentación, empacar y ejecutar.

Una observación que surge a partir de la practica anterior es que la construcción sigue un proceso, es decir una serie de pasos bien definidos. Si quisieramos hacer que la construcción fuera repetible podriamos escribir un script (.bat en windows o un script de shell en linux). Sin embargo este enfoque tiene desventajas, ya que esos scripts no son compatibles.

La herramienta Ant [http://ant.apache.org/] nos va a permitir automatizar el proceso de construcción a través de su descripción en un archivo portable. La ventaja de esta herramienta es que una vez que escribamos el archivo de ant, podremos utilizarlo en cualquier lugar en donde esté Java. Una herramienta similar a ANT es Make, que tal vez hayas utilizado anteriormente para hacer compilación en C.

El objetivo de esta práctica sera de:

  1. Escribir un archivo Ant que permita automatizar el proceso de construcción del proyecto visto en la práctica anterior
  2. Experimentar con el cargado tardío de implementaciones de la clase Motor. Esto último es escencial para entender la mecánica de las aplicaciones a base de plug-ins.

I. La herramienta ANT

ANT es una herramienta extensible que permite automatizar el proceso de construcción. El proceso de construcción se describe en un archivo en formato XML (tipicamente llamado build.xml).

El archivo build.xml

El archivo build.xml está definido en termino de un proyecto, un conjunto de objetivos (targets) de tareas (tasks) y de propiedades. La figura siguiente muestra un ejemplo de archivo build.xml simple que crea un directorio llamado build:

<project name="Simple" default="unTarget">
   <property name="nombredir" value="build">
   <target name="unTarget">
      <mkdir dir="${nombredir}">
   </target>
</project>
Archivo build.xml simple

Proyecto

Un sólo proyecto está definido por archivo. El proyecto tiene un nombre asociado y un target principal. El proyecto se define usando los tags siguientes:

<project name="Simple" default="unTarget">
   ...
</project>

Objetivos (targets)

Un objetivo representa una actividad dentro del proceso de construcción. Ejemplos de objetivos son compilación, empaquetado, ejecución, etc... Cada objetivo está nombrado y puede depender de otros objetivos. Un objetivo se define usando los tags siguientes:

<target name="nombreTarget" depends="otroTarget" description="descripción">
   ...
</target>

Por lo general se recomienda usar nombres estándar para los objetivos:

init – inicialización de propiedades
prepare – creación de directorios que contienen archivos generados.
clean – limpiar directorios de directorios que contienen archivos generados.
compile – lanzar compilación
javadoc – generar documentación
package – crear paquete para ejecución
dist – crear paquete que contiene el proyecto
run – ejecutar
all – hacerlo todo (compilar, empacar, generar documentación y ejecutar).

Tareas (tasks)

Las tareas representan operaciones básicas como la creación de un directorio, la invocación de alguna de las herramientas del JDK, etc... El conjunto de tareas básicas de ANT están listadas aquí. El nombre del tag XML difiere según el tipo de tarea, y cada tipo de tarea puede contener tags particulares a la tarea especifica. A continuación unos ejemplos:

Ejemplo: Ejecución en Java desde un JAR (ver todos los tags asociados a java)

<java classname="mx.uam.ejemplo.Ejemplo1">
   <classpath>
      <pathelement location="dist/ejemplo.jar"/>
   </classpath>
</java>

Propiedades

Las propiedades representan valores que son accesibles de manera global dentro del proyecto. Las propiedades son definidas dentro o fuera de los targets y cada propiedad tiene un nombre y un valor asociados (las propiedades son de tipo cadena). Los valores de las propiedades pueden ser utilizados dentro de los targets poniendo el nombre de la propiedad entre "${" y "}". En el ejemplo siguiente se definen varias propiedades que son utilizadas dentro de un target para ejecutar la clase principal del ejemplo visto en clase anteriormente.

<project name="Vehiculos" default="run">
                                                                               
   <!-- Aqui se definen propiedades -->
   <target name="init">
    
<property name="distdir" value="dist"/>
      <property name="classname" value="mx.uam.vehiculos.principal.Principal"/>
      <property name="jarname" value="vehiculos.jar"/>
   </target>   
                                                                          
   <!-- Aqui se utilizan las propiedades -->
   <target name="run" depends="init">
      <java classname="${classname}">
         <classpath>
            <pathelement location="${distdir}/${jarname}"/>
         </classpath>
      </java>
   </target>
</project>


Una practica recomendable es que se utilicen propiedades para contener valores que varian de un proyecto a otro, así es posible re-utilizar un archivo build.xml en proyectos distintos con sólo cambiar las propiedades.

Ejemplos de propiedades que varian entre proyectos:
  • nombres de directorios (fuentes, librerias, etc...)
  • nombre de clase principal
  • nombre de package

Ant define un conjunto de propiedades por default:

basedir: camino absoluto del directorio de base del proyecto
ant.file: camino absoluto del archivo de construcción
ant.version: versión de Ant
ant.project.name: nombre del proyecto
ant.java.version: version de la máquina virtual Java

Escritura de archivo build.xml

El primer ejercicio consiste en escribir el archivo build.xml para automatizar el proceso de construcción del proyecto visto en la práctica anterior. El archivo build.xml debe ponerse a la raiz del proyecto:

Proyecto
+build.xml
+src/
+lib/
+docs/
|

Ejercicio 1: Ejecución de Ant

  1. Crea el archivo build.xml con el target de ejecución mostrado anteriormente (en sección propiedades).
  2. Lanza ant (tecleando ant desde la línea de comando estando en el directorio donde se encuentra build.xml)
  3. Verifica que la salida sea correcta:
$ ant
Buildfile: build.xml
 
run:
     [java] Coche creado!
     [java] Camion creado!
     [java] Encendiendo:coche
     [java] Motor Turbo activado!
     [java] Apagando:coche
     [java] Motor Turbo desactivado!
     [java] Checando llenado...
     [java] Encendiendo:camion
     [java] Motor Alto Rendimiento activado!
     [java] Apagando:camion
     [java] Motor Alto Rendimiento desactivado!
 
BUILD SUCCESSFUL
Total time: 0 seconds


Ejercicio 2: Extensión de archivo build.xml

1.- Extender archivo build.xml para que contenga los targets siguientes. Cuidado con las dependencias!:
  • init: inicializa propiedades que varian entre proyectos
  • prepare: prepara los directorios en donde se generan archivos (build, doc, dist)
  • clean: borra directorios creados por prepare
  • compile: realiza compilación
  • javadoc: genera documentación (crear un directorio bajo doc llamado javadoc)
  • package: genera paquete para ser usado en ejecución
  • dist: genera paquete que contiene todo el proyecto sin directorios generados
  • run: ejecuta (ese ya está hecho)
  • all: ejecuta todo
Nuevamente la liga a la documentación de ANT (ver sección Ant Tasks / Core Tasks)

2.- Verifica que la ejecución sea correcta lanzando ant sin parámetros o bien pasandole cómo parametro el nombre de un target a ejecutar.

II.- Cargado Tardío

En la clase anterior vimos un ejemplo de separación de interfase e implementación. Esta separación es uno de los conceptos más importantes del desarrollo pues permite que la interfase y la implementación varien de forma independiente. Las implementaciones son sustituibles unas por otras.

En la práctica anterior, vimos dos implementaciones de la interface Motor (MotorAltoRendimiento y MotorTurbo). Sin embargo, en ese ejemplo esas implementaciones fueron compiladas al mismo tiempo que el resto del proyecto. El cargado tardío nos va a permitir incorporar implementaciónes que no sean compiladas en el mismo momento que el resto del proyecto y junto con la separación de la interfase e implementación forman el principio de la extensibilidad a base de plug-ins.

Paso 1.- Introducción de un mecanismo de cargado tardío.

Modificar la clase principal cómo sigue (ajustar a tu propio espacio de nombres):

package mx.uam.vehiculos.principal;

import mx.uam.vehiculos.Camion;
import mx.uam.vehiculos.Coche;
import mx.uam.vehiculos.MotorAltoRendimiento;
import mx.uam.vehiculos.MotorTurbo;
import mx.uam.vehiculos.Motor;

/**
 * Clase Principal con mecanismo de cargado tardio
 *
 * @author Humberto Cervantes
 * @version 2.0
 */
public class Principal
{   
    /**
      *Este es el metodo principal main
      *@param args Argumentos de main
    */
    public static void main(String []args)
    {   
        Motor mt=new MotorTurbo();  //se crea una instancia motor turbo
        Motor mar=new MotorAltoRendimiento(); //se crea una instancia de motor de alto rendimiento

        Coche coche1=new Coche(mt); //se crea una instancia coche
        Coche coche2=new Coche(mar); //se crea otra instancia de coche

        coche1.enciende();  //se envia un mensaje al coche 1 para que encienda
        coche2.enciende();  //se envia un mensaje al coche 2 para que encienda

        coche1.apaga();     //se envia un mensaje al coche para que se apague
        coche2.apaga();     //se envia un mensaje al coche para que se apague

        if(args.length>0)
        {
            try
       
    {
           
    Motor mcd=(Motor) creaInstancia(args[0]); // Se instancia motor cargado dinamicamente

                Coche coche3 = new Coche(mcd);

   
            coche3.enciende();  //se envia un mensaje al coche 3 para que encienda
   
       
        coche3.apaga();     //se envia un mensaje al coche para que se apague
            }
   
        catch(Exception ex) // Hubo una falla!
       
    {
           
    ex.printStackTrace();
            }
        }
    }

    /**
     * Este metodo fabrica crea una instancia a partir de una cadena que contiene un nombre de clase
     *
     * @param nombreClase una cadena con el nombre de una clase
     * @return un objeto obtenido a partir de la clase
     * @exception java.lang.Exception clase de base de una de las multiples excepciones que puede lanzar
     *                                (Es mejor describir cada excepcion individualmente)
     *
    */
    public static Object creaInstancia(String nombreClase) throws Exception
    {
        System.out.println("Creando instancia de clase "+nombreClase);

        // Obtiene una referencia al cargador de clases del sistema
        ClassLoader cargadorClases = ClassLoader.getSystemClassLoader();

        // Carga la clase
        Class clase = cargadorClases.loadClass(nombreClase);

        // Crea una instancia
        Object instancia = clase.newInstance();   // Crea una instancia

        return instancia;
    }
}


Ejercicio 1. Modifica la clase principal para que quede cómo el ejemplo.

La nueva clase Principal va a crear 3 coches, cada uno con un motor distinto. Sin embargo, la clase a partir de la cual se obtendrá el motor del tercer coche no se conoce sino hasta el momento de ejecución y se recibirá cómo un parámetro al momento de lanzar la ejecución.

El metodo creaInstancia es un método fábrica (esto es un patrón de diseño que veremos más adelante). Se encarga de crear una instancia a partir de una clase que se recibe en forma de cadena. Esto es posible gracias al ClassLoader que es el mecanismo de cargado de clase de Java así como los mecanismos de reflexión de Java (en particular la clase Class)

Ejercicio 2. Ejecuta la nueva clase principal

$ java -classpath build/ mx.uam.vehiculos.principal.Principal mx.uam.motores.MotorTardio

Al ejecutar la clase principal se genera una excepción ya que la clase mx.uam.motores.MotorTardio no se encuentra (ClassNotFoundException)! El error ocurre en la línea 62 del programa...

Coche creado!
Coche creado!
Motor Turbo activado!
Motor Alto Rendimiento activado!
Apagando:coche
Motor Turbo desactivado!
Apagando:coche
Motor Alto Rendimiento desactivado!
Creando instancia de clase mx.uam.motores.MotorTardio
java.lang.ClassNotFoundException: mx.uam.motores.MotorTardio
        at java.net.URLClassLoader$1.run(URLClassLoader.java:199)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(URLClassLoader.java:187)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:289)
        at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:274)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:235)
        at mx.uam.vehiculos.Principal.creaInstancia(Principal.java:62)
        at mx.uam.vehiculos.Principal.main(Principal.java:34)


La nueva clase Principal muestra el uso de excepciones, que es el mecanismo que provee Java para manejar los errores. Varias excepciones puede ocurrir al momento de crear una instancia, la clase puede no existir, puede que el constructor no sea accesible, etc... Las excepciones son objetos cómo cualquier otro y pueden ser "cachadas" para recobrarse ante un problema. Para ello se usa la construcción try...catch. Dentro del try se intenta hacer algo y dentro del catch se atrapa alguna excepción que se genere. El objeto excepción puede mostrar una traza del stack de ejecución al invocar su metodo ex.printStackTrace() que nos muestra el número de línea donde ocurrio la excepción.

Para ser un buen programador en Java tienes que aprender a leer la salida de estas trazas (aunque al principio parezca dificil!)

Ejercicio 3. Escritura de un motor que será cargado dinámicamente

Crea una nueva clase MotorTardio en un package que sea distinto a los que creaste anteriormente.

SIN RECOMPILAR NADA DEL PROYECTO ANTERIOR compila la nueva clase (enviando a un directorio build distinto):

$ javac -classpath dist/vehiculos.jar -d build2 src/mx/uam/motores/MotorTardio.java

Ahora ejecutala

$java -classpath dist/vehiculos.jar:build2/ mx.uam.vehiculos.principal.Principal mx.uam.motores.MotorTardio

Comprueba que ya no sale la excepción!

Coche creado!
Coche creado!
Motor Turbo activado!
Motor Alto Rendimiento activado!
Apagando:coche
Motor Turbo desactivado!
Apagando:coche
Motor Alto Rendimiento desactivado!
Creando instancia de clase mx.uam.motores.MotorTardio
Se ha creado la instancia del motor tardio!
Coche creado!
Motor Tardio activado!
Apagando:coche
Motor Tardio desactivado!

Básicamente acabas de crear una aplicación extensible por plug-ins donde distintas implementaciones de Motor pueden ser "enchufadas" despues de que el proyecto ha sido liberado!

Ejercicio 4.- Hacer del MotorTardío un proyecto independiente

Haz un nuevo proyecto unicamente con la clase del motor tardío. Dentro del directorio lib de este directorio copia el archivo vehiculos.jar y modifica el target de compilación para que incluya todos los jars del directorio lib.

III.- Síntesis

En esta práctica vimos:
  • Uso de la herramienta ANT
  • Introducción de un mecanismo de cargado tardío
Una cosa a tomar en cuenta es que es una buena práctica poner las interfases y las implementaciones en packages distintos, así podriamos hacer un archivo JAR que contuviera sólo las interfaces que se necesitan para compilar las implementaciones y no el resto del proyecto.

A entregar:

Archivos build.xml del ejercicio I y II.4 (enviarmelos por mail)



Ultima actualización: 31 Enero 2005
contacto: hcm@xanum.uam.mx
Homepage