Java
La simplicidad de Apache Jexl
0
Este artículo puede ser útil para todos aquellos que alguna vez tuvieron que hacer cientos o miles de líneas de código usando reflexión para poder acceder a atributos, navegar mapas y recorrer listas en Java. Ahora bien si le agregamos que esas listas o mapas pueden tener más de lo mismo o más complejo aún, objetos que contienen listas o mapas.
Aquí Apache Jexl hace bien su trabajo utilizando un lenguaje cómodo para poder acceder a esos recónditos lugares.
Apache Jexl (Java EXpresion Language) fue inspirado por Apache Velocity y por la definición de Expresion Language para JSTL y JSP 2.0. Ademas en la versión 2.0 se agregaron funcionalidades de Unified EL.
Un pequeño ejemplo para que lo véan en acción:
public class TestDTO { private String _name; private Map<String, Object> _dataBag; /** Se omiten los setter y getter **/ }
Esta es la definición de un DTO que tiene dos atributos, uno de ellos es un String y otro un Map cuya clave siempre será String y su valor puede ser un Object.
La problemática es obtener un valor desde ese DTO mediante reflexión, es decir, el ente que ejecuta la extracción del parámetro no conoce la definición a priori de esa clase. Es aquí donde entra Jexl y mediante un simple lenguaje le podemos indicar hasta donde acceder. A ese cómo y qué le llamaremos Expresión.
public class JexlEvaluator { private static final JexlEngine jexl = new JexlEngine(); static { jexl.setCache(512); jexl.setLenient(false); jexl.setSilent(false); } public Object getValue(String expresion, Object object) { Expression e = jexl.createExpression(expresion); JexlContext context = new MapContext(); context.set("fua", object); return e.evaluate(context); } }
Esta simple clase en su bloque estático configura Apache Jexl para luego ser utilizado en el método getValue.
El método getValue recibe como parámetros, la expresión y el objeto a evaluar. Dentro del método se crea el contexto de Apache Jexl (muy similar a un HashMap) al cual se le pasa el Objeto a inspecionar y luego evalúa la expresión.
Aquí les dejo el test unitario y la salida:
public class JexlEvaluatorTest { private static Log logger = LogFactory.getLog(JexlEvaluatorTest.class); @Test public void testJexl() { JexlEvaluator je = new JexlEvaluator(); /* Expresión a evaluar */ String expresion = "fua.dataBag.hola2"; Map<String, Object> map = new HashMap<String, Object>(); map.put("hola", "Hola 1 del MAP"); Map<String, Object> map2 = new HashMap<String, Object>(); map2.put("hola2", "Hola 2 del MAP"); map.put("hola2", map2); TestDTO testDTO = new TestDTO(); testDTO.setDataBag(map2); testDTO.setName("DTO Interesante"); Object result = je.getValue(expresion, testDTO); logger.info(result.toString()); } }
La ejecución de ese test debería retornar lo siguiente:
[29/08/11 09:08:16:016 CLT] [ INFO] [JexlEvaluatorTest:44] - Hola 2 del MAP
Más adelante espero poder escribir sobre Spring EL, otra solución para evaluación de expresiones para Java.
Junit, ServletContextAware y MockServletContext
0Se me generó el siguiente problema al hacer un test unitario sobre una clase que implementa la interfaz de Spring Framework ServletContextAware.
public interface ServletContextAware { void setServletContext(ServletContext servletContext); }
Mediante esta interfaz puedes inyectar el servletContext a la configuración de tu Bean, siempre y cuando tengas un contexto web funcionando.
He aquí el segundo problema, ¿ Cómo puedo tener el un contexto web dentro de un Junit ?. Puede que exista una mejor forma de hacerlo pero lo logré de la siguiente manera.
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <bean class="org.springframework.mock.web.MockServletContext" /> </beans>
Con esto y usando la anotación Autowired funciona de pelos la inyección del ServletContext.
public class ReadFileFromWebinf { @Autowired private ServletContext _servletContext; /*Se omite el resto de los métodos*/ }
Pero!!! cuando quise utilizar la interfaz ServletContextAware no funcionó y me quede con una propiedad en null.
public class ReadFileFromWebinf implement ServletContextAware { private ServletContext _servletContext; public void setServletContext(ServletContext servletContext){ _servletContext=servletContext; }; /*Se omite el resto de los métodos*/ }
Buscando por todos lados encontré que a mi test unitario le faltaba un par de configuraciones para la lectura del contexto Spring.
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = { "/test/test-context.xml" } @TestExecutionListeners({ DependencyInjectionTestExecutionListener.class }) public class ReadFileFromWebinfTest { @Autowired private ReadFileFromWebinf _readFileFromWebinf; public void testReadFile(){ String fileContent = _readFileFromWebinf.load("archivo.xml"); System.out.println(fileContent); } }
Las dos primeras anotaciones son para decirle a Spring que se va a utilizar Junit para ejecutar la clase y cual o cuales son los contextos a cargar.
Luego viene la anotación que me arregló el día, TestExecutionListeners y el listener asociado para la completar las inyecciones de dependencia de todos los beans. (Antes con Autowired solo hacía algunos).
Espero les sirva, cualquier duda bienvenida sea.
Spring Framework – InitializingBean
0
InitializingBean
Esta interfaz de Spring Framework te permite una vez configurado el contexto, ejecutar el método afterPropertiesSet(), y como su nombre lo dice, lo ejecuta luego de ejecutar los métodos setter del bean (post configurar el bean).
public interface InitializingBean { void afterPropertiesSet() throws Exception; }
Algunas aplicaciones de esta interfaz:
- Validar que los atributos de un Bean estén correctamente inicializados.
- A partir de los atributos ya setteados, generar otra propiedad en tiempo de configuración. Por ejemplo, se configura el bean con dos atributos de tipo int y en tiempo de configuración quiero calcular la suma de ambos y dejarlo en otra propiedad. ¿ Qué gano con esto ? que esa propiedad no se calcula cada vez que hago el getter sino que ya esta previamente calculada.
Para el primer caso, recomiendo utilizar la clase Assert ya que provee métodos con los que puedes invalidar que el contexto Spring levante si hay errores en la configuración.
Les dejo un código de ejemplo para que le echen un vistazo (le agregué la interfaz al ejemplo del post anterior)
public class ExampleBean implements InitializingBean { private Integer _randomValue; private Integer _newValue; public final Integer getRandomValue() { return _randomValue; } public final Integer getNewValue() { return _newValue; } public final void setRandomValue(Integer randomValue) { _randomValue = randomValue; } public void afterPropertiesSet() throws Exception { Assert.notNull(_randomValue, "randomValue properties cannot be null"); _newValue = _randomValue * 10; } }
Voy a explicar un poco y a groso modo el como funciona esta clase desde el punto de vista de Spring:
- Se lee la configuración asociada a la clase ExampleBean
- Se crea la instancia de la clase ExampleBean
- Luego vía inyección se le settean los atributos, entre ellos randomValue
- Luego de la creación del bean ExampleBean se ejecuta el método afterPropertiesSet(), en donde se realiza una validación del randomValue (este NO puede ser nulo, de serlo, el contexto de Spring no quedara operativo), si eso pasa OK, se calculará el atributo newValue y que será randomValue multipliado por 10.
- Finalmente y si no hay errores en el contexto de Spring, se deja la instancia del bean ExampleBean en el contexto, es decir, el valor randomValue y newValue quedan listos para ser usados.
Este proceso se realiza sólo una vez (salvo que el scope no sea singleton) y es en el momento de la creación del contexto.
Demás esta decir que esta clase/bean (ExampleBean) debe estar configurada en el archivo del contexto de Spring o vía las anotaciones (metadata) provistas para eso.
¿Por que FactoryBean<T> es útil?
1En este post voy a tratar de explicar porque FactoryBean<T> es increíblemente útil para alambrar (aplicar DI) aplicaciones hechas con Spring Framework.
La interfaz de FactoryBean dice lo siguiente:
public interface FactoryBean<T> { T getObject() throws Exception; Class<?> getObjectType(); boolean isSingleton(); }
La utilidad de la implementación de esta interfaz y su posterior declaración como Bean dentro del contexto de la aplicación, es que puede ser usada (como su nombre lo indica) como Factory para inyectar algún valor en alguna propiedad de otro Bean.
¿Dónde y cómo funciona? Dentro del ciclo de vida de la carga del Contexto de spring, la ejecución de la implementación de esta interfaz, esta justo antes de hacer los setters de las propiedades de un Bean. El funcionamiento es simple, siempre se ejecutará el método getObject() que retornará la instancia del tipo T.
Veamos una implementación simple y una configuración de contexto de spring:
public class RandomNumberFactoryBean implements FactoryBean<Integer> { public Integer getObject() throws Exception { return Integer.valueOf(new Random().nextInt()); } public Class<?> getObjectType() { return Integer.class; } public boolean isSingleton() { return true; } }
Esta simple implementación de FactoryBean
El método getObjectType() es usado internamente por el framework para validar que el retorno de getObject() sea correcto o que este dentro de la jerarquía de clases, esto es en caso de que quieras restringir por tipo.
El último método isSingleton() es si quieres que esta clase (no importa la cantidad de declaraciones que tengas, siempre será la misma), es un tanto dificil de entender pero les sugiero que lean sobre el concepto de Singleton que tiene SpringFramework. En el ejemplo de configuración espero que les quede claro, de lo contrario, bienvenidas las preguntas.
Esta es la definición del Bean de ejemplo, en donde se hará uso del FactoryBean:
public class ExampleBean { private Integer _randomValue; public final Integer getRandomValue() { return _randomValue; } public final void setRandomValue(Integer randomValue) { _randomValue = randomValue; } }
Aquí las configuraciones de contexto de spring.
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- Ejemplo 1 --> <bean id="exampleBean1" class="cl.pcollaog.factory.ExampleBean"> <property name="randomValue"> <bean class="cl.pcollaog.factory.RandomNumberFactoryBean" /> </property> </bean> <!-- Ejemplo 2 --> <bean id="randomNumberFactoryBean" class="cl.pcollaog.factory.RandomNumberFactoryBean" /> <bean id="exampleBean21" class="cl.pcollaog.factory.ExampleBean"> <property name="randomValue" ref="randomNumberFactoryBean" /> </bean> <bean id="exampleBean22" class="cl.pcollaog.factory.ExampleBean"> <property name="randomValue" ref="randomNumberFactoryBean" /> </bean> </beans>
Ejemplo 1:
Se declara el Bean exampleBean1 y que tiene como propiedad el atributo randomValue y cuyo valor será producto de la ejecución del Bean RandomNumberFactoryBean en el momento que el contexto de Spring es cargado. En este caso se utiliza la técnica de InnerBean, es decir, no se tiene una instancia del Bean para ser reutilizada sino que esta en la misma declaración del setter del atributo.
Ejemplo 2:
Este ejemplo hace lo mismo indicado arriba, sólo que no se aplica InnerBean sino que se extrae y es reutilizado en el bean exampleBean21 y exampleBean22. En este ejemplo entra en juego el método isSingleton(), si es true, quiere decir que el valor que entregará el FactoryBean siempre será el mismo para ambos Beans. Ahora bien, si se cambia a false, por cada setter, es decir, para exampleBean21 y exampleBean22 se realizara la ejecución de RandomNumberFactoryBean 2 veces. (En este caso, nos entregará dos valores aleatorios para cada bean)
En pocas palabras para cada Bean del ejemplo 2 habrá dos instancias de RandomNumberFactoryBean que entregará cada una un Integer para los atributos de los beans declarados.
Algo interesante de las implementaciones de FactoryBean
Espero les sea útil y cualquier pregunta sera bienvenida.
Java7: null-safe y null-default
0Seguramente les ha pasado y mucho que tienen que llenarse de validaciones contra NullPointerException cuando los métodos retornan null, hay una propuesta para poder manejar este tipo de problemas. Veamos uno ejemplos:
Null-Default
Hoy en dia:
String maybeNull = metodoNull(); if (null == maybeNull) { maybeNull="Ahora no es null"; } // Otra Opcion String nullVar = metodoNull(); nullVar = (nullVar != null ? nullVar : "Ahora no es null");
Ahora la propuesta es la siguiente:
String maybeNull = metodoMaybeNull(); maybeNull = maybeNull ?: "Ahora no es null";
Como verán nos ahorramos unas cuantas líneas de código. Ahora veamos el otro esquema
Null-safe
Hoy en dia:
String result = null;
Foo foo = getFooMayBeNull();
if (foo != null) {
Bar bar = foo.getBarMayBeNull();
if (bar != null) {
result = bar.getResult();
}
}
Con la propuesta de null-safe quedaría así:
String result = getFooMayBeNull()?.getBarMayBeNull()?.getResult();
Esto hasta el dia de hoy seguía en propuesta y por lo que veo en las Características de Java7 no viene, así que a esperar. (Estoy bajando el jdk7 para probar, luego updates)
Estas modificaciones al lenguaje (sintaxis) vienen de los operadores que tiene Goovy.
Más información sobre esta propuesta de mejora al lenguaje en: