Integración de Groovy, JRuby y BeanShell con Spring 2

0
8001

Integración de Groovy, JRuby y BeanShell con Spring 2

Introducción

Normalmente el comportamiento de una aplicación está compilado y empaquetado en un JAR, WAR o EAR de manera estática, de modo que si se necesita cambiar el comportamiento
de un determinado punto habría que volver a compilarlo, empaquetarlo e instalarlo para poner en marcha ese nuevo comportamiento.

En ciertas ocasiones puede ser interesante que determinados algoritmos puedan modificarse sin tener que realizar estos pasos (compilación, empaquetado e instalación).

En este tutorial vamos a ver como podríamos hacerlo haciendo uso de scripts externos (interpretados) que implementen determinados algoritmos que se desean sean dinámicos.
Para ellos haremos uso de la funcionalidad que Spring nos pone a mano para integrarnos con los lenguajes Groovy, JRuby y BeanShell.

En este tutorial voy a centarme en realizar un ejemplo de esta característica usando BeanShell (tiene sintaxis Java), dejando al lector una plantilla de pasos a seguir para conseguir hacerlo con el resto de lenguajes.

Se presupone que el lector ya posee conocimientos de JUnit 4, Maven 2, Spring 2.

Entorno

  • HP Pavilion.
  • Windows Vista Home Premium.
  • Eclipse Ganymede.
  • Java 6.
  • Maven 2.
  • Plugin Maven 4QE para Eclipse.

Ejemplo

Imagine una aplicación de comercio electrónico, en donde el usuario puede comprar productos online para realizar un pedido, y en donde el cálculo del precio cambia constantemente en base a ciertos parámetros.
Pues bien, vamos a extraer este cálculo a un script BeanShell que realizará el cálculo de la siguiente manera:

Si el número de productos es mayor a o igual a 10 y el coste del pedido es mayor o igual a 200 euros, se le aplica un descuento del 4% sobre el total.

El código fuente de este tutorial puede ser descargado desde aquí (proyecto Eclipse con Maven 2).

Producto:

La siguiente clase representa un producto que el usuario puede comprar a través de la aplicación de comercio electrónico.

Para simplificar el código expongo sólo lo extrictamente necesario con fines de legibilidad.

package com.autentia.tutoriales.spring.beanshell;

/**
 * Producto comercial
 * @author Carlos García. Autentia.
 * @see http://www.mobiletest.es 
 */
public class Producto {
	/**
	 * Precio unitario del producto
	 */
	private double precio;
	
	public double getPrecio() {
		return precio;
	}

	public void setPrecio(double precio) {
		this.precio = precio;
	}	
}

    

Carrito de compra:

A continuación, exponemos una clase que representa los productos que el usuario ha solicitado es decir, su carrito de compra online.
Al igual que en el caso anterior expongo lo estrictamente necesario.

Observer el método getPrecio, pues el cálculo será delegado a la implementación (en esta caso una clase programada bajo BeanShell) de la interfaz CalculadorCoste y que será inyectada por Spring.

package com.autentia.tutoriales.spring.beanshell;

import java.util.List;
import java.util.ArrayList;

/**
 * @author Carlos García. Autentia.
 * @see http://www.mobiletest.es
 */
public class CarritoCompra {
	private List	productos;
	private CalculadorCoste calculador;
	
	/**
	 * Constructor por defecto
	 */
	public CarritoCompra(){
		super();
		this.productos = new ArrayList();
	}
	
	/**
	 * Agrega un producto al carrito de compra
	 * @param producto Producto a agregar
	 */
	public void addProduct(Producto producto){
		this.productos.add(producto);
	}

	/**
	 * Establece el calculador de costes.
	 * @param calculador Calculador del coste de compra del pedido.
	 */
	public void setCalculador(CalculadorCoste calculador) {
		this.calculador = calculador;
	}
	
	/**
	 * @return Devuelve el coste del pedido
	 */
	public double getPrecio() {
		return calculador.getPrecio(this.productos);
	}
}
    

Interfaz de comunicación Java <=> Beanshell:

Al realizar el cálculo a través de una interfaz, no nos casaremos (acoplaremos) con una implementación especifica.

Somo sabeis, una de las muchas ventajas de Spring es permite bajo acoplamiento (mejor mantenibilidad, …) a través de la inyección de dependencias via interfaces.

La siguiente interfaz será posteriormente implementada en un Script BeanShell.

      package com.autentia.tutoriales.spring.beanshell;
      
      /**
       * Interfaz a implementar por la clase que realize el cálculo de costes de un pedido.
       * @author Carlos García. Autentia.
       * @see http://www.mobiletest.es
       */
      public interface CalculadorCoste {
      	/**
      	 * @param productos Productos sobre el que realizar el cálculo
      	 * @return Devuelve el coste de compra de un conjunto de productos
      	 */
      	public double getPrecio(java.util.List productos);
      }    
    

Script BeanShell (script/calculador.bsh):

A continuación, mostramos el script de cálculo bajo BeanShell, este código puede ser modificado en cualquier momento sin tener que recompilar nada, de hecho será un archivo de texto dentro de nuestro sistema de archivos.

class CalculadorDinamicoImpl implements com.autentia.tutoriales.spring.beanshell.CalculadorCoste {
	/**
 	 * Implementación del algoritmo de cálculo de coste de un pedido.
 	 * Si el número de productos es mayor a 9 y el coste del pedido es superior a 200 euros
 	 * Le aplicamos un descuento del 4%. 
	 */
	public double getPrecio(java.util.List productos){
		double total = 0;
		int    numProductos = productos.size();
		
		for (int i = 0; i < numProductos; i++){
			total += productos.get(i).getPrecio();
		} 
		
		if ((numProductos > 9) && (total >= 200)){
			return total - (total * 0.04);
		} else {
			return total;
		}
	}
}
    

Test de la aplicación:

A continuación, ¿qué mejor forma de probar algo que con tests?, pues mostramos dos tests para dos casos concretos, un carrito de compra que cumple las condiciones para que se le aplique el descuento y otro que no las cumple.

Se presupone que el lector ya conoce JUnit 4…

package com.autentia.tutoriales.spring.beanshell;

import org.junit.Assert;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

/**
 * Tests de verificación del cálculo de costes
 * @author Carlos García. Autentia
 * @see http://www.mobiletest.es
 */
public class CalculoCostesTest  {
	private ApplicationContext	factory;
	private final int PRECIO_PRODUCTO = 10;
	
	/**
	 * Inicializamos el contexto de Spring
	 */
	@org.junit.Before
	public void initTests(){
		factory  = new FileSystemXmlApplicationContext("applicationContext.xml");
	}
	
	/**
	 * @param numeroProductos Número de productos que tendrá el carrito
	 * @return Devuelve un carrito de compra con todos los productos al mismo precio
	 */
	private CarritoCompra getCarrito(int numeroProductos){
    	CarritoCompra carrito  = (CarritoCompra) factory.getBean("carrito");
    	Producto	  producto = null;
    	
    	for (int i = 0; i < numeroProductos; i++){
    		producto = (Producto) factory.getBean("producto");
    		producto.setPrecio(PRECIO_PRODUCTO);
    		carrito.addProduct(producto);
    	}
    	return carrito;
	}
	
	/**
	 * Test para verificar el cálculo del precio para un pedido CON descuento (Hay más de 9 productos)
	 */
    @org.junit.Test
    public void calculoConDescuento(){
    	final int	  NUMERO_PRODUCTOS = 20;
    	CarritoCompra carrito  = this.getCarrito(NUMERO_PRODUCTOS);
    	final double TOTAL	   = PRECIO_PRODUCTO * NUMERO_PRODUCTOS;
    	final double DESCUENTO = TOTAL - (TOTAL * 0.04);
    	
    	Assert.assertTrue(carrito.getPrecio() == DESCUENTO);
    }

	/**
	 * Test para verificar el cálculo del precio para un pedido SIN descuento (Hay menos de 9 productos)
	 */
    @org.junit.Test
    public void calculoSinDescuento(){
    	final int	  NUMERO_PRODUCTOS = 5;
    	CarritoCompra carrito  = this.getCarrito(NUMERO_PRODUCTOS);
    	Assert.assertTrue(carrito.getPrecio() == (PRECIO_PRODUCTO * NUMERO_PRODUCTOS));  	
    }    
}

    

Archivo de configuración de Spring:

A continuación exponemos el archivo de configuración de Spring, observe:

  1. Cómo importamos los espacios de nombres de Spring lang (líneas 4 y 6).
  2. Cómo definimos la clase que implementará la interfaz CalculadorCoste y como le decimos donde está ubicada dentro de nuestro sistema de ficheros o classpath. (líneas 22-25)
  3. Cómo inyectamos la clase anterior en el Bean CarritoCompra (línea 13).

En relación al resto del archivo, se presupone que el lector ya tiene conocimientos de Spring como para comprenderlo...

<?xml version="1.0" encoding="UTF-8"?>

					

	
	
	
	
		  	
	
	
		
	
	

		
	
	

Archivo de configuración de Maven: pom.xml:

A continuación exponemos el archivo de configuración de Maven, se presupone que el lector ya tiene nociones de Maven.

<?xml version="1.0" encoding="UTF-8"?>

	4.0.0
	com.autentia.tutoriales
	spring_beanshell
	jar
	1.0-SNAPSHOT
	spring_beanshell
	http://www.adictosaltrabajo.com

	
		
			
				maven-compiler-plugin
				
					1.5
					1.5
					UTF-8
				
			
		
		
	
	
	
		
			org.springframework
			spring
			2.5.6
		
		
		
			org.springframework
			spring-core
			2.5.6
		

		
			org.beanshell
			bsh
			2.0b4
		
		
		
			junit
			junit
			4.3.1
			test
		
	


Ejecutamos la aplicación

A continuación ejecutamos la aplicación a través del comando mvn test y veremos como Maven se descarga las dependencias, compila el proyecto, ejecuta los tests...

En la imagen anterior vemos el resultado de todo... ahora pruebe a cambiar el script BeanShell tendrá otro comportamiento sin recompilar.

Integración con JRuby y Groovy

Es todo igual, excepto el tag del archivo de configuración de Spring (arriba en el mismo archivo, está comentado como sería) y por supuesto la implementación concreta de la interfaz CalculadorCoste.
Tiene ejemplos de su uso en el enlace de la sección Preferencias, de este mismo tutorial.

Referencias

http://static.springframework.org/spring/docs/2.5.x/reference/dynamic-language.html.

Conclusiones

Bueno, como veis el FrameWork Spring no deja de sorprendernos en cuanto a su potencia y ventajas en el desarrollo de software de calidad.

Espero que os haya parecido útil este tutorial.

Carlos García Pérez. Creador de MobileTest, un complemento educativo para los profesores y sus alumnos.

cgpcosmad@gmail.com

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad