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:
- Escribir un archivo Ant que permita automatizar el proceso
de construcción del proyecto visto en la práctica anterior
- 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
- Crea el archivo build.xml con el target de ejecución
mostrado anteriormente (en sección propiedades).
- Lanza ant (tecleando ant desde la línea de comando
estando en el directorio donde se encuentra build.xml)
- 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)
|