tag:blogger.com,1999:blog-20122320692899570922024-03-12T23:32:52.793-07:00Tutoriales de Programacion JavaBlog dedicado a programación usando el lenguaje Java y las últimas versiones de sus APIs y Herramientas.Alexhttp://www.blogger.com/profile/06974037481671868076noreply@blogger.comBlogger62125tag:blogger.com,1999:blog-2012232069289957092.post-15301260813490095422022-03-02T07:06:00.000-08:002022-03-02T07:06:03.472-08:00Patrón de diseño Builder<div style="text-align: justify;">
<img src="https://img.shields.io/badge/JT-Design%20Patterns-orange" /><br />
<br />
<span class="codigo">Builder</span> es un <a href="https://www.javatutoriales.com/2021/11/introduccion-los-patrones-de-diseno.html" target="_blank">patrón de diseño</a> <span class="negritas">creacional</span> con ámbitos de <span class="negritas">objeto</span>. Nos ayuda a crear objetos complejos de una forma sencilla usando un procedimiento de "paso por paso". Esto facilita el trabajo enormemente cuando tenemos objetos con muchos atributos, pero no necesitamos establecer de inicio todos sus valores, o no siempre establecemos los mismos. De esta forma nos guía en la construcción de estos obejtos. También ayuda en el ensamblado de objetos complejos que están formados por otros objetos.<br />
<br />
Debido a esto, este patrón es una de las formas más utilizadas para la creación de objetos; ya que es un elemento externo a los objetos que estamos creando, pero con la suficiente información para ayudarnos en el ensamblaje correcto de estos<br />
<br />
En este tutorial te explico los detalles de este patrón, y te muestro tres estrategias diferentes de implementación.<br />
<br />
<a name='more'></a>
El patrón <span class="codigo">Builder</span> permite la construcción de objetos de dos formas que, aunque a primera vista podrían parecer diferentes, ambas siguen la esencia del patrón.<br />
<br />
Con la primera estrategia buscamos delegar la construcción de objetos complejos a dos elementos externos, el <span class="codigo">Builder</span> y el <span class="codigo">Director</span>, para que no nos preocupemos de los detalles de la construcción de este. Esto sirve cuando ese objeto complejo debe estar ensamblado con un cuidado y precisión milimétrica (sí, suena exagerado pero muchas veces esto es un requisito para el correcto funcionamiento del sistema). El objeto <span class="codigo">Director</span> hace uso del <span class="codigo">Builder</span> para ensamblar el objeto y entregárnoslo listo para su uso.<br />
<br />
Con la segunda estrategia no hay un <span class="codigo">Director</span> y el <span class="codigo">Builder</span> actúa también como un objeto externo para la construcción, pero en este caso nos ayuda guiándonos (mostrándonos o limitando las opciones que tenemos) sobre cómo construir de forma correcta el objeto. Esta segunda estrategia sirve cuando el objeto que queremos construir tiene muchas opciones o posibilidades correctas para su construcción; en otras palabras, <span class="negritas">la inicialización de este es flexible en cuanto a los parámetros que necesitamos para su construcción</span>.<br />
<br />
Si no usamos este patrón podemos lograr un efecto algo parecido, proporcionando muchos constructores en nuestra clase, y de esta forma el usuario puede elegir el constructor que más se adecúe a sus necesidades. Sin embargo, el hacerlo así haría que el mantenimiento de la clase sea muy complejo y la construcción de esta termine volviéndose algo obscura. Vemos esto con un ejemplo sencillo.<br />
<br />
Imagina que tenemos una clase <span class="clase">Usuario</span>, la cual tiene los siguientes atributos:
<pre class="brush: java;">private String nombre;
private String apellido;
private int edad;
private int numeroDepartamento;
private String apodo;
private String comidaFavorita;
</pre><br />
La clase proporciona una serie de constructores para que no sea necesario pasar todos los valores de los atributos, y en un momento dado nos encontramos con esto:
<pre class="brush: java;">new Usuario ("Alfonso", "Olaguibert", 34, 42, "pollo");
</pre><br />
Con solo eso es difícil saber, a primera vista, qué valores estamos proporcionando. ¿"Alfonso" es nombre o apellido? ¿Cuál es la edad y cuál el número de departamento? <br />
<br />
A primera vista es difícil responder a alguna (o todas) las preguntas anteriores ya que para hacerlo necesitamos conocer el orden en el que debemos proporcionar los valores a la versión anterior del constructor (la cual puede variar entre las distintas versiones sobrecargadas del constructor). Claro, siempre tenemos la ayuda contextual que nos da el IDE, pero eso hace que tengamos que detenernos (aunque sea unos segundos) a analizar y entender lo que estamos viendo.<br />
<br />
Podemos facilitar un poco el proceso anterior si usamos variables en lugar de constantes, de esta forma:
<pre class="brush: java;">String nombre = "Olaguibert";
String apellido = "Alfonso";
int edad = 34;
int numeroDepartamento = 42;
String apodo = "pollo";
new Usuario (apellido, nombre, edad, numeroDepartamento, apodo);
</pre><br />
Pero esto solo refuerza lo que dije al inicio: hace que sea más complejo el mantenimiento del código.<br />
<br />
Veremos cómo este patrón nos ayuda a simplificar casos como el anterior (y mucho peores).<br />
<br />
<br /><h2 class="titulo"><a class="anchorTitle" href="#objetivo" name="objetivo">
<svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z" fill-rule="evenodd"></path></svg>
Objetivo o intención del patrón</a></h2>
Como podemos ver, el objetivo de este patrón es:
<ul>
<li>Separar la construcción de un objeto complejo de su representación, de forma tal que con el mismo proceso se puedan crear diferentes representaciones.</li>
</ul><br />
Esto quiere decir que podemos tener dos instancias, de <span class="clase">Usuario</span> siguiendo el ejemplo anterior, y que a pesar de que usemos el mismo proceso de construcción las dos instancias son diferentes; y no hablando solo de que son dos instancias diferentes de la misma clase, sino que sus valores internos (su estado) es diferente también. Esto se verá más claramente en los ejemplos.<br />
<br /><br />
<h2 class="titulo"><a class="anchorTitle" href="#implementacion" name="implementacion">
<svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z" fill-rule="evenodd"></path></svg>
Implementación</a></h2>
Antes de ver los detalles en código, un poco de teoría de cómo implementar el patrón. Algunos de estos pasos serán opcionales dependiendo de la estrategia de implementación.<br />
<br /><ol>
<li><span class="negritas">Definir un tipo de objeto complejo de crear</span>. Este será el tipo del objeto que obtendremos como resultado del uso del <span class="codigo">Builder</span>. Puede ser un tipo abstracto o concreto.</li>
<li>Si el tipo es abstracto, crear una representación concreta.</li>
<li><span class="negritas">Definir un tipo <span class="codigo">Builder</span></span>. Este tendrá los pasos para la construcción del objeto concreto. Dependiendo de la implementación puede ser abstracto o concreto.</li>
<li>Si el <span class="codigo">Builder</span> es abstracto, definir un tipo concreto.</li>
<li><span class="negritas">Definir una clase que sirva como <span class="codigo">Director</span> y que controle los pasos para la creación del objeto</span>. Dependiendo de la estrategia, el <span class="codigo">Builder</span> puede funcionar también como el <span class="codigo">Director</span>.</li>
</ol>
Sí, sé que ahora todos los pasos suenan un poco confusos, pero todo quedará explicado y más claro con los diagramas y los ejemplos en las siguientes secciones.<br />
<br />
Te recomiendo que una vez que hayas visto los diagramas con sus correspondientes explicaciones y hayas visto los ejemplos en código vuelvas a leer los pasos de la implementación para que todo quede mucho más claro.<br />
<br />
<br /><h2 class="titulo"><a class="anchorTitle" href="#diagrama" name="diagrama">
<svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z" fill-rule="evenodd"></path></svg>
Diagrama</a></h2>
Tengo que aclarar algo importante sobre los diagramas y las estrategias antes de ir a la explicación.<br />
<br />
El diagrama clásico que encontrarás si buscas este patrón es el siguiente, al cual llamaré <span class="negritas">Estrategia A</span>.<br />
<span class="nota">📌Nota: Estos nombres (estrategia A, B y C) son nombres que yo le estoy dando para diferenciar las formas de implementar el patrón, no son nombres "oficiales" de las diferentes estrategias.</span>
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEgAH7wrzishFZAO7HuN4Y6DmbOb4DFID4LbZiRcIo0Kxo0Oyz_-m_kanqeCoAzWvxE0ntcMPiol2qOmVDto9RFMrkm1hpAbvd_aDKBNRC7UE45V6tLAv2jMu_Z4CZmpXdA2ifMsvsfTRVo3sXzNSlgDyt66fGotw7ylmsZ1_HBqZzMYhblt-7OGCrph_Q" style="display: block; padding: 1em 0px; text-align: center;"><img border="0" data-original-height="312" data-original-width="753" src="https://blogger.googleusercontent.com/img/a/AVvXsEgAH7wrzishFZAO7HuN4Y6DmbOb4DFID4LbZiRcIo0Kxo0Oyz_-m_kanqeCoAzWvxE0ntcMPiol2qOmVDto9RFMrkm1hpAbvd_aDKBNRC7UE45V6tLAv2jMu_Z4CZmpXdA2ifMsvsfTRVo3sXzNSlgDyt66fGotw7ylmsZ1_HBqZzMYhblt-7OGCrph_Q=s16000" /></a></div><br />
<br />
No hay nada de malo con el diagrama anterior; sin embargo, este representa solo una de las estrategias de <span class="codigo">Builder</span> y no da ni una pista de las otras dos implementaciones (más simples). Así que me he dado a la tarea de crear la representación de las otras dos estrategias de Builder.<br />
<br />
Para la <span class="negritas">Estrategia B</span> el diagrama es el siguiente:<br />
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEi6HT5X0SRTKRV8xxd5Bm9eKqyYwJ_s8n_-2O5uFNb0RCFOtgk-UTmw_GsTAEfcMmjANYv2i7yYjRvI6lp5XqwqCwIx89VZEFUd0gv7cFhQLQw9gTpEQ5msZRDFdDdpRo2vIAVRPr5LHjK8DH3IieBxtx1M1JKWwcIvbwg_LydDEUO2F35srCQzq_zSqw" style="display: block; padding: 1em 0px; text-align: center;"><img alt="" border="0" data-original-height="232" data-original-width="637" src="https://blogger.googleusercontent.com/img/a/AVvXsEi6HT5X0SRTKRV8xxd5Bm9eKqyYwJ_s8n_-2O5uFNb0RCFOtgk-UTmw_GsTAEfcMmjANYv2i7yYjRvI6lp5XqwqCwIx89VZEFUd0gv7cFhQLQw9gTpEQ5msZRDFdDdpRo2vIAVRPr5LHjK8DH3IieBxtx1M1JKWwcIvbwg_LydDEUO2F35srCQzq_zSqw" /></a></div>
<br />
Y para la <span class="negritas">Estrategia C</span> tenemos:<br />
<br />
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEi6l2TJrNAWvxCxtK_uNDBIQmoxHxoixfA5N6a9UHJ4vvTtomp2L-_pgQ5yLsm7v4L4IPe4tt9NFwHU8rKnzc4sHcGgiOJm68Gr0mGAeDwXVq1SKeomdW2AezKtEMf23gMTDbLlZlGwHIKmwZ2w2tQoLKnsAZFwCuihY8lKJ5MIxAqiiCRfHgjngdirYA" style="display: block; padding: 1em 0px; text-align: center;"><img alt="" border="0" data-original-height="412" data-original-width="443" src="https://blogger.googleusercontent.com/img/a/AVvXsEi6l2TJrNAWvxCxtK_uNDBIQmoxHxoixfA5N6a9UHJ4vvTtomp2L-_pgQ5yLsm7v4L4IPe4tt9NFwHU8rKnzc4sHcGgiOJm68Gr0mGAeDwXVq1SKeomdW2AezKtEMf23gMTDbLlZlGwHIKmwZ2w2tQoLKnsAZFwCuihY8lKJ5MIxAqiiCRfHgjngdirYA" /></a></div><br />
<br />
He comenzado con el diagrama de la estrategia A porque, como lo mencioné, este es el diagrama que encontrarás normalmente en Internet. Pero, comenzaré explicando de atrás hacia adelante, es decir de la estrategia C a la A, ya que me parece que es más fácil entender la utilidad del patrón de esta forma. Haré lo mismo con los ejemplos. <br />
<br />
<span class="nota">🏆 Es importante que leas con cuidado cada estrategia, ya que explico <span class="negritas">algunas variaciones de cada una de ellas</span> para poder mostrar la enorme flexibilidad que tiene este patrón.</span><br />
<br />
Nuevamente echa un vistazo al diagrama de la <span class="negritas">Estrategia C</span>.<br />
<br />
En la Estrategia C, el <span class="codigo">Cliente</span> necesita una instancia de la clase <span class="codigo">Producto</span> pero, si observas bien, el constructor de <span class="codigo">Producto</span> no tiene ningún modificador de acceso. Eso quiere decir que <span class="codigo">Producto</span> solo puede ser construido desde clases que se encuentran en el mismo paquete. Lo más seguro es que <span class="codigo">Cliente</span> se encuentre en un paquete distinto al de <span class="codigo">Producto</span>. <br />
<br />
Por otro lado, tenemos la clase <span class="codigo">Builder</span> (que en este caso es concreta pero también podría ser una interface). <span class="codigo">Builder</span> está en una situación similar a <span class="codigo">Producto</span>, tiene un constructor sin modificador de acceso. Lo único con lo que cuenta <span class="codigo">Builder</span> es con una serie de métodos que permiten establecer los valores de los atributos de <span class="codigo">Producto</span>, representados en el diagrama con el método <span class="clase">atributo</span> (el cual en la implementación será reemplazado por cada uno de los nombres de los atributos de la clase <span class="codigo">Producto</span>), y un método <span class="clase">build</span> que regresa una instancia de <span class="codigo">Producto</span> con los valores establecidos.<br />
<br />
Así que, el <span class="codigo">Cliente</span> necesita del <span class="codigo">Builder</span> para crear una instancia de <span class="codigo">Producto</span>, pero no se puede crear una instancia de <span class="codigo">Builder</span> directamente. ¿Qué podemos hacer en este caso? Bien, la solución se encuentra en la clase <span class="codigo">Producto</span>, tiene un método estático, <span class="clase">builder</span> que regresa una nueva instancia de <span class="codigo">Builder</span>. Con esta instancia podemos comenzar la construcción, la cual sería algo así:
<pre class="brush: java;">Builder builder = Producto.builder();
Producto producto = builder.nombre("...").precio(0.0).categoria("...").build();
</pre><br />
Los métodos <span class="clase">nombre</span>, <span class="clase">precio</span> y <span class="clase">categoria</span> son los que reemplazan el método <span class="clase">atributo</span> en el diagrama del patrón.<br />
<br />
En este ejemplo el <span class="codigo">Builder</span> es una clase concreta, pero bien podría ser una interface. Esto daría la ventaja de que no necesariamente debemos usar siempre la misma clase <span class="codigo">Builder</span>, podríamos (a través de un patrón <span class="codigo">Factory</span>) utilizar diferentes <span class="codigo">Builder</span>s dependiendo de distintas condiciones o situaciones.<br />
<br />
Este patrón tiene la ventaja de que podemos extenderlo para guiar la construcción de objetos complejos. Pensemos que tenemos una clase <span class="clase">Usuario</span> que tiene una <span class="clase">Direccion</span> y varios <span class="clase">Telefono</span>s asociados, algo similar a esto:<br />
<br />
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEiZhxMb8w5fgY8pQ1zqjJtffVyzTmEfuvoSIfLYrTJWjsug0JgjGbNVd2o8WAKt1MBNW1oKImSpHl_9-ZP9hxpn7-OW-5HAkIu_VFNiYhAwtz7voDK-MGJJ5vzFL6EtDdrhTWq8D2M-HXw3Ct8GBOA5Eto_iQesoMxhAw7mBeAeN4nDIrihRWVSnSBTEA=s933" style="display: block; padding: 1em 0px; text-align: center;"><img alt="" border="0" data-original-height="932" data-original-width="933" src="https://blogger.googleusercontent.com/img/a/AVvXsEiZhxMb8w5fgY8pQ1zqjJtffVyzTmEfuvoSIfLYrTJWjsug0JgjGbNVd2o8WAKt1MBNW1oKImSpHl_9-ZP9hxpn7-OW-5HAkIu_VFNiYhAwtz7voDK-MGJJ5vzFL6EtDdrhTWq8D2M-HXw3Ct8GBOA5Eto_iQesoMxhAw7mBeAeN4nDIrihRWVSnSBTEA=s600" width="600" /></a></div><br />
<br />
En el diagrama anterior podemos ver que usamos un <span class="clase">UsuarioBuilder</span> para construir el <span class="clase">Usuario</span>, y el <span class="codigo">Builder</span> ayuda también a construir las instancias correspondientes de <span class="clase">Direccion</span> y <span class="clase">Telefono</span> a través de un <span class="clase">DireccionBuilder</span> y un <span class="clase">TelefonoBuilder</span>. Cuando invocamos el método <span class="clase">build</span> de estos dos últimos, obtenemos de regreso un <span class="clase">UsuarioBuilder</span>. Al final incluso podemos validar que tengamos por lo menos un teléfono y una dirección (de hecho, esto es lo que haremos en el ejemplo del tutorial).<br />
<br />
Esta estrategia es muy flexible y existen algunas variaciones la misma en las que el constructor de <span class="clase">Usuario</span> recibe al <span class="clase">UsuarioBuilder</span> y lo usa para inicializar sus datos, similar a esta forma, en la que solo dejo las partes más importantes del código:<br />
<br />
<pre class="brush: java; highlight: [8, 15, 26, 31];">public class Usuario {
private String nombre;
private String username;
private int edad;
private String password;
Usuario(UsuarioBuilder builder) {
this.nombre = builder.getNombre();
this.username = builder.getUsername();
this.edad = builder.getEdad();
this.password = builder.getPassword();
}
public static UsuarioBuilder builder(){
return new UsuarioBuilder();
}
}
public class UsuarioBuilder {
private String nombre;
private String username;
private int edad;
private String password;
UsuarioBuilder() {
}
public Usuario build() {
Usuario usuario = new Usuario(this);
return usuario;
}
}
</pre><br />
<br />
Y lo usaríamos de la siguiente forma:
<pre class="brush: java; highlight: [8, 22, 26];">Usuario usuario = Usuario.builder()
.edad(30)
.username("javatutoriales")
.nombre("Programador Java")
.password("123456789")
.build();
</pre><br />
Con <span class="codigo">Builder</span> incluso podemos forzar el que se establezcan los atributos obligatorios. Digamos que los campos <span class="clase">nombre</span> y <span class="clase">username</span> del <span class="clase">Usuario</span> son obligatorios. Lo único que debemos hacer es solicitarlos en el constructor de <span class="clase">UsuarioBuilder</span>:<br />
<br />
<pre class="brush: java;">UsuarioBuilder(String nombre, String username) {
this.nombre = nombre;
this.username = username;
}
</pre><br />
Y lo usaríamos de esta forma:
<pre class="brush: java;">Usuario usuario = Usuario.builder("Programador Java", "javatutoriales")
.edad(30)
.password("123456789")
.build();
</pre><br />
Ahora hablemos de la <span class="negritas">Estrategia B</span>. <br />
<br />
La Estrategia B es muy parecida, al menos en los conceptos y las ideas de la Estrategia C. Veamos nuevamente su diagrama:
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEi6HT5X0SRTKRV8xxd5Bm9eKqyYwJ_s8n_-2O5uFNb0RCFOtgk-UTmw_GsTAEfcMmjANYv2i7yYjRvI6lp5XqwqCwIx89VZEFUd0gv7cFhQLQw9gTpEQ5msZRDFdDdpRo2vIAVRPr5LHjK8DH3IieBxtx1M1JKWwcIvbwg_LydDEUO2F35srCQzq_zSqw" style="display: block; padding: 1em 0px; text-align: center;"><img alt="" border="0" data-original-height="232" data-original-width="637" src="https://blogger.googleusercontent.com/img/a/AVvXsEi6HT5X0SRTKRV8xxd5Bm9eKqyYwJ_s8n_-2O5uFNb0RCFOtgk-UTmw_GsTAEfcMmjANYv2i7yYjRvI6lp5XqwqCwIx89VZEFUd0gv7cFhQLQw9gTpEQ5msZRDFdDdpRo2vIAVRPr5LHjK8DH3IieBxtx1M1JKWwcIvbwg_LydDEUO2F35srCQzq_zSqw" /></a></div>
<br />
Prácticamente la única diferencia que existe en esta estrategia es que el <span class="codigo">Builder</span> es una clase anidada dentro de la clase <span class="codigo">Producto</span>. Esta estrategia ayuda mucho si siempre tendremos una sola forma de crear el <span class="codigo">Producto</span> (lo cual es, a mi parecer, lo normal).<br />
<br />
Con esta estrategia también podemos usar la variación en la que el constructor de <span class="clase">Usuario</span> recibe al <span class="clase">UsuarioBuilder</span>, pero con las clases anidadas podemos tomarnos algunas libertades en cuanto al uso de los modificadores de acceso. El constructor de <span class="clase">UsuarioBuilder</span> puede ser público. Sería algo como:
<pre class="brush: java; highlight: [8, 15, 21, 31];">public class Usuario {
private String nombre;
private String username;
private int edad;
private String password;
private Usuario(UsuarioBuilder builder) {
this.nombre = builder.nombre;
this.username = builder.username;
this.edad = builder.edad;
this.password = builder.password;
}
public static class UsuarioBuilder {
private String nombre;
private String username;
private int edad;
private String password;
public UsuarioBuilder() {
}
public Usuario build() {
Usuario usuario = new Usuario(this);
return usuario;
}
}
}
</pre><br />
<br />
Y lo usaríamos de la siguiente forma:
<pre class="brush: java; highlight: [8, 22, 26];">Usuario usuario = new Usuario.UsuarioBuilder()
.edad(30)
.username("javatutoriales")
.nombre("Programador Java")
.password("123456789")
.build();
</pre><br />
Toda la explicación que dimos de las variaciones de la estrategia C aplica a la estrategia B, con la excepción de que aquí el <span class="codigo">Builder</span> no puede ser una interface, forzosamente debemos usar una clase concreta.<br />
<br />
Finalmente, hablemos de la <span class="negritas">Estrategia A</span>. Veamos nuevamente el diagrama:<br />
<br />
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEgAH7wrzishFZAO7HuN4Y6DmbOb4DFID4LbZiRcIo0Kxo0Oyz_-m_kanqeCoAzWvxE0ntcMPiol2qOmVDto9RFMrkm1hpAbvd_aDKBNRC7UE45V6tLAv2jMu_Z4CZmpXdA2ifMsvsfTRVo3sXzNSlgDyt66fGotw7ylmsZ1_HBqZzMYhblt-7OGCrph_Q" style="display: block; padding: 1em 0px; text-align: center;"><img border="0" data-original-height="312" data-original-width="753" src="https://blogger.googleusercontent.com/img/a/AVvXsEgAH7wrzishFZAO7HuN4Y6DmbOb4DFID4LbZiRcIo0Kxo0Oyz_-m_kanqeCoAzWvxE0ntcMPiol2qOmVDto9RFMrkm1hpAbvd_aDKBNRC7UE45V6tLAv2jMu_Z4CZmpXdA2ifMsvsfTRVo3sXzNSlgDyt66fGotw7ylmsZ1_HBqZzMYhblt-7OGCrph_Q=s16000" /></a></div><br />
<br />
En esta estrategia tenemos un elemento adicional.<br />
<ul>
<li>El <span class="codigo">Producto</span> representa al objeto complejo que queremos generar.</li>
<li>El <span class="codigo">Builder</span> es una interface o clase abstracta que define todos los pasos que se pueden seguir para crear de forma correcta el <span class="codigo">Producto</span>.</li>
<li>El <span class="codigo">BuilderConcreto</span> representa a una de varias posibles clases concretas que heredan de o implementan la interface <span class="codigo">Builder</span>. Estas clases concretas contienen la lógica particular para crear el <span class="codigo">Producto</span>. ¿Por qué podríamos necesitar varios tipos <span class="codigo">BuilderConcreto</span>? Este es el secreto de la estrategia A, y es que podemos usar diferentes procesos de construcción o distintos "materiales" dependiendo de las características del <span class="codigo">Producto</span> que queramos crear.</li>
<li>El <span class="codigo">Director</span> es la parte menos clara de esta estrategia; este define el orden en el que se realizarán los pasos del proceso de construcción. Por lo tanto, controla el algoritmo para la creación del <span class="codigo">Producto</span> final. Es a través del <span class="codigo">Director</span> que usamos el <span class="codigo">BuilderConcreto</span> para la construcción del <span class="codigo">Producto</span>, y la interacción para obtener el <span class="codigo">Producto</span> ocurre entre el <span class="codigo">Director</span> y el <span class="codigo">BuilderConcreto</span>. El <span class="codigo">Director</span> también permite tener diferentes configuraciones de <span class="codigo">Productos</span>.</li>
</ul><br />
Como te estarás imaginando, existen algunas variaciones de esta Estrategia, en algunas le damos más peso al <span class="codigo">Director</span> y en otras más peso a los distintos <span class="codigo">BuilderConcretos</span>.<br />
<br />
Veamos rápidamente dos de estas variaciones, en la primera le daremos más peso a los <span class="codigo">BuilderConcretos</span>. <br />
<br />
Digamos que tenemos como <span class="codigo">Producto</span> una <span class="clase">Casa</span>. Todas las <span class="clase">Casa</span>s tienen más o menos los mismos elementos: unos cimientos, una estructura, techo, e interior. Para facilitar la explicación, todos los atributos serán de tipo <span class="clase">String</span>, pero bien podrían ser interfaces o clases abstractas para que pudiéramos tener distintos tipos de <span class="clase">Cimiento</span>, <span class="clase">Techo</span>, etc.<br />
<br />
<pre class="brush: java;">public class Casa {
private String cimientos;
private String estructura;
private String techo;
private String interior;
}
</pre><br />
La interface <span class="clase">CasaBuilder</span> permite establecer los valores de cada uno de los atributos definidos y obtener la <span class="clase">Casa</span> final ensamblada:
<pre class="brush: java;">public interface CasaBuilder {
public void construyeCimientos();
public void construyeEstructura();
public void construyeTecho();
public void construyeInterior();
public Casa getCasa();
}
</pre><br />
Antes de pasar a los <span class="codigo">Builder</span> concretos veamos al <span class="codigo">Director</span>, que en este caso estará representado por un <span class="codigo">IngenieroCivil</span>, quien ejecutará los pasos para la construcción de la <span class="clase">Casa</span>:
<pre class="brush: java;">class IngenieroCivil {
private CasaBuilder casaBuilder;
public IngenieroCivi(CasaBuilder casaBuilder){
this.casaBuilder = casaBuilder;
}
public Casa getCasa(){
return this.casaBuilder.getCasa();
}
public void construyeCasa(){
this.casaBuilder.construyeCimientos();
this.casaBuilder.construyeEstructura();
this.casaBuilder.construyeTecho();
this.casaBuilder.construyeInterior();
}
}
</pre><br />
Como podemos ver, el <span class="clase">IngenieroCivil</span> recibe una instancia de <span class="clase">CasaBuilder</span> en su constructor. Esto permite que, por composición, el <span class="clase">IngenieroCivil</span> pueda construir distintos tipos de <span class="clase">Casa</span>.<br />
<br />
Vemos ahora un par de ejemplos de implementaciones de <span class="clase">CasaBuilder</span>. Pensemos que podemos construir dos tipos de <span class="clase">Casa</span>, un Iglú y una casa de Madera. Así que tenemos dos clases concretas, la primera es <span class="clase">IgluBuilder</span>:
<pre class="brush: java;">class IgluBuilder implements CasaBuilder {
private Casa casa;
public IgluBuilder() {
this.casa = new Casa();
}
public void construyeCimientos() {
casa.cimientos = "Barillas de hielo";
}
public void construyeEstructura(){
casa.estructura = "Bloques de hielo";
}
public void construyeInterior() {
casa.interior = "Decoraciones de hielo";
}
public void construyeTecho() {
casa.techo = "Domo de hielo";
}
public Casa getCasa(){
return this.casa;
}
}
</pre><br />
Y ahora, para la casa de madera:
<pre class="brush: java;">class CasaMaderaBuilder implements CasaBuilder {
private Casa casa;
public CasaMaderaBuilder() {
this.casa = new Casa();
}
public void construyeCimientos() {
casa.cimientos = "Vigas de madera";
}
public void construyeEstructura(){
casa.estructura = "Tablones de madera";
}
public void construyeInterior() {
casa.interior = "Decoraciones grabadas";
}
public void construyeTecho() {
casa.techo = "Tablones de madera";
}
public Casa getCasa(){
return this.casa;
}
}
</pre><br />
Al pasar una instancia diferente de <span class="clase">CasaBuilder</span> al <span class="clase">IngenieroCivil</span> este seguirá el proceso de construcción y creará una <span class="clase">Casa</span> diferente dependiendo del <span class="codigo">Builder</span>.<br />
<br />
Su uso sería de la siguiente forma:
<pre class="brush: java;">CasaBuilder builder = new IgluBuilder();
IngenieroCivil ingeniero = new IngenieroCivil(builder);
ingeniero.construyeCasa();
Casa casa = ingeniero.getCasa();
</pre><br />
El proceso es igual, aunque el <span class="codigo">Builder</span> hará que los materiales usados para construir la <span class="clase">Casa</span> sean diferentes.<br />
<br />
Veamos una variación de esta estrategia en la cual le damos una importancia mayor al <span class="codigo">Director</span>. <br />
<br />
Digamos que nuestro <span class="codigo">Producto</span> es un <span class="clase">Curso</span> y un <span class="clase">Curso</span> esta formado por diversas <span class="clase">Partes</span>, así que podemos tener <span class="clase">Partes</span> como: <span class="clase">Introduccion</span>, <span class="clase">Evaluacion</span>, <span class="clase">Ejemplo</span>, <span class="clase">Contenido</span>, <span class="clase">ExamenFinal</span>.<br />
<br />
Ahora el componente de <span class="codigo">Director</span> será implementado por la clase <span class="clase">Instructor</span>, el cual puede armar distintos tipos de <span class="clase">Cursos</span>. Para no complicar la explicación lo dejaremos en dos tipos, un curso gratuito y un curso pagado. Ambos generarán objetos <span class="clase">Curso</span> pero sus <span class="clase">Partes</span> establecidas serán diferentes, los cursos gratuitos constarán de <span class="clase">Introduccion</span>, <span class="clase">Ejemplo</span> y <span class="clase">Evaluación</span>; los cursos pagados estarán formados por todas las partes. <br />
<br />
Omitiré algunos (muchos) detalles en el siguiente código para centrarme solo en las partes importantes; un <span class="clase">Curso</span> debería estar formado por más detalles como el nombre, las descripciones, el tema, etc.; pero me centro solo en la información referente al patrón. El código sería algo como:<br />
<pre class="brush: java;">public interface CursoBuilder {
addIntroduccion(Introduccion introduccion);
addContenido(Contenido contenido);
addEvaluacion(Evaluacion evaluacion);
addEjemplo(Ejemplo ejemplo);
addExamenFinal(ExamenFinal examenFinal);
Curso build();
}
</pre><br />
Y el <span class="clase">Instructor</span> algo como:<br />
<pre class="brush: java; highlight: [4, 8, 16];">public class Instructor {
private CursoBuilder builder;
public void setBuilder(CursoBuilder builder){
this.builder = builder;
}
public Curso creaCursoGratuito(){
builder.addIntroduccion(new Introduccion());
builder.addEjemplo(new Ejemplo());
builder.addEvaluacion(new Evaluacion());
return builder.build();
}
public Curso creaCursoPagado(){
builder.addIntroduccion(new Introduccion ());
builder.addContenido(new Contenido());
builder.addEjemplo(new Ejemplo());
builder.addEvaluacion(new Evaluacion());
builder.addExamenFinal(new ExamenFinal());
return builder.build();
}
}
</pre><br />
Y su uso sería:<br />
<pre class="brush: java;">CursoBuilder builder = new CursoBuilderConcreto();
Instructor instructor = new Instructor();
instructor.setBuilder(builder);
Curso cursoGratuito = instructor.creaCursoGratuito();
</pre><br />
La Estrategia C puede tener algunas variaciones más, pero estas que hemos comentado con las más comunes.<br />
<br />
En muchos lugares he visto que, aunque usan el diagrama de la estrategia A, la explicación que dan corresponde a la estrategia C, 🤦♂️. Así que, por favor, tú no te confundas 😊.<br />
<br />
He intentado mostrar bastante código en esta sección, además de para aclarar cada una de las Estrategias, porque en los ejemplos solo mostraré 2, la estrategia C y la estrategia B.<br />
<br />
¿Dónde se usa Builder en el JDK?<br />
<br />
Builder se usa en la clase <a href="https://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/classes/java/lang/StringBuilder.java" target="_blank"><span class="clase">StringBuilder</span></a> (supongo que de ahí viene el nombre). Cada vez que invocamos el método append recibimos una nueva instancia de <span class="clase">StringBuilder</span>. Intérname va generando una nueva cadena la cual obtenemos al invocar el método <span class="clase">toString</span> (que es el equivalente de <span class="clase">build</span>).<br />
<br />
Además, también podemos encontrarlo en <span class="clase"><a href="https://docs.oracle.com/javase/9/docs/api/java/util/stream/Stream.Builder.html" target="_blank">Stream.Builder</a></span>
<br />
<br /><h2 class="titulo"><a class="anchorTitle" href="#ejemplo" name="ejemplo">
<svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z" fill-rule="evenodd"></path></svg>
Ejemplo</a></h2>
Para los ejemplos uso Java 17 y Gradle. No uso ninguna característica particular de esta versión de Java, por lo que debe funcionar en cualquier versión (incluso en la 5). También, uso <a href="https://www.javatutoriales.com/2019/03/lombok-escribiendo-menos-codigo-y.html" target="_blank">Lombok</a> para evitar escribir código repetitivo de forma innecesaria.<br />
<br />
Para validar el correcto funcionamiento de la aplicación usaré JUnit y AssertJ. Agrega las siguientes dependencias en el archivo <span class="codigo">build.gradle</span> (si usas Maven, coloca las dependencias correspondientes en el archivo <span class="codigo">pom.xml</span>).<br />
<br />
<pre class="brush: groovy;" title="build.gradle">dependencies {
compileOnly 'org.projectlombok:lombok:1.18.22'
annotationProcessor 'org.projectlombok:lombok:1.18.22'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
testImplementation 'org.assertj:assertj-core:3.21.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
}</pre><br />
Ahora sí, al código.<br />
<br />
Primero mostraré un ejemplo de la Estrategia C. De hecho, será el mismo del que hablé en el diagrama. Crearemos un <span class="clase">Usuario</span> el cual tiene una <span class="clase">Direccion</span> y varios <span class="clase">Telefono</span>s. El <span class="clase">Usuario</span> tendrá dos atributos obligatorios: <span class="clase">nombre</span> y <span class="clase">username</span>, y varios atributos opcionales. La <span class="clase">Dirección</span> también tendrá dos atributos obligatorios: <span class="clase">ciudad</span> y <span class="clase">codigoPostal</span>. En el caso del <span class="clase">Telefono</span> todos sus atributos son obligatorios. Después de construir al <span class="clase">Usuario</span> validaremos que tenga al menos un <span class="clase">Telefono</span> y una <span class="clase">Direccion</span>.<br />
<br />
Comenzamos entonces con la clase <span class="clase">Usuario</span>. Esta tiene dos atributos obligatorios, <span class="clase">nombre</span> y <span class="clase">username</span>. El resto de sus atributos son opcionales. Los atributos obligatorios los marcaré como <span class="clase">final</span>. Coloco también un atributo de tipo <span class="clase">Direccion</span> y un <span class="clase">Set</span> de tipo <span class="clase">Telefono</span>:
<pre class="brush: java;" title="Usuario.java">@Data
public class Usuario {
private final String nombre;
private final String username;
private String password;
private short edad;
private String nickname;
private Direccion direccion;
private Set<Telefono> telefonos;
}
</pre><br />
Como los primeros dos atributos son obligatorios, crearé un constructor que obligue a establecerlos al momento de crear una nueva instancia:<br />
<span class="nota">📌Nota: Estoy creando el constructor de forma explícita para tener mayor claridad del tutorial, podría usar la anotación <span class="clase">@lombok.RequiredArgsConstructor</span> para que este constructor sea creado automáticamente por Lombok.</span><br />
<pre class="brush: java;" title="Usuario.java">Usuario(String nombre, String username) {
this.nombre = nombre;
this.username = username;
}
</pre><br />
El constructor no es público, de hecho <span class="negritas">no tiene modificar de acceso</span>. Eso quiere decir que solo puede ser usado por clases que se encuentren en el mismo paquete que <span class="clase">Usuario</span>.<br />
<br />
Dejaremos por el momento esta clase y regresaremos a ella un poco más adelante. Por ahora, creamos las otras dos clases de datos: <span class="clase">Direccion</span> y <span class="clase">Telefono</span> siguiendo el mismo patrón, los atributos obligatorios los marcamos como <span class="clase">final</span> y creamos un constructor sin modificador de acceso que establezca estos valores:
<pre class="brush: java;" title="Direccion.java">@Data
public class Direccion {
private final String ciudad;
private final String codigoPostal;
private String calle;
private int numeroExterior;
private int numeroInterior;
Direccion(String ciudad, String codigoPostal) {
this.ciudad = ciudad;
this.codigoPostal = codigoPostal;
}
}
</pre><br />
<pre class="brush: java;" title="Telefono.java">@Data
public class Telefono {
private final String numero;
private final TipoTelefono tipoTelefono;
Telefono(String numero, TipoTelefono tipoTelefono) {
this.numero = numero;
this.tipoTelefono = tipoTelefono;
}
}
</pre><br />
Ahora pasamos a la parte interesante del ejemplo. Creamos una nueva clase <span class="clase">UsuarioBuilder</span> en el mismo paquete en el que se encuentra la clase <span class="clase">Usuario</span>. Esta clase tiene un solo atributo, el <span class="clase">Usuario</span> que será creado, al cual le estableceremos sus valores, y que regresaremos una vez que terminemos su construcción.<br />
<pre class="brush: java;" title="UsuarioBuilder.java">public class UsuarioBuilder {
private final Usuario usuario;
}
</pre><br />
El <span class="codigo">Builder</span> tendrá un constructor sin modificador de acceso y recibirá dos parámetros, los necesarios para crear la instancia de <span class="clase">Usuario</span>. Dentro del constructor creamos la nueva instancia de <span class="clase">Usuario</span> (lo cual podemos hacer ya que ambas clases están en el mismo paquete) e inicializamos sus valores:<br />
<pre class="brush: java;" title="UsuarioBuilder.java">UsuarioBuilder(String nombre, String username) {
this.usuario = new Usuario(nombre, username);
this.usuario.setTelefonos(new HashSet<>());
}
</pre><br />
Ahora, creamos una serie de métodos públicos que permitan establecer el valor de cada uno de los atributos no obligatorios del usuario (<span class="clase">password</span>, <span class="clase">edad</span> y <span class="clase">nickname</span>). Algo importante es que, por convención, <span class="negritas">estos métodos deben tener el mismo nombre que la propiedad que van a establecer</span> (así es, aunque son una especie de métodos setter estos no comienzan por la palabra set), es por eso que en diagrama los representamos como "<span class="clase">atributo()</span>". Además de establecer el valor del atributo correspondiente del <span class="clase">usuario</span>, también regresarán la misma instancia de <span class="clase">UsuarioBuilder</span> con la que estamos trabajando (o sea, <span class="clase">this</span>). Esto facilitará el encadenar las llamadas a los métodos para simplificar la estructura del código:<br />
<pre class="brush: java;highlight: [1, 7, 13];" title="UsuarioBuilder.java">public UsuarioBuilder password(String password) {
usuario.setPassword(password);
return this;
}
public UsuarioBuilder edad(short edad) {
usuario.setEdad(edad);
return this;
}
public UsuarioBuilder nickname(String nickname) {
usuario.setNickname(nickname);
return this;
}
</pre><br />
Aún falta establecer dos valores importantes del <span class="clase">usuario</span>, la <span class="clase">Direccion</span> y el <span class="clase">Telefono</span>. Para ello vamos a crear dos nuevos métodos, estos no serán públicos sino que también tienen un nivel de acceso por default, ya que buscamos que solo los <span class="codigo">Builders</span> correspondientes puedan usarlos. Serán como métodos auxiliares para establecer esos valores. En el caso del <span class="clase">telefono</span>, como podemos tener más de uno, lo que haremos es agregar un nuevo teléfono al <span class="clase">Set</span> de teléfonos del <span class="clase">usuario</span>:<br />
<pre class="brush: java;" title="UsuarioBuilder.java">void addTelefono(Telefono telefono) {
usuario.getTelefonos().add(telefono);
}
void addDireccion(Direccion direccion) {
usuario.setDireccion(direccion);
}
</pre><br />
Por el momento dejamos esta clase como está y creamos una nueva clase <span class="clase">DireccionBuilder</span>. Esta sigue el mismo patrón inicial que <span class="clase">UsuarioBuilder</span>: tiene un constructor sin modificador de acceso y en su constructor recibe un objeto de tipo <span class="clase">UsuarioBuilder</span>, ya que al momento de terminar de construir la instancia de <span class="clase">Direccion</span> debemos continuar con el proceso de construcción del <span class="clase">Usuario</span> usando el <span class="codigo">Builder</span> original y para eso necesitamos mantener una referencia a este:<br />
<pre class="brush: java;" title="DireccionBuilder.java">public class DireccionBuilder {
private final UsuarioBuilder usuarioBuilder;
private final Direccion direccion;
DireccionBuilder(UsuarioBuilder usuarioBuilder, String ciudad, String codigoPostal) {
this.direccion = new Direccion(ciudad, codigoPostal);
this.usuarioBuilder = usuarioBuilder;
}
}
</pre><br />
A continuación, agregamos un método para establecer cada uno de los valores de los atributos opcionales de <span class="clase">Direccion</span>. Seguimos la misma convención de hace un momento, el nombre del método será el mismo nombre del atributo que queremos establecer y además de establecer el valor correspondiente en la instancia de <span class="clase">Direccion</span> regresará una referencia al mismo <span class="clase">DireccionBuilder</span> que estamos usando:<br />
<pre class="brush: java;" title="DireccionBuilder.java">public DireccionBuilder calle(String calle) {
direccion.setCalle(calle);
return this;
}
public DireccionBuilder numeroExterior(int numeroExterior) {
direccion.setNumeroExterior(numeroExterior);
return this;
}
public DireccionBuilder numeroInterior(int numeroInterior) {
direccion.setNumeroInterior(numeroInterior);
return this;
}
</pre><br />
Una vez que hayamos establecido los valores correspondientes de <span class="clase">Direccion</span> y estemos listos para obtener su instancia debemos invocar un método que, por convención, llamamos <span class="clase">build</span>. Este método establecerá el valor de la nueva <span class="clase">Direccion</span> en el <span class="clase">Usuario</span> que hemos estado construyendo. Para eso usamos el método auxiliar <span class="clase">addDireccion</span> de <span class="clase">UsuarioBuilder</span>. También, como después de crear y establecer la <span class="clase">Direccion</span> queremos continuar con la construcción del <span class="clase">Usuario</span>, debemos regresar la referencia al <span class="clase">UsuarioBuilder</span> original:<br />
<pre class="brush: java;" title="DireccionBuilder.java">public UsuarioBuilder build() {
usuarioBuilder.addDireccion(this.direccion);
return usuarioBuilder;
}
</pre><br />
La clase <span class="clase">DireccionBuilder</span> completa queda de la siguiente forma:<br />
<pre class="brush: java;" title="DireccionBuilder.java">public class DireccionBuilder {
private final UsuarioBuilder usuarioBuilder;
private final Direccion direccion;
DireccionBuilder(UsuarioBuilder usuarioBuilder, String ciudad, String codigoPostal) {
this.direccion = new Direccion(ciudad, codigoPostal);
this.usuarioBuilder = usuarioBuilder;
}
public DireccionBuilder calle(String calle) {
direccion.setCalle(calle);
return this;
}
public DireccionBuilder numeroExterior(int numeroExterior) {
direccion.setNumeroExterior(numeroExterior);
return this;
}
public DireccionBuilder numeroInterior(int numeroInterior) {
direccion.setNumeroInterior(numeroInterior);
return this;
}
public UsuarioBuilder build() {
usuarioBuilder.addDireccion(this.direccion);
return usuarioBuilder;
}
}
</pre><br />
Ahora, ya tenemos una forma de crear una nueva instancia de <span class="clase">Direccion</span> y de establecer esa instancia en el <span class="clase">Usuario</span>. Lo único que nos hace falta es una forma de obtener una referencia a <span class="clase">DireccionBuilder</span>. Para lograrlo creamos un nuevo método público en la clase <span class="clase">UsuarioBuilder</span> el cual recibirá los parámetros obligatorios para crear la instancia de <span class="clase">DireccionBuilder</span>. Una vez creada esta instancia la regresará como valor de retorno del método:<br />
<pre class="brush: java;" title="UsuarioBuilder.java">public DireccionBuilder direccion(String ciudad, String codigoPostal) {
return new DireccionBuilder(this, ciudad, codigoPostal);
}
</pre><br />
Creo que esto puede parecer un poco confuso en este punto, pero cuando comencemos a construir al <span class="clase">Usuario</span> en unos momentos quedará mucho más claro el por qué lo hacemos de esta forma.<br />
<br />
Creamos la clase <span class="clase">TelefonoBuilder</span> la cual, como puedes imaginar por el nombre, será el <span class="codigo">Builder</span> que usaremos para construir los objetos de tipo <span class="clase">Telefono</span>. Este <span class="codigo">Builder</span> sigue el mismo patrón inicial que <span class="clase">UsuarioBuilder</span> y <span class="clase">DireccionBuilder</span>: tiene un constructor sin modificador de acceso que recibe los parámetros obligatorios para crear un nuevo <span class="clase">Telefono</span> y también recibe un objeto de tipo <span class="clase">UsuarioBuilder</span>:<br />
<pre class="brush: java; highlight: [5];" title="TelefonoBuilder.java">class TelefonoBuilder {
private final Telefono telefono;
private final UsuarioBuilder usuarioBuilder;
TelefonoBuilder(UsuarioBuilder usuarioBuilder, String numero, TipoTelefono tipoTelefono) {
this.telefono = new Telefono(numero, tipoTelefono);
this.usuarioBuilder = usuarioBuilder;
}
}
</pre><br />
Como en este caso desde el constructor creamos el <span class="clase">Telefono</span> estableciendo todos sus valores, lo único que nos queda es implementar el método que relaciona la instancia de <span class="clase">Telefono</span> que acabamos de construir con el <span class="clase">Usuario</span>. Como ya sabemos, por convención el nombre de este método es <span class="clase">build</span>. Cuando sea invocado vamos a agregar un teléfono al <span class="clase">Set</span> de teléfonos del <span class="clase">usuario</span> usando el método auxiliar <span class="clase">addTelefono</span> de la clase <span class="clase">UsuarioBuilder</span>. Al final de la invocación regresamos el <span class="clase">UsuarioBuilder</span> con el que estábamos trabajando:<br />
<pre class="brush: java;" title="TelefonoBuilder.java">public UsuarioBuilder build(){
usuarioBuilder.addTelefono(this.telefono);
return usuarioBuilder;
}
</pre><br />
La clase completa queda de la siguiente forma:<br />
<pre class="brush: java;" title="TelefonoBuilder.java">class TelefonoBuilder {
private final Telefono telefono;
private final UsuarioBuilder usuarioBuilder;
TelefonoBuilder(UsuarioBuilder usuarioBuilder, String numero, TipoTelefono tipoTelefono) {
this.telefono = new Telefono(numero, tipoTelefono);
this.usuarioBuilder = usuarioBuilder;
}
public UsuarioBuilder build(){
usuarioBuilder.addTelefono(this.telefono);
return usuarioBuilder;
}
}
</pre><br />
Nuevamente, ya tenemos una forma de crear el teléfono (usando <span class="clase">TelefonoBuilder</span>), de asociar el nuevo teléfono con el <span class="clase">usuario</span> (usando el método auxiliar <span class="clase">addTelefono</span>), lo único que nos falta en este momento es una forma de obtener un <span class="clase">TelefonoBuilder</span>. Agregamos un nuevo método en <span class="clase">UsuarioBuilder</span> al cual llamamos <span class="clase">telefono</span>. Este método recibirá los parámetros obligatorios para crear el nuevo <span class="clase">Telefono</span>. Como en el caso de <span class="clase">Telefono</span> todos sus parámetros son obligatorios y al momento de crear el <span class="codigo">Builder</span> ya tenemos todos los datos necesarios para obtener y establecer la nueva instancia, dentro de este mismo método invocamos al método <span class="clase">build</span> y regresamos al <span class="clase">UsuarioBuilder</span>. Eso quiere decir que el <span class="clase">Cliente</span> nunca usará o trabajará directamente con <span class="clase">TelefonoBuilder</span>:<br />
<pre class="brush: java;" title="UsuarioBuilder.java">public UsuarioBuilder telefono(String numero, TipoTelefono tipoTelefono) {
return new TelefonoBuilder(this, numero, tipoTelefono).build();
}
</pre><br />
Ya tenemos una forma de establecer los valores del <span class="clase">Usuario</span>, de la <span class="clase">Direccion</span> y del <span class="clase">Telefono</span>. Ahora solo falta parte final del <span class="codigo">Builder</span> en donde obtenemos el objeto <span class="clase">Usuario</span> completamente ensamblado con los valores que hemos establecido. Para esto creamos el método <span class="clase">build</span> de <span class="clase">UsuarioBuilder</span>. En el enunciado del problema indicamos que debemos validar que el <span class="clase">Usuario</span> tenga una <span class="clase">Direccion</span> y al menos un <span class="clase">Telefono</span> establecido. Estas validaciones las realizamos dentro del mismo método <span class="clase">build</span> antes de regresar la instancia de <span class="clase">Usuario</span>:<br />
<pre class="brush: java; highlight: [2];" title="UsuarioBuilder.java">public Usuario build() {
validaUsuario();
return this.usuario;
}
private void validaUsuario() {
if (usuario.getDireccion() == null || usuario.getTelefonos().isEmpty()) {
String mensaje = String.format("El usuario debe tener una dirección y al menos un telefono. Dirección: %s. Teléfono: %s",
usuario.getDireccion(), usuario.getTelefonos());
throw new IllegalStateException(mensaje);
}
}
</pre><br />
La clase <span class="clase">UsuarioBuilder</span> completa queda así:<br />
<pre class="brush: java;" title="UsuarioBuilder.java">public class UsuarioBuilder {
private final Usuario usuario;
UsuarioBuilder(String nombre, String username) {
this.usuario = new Usuario(nombre, username);
this.usuario.setTelefonos(new HashSet<>());
}
public UsuarioBuilder password(String password) {
usuario.setPassword(password);
return this;
}
public UsuarioBuilder edad(short edad) {
usuario.setEdad(edad);
return this;
}
public UsuarioBuilder nickname(String nickname) {
usuario.setNickname(nickname);
return this;
}
public UsuarioBuilder telefono(String numero, TipoTelefono tipoTelefono) {
return new TelefonoBuilder(this, numero, tipoTelefono).build();
}
public DireccionBuilder direccion(String ciudad, String codigoPostal) {
return new DireccionBuilder(this, ciudad, codigoPostal);
}
void addTelefono(Telefono telefono) {
usuario.getTelefonos().add(telefono);
}
void addDireccion(Direccion direccion) {
usuario.setDireccion(direccion);
}
public Usuario build() {
validaUsuario();
return this.usuario;
}
private void validaUsuario() {
if (usuario.getDireccion() == null || usuario.getTelefonos().isEmpty()) {
String mensaje = String.format("El usuario debe tener una dirección y al menos un telefono. Dirección: %s. Teléfono: %s",
usuario.getDireccion(), usuario.getTelefonos());
throw new IllegalStateException(mensaje);
}
}
}
</pre><br />
Ahora, esta es una forma de implementar esta estrategia en la que al momento de construir el <span class="clase">UsaurioBuilder</span> creamos una instancia de <span class="clase">Usuario</span> y cada vez que invocamos a los diferentes métodos del <span class="codigo">Builder</span> vamos estableciendo los valores correspondientes de los atributos de <span class="clase">Usuario</span>. Hay una variación de esto en las que almacenamos de forma temporal los valores en el <span class="codigo">Builder</span> y es hasta que invocamos al método <span class="clase">build</span> que creamos la instancia de <span class="clase">Usuario</span>. Sería algo parecido a esto:<br />
<pre class="brush: java; highlight: [12, 50, 62];" title="UsuarioBuilder.java">public class UsuarioBuilder {
private final String nombre;
private final String username;
private String password;
private short edad;
private String nickname;
private Direccion direccion;
private Set<Telefono> telefonos;
UsuarioBuilder(String nombre, String username) {
this.nombre = nombre;
this.username = username;
}
public UsuarioBuilder password(String password) {
this.password = password;
return this;
}
public UsuarioBuilder edad(short edad) {
this.edad = edad;
return this;
}
public UsuarioBuilder nickname(String nickname) {
this.nickname = nickname;
return this;
}
public UsuarioBuilder telefono(String numero, TipoTelefono tipoTelefono) {
return new TelefonoBuilder(this, numero, tipoTelefono).build();
}
public DireccionBuilder direccion(String ciudad, String codigoPostal) {
return new DireccionBuilder(this, ciudad, codigoPostal);
}
void addTelefono(Telefono telefono) {
this.telefonos.add(telefono);
}
void addDireccion(Direccion direccion) {
this.direccion = direccion;
}
public Usuario build() {
Usuario usuario = new Usuario(this.nombre, this.username);
usuario.setPassword(this.password);
usuario.setEdad(this.edad);
usuario.setNickname(this.nickname);
usuario.setTelefonos(this.telefonos);
usuario.setDireccion(this.direccion);
validaUsuario(usuario);
return usuario;
}
private void validaUsuario(Usuario usuario) {
if (usuario.getDireccion() == null || usuario.getTelefonos().isEmpty()) {
String mensaje = String.format("El usuario debe tener una dirección y al menos un telefono. Dirección: %s. Teléfono: %s",
usuario.getDireccion(), usuario.getTelefonos());
throw new IllegalStateException(mensaje);
}
}
}
</pre><br />
Ambas variaciones son ligeramente diferentes aunque el resultado final es el mismo. ¿Cuál es mejor? Como siempre digo: depende del problema que estés tratando de resolver. Solo quería presentar esta segunda forma para mostrar que este patrón es bastante flexible.<br />
<br />
Ahora validemos el funcionamiento de nuestro Builder. Para eso creamos una nueva clase de pruebas. Haremos tres pruebas; en la primera estableceremos algunos de los valores del <span class="clase">Usuario</span>, incluyendo una <span class="clase">Direccion</span> y <span class="clase">Telefono</span>, y validaremos que el <span class="clase">Usuario</span> que obtenemos al final tenga los valores que hemos establecido. En la segunda prueba omitiremos la <span class="clase">Direccion</span>, con lo cual esperamos recibir una excepción, y en la tercera no colocaremos ningún <span class="clase">Telefono</span>, con lo cual también esperamos recibir una excepción.<br />
<br />
Lo primero que debemos hacer es obtener una instancia de <span class="clase">UsuarioBuilder</span>. Como vimos, la única forma que tenemos de hacerlo es invocando el método <span class="clase">builder</span> de la clase <span class="clase">Usuario</span>. Como <span class="clase">builder</span> es un método estático podemos hacer la invocación de la siguiente forma:<br />
<pre class="brush: java;">UsuarioBuilder usuarioBuilder = Usuario.builder("Programador Java", "programadorjava");
</pre><br />
Ya con el <span class="clase">UsuarioBuilder</span> podemos comenzar a establecer los valores del <span class="clase">Usuario</span> llamando a los métodos correspondientes del <span class="codigo">Builder</span>.<br />
<pre class="brush: java;">usuarioBuilder.password("123456");
usuarioBuilder.nickname("Programador");
</pre><br />
Para agregar un nuevo <span class="clase">Telefono</span> solo tenemos que invocar al método <span class="clase">telefono</span>:<br />
<pre class="brush: java;">usuarioBuilder.telefono("12345780", TipoTelefono.MOVIL);
usuarioBuilder.telefono("87654321", TipoTelefono.FIJO);
</pre><br />
Y para la <span class="clase">Direccion</span> el método <span class="clase">direccion</span>:<br />
<pre class="brush: java;">DireccionBuilder direccionBuilder = usuarioBuilder.direccion("Ficticia", "12345");
direccionBuilder.calle("Calle");
direccionBuilder.numeroExterior(13);
direccionBuilder.build();
</pre><br />
Es en el bloque anterior en donde podemos ver de forma clara un ejemplo de cuando decimos que "<span class="negritas"><span class="codigo">Builder</span> nos guía en la construcción del objeto</span>". Una vez que indicamos que queremos crear una instancia de <span class="clase">Direccion</span> obtenemos un nuevo <span class="clase">DireccionBuilder</span> el cual usamos para establecer todos los valores de la <span class="clase">Direccion</span>, y únicamente cuando hemos establecido sus valores es cuando podemos continuar con la construcción del <span class="clase">Usuario</span>.<br />
<br />
Una vez que hemos terminado, obtenemos la instancia de <span class="clase">Usuario</span> invocando al método <span class="clase">build</span> de <span class="clase">UsuarioBuilder</span>:<br />
<pre class="brush: java;">Usuario usuario = usuarioBuilder.build();
</pre><br />
Y listo, el método completo se ve así:<br />
<pre class="brush: java;">@Test
void usuarioCorrecto(){
UsuarioBuilder usuarioBuilder = Usuario.builder("Programador Java", "programadorjava");
usuarioBuilder.password("123456");
usuarioBuilder.nickname("Programador");
usuarioBuilder.telefono("12345780", TipoTelefono.MOVIL);
usuarioBuilder.telefono("87654321", TipoTelefono.FIJO);
DireccionBuilder direccionBuilder = usuarioBuilder.direccion("Ficticia", "12345");
direccionBuilder.calle("Calle");
direccionBuilder.numeroExterior(13);
direccionBuilder.build();
Usuario usuario = usuarioBuilder.build();
}
</pre><br />
Aquí estoy usando la referencia <span class="clase">usuarioBuilder</span> en cada uno de los pasos, pero como cada método regresa la misma referencia de <span class="clase">usuarioBuilder</span>, puedo encadenar todas las invocaciones:<br />
<pre class="brush: java;">UsuarioBuilder usuarioBuilder = Usuario.builder("Programador Java", "programadorjava")
.password("123456")
.nickname("Programador")
.telefono("12345780", TipoTelefono.MOVIL)
.telefono("87654321", TipoTelefono.FIJO)
.direccion("Ficticia", "12345")
.calle("Calle")
.numeroExterior(13).build();
Usuario usuario = usuarioBuilder.build();
</pre><br />
Esto es mucho más simple y claro que si usáramos directamente los constructores y métodos setters correspondientes. Además de que es claro qué atributo estamos estableciendo. Recordemos que anteriormente el constructor que teníamos para <span class="clase">Usuario</span> era algo así: <br />
<pre class="brush: java;">new Usuario ("Alfonso", "Olaguibert", 34, 42, "pollo");
</pre><br />
Terminemos el método de prueba validando que el <span class="clase">usuario</span> obtenido tiene los valores correspondientes.<br />
<pre class="brush: java;">@Test
void usuarioCorrecto() {
UsuarioBuilder usuarioBuilder = Usuario.builder("Programador Java", "programadorjava")
.password("123456")
.nickname("Programador")
.telefono("12345780", TipoTelefono.MOVIL)
.telefono("87654321", TipoTelefono.FIJO)
.direccion("Ficticia", "12345")
.calle("Calle")
.numeroExterior(13).build();
Usuario usuario = usuarioBuilder.build();
assertThat(usuario.getNombre()).isEqualTo("Programador Java");
assertThat(usuario.getUsername()).isEqualTo("programadorjava");
assertThat(usuario.getPassword()).isEqualTo("123456");
assertThat(usuario.getTelefonos()).hasSize(2);
assertThat(usuario.getDireccion().getCiudad()).isEqualTo("Ficticia");
assertThat(usuario.getDireccion().getCodigoPostal()).isEqualTo("12345");
assertThat(usuario.getDireccion().getCalle()).isEqualTo("Calle");
assertThat(usuario.getDireccion().getNumeroExterior()).isEqualTo(13);
}
</pre><br />
Si ejecutas el método de prueba anterior puedes comprobar que el <span class="clase">Usuario</span> se ha creado de forma correcta.<br />
<br />
Finalmente, validemos que se lanza una excepción si no establecemos la <span class="clase">Direccion</span> y al menos un <span class="clase">Telefono</span>:<br />
<pre class="brush: java;">@Test
void excepcion_cuandoNoHayDireccion() {
UsuarioBuilder usuarioBuilder = Usuario.builder("Programador Java", "programadorjava")
.password("123456")
.nickname("Programador")
.telefono("12345780", TipoTelefono.MOVIL)
.telefono("87654321", TipoTelefono.FIJO);
assertThatThrownBy(() -> {
Usuario usuario = usuarioBuilder.build();
}).isExactlyInstanceOf(IllegalStateException.class);
}
@Test
void excepcion_cuandoNoHayTelefono() {
UsuarioBuilder usuarioBuilder = Usuario.builder("Programador Java", "programadorjava")
.password("123456")
.nickname("Programador")
.telefono("12345780", TipoTelefono.MOVIL)
.telefono("87654321", TipoTelefono.FIJO);
assertThatThrownBy(() -> {
Usuario usuario = usuarioBuilder.build();
}).isExactlyInstanceOf(IllegalStateException.class);
}
</pre><br />
El diagrama del ejemplo que acabamos de implementar queda de la siguiente forma:<br />
<br /><div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEiam-BZuxOeXHvIJvdXBmsOAZHvdMyvelEGwkPTuwJAQ6NPthktLsGykGxm1KtOoIyOGx9W4rOpC0RtrP3lP5O5b83guI8Tmo2Q5ukcfsNA2SMTdF-iFyHHjSKUrQv8Z_cRDoDPfIsd_NJUtaH8qEbrR-1A0bVkqDdvaHcZXG6JzcFz3muGOqbtRqUHMQ=s1238" style="display: block; padding: 1em 0px; text-align: center;"><img alt="" border="0" data-original-height="1086" data-original-width="1238" src="https://blogger.googleusercontent.com/img/a/AVvXsEiam-BZuxOeXHvIJvdXBmsOAZHvdMyvelEGwkPTuwJAQ6NPthktLsGykGxm1KtOoIyOGx9W4rOpC0RtrP3lP5O5b83guI8Tmo2Q5ukcfsNA2SMTdF-iFyHHjSKUrQv8Z_cRDoDPfIsd_NJUtaH8qEbrR-1A0bVkqDdvaHcZXG6JzcFz3muGOqbtRqUHMQ=s600" width="600" /></a></div><br />
<br />
Con esto damos por concluido el ejemplo de la Estrategia C.<br />
<br />
Para la <span class="negritas">Estrategia B</span> haremos un ejemplo parecido, pero mucho más sencillo ya que solo usaremos la clase <span class="clase">Usuario</span> excluyendo<span class="clase">Direccion</span> y <span class="clase">Telefono</span>, ya que creo que con el ejemplo anterior quedó claro cómo podemos construirlos.<br />
<br />
Para el ejemplo de la Estrategia B crearemos nuevamente un <span class="clase">Usuario</span> usando un <span class="codigo">Builder</span>. La diferencia será que ahora ese <span class="codigo">Builder</span> será una <span class="negritas">clase anidada</span> dentro de la clase <span class="clase">Usuario</span>. Así que comenzamos creando la representación del <span class="clase">Usuario</span>, el cual tendrá los mismos atributos que en el ejemplo anterior:<br />
<pre class="brush: java;" title="Usuario.java">@Getter
public class Usuario {
private final String nombre;
private final String username;
private String password;
private short edad;
private String nickname;
}
</pre><br />
Agregamos un constructor que reciba los atributos obligatorios. Este constructor será <span class="clase">private</span>, ya que solo el <span class="clase">UsuarioBuilder</span> debe poder crear instancias: <br />
<pre class="brush: java;" title="Usuario.java">private Usuario(String nombre, String username) {
this.nombre = nombre;
this.username = username;
}
</pre><br />
Lo siguiente es crear la clase <span class="clase">UsuarioBuilder</span> la cual, como había dicho, será una clase anidad dentro de <span class="clase">Usuario</span>:<br />
<pre class="brush: java; highlight: [4];" title="Usuario.java">@Data
public class Usuario {
public static class UsuarioBuilder {
}
}
</pre><br />
<span class="clase">UsuarioBuilder</span> tendrá como único atributo la instancia de <span class="clase">Usuario</span> a la cual establecerá sus valores. Además, tendrá un constructor que también será <span class="clase">private</span> y recibirá los valores necesarios para inicializar la instancia de <span class="clase">Usuario</span>:<br />
<pre class="brush: java;" title="Usuario.java">@Data
public class Usuario {
public static class UsuarioBuilder {
private final Usuario usuario;
private UsuarioBuilder(String nombre, String username) {
usuario = new Usuario(nombre, username);
}
}
}
</pre><br />
El resto es muy parecido a lo que teníamos anteriormente, una serie de métodos para establecer los atributos no obligatorios de <span class="clase">Usuario</span>, y un método <span class="clase">build</span> que regresa la instancia ensamblada del <span class="clase">Usuario</span>. Una de las ventajas de esta estrategia es que no necesitamos métodos setter en los atributos de <span class="clase">Usuario</span>, como <span class="clase">UsuarioBuilder</span> está dentro de esta clase, puede acceder y establecer directamente los valores de sus atributos:<br />
<pre class="brush: java;highlight: [10, 15, 20, 25];" title="Usuario.java">public static class UsuarioBuilder {
private final Usuario usuario;
private UsuarioBuilder(String nombre, String username) {
usuario = new Usuario(nombre, username);
}
public UsuarioBuilder password(String password) {
usuario.password = password;
return this;
}
public UsuarioBuilder edad(short edad) {
usuario.edad = edad;
return this;
}
public UsuarioBuilder nickname(String nickname) {
usuario.nickname = nickname;
return this;
}
public Usuario build() {
return this.usuario;
}
}
</pre><br />
Lo único que nos hace falta es una forma de obtener una instancia de <span class="clase">UsuarioBuilder</span>. Para esto nuevamente creamos un método estático en <span class="clase">Usuario</span> que reciba los valores necesarios para inicializar el <span class="codigo">Builder</span> y regresar su instancia:<br />
<pre class="brush: java;" title="Usuario.java">public static UsuarioBuilder builder(String nombre, String username){
return new UsuarioBuilder(nombre, username);
}
</pre><br />
Y eso es todo, como vemos esta estrategia es un poco más compacta que la anterior. La clase <span class="clase">Usuario</span> completa queda de la siguiente forma:<br />
<pre class="brush: java;" title="Usuario.java">@Getter
public class Usuario {
private final String nombre;
private final String username;
private String password;
private short edad;
private String nickname;
private Usuario(String nombre, String username) {
this.nombre = nombre;
this.username = username;
}
public static UsuarioBuilder builder(String nombre, String username){
return new UsuarioBuilder(nombre, username);
}
public static class UsuarioBuilder {
private final Usuario usuario;
private UsuarioBuilder(String nombre, String username) {
usuario = new Usuario(nombre, username);
}
public UsuarioBuilder password(String password) {
usuario.password = password;
return this;
}
public UsuarioBuilder edad(short edad) {
usuario.edad = edad;
return this;
}
public UsuarioBuilder nickname(String nickname) {
usuario.nickname = nickname;
return this;
}
public Usuario build() {
return this.usuario;
}
}
}
</pre><br />
Lo único que nos falta es validar que la instancia de <span class="clase">Usuario</span> efectivamente tiene todos sus valores establecidos al momento de invocar a <span class="clase">build</span>. Para ello creamos una prueba unitaria:<br />
<pre class="brush: java;">@Test
void usuarioCorrecto() {
Usuario.UsuarioBuilder usuarioBuilder = Usuario.builder("Programador Java", "programadorjava")
.password("123456")
.nickname("Programador");
Usuario usuario = usuarioBuilder.build();
assertThat(usuario.getNombre()).isEqualTo("Programador Java");
assertThat(usuario.getUsername()).isEqualTo("programadorjava");
assertThat(usuario.getPassword()).isEqualTo("123456");
}
</pre><br />
Y listo, si ejecutas la prueba anterior podrás verificar que la instancia es creada e inicializada de forma correcta.<br />
<br />
Para terminar, este es el diagrama de este ejemplo:<br />
<br /><div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEgIwOCFkd8ztHgB8sSjLVDlBjiKpDMDOkVsyqoqHpFnEAwMl6-MWiJXwRLKJJVJB_SurAuz7YJw_kKTXfwnvquz7-x59eB_iRpUFuJXuxHJVZkI_sovnsSrZ3c1YEweHlijY2P6CW0gp0A5P8jjmAWURViIPhm3obhswE_xMEnQnWif5zRlGgYK7OlXtA=s896" style="display: block; padding: 1em 0px; text-align: center;"><img alt="" border="0" data-original-height="232" data-original-width="896" src="https://blogger.googleusercontent.com/img/a/AVvXsEgIwOCFkd8ztHgB8sSjLVDlBjiKpDMDOkVsyqoqHpFnEAwMl6-MWiJXwRLKJJVJB_SurAuz7YJw_kKTXfwnvquz7-x59eB_iRpUFuJXuxHJVZkI_sovnsSrZ3c1YEweHlijY2P6CW0gp0A5P8jjmAWURViIPhm3obhswE_xMEnQnWif5zRlGgYK7OlXtA=s600" width="600" /></a></div><br />
<br />
Ahora si lo deseas, puedes regresar a leer las secciones de los <a href="#diagrama" target="_blank">diagramas</a> y la <a href="#implementacion" target="_blank">implementación</a> del patrón, después de haber visto los ejemplos deben quedar mucho más claros.
<br />
<br />
<h2 class="titulo"><a class="anchorTitle" href="#ventajasydesventajas" name="ventajasydesventajas">
<svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z" fill-rule="evenodd"></path></svg>
Ventajas y desventajas</a></h2>
<ul>
<li>➕Proporciona una separación clara entre la construcción y la representación o uso de un objeto.</li>
<li>➕Proporciona un mejor control sobre la construcción de un objeto, escondiendo los detalles de su implementación, además de que nos guía en la construcción de este.</li>
<li>➕Tiene muchas estrategias y variaciones para su implementación.</li>
<li>➕Soporta cambios en la representación interna del objeto sin afectar a los clientes que lo usan.</li>
<li>➖Como pudimos ver en los ejemplos, dependiendo de la estrategia se necesita mucho código para poder implementar este patrón.</li>
<li>➖Si lo vamos a usar para diferentes familias de productos, se debe crear (y mantener) una clase Builder por cada tipo concreto.</li>
</ul>
<br />
<h2 class="titulo"><a class="anchorTitle" href="#otrosnombres" name="otrosnombres">
<svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z" fill-rule="evenodd"></path></svg>
Otros nombres</a></h2>
Ninguno
<br />
<br />
<br /><h2 class="titulo"><a class="anchorTitle" href="#conclusion" name="conclusion">
<svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z" fill-rule="evenodd"></path></svg>
Conclusión</a></h2>
Como pudimos ver a lo largo de este tutorial, Builder es un patrón que simplifica mucho la construcción e inicialización de objetos, brindando una forma muy sencilla de establecer tanto los atributos obligatorios como los opcionales, además de hacerlo de una forma muy sencilla y fácil de entender. La realidad es que es más fácil implementarlo y aplicarlo que explicarlo.
Este patrón es muy flexible. Vimos tres de las estrategias de implementación más utilizadas, pero aún dentro de las estrategias pudimos ver que existen variaciones a las mismas. Esto nos permite ajustar la implementación de Builder a prácticamente todas nuestras necesidades.<br />
<br />
Como nota adicional, en el repositorio dejo, además del código, el archivo original en draw.io con los esquemas de los patrones.<br />
<br />
Espero que este tutorial les sea de utilidad. Si tienen alguna duda, sugerencia, comentario o aclaración, pueden dejarla en la sección de comentarios o enviar un correo a <a href="mailto:programadorjavablog@gmail.com">programadorjavablog@gmail.com</a> (pueden agregarme al chat de gmail). También puedes seguir JavaTutoriales en las siguientes redes sociales:
<ul>
<li><a href="https://www.facebook.com/JavaTutoriales/" target="_blank">Facebook</a></li>
<li><a href="https://twitter.com/JavaTutoriales" target="_blank">Twitter</a></li>
</ul>
Saludos y gracias.<br /><br />
<span class="negritas">Descarga los archivos de este tutorial desde mi repositorio en GitHub:</span>
<ul>
<li><a href="https://github.com/JavaTutoriales/Builder" target="_blank">Patrón de diseño Builder</a>.</li>
</ul>
<br />
<span class="negritas">Entradas relacionadas:</span>
<ul>
<li><a href="https://www.javatutoriales.com/2021/11/introduccion-los-patrones-de-diseno.html" target="_blank">Introducción a los patrones de diseño</a>.</li>
<li><a href="https://www.javatutoriales.com/2021/12/patron-de-diseno-factory-method.html" target="_blank">Patrón de diseño Factory Method</a>.</li>
<li><a href="https://www.javatutoriales.com/2021/12/patron-de-diseno-abstract-factory.html" target="_blank">Patrón de diseño Abstract Factory</a>.</li>
<li><a href="https://www.javatutoriales.com/2021/12/diferencias-simple-factory-vs-factory.html" target="_blank">Diferencias: Simple Factory vs. Factory Method vs. Abstrac Factory</a>.</li>
<li><a href="https://www.javatutoriales.com/2022/01/patron-de-diseno-template-method.html" target="_blank">Patrón de Diseño Template Method</a>.</li>
<li><a href="https://www.javatutoriales.com/2022/01/patron-de-diseno-strategy.html" target="_blank">Patrón de Diseño Strategy</a>.</li>
<li><a href="https://www.javatutoriales.com/2022/01/patron-de-diseno-adapter.html" target="_blank">Patrón de Diseño Adapter</a>.</li>
</ul>
<br />
<br />
</div>
Alexhttp://www.blogger.com/profile/06974037481671868076noreply@blogger.comtag:blogger.com,1999:blog-2012232069289957092.post-41352400094931088372022-01-20T05:54:00.000-08:002022-01-20T05:54:28.566-08:00Patrón de diseño Adapter<div style="text-align: justify;">
<img src="https://img.shields.io/badge/JT-Design%20Patterns-orange" /><br />
<br />
<span class="codigo">Adapter</span> es un <a href="https://www.javatutoriales.com/2021/11/introduccion-los-patrones-de-diseno.html" target="_blank">patrón de diseño</a> <span class="negritas">estructural</span>. Este es el único patrón que tiene los dos ámbitos, clases y objetos, ya que podemos implementarlos de dos formas diferentes. Este patrón se utiliza cuando tenemos dos elementos de una aplicación los cuales necesitamos que trabajen juntos, pero sus interfaces de comunicación no son compatibles. Para esos casos creamos una <span class="negritas">clase intermedia</span> que facilita la comunicación entre ambas. Así, <span class="codigo">Adapter</span> servirá como puente entre ambos elementos.<br />
<br />
Algunas personas describen a este patrón con la frase: "Obtener la interface que quieres a partir de la interface que tienes" o "proporcionarle al cliente un objeto con la interface que necesita".<br />
<br />
En este tutorial se mostraré la funcionalidad de este patrón, junto con dos estrategias diferentes de implementación, una para el ámbito de clases y otro para el ámbito de objetos.<br />
<br />
<a name='more'></a>
<span class="codigo">Adapter</span> facilita la reutilización de código, ya que adapta código existente dentro de una nueva interface, la cual muy probablemente era desconocida al momento de diseñar el código original. La idea es tomada directamente de los adaptadores de hardware donde tenemos un dispositivo con un tipo de puerto, digamos USB-C, y queremos conectarlo con otro elemento que tiene un tipo de conexión diferente, digamos micro USB. ¿Qué hacemos en ese caso? Usamos un tercer elemento (sí, un adaptador) que ayuda a conectar los dos elementos de forma que puedan entenderse. Internamente tal vez simplemente envíe directamente los datos, pero también es posible que realice algún tipo de conversión o modificación de estos.<br />
<br />
Así, creamos una nueva clase que es responsable de unir las funcionalidades de dos componentes independientes o incompatibles.<br />
<br />
Este patrón se usa mucho cuando trabajamos con código legado, ya que al adaptar el código existente (el cual probablemente ya ha sido probado y existe en producción desde hace algún tiempo) podemos usarlo con nuevas funcionalidades.<br />
<br />
<br /><h2 class="titulo"><a class="anchorTitle" href="#objetivo" name="objetivo">
<svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z" fill-rule="evenodd"></path></svg>
Objetivo o intención del patrón</a></h2>
Como podemos ver, el objetivo de este patrón es:
<ul>
<li>Adaptar la interface de una clase a la interface que espera el cliente (asumiendo que ambas interfaces son diferentes).</li>
<li>Permitir que clases con interfaces incompatibles trabajen juntas.</li>
</ul><br />
Cuando hablamos de "interfaces", nos referimos a los elementos o firmas que una clase expone para que otras puedan utilizarla. Esta es una palabra que leeras mucho en este tutorial.
<br /><h2 class="titulo"><a class="anchorTitle" href="#implementacion" name="implementacion">
<svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z" fill-rule="evenodd"></path></svg>
Implementación</a></h2>
Antes de ver los detalles en código, un poco de teoría de cómo implementar el patrón.<br />
<br />
Para este patrón debemos tener ciertos prerrequisitos:
<ol>
<li>Debemos tener un <span class="codigo">Cliente</span>, el cual delegue alguna funcionalidad importante a un objeto diferente. El <span class="codigo">Cliente</span> debe tener una referencia al objeto usando un tipo <span class="negritas">abstracto</span> (una interface o clase abstracta). A esta interface o tipo abstracto se le da el nombre de <span class="codigo">Target</span>. Sé que esto suena muy general pero quedará claro con el ejemplo.</li>
<li>Tener un objeto que implemente una funcionalidad, o tenga información, que pueda ser de interés para el <span class="codigo">Cliente</span>, pero que no implemente la interface que este necesita. A este objeto se le da el nombre de <span class="codigo">Adaptee</span>.</li>
</ol><br />
En otras palabras, debemos tener una clase que queramos usar para implementar una funcionalidad, pero esa clase debe tener una interface que no sea compatible.<br />
<br />
No te preocupes por los nombres raros. Puedes echar un ojo al diagrama que está más abajo para que queden claros qué son estos objetos y cómo se relacionan entre ellos.<br />
<br />
Ahora, implementar el patrón es en realidad muy sencillo.<br />
<br />
Para el ámbito de <span class="negritas">objeto</span>:
<ol>
<li><span class="negritas">Crear una clase que implemente la interface que el <span class="codigo">Cliente</span> conoce y usa</span>. Esta clase es el <span class="codigo">Adapter</span> y la interface es el <span class="codigo">Target</span>.</li>
<li><span class="negritas">Colocar en el <span class="codigo">Adapter</span> una referencia al <span class="codigo">Adaptee</span></span>. Esta referencia normalmente se obtiene en el constructor del <span class="codigo">Adapter</span>.</li>
<li><span class="negritas">Delegar las peticiones recibicas por el <span class="codigo">Adapter</span> hacia el <span class="codigo">Adaptee</span></span>. Para esto depende de qué tan compleja sea la funcionalidad que estemos conectando; puede ser llamadas directas o algunas veces será necesario transformar los datos antes de enviarlos.</li>
<li><span class="negritas">Regresar al <span class="codigo">Cliente</span> la respuesta recibida por el <span class="codigo">Adapter</span> desde el <span class="codigo">Adaptee</span></span>. Parecido al punto anterior, algunas veces regresaremos directamente la respuesta y en otras ocasiones habrá que transformarla.</li>
</ol><br />
Para el ámbito de <span class="negritas">clase</span>:
<ol>
<li><span class="negritas">Crear una clase que implemente la interface que el <span class="codigo">Cliente</span> conoce y usa</span>. Esta clase es el <span class="codigo">Adapter</span> y la interface es el <span class="codigo">Target</span>.</li>
<li><span class="negritas">Indicar que la clase del <span class="codigo">Adapter</span> extiende del <span class="codigo">Adaptee</span></span>. Si el <span class="codigo">Adaptee</span> recibe algún parámetro en su constructor, el <span class="codigo">Adapter</span> debe enviarlo al momento de su construcción.</li>
<li><span class="negritas">Delegar las peticiones recibidas por el <span class="codigo">Adapter</span> a su súper clase (al <span class="codigo">Adaptee</span>)</span>. Para esto depende de qué tan compleja sea la funcionalidad que estemos conectando; puede ser llamadas directas o algunas veces será necesario transformar los datos antes de enviarlos.</li>
<li><span class="negritas">El Adapter recibe las respuestas y las regresa al Cliente</span>. </li>
</ol><br />
Como puedes ver en ambas descripciones, las implementaciones son muy parecidas, con la excepción del punto 2. En la primera implementación usamos una referencia (<span class="negritas">composición</span>) y en la segunda extendemos una clase (<span class="negritas">herencia</span>).<br />
<br />
Eso quiere decir que en el ámbito de clase tenemos una restricción importante: El <span class="codigo">Target</span> debe ser una interface, no podemos usar una clase abstracta o concreta. ¿Por qué? Porque el <span class="codigo">Adapter</span> debe extender del <span class="codigo">Adaptee</span> y en Java no se permite la herencia múltiple. Además, el <span class="codigo">Adaptee</span> no debe ser una clase <span class="clase">final</span>, de lo contrario no podremos extenderla.<br />
<br />
Aunque también tiene una ventaja, podemos usar el <span class="codigo">Adapter</span> en cualquier lugar en donde normalmente usamos al <span class="codigo">Adaptee</span> y/o al <span class="codigo">Target</span>.<br />
<br />
<br /><h2 class="titulo"><a class="anchorTitle" href="#diagrama" name="diagrama">
<svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z" fill-rule="evenodd"></path></svg>
Diagrama</a></h2>
Para este patrón tenemos dos diagramas, uno por cada alcance. Los dos son muy parecidos, pero en la diferencia radica el cambio del alcance. <br />
<br />
Explicaré los elementos que participan en el patrón antes de pasar a los diagramas, de esta forma espero que sea más fácil entender las dos versiones. Los elementos que participan en este patrón son:
<ul>
<li><span class="codigo">Target</span>: El objeto o clase que tiene la interface que el cliente conoce y utiliza.</li>
<li><span class="codigo">Adaptee</span>: El objeto o clase existente y cuya funcionalidad queremos utilizar, pero su interface no es la que el cliente conoce o puede manejar.</li>
<li><span class="codigo">Adapter</span>: La clase u objeto intermedio que usamos para adaptar al <span class="codigo">Adaptee</span>, para que use la misma interface que el <span class="codigo">Target</span>.</li>
<li><span class="codigo">Cliente</span>: Objeto que usa a los objetos con la interface <span class="codigo">Target</span>. Podemos decir que es el cliente o usuario del patrón.</li>
</ul><br />
En el primer diagrama podemos ver el patrón aplicado con el alcance de <span class="negritas">objeto</span>:<br />
<br />
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEiHrJrr7Oz-ydo03XP7tBwyTbyXCzc3x26LIIrJQz5dpMWRu9OX_9KUUniUMoIOxPeefWsA-zklckhfgXZV_75wD-y03G2Y2tTWwzdf_DYHdGTfbCf9llCIa4VSW61X5fU0HSXfVPznxjMq63VsalsiFKv8BDKMWyniYNmG81pU58CM4278VRHtGnbhYw" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" data-original-height="322" data-original-width="753" src="https://blogger.googleusercontent.com/img/a/AVvXsEiHrJrr7Oz-ydo03XP7tBwyTbyXCzc3x26LIIrJQz5dpMWRu9OX_9KUUniUMoIOxPeefWsA-zklckhfgXZV_75wD-y03G2Y2tTWwzdf_DYHdGTfbCf9llCIa4VSW61X5fU0HSXfVPznxjMq63VsalsiFKv8BDKMWyniYNmG81pU58CM4278VRHtGnbhYw"/></a></div>
<br />
En la imagen, la parte importante es la conexión que existe entre el <span class="codigo">Adapter</span> y el <span class="codigo">Adaptee</span>, ya que hacemos uso de la <span class="negritas">composición</span> de objetos. Aquí, el <span class="codigo">Adapter</span> tendrá una referencia al <span class="codigo">Adaptee</span> (una variable declarada del tipo del <span class="codigo">Adaptee</span>) y delega las llamadas a este. Esto quedará más claro cuando pasemos al ejemplo en código.<br />
<br />
El segundo diagrama muestra cómo funciona este patrón en el ámbito de <span class="negritas">clase</span>:<br />
<br />
<p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEjM8YqiUvF3XyCilmz_dq1dj_dV6wM1aIfr0EpiR8Xd2ATxJW7P7J9ouqR8AciMjvv9vnJS-MlAolCCaO1_8JtGkeq8kZ6irqeaN7iFgrIsKtJYFl76XHxHQHHjzv4HOSArQm1ub2gZAr1w_ZvFnCNfcjWb3rtnL8h2SXcUpVetYDn4koiGKTjq4w6hKQ=s664" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="322" data-original-width="664" src="https://blogger.googleusercontent.com/img/a/AVvXsEjM8YqiUvF3XyCilmz_dq1dj_dV6wM1aIfr0EpiR8Xd2ATxJW7P7J9ouqR8AciMjvv9vnJS-MlAolCCaO1_8JtGkeq8kZ6irqeaN7iFgrIsKtJYFl76XHxHQHHjzv4HOSArQm1ub2gZAr1w_ZvFnCNfcjWb3rtnL8h2SXcUpVetYDn4koiGKTjq4w6hKQ=s16000" /></a></div><br /> <p></p><br />
<br />
El <span class="codigo">Adapter</span> y el <span class="codigo">Adaptee</span> tienen una relación de <span class="negritas">herencia</span>. Esto quiere decir que el <span class="codigo">Adapter</span> extiende de la clase del <span class="codigo">Adaptee</span>. Como puedes imaginar tenemos varias restricciones impuestas por el lenguaje Java. La primera y más importante es que nuestro <span class="codigo">Adapter</span> solo podrá extender de una clase (el <span class="codigo">Adaptee</span>), ya que Java no permite la herencia múltiple. <br />
<br />
Otra restricción importante es que el <span class="codigo">Adaptee</span> no debe ser una clase <span class="clase">final</span>, de lo contrario no podremos extender de esta. La buena noticia es que si no es posible extender del <span class="codigo">Adaptee</span> siempre podemos regresar a la implementación mostrada en el primer diagrama.<br />
<br />
Este es un patrón algo más variable en su implementación, ya que la funcionalidad y el tamaño del adaptar dependen mucho de la situación particular que estemos... bueno, adaptando. ¿A qué me refiero con esto? En algunos casos el <span class="codigo">Adapter</span> solo se encargará de tomar las peticiones que el <span class="codigo">Cliente</span> hace al <span class="codigo">Target</span>, mandarlas al <span class="codigo">Adaptee</span>, tomar la respuesta y enviarla de regreso. En ese caso solo funciona como un elemento de paso de mensajes. ¿Pero qué pasa si tiene que hacer alguna conversión? Digamos que tenemos un <span class="codigo">Adaptee</span> que requiere de información cifrada usando cierto algoritmo propietario y un <span class="codigo">Target</span> que trabaja con información en plano; en este caso particular el <span class="codigo">Adapter</span> tendrá la responsabilidad de cifrar los mensajes que recibe del <span class="codigo">Target</span>, para poder enviarlos al <span class="codigo">Adaptee</span> y de descifrar las respuestas para poder enviarlas de regreso.<br />
<br />
Así que la respuesta a la pregunta de "¿qué tanto debe hacer el <span class="codigo">Adapter</span>?" es: <span class="negritas">tanto como sea necesario</span>.<br />
<br />
¿Dónde se usa este patrón? Dentro del JDK podemos ver dos ejemplos muy claros, <a href="https://docs.oracle.com/javase/8/docs/api/java/io/InputStreamReader.html" target="_blank"><span class="clase">InputStreamReader</span></a> y <a href="https://docs.oracle.com/javase/8/docs/api/java/io/OutputStreamWriter.html" target="_blank"><span class="clase">OutputStreamWriter</span></a>. En el primer caso podemos ver que <span class="clase">InputStreamReader</span> ayuda a conectar Clientes que requieren de una fuente de datos de tipo de carácter, con fuentes de datos de tipo bytes. <span class="clase">InputStreamReader</span> (nuestro <span class="codigo">Adapter</span>) extiende de <span class="clase">Reader</span> (nuestro <span class="codigo">Target</span>) y recibe en su constructor un <span class="clase">InputStream</span>. <br />
<br />
<p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEhI0jPHd3tHTFoG8PhyKzCqsRCvzli9LAA7Xy36dqYysn_-HzcLGxjJPatuC539d1AvmLwkEwe5k5ZnPifNYTEuTcDIHuWqsGI6cqzv-Oo24YhE8oMUYJ5UovkNmLVqLQGjemthheJuUKSLyt8poQ2Gw2OE7vwH1IwRzVVJSpsbHJlvfHDvpbgeMk4gsQ=s475" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="183" data-original-width="475" src="https://blogger.googleusercontent.com/img/a/AVvXsEhI0jPHd3tHTFoG8PhyKzCqsRCvzli9LAA7Xy36dqYysn_-HzcLGxjJPatuC539d1AvmLwkEwe5k5ZnPifNYTEuTcDIHuWqsGI6cqzv-Oo24YhE8oMUYJ5UovkNmLVqLQGjemthheJuUKSLyt8poQ2Gw2OE7vwH1IwRzVVJSpsbHJlvfHDvpbgeMk4gsQ=s16000" /></a></div><br /> <p></p><br />
<br />
El diagrama de <span class="clase">InputStreamReader</span> es parecido el siguiente, por lo que podemos ver que su implementación tiene el ámbito de objeto.<br />
<br />
<p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEhNsoJ08As1OT--qmKyf1KMZ45S920XB53vzVBr8aS5bEBpz1yujzZ67EVOPIFKOYtJO3pysEmVjk3CeN1g1HlLNIRQQMtJ8ep4fz2KGjQojo6GFGTYyL2sp5DtvHaTbJjCcOruXQNvhFuTmZB5Ckp-VQgwbWSnDIbb2f8eUShg0thOwXgIcS0Blasjkw=s753" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="322" data-original-width="753" src="https://blogger.googleusercontent.com/img/a/AVvXsEhNsoJ08As1OT--qmKyf1KMZ45S920XB53vzVBr8aS5bEBpz1yujzZ67EVOPIFKOYtJO3pysEmVjk3CeN1g1HlLNIRQQMtJ8ep4fz2KGjQojo6GFGTYyL2sp5DtvHaTbJjCcOruXQNvhFuTmZB5Ckp-VQgwbWSnDIbb2f8eUShg0thOwXgIcS0Blasjkw=s16000" /></a></div><br /> <p></p><br />
<br />
<br /><h2 class="titulo"><a class="anchorTitle" href="#ejemplo" name="ejemplo">
<svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z" fill-rule="evenodd"></path></svg>
Ejemplo</a></h2>
Para los ejemplos uso Java 17 y Gradle. No uso ninguna característica particular de esta versión de Java, por lo que debe funcionar en cualquier versión (incluso en la 5). También, uso <a href="https://www.javatutoriales.com/2019/03/lombok-escribiendo-menos-codigo-y.html" target="_blank">Lombok</a> para evitar escribir código repetitivo de forma innecesaria.<br />
<br />
Para validar el correcto funcionamiento de la aplicación usaré JUnit y AssertJ. Agrega las siguientes dependencias en el archivo <span class="codigo">build.gradle</span> (si usas Maven, coloca las dependencias correspondientes en el archivo <span class="codigo">pom.xml</span>).<br />
<br />
<pre class="brush: groovy;" title="build.gradle">
dependencies {
compileOnly 'org.projectlombok:lombok:1.18.22'
annotationProcessor 'org.projectlombok:lombok:1.18.22'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
testImplementation 'org.assertj:assertj-core:3.21.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
}</pre><br />
Ahora sí, al código.<br />
<br />
El problema que resolveremos es el siguiente: Tenemos una clase que se encarga de colorear los fondos de las imágenes, usando un color predeterminado. El funcionamiento de esta clase es muy sencilla, recibe la información del color que usará usando el formato RGB, toma ese color y colorea el fondo de la imagen. Para indicar el color hemos creado una interface <span class="clase">RgbColor</span>, la cual tiene un método para regresar cada uno de sus componentes (red, green, y blue).<br />
<br />
Queremos ofrecerles a nuestros clientes la gama de colores más amplia posible, y hemos encontrado una librería (gratuita) con millones de colores, así que hemos decidido usar esa librería. Sin embargo existe un pequeño problema: esta librería no tiene la información de los colores en RGB, sino en <span class="negritas">hexadecimal</span>.<br />
<br />
Como no queremos modificar la clase original que colorea los fondos para adaptarla a que use los colores el hexadecimal (ya que además de ser una mala práctica podría romper algunas partes del sistema que ya se encuentran en funcionamiento, y tendríamos que volver a probar el funcionamiento completo del mismo) uno de nuestros mejores programadores ha sugerido una solución simple y elegante: usar el patrón de diseño <span class="codigo">Adapter</span> (¡Qué conveniente!).<br />
<br />
Presentaré dos implementaciones para este mismo problema, una con el ámbito de objeto, y una con el ámbito de clase. Como las diferencias son realmente pocas, daré la explicación usando la implementación del ámbito de objeto y posteriormente hablaré de las modificaciones necesarias para implementar el ámbito de clase.<br />
<br />
Lo primero que haremos es definir la interface <span class="clase">RgbColor</span>. Esta interface representa al <span class="codigo">Target</span> en el diagrama del patrón.
<pre class="brush: java;" title="RgbColor.java">
public interface RgbColor {
int getRed();
int getGreen();
int getBlue();
}
</pre><br />
Ahora, la clase que hará uso de <span class="clase">RgbColor</span> para colorear el fondo de las imágenes. Para este ejemplo, la clase cuenta con un método <span class="clase">colorea</span>, que recibe el objeto <span class="clase">RgbColor</span>, y regresa una cadena con la información de cada uno de los elementos obtenidos de la instancia anterior. Esta clase representa al <span class="codigo">Client</span>.
<pre class="brush: java;" title="ColoreadorFondos.java">
public String colorea(RgbColor color) {
return String.format("%d,%d,%d", color.getRed(), color.getGreen(), color.getBlue());
}
</pre><br />
La clase que representa los colores de la librería ficticia que hemos decidido usar, usa los colores en formato hexadecimal. La clase recibe en su constructor la información del color que representa como un entero en formato hexadecimal, y contiene un solo método getter que regresa este mismo color. Una nota sobre la siguiente clase: el constructor y el método getter los estoy generando de forma explícita para que este ejemplo sea más fácil de entender, pero es mejor usar <a href="https://www.javatutoriales.com/2019/03/lombok-escribiendo-menos-codigo-y.html" target="_blank">Lombok</a> para generar ambos de manera automática.
<pre class="brush: java;" title="HexColor.java">
public class HexColor {
private final int colorCode;
public HexColor(int colorCode) {
this.colorCode = colorCode;
}
public int getColorCode() {
return colorCode;
}
}
</pre><br />
Ahora crearemos el <span class="codigo">Adapter</span>; la clase del <span class="codigo">Adapter</span> se llama <span class="clase">HexColorAdapter</span> y, como lo indica el patrón, implementará la interface <span class="clase">RgbColor</span>:
<pre class="brush: java;" title="HexColorAdapter.java">
public class HexColorAdapter implements RgbColor {
}
</pre><br />
En la estrategia utilizada para el ámbito de objeto, <span class="clase">HexColorAdapter</span> recibe una referencia a un objeto de tipo <span class="clase">HexColor</span>, y delegará las peticiones recibidas por <span class="clase">ColoreadorFondos</span> a esta instancia. La forma más común de pasarle esta instancia es a través del constructor, de esta forma:
<pre class="brush: java;" title="HexColorAdapter.java">
public class HexColorAdapter implements RgbColor {
private final HexColor hexColor;
public HexColorAdapter(HexColor hexColor) {
this.hexColor = hexColor;
}
}
</pre><br />
Y ahora podemos implementar los métodos de <span class="clase">RgbColor</span> usando el código de color obtenido de la instancia de <span class="clase">HexColor</span>. El cómo hacer esta conversión es un método estándar, pero no te preocupes por los detalles ya que no son importantes para entender el patrón:
<pre class="brush: java;" title="HexColorAdapter.java">
@Override
public int getRed() {
return (hexColor.getColorCode() & 0xFF0000) >> 16;
}
@Override
public int getGreen() {
return (hexColor.getColorCode() & 0xFF00) >> 8;
}
@Override
public int getBlue() {
return (hexColor.getColorCode() & 0xFF);
}
</pre><br />
La clase <span class="clase">HexColorAdapter</span> completa queda de la siguiente forma:
<pre class="brush: java;" title="HexColorAdapter.java">
public class HexColorAdapter implements RgbColor {
private final HexColor hexColor;
public HexColorAdapter(HexColor hexColor) {
this.hexColor = hexColor;
}
@Override
public int getRed() {
return (hexColor.getColorCode() & 0xFF0000) >> 16;
}
@Override
public int getGreen() {
return (hexColor.getColorCode() & 0xFF00) >> 8;
}
@Override
public int getBlue() {
return (hexColor.getColorCode() & 0xFF);
}
}
</pre><br />
Para validar que todo está funcionando de manera adecuada creamos una prueba unitaria, la prueba se encargará de validar que el color recibido en hexadecimal corresponda al color esperado en RGB. Lo primero que hacemos es crear una instancia de <span class="clase">HexColor</span>, pasándole como parámetro el código del color que deseamos. Iniciemos con una prueba simple: el color negro, en hexadecimal se representa como seis 0s:<br />
<br />
<pre class="brush: java;" title="ColoreadorFondosTest.java">
HexColor colorNegro = new HexColor(0x000000);
</pre><br />
Lo siguiente es crear una instancia del <span class="codigo">Adapter</span>. Recuerda que esta instancia recibe en su constructor la instancia de <span class="clase">HexColor</span> que acabamos de crear:
<pre class="brush: java;" title="HexColorAdapter.java">
HexColorAdapter hexColorAdapter = new HexColorAdapter(colorNegro);
</pre><br />
Y con esta instancia ya podemos usar el método <span class="clase">colorea</span> de la clase <span class="clase">ColoreadorFondos</span>:
<pre class="brush: java;" title="ColoreadorFondosTest.java">
ColoreadorFondos coloreadorFondos = new ColoreadorFondos();
String colorFondo = coloreadorFondos.colorea(hexColorAdapter);
</pre><br />
Validemos que el color obtenido al final corresponde con negro en RGB, en donde los tres componentes deben estar en <span class="codigo">0</span>:
<pre class="brush: java;" title="ColoreadorFondosTest.java">
assertThat(colorFondo).isEqualTo("0,0,0");
</pre><br />
El método de prueba completo queda de la siguiente forma:
<pre class="brush: java;" title="ColoreadorFondosTest.java">
@Test
void testColorRgbCuando_colorNegro() {
HexColor colorNegro = new HexColor(0x000000);
hexColorAdapter = new HexColorAdapter(colorNegro);
String colorFondo = coloreadorFondos.colorea(hexColorAdapter);
assertThat(colorFondo).isEqualTo("0,0,0");
}
</pre><br />
Si ejecutas la prueba puedes comprobar que esta se ejecuta de forma correcta, lo que quiere decir que el <span class="codigo">Adapter</span> funciona. <br />
<br />
Agregaré las validaciones algunos colores adicionales solo para estar seguro y refactorizaré el código:
<pre class="brush: java;" title="ColoreadorFondosTest.java">
class ColoreadorFondosTest {
private ColoreadorFondos coloreadorFondos = new ColoreadorFondos();
private HexColorAdapter hexColorAdapter;
@Test
void testColorRgbCuando_colorNegro() {
HexColor colorNegro = new HexColor(0x000000);
hexColorAdapter = new HexColorAdapter(colorNegro);
String colorFondo = coloreadorFondos.colorea(hexColorAdapter);
assertThat(colorFondo).isEqualTo("0,0,0");
}
@Test
void testColorRgbCuando_colorBlanco() {
HexColor colorBlanco = new HexColor(0xFFFFFF);
hexColorAdapter = new HexColorAdapter(colorBlanco);
String colorFondo = coloreadorFondos.colorea(hexColorAdapter);
assertThat(colorFondo).isEqualTo("255,255,255");
}
@Test
void testColorRgbCuando_colorVerde() {
HexColor colorVerde = new HexColor(0x00FF00);
hexColorAdapter = new HexColorAdapter(colorVerde);
String colorFondo = coloreadorFondos.colorea(hexColorAdapter);
assertThat(colorFondo).isEqualTo("0,255,0");
}
@Test
void testColorRgbCuando_colorMorado() {
HexColor colorMorado = new HexColor(0xC678AA);
hexColorAdapter = new HexColorAdapter(colorMorado);
String colorFondo = coloreadorFondos.colorea(hexColorAdapter);
assertThat(colorFondo).isEqualTo("198,120,170");
}
}
</pre><br />
Si ejecutas los métodos de prueba anteriores, todos deben terminar con un resultado exitoso.<br />
<br />
El diagrama de esta implementación queda de la siguiente forma:<br />
<br />
<p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEjltZTgL7aMWR8Xs1TUWp84C5kzc_94s1rIcd3LotltWL93WI1TQvA7Jd6MjhjCdAN3OIOxNz-KeB5woZ78wDJgEnou4AS1ca32NNGC7bAKwGrSl_PvKXn8izJGdwf7t7PO20_eyPD6Izi1nbnJQo4OHRtTXIlLU1PcoFHqiundahRbobZ13uk2rEkk-w=s753" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="322" data-original-width="753" src="https://blogger.googleusercontent.com/img/a/AVvXsEjltZTgL7aMWR8Xs1TUWp84C5kzc_94s1rIcd3LotltWL93WI1TQvA7Jd6MjhjCdAN3OIOxNz-KeB5woZ78wDJgEnou4AS1ca32NNGC7bAKwGrSl_PvKXn8izJGdwf7t7PO20_eyPD6Izi1nbnJQo4OHRtTXIlLU1PcoFHqiundahRbobZ13uk2rEkk-w=s16000" /></a></div><br /> <p></p><br />
<br />
Ahora, veamos qué cambios tenemos que hacer de la clase <span class="clase">HexColorAdapter</span> en el ámbito de clases.<br />
<br />
Cuando implementamos este patrón usando esta estrategia, el <span class="codigo">Adapter</span> extiende de la clase <span class="codigo">Adaptee</span>, que en este caso es <span class="clase">HexColor</span>. También recuerda que <span class="clase">HexColorAdapter</span> debe implementar la interface <span class="clase">RgbColor</span>, por lo que la declaración de la clase queda de la siguiente forma:
<pre class="brush: java;" title="HexColor.java">
public class HexColorAdapter extends HexColor implements RgbColor {
}
</pre><br />
<span class="clase">HexColor</span> espera en su constructor el código del color en hexadecimal, así que debemos recibir este como parámetro en el constructor de <span class="clase">HexColorAdapter</span> y pasarlo al constructor de <span class="clase">HexColor</span>:
<pre class="brush: java;" title="HexColorAdapter.java">
public HexColorAdapter(int colorCode) {
super(colorCode);
}
</pre><br />
El resto de la implementación es muy parecido a la que hicimos en la implementación anterior; así que la clase <span class="clase">HexColorAdapter</span> completa queda así:
<pre class="brush: java;" title="HexColorAdapter.java">
public class HexColorAdapter extends HexColor implements RgbColor {
public HexColorAdapter(int colorCode) {
super(colorCode);
}
@Override
public int getRed() {
return (getColorCode() & 0xFF0000) >> 16;
}
@Override
public int getGreen() {
return (getColorCode() & 0xFF00) >> 8;
}
@Override
public int getBlue() {
return (getColorCode() & 0xFF);
}
}
</pre><br />
Para terminar, validemos que todo sigue funcionando de forma correcta. Las pruebas unitarias son muy parecidas a las anteriores, con la diferencia de que ahora <span class="clase">HexColorAdapter</span> recibe directamente el color en hexadecimal en su constructor. Quiero que veas algo en la línea 3 del siguiente bloque de código, y es que la variable la estamos declarando como un tipo <span class="clase">RgbColor</span> (el tipo esperado por <span class="clase">ColoreadorFondos</span>). La prueba del color negro queda de la siguiente forma:
<pre class="brush: java; highlight: [3];" title="ColoreadorFondosTest.java">
@Test
void testColorRgbCuando_colorNegro() {
RgbColor hexColorAdapter = new HexColorAdapter(0x000000);
ColoreadorFondos coloreadorFondos = new ColoreadorFondos();
String colorFondo = coloreadorFondos.colorea(hexColorAdapter);
assertThat(colorFondo).isEqualTo("0,0,0");
}
</pre><br />
Y la clase completa de la prueba queda así:
<pre class="brush: java;" title="ColoreadorFondosTest.java">
class ColoreadorFondosTest {
private ColoreadorFondos coloreadorFondos = new ColoreadorFondos();
private RgbColor hexColorAdapter;
@Test
void testColorRgbCuando_colorNegro() {
hexColorAdapter = new HexColorAdapter(0x000000);
String colorFondo = coloreadorFondos.colorea(hexColorAdapter);
assertThat(colorFondo).isEqualTo("0,0,0");
}
@Test
void testColorRgbCuando_colorBlanco() {
hexColorAdapter = new HexColorAdapter(0xFFFFFF);
String colorFondo = coloreadorFondos.colorea(hexColorAdapter);
assertThat(colorFondo).isEqualTo("255,255,255");
}
@Test
void testColorRgbCuando_colorVerde() {
hexColorAdapter = new HexColorAdapter(0x00FF00);
String colorFondo = coloreadorFondos.colorea(hexColorAdapter);
assertThat(colorFondo).isEqualTo("0,255,0");
}
@Test
void testColorRgbCuando_colorMorado() {
hexColorAdapter = new HexColorAdapter(0xC678AA);
String colorFondo = coloreadorFondos.colorea(hexColorAdapter);
assertThat(colorFondo).isEqualTo("198,120,170");
}
}
</pre><br />
Al ejecutar las pruebas anteriores podemos validar que todo funciona de manera correcta, por lo que ambas implementaciones son exitosas.<br />
<br />
El diagrama de la implementacion queda de la siguiente forma:<br />
<br />
<p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEhRQGbBGiEGtsTYPq--kJdj_KD-IXTQbafWHaF8kywNj49LqQoq7lDwf18VfNaOVTZJ93X6tCjVrt5VW09lrbrjYeVfeVJW8bW1kKJyP3jzDexmwEA_mnuOD8UXjXvmC2Wo61sMbdT7K12hIYQuPEiR0vO3ZS2ing47n2e7eFdnuerlAfuZCFIBS93Atg=s664" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="322" data-original-width="664" src="https://blogger.googleusercontent.com/img/a/AVvXsEhRQGbBGiEGtsTYPq--kJdj_KD-IXTQbafWHaF8kywNj49LqQoq7lDwf18VfNaOVTZJ93X6tCjVrt5VW09lrbrjYeVfeVJW8bW1kKJyP3jzDexmwEA_mnuOD8UXjXvmC2Wo61sMbdT7K12hIYQuPEiR0vO3ZS2ing47n2e7eFdnuerlAfuZCFIBS93Atg=s16000" /></a></div><br /> <p></p><br />
<br />
<br /><h2 class="titulo"><a class="anchorTitle" href="#ventajasydesventajas" name="ventajasydesventajas">
<svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z" fill-rule="evenodd"></path></svg>
Ventajas y desventajas</a></h2>
<ul>
<li>➕Permite utilizar código nuevo en sistemas existentes, agregando nuevas funcionalidades sin romper las que ya existen.</li>
<li>➕Al tener dos ámbitos diferentes podemos usar el que más nos convenga dependiendo del problema que queremos resolver.</li>
</ul><br />
<br /><h2 class="titulo"><a class="anchorTitle" href="#otrosnombres" name="otrosnombres">
<svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z" fill-rule="evenodd"></path></svg>
Otros nombres</a></h2>
<ul>
<li>Wrapper</li>
</ul><br />
<br /><h2 class="titulo"><a class="anchorTitle" href="#conclusion" name="conclusion">
<svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z" fill-rule="evenodd"></path></svg>
Conclusión</a></h2>
Al igual que el resto de los patrones que hemos visto hasta ahora, <span class="codigo">Adapter</span> aumenta mucha la flexibilidad de una aplicación al permitir conectar diferentes objetos a través de un tercer elemento, con lo cual evitamos modificar o alterar el código ya existente (lo cual es una excelente práctica).<br />
<br />
Es el único patrón de diseño de los 23 definidos por la GoF que tiene tanto el ámbito de objeto como el de clase, lo que le da aún más flexibilidad al momento de usarlo en nuestras aplicaciones.<br />
<br />
Como nota adicional, en el repositorio dejo, además del código, el archivo original en draw.io con los esquemas de los patrones.<br />
<br />
Espero que este tutorial les sea de utilidad. Si tienen alguna duda, sugerencia, comentario o aclaración, pueden dejarla en la sección de comentarios o enviar un correo a <a href="mailto:programadorjavablog@gmail.com">programadorjavablog@gmail.com</a> (pueden agregarme al chat de gmail). También puedes seguir JavaTutoriales en las siguientes redes sociales:
<ul>
<li><a href="https://www.facebook.com/JavaTutoriales/" target="_blank">Facebook</a></li>
<li><a href="https://twitter.com/JavaTutoriales" target="_blank">Twitter</a></li>
</ul>
Saludos y gracias.<br /><br />
<span class="negritas">Descarga los archivos de este tutorial desde mi repositorio en GitHub:</span>
<ul>
<li><a href="https://github.com/JavaTutoriales/Adapter" target="_blank">Patrón de diseño Adapter</a>.</li>
</ul>
<br />
<span class="negritas">Entradas relacionadas:</span>
<ul>
<li><a href="https://www.javatutoriales.com/2021/11/introduccion-los-patrones-de-diseno.html" target="_blank">Introducción a los patrones de diseño</a>.</li>
<li><a href="https://www.javatutoriales.com/2021/12/patron-de-diseno-factory-method.html" target="_blank">Patrón de diseño Factory Method</a>.</li>
<li><a href="https://www.javatutoriales.com/2021/12/patron-de-diseno-abstract-factory.html" target="_blank">Patrón de diseño Abstract Factory</a>.</li>
<li><a href="https://www.javatutoriales.com/2021/12/diferencias-simple-factory-vs-factory.html" target="_blank">Diferencias: Simple Factory vs. Factory Method vs. Abstrac Factory</a>.</li>
<li><a href="https://www.javatutoriales.com/2022/01/patron-de-diseno-template-method.html" target="_blank">Patrón de Diseño Template Method</a>.</li>
<li><a href="https://www.javatutoriales.com/2022/01/patron-de-diseno-strategy.html" target="_blank">Patrón de Diseño Strategy</a>.</li>
</ul>
<br />
<br />
</div>Alexhttp://www.blogger.com/profile/06974037481671868076noreply@blogger.comtag:blogger.com,1999:blog-2012232069289957092.post-38986742202896356472022-01-07T23:41:00.001-08:002022-01-07T23:41:42.326-08:00Patrón de Diseño Strategy<div style="text-align: justify;">
<a href="https://www.javatutoriales.com/search/label/patrones%20de%20dise%C3%B1o" target="_blank"><img src="https://img.shields.io/badge/JT-Design%20Patterns-orange" /></a><br />
<br />
<span class="codigo">Strategy</span> es un <a href="https://www.javatutoriales.com/2021/11/introduccion-los-patrones-de-diseno.html" target="_blank">patrón de diseño</a> de <span class="negritas">comportamiento</span> con el ámbito de objeto. Su finalidad es la de poder cambiar el algoritmo o comportamiento de un objeto en tiempo de ejecución. Esto ayuda cuando tenemos una aplicación en la que varían solo ciertas partes de la lógica. Con este patrón podemos aislar estos comportamiento o algoritmos en clases diferentes y elegir entre uno u otro en tiempo de ejecución.<br />
<br />
En este tutorial te explicó más a fondo cómo funciona este patrón y te muestro una estrategia de implementación del mismo.<br />
<br /><a name='more'></a>
Con este patrón creamos un objeto que se usará como <span class="negritas">contexto</span> y distintas estrategias de las cuales se podrá seleccionar una u otra dependiendo de ciertas condiciones. Uniremos las estrategias al contexto haciendo uso de la <span class="negritas">composición</span>.<br />
<br />
Imagina que trabajamos en una empresa que se dedica a la exportación de productos. Antes de ser enviados, todos los productos deben seguir la misma serie de pasos:
<ol>
<li>Tomarlos de los estantes.</li>
<li>Empaquetarlos.</li>
<li>Colocar una etiqueta con el nombre del destinatario.</li>
<li>Llevarlos a la empresa de paquetería para ser enviados.</li>
</ol><br />
Todos los productos siguen los mismos 4 pasos; sin embargo, el paso 2 (empaquetarlos) <span class="negritas">varía</span> dependiendo del tipo de producto y el destino a donde estemos enviando. Por ejemplo, si enviamos artículos frágiles debemos envolverlos en plástico de burbujas; si enviamos revistas o periódicos no es necesario envolverlos; si enviamos artículos que pesen más de 20 kilos debemos usar una caja de madera y cerrarla con clavos en lugar de una caja de cartón cerrada con cinta, etc.<br />
<br />
Los pasos anteriores son el <span class="negritas">contexto</span> de la aplicación de envío de artículos. Las variaciones del paso 2 descritas en el párrafo anterior son los diferentes algoritmos de empaquetamiento que queremos separar en diferentes clases; así, cuando hagamos un envío podemos revisar los artículos que enviaremos y seleccionar el algoritmo de empaquetado más adecuado. Cuando llegue un paquete con artículos diferentes podremos realizar nuevamente la selección del algoritmo y cambiarlo y usar uno diferente. <br />
<br />
Cada uno de esos diferentes algoritmos se coloca en una instancia distinta de <span class="codigo">Strategy</span>; así que es importante mencionar que estos algoritmos deben poder ser <span class="negritas">intercambiables</span>.<br />
<br />
Si estás programando un juego, puedes tener diferentes implementaciones de <span class="codigo">Strategy</span> dependiendo del nivel de los enemigos: una estrategia para los enemigos agresivos, otra para los normales y otra para los defensivos; por ejemplo.<br />
<br />
<br /><h2 class="titulo"><a class="anchorTitle" href="#objetivo" name="objetivo">
<svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z" fill-rule="evenodd"></path></svg>
Objetivo o intención del patrón</a></h2>
Con la explicación anterior podemos ver que el objetivo de <span class="codigo">Strategy</span> es:
<ol>
<li>Definir una familia de algoritmos, encapsular cada uno y volverlos intercambiables.</li>
<li>Permitir que el algoritmo cambie (o se seleccione) dependiendo del cliente que lo utilice.</li>
</ol><br />
<br /><h2 class="titulo"><a class="anchorTitle" href="#implementacion" name="implementacion">
<svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z" fill-rule="evenodd"></path></svg>
Implementación</a></h2>
Para implementar <span class="codigo">Strategy</span> debemos realizar los siguientes pasos:
<ol>
<li><span class="negritas">Definir una interface, o clase abstracta, <span class="codigo">Strategy</span></span>. Esta interface puede tener varios métodos que serán los que se invoquen para ejecutar el algoritmo. </li>
<li><span class="negritas">Crear un conjunto de clases concretas que implementen la interface <span class="codigo">Strategy</span></span>. Estas son las que contendrán las diferentes operaciones lógicas que aplicaremos. En el ejemplo de envíos, las clases concretas serían las diferentes formas que tenemos de empaquetar los artículos.</li>
<li><span class="negritas">Crear una clase "contexto"</span>. Contexto es el nombre formal que se le da a la clase que hace uso de <span class="codigo">Strategy</span>. En el ejemplo anterior, el contexto sería el módulo o parte de la aplicación que ejecuta los 4 pasos para realizar el envío.</li>
<li><span class="negritas">Inyectar o crear la instancia de <span class="codigo">Strategy</span> adecuada para la operación que queremos realizar</span>. La forma en la que se obtiene esta instancia queda fuera del alcance del patrón, pero podemos usar <a href="https://www.javatutoriales.com/2021/12/patron-de-diseno-factory-method.html" target="_blank">Factory Method</a> para decidir cuál instancia debemos usar.</li>
</ol><br />
El paso 4 es donde vemos la utilidad de este patrón. Como mencioné al inicio del tutorial: el objeto Contexto tiene una relación de <a href="https://stackify.com/oop-concepts-composition/" target="_blank">composición</a> con el objeto <span class="negritas">Strategy</span> que usará. Esto quiere decir que tiene una variable del tipo de la interface <span class="codigo">Strategy</span> y esto es lo que nos permite usar una instancia u otra. Esto quedará mucho más claro cuando veamos el ejemplo.<br />
<br /><h2 class="titulo"><a class="anchorTitle" href="#diagrama" name="diagrama">
<svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z" fill-rule="evenodd"></path></svg>
Diagrama</a></h2>
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEg8wmr00Qa7DGxe_xem1_MtMW6apWtu5jLVL4sHOQJzAtH-xZ4cBKbxF6K3Y2ucHtyZ6kPD-aU4cl9KYVyQSKCXg6ZsYMkeIHHzw3coCuCGFH8VkBKllARw8AbRr8exdQFLzv1KGcRUJkgCW-W3QsUWkPEyaAGYbEFHYSFujZcPfok_KyDaXtAnZUlTdw=s643" style="display: block; padding: 1em 0px; text-align: center;"><img alt="" border="0" data-original-height="532" data-original-width="643" src="https://blogger.googleusercontent.com/img/a/AVvXsEg8wmr00Qa7DGxe_xem1_MtMW6apWtu5jLVL4sHOQJzAtH-xZ4cBKbxF6K3Y2ucHtyZ6kPD-aU4cl9KYVyQSKCXg6ZsYMkeIHHzw3coCuCGFH8VkBKllARw8AbRr8exdQFLzv1KGcRUJkgCW-W3QsUWkPEyaAGYbEFHYSFujZcPfok_KyDaXtAnZUlTdw=s16000" /></a></div><br />
<br />
Como puedes ver en el diagrama anterior este patrón tiene solamente 3 componentes:
<ul>
<li><span class="codigo">Strategy</span>. Define la interface común a todos los algoritmos soportados.</li>
<li><span class="codigo">Estrategia concreta</span>. Las distintas clases que implementan la interface <span class="codigo">Strategy</span>.</li>
<li><span class="codigo">Contexto</span>. Objeto que contiene la referencia a <span class="codigo">Strategy</span>. El contexto es invocado por los clientes y delega la funcionalidad correspondiente al objeto <span class="codigo">Strategy</span>.</li>
</ul><br />
<span class="nota">📌 Algoritmo es el nombre que se le da a este patrón a esos bloques de código “intercambiable” que se aplicarán dependiendo de la instancia de Strategy que se vaya a utilizar.</span><br />
<br />
¿Dónde se usa este patrón? <span class="codigo">Strategy</span> se usa mucho en desarrollo web. En particular en <span class="codigo">javax.servlet.http.HttpServlet</span>, el método <span class="codigo">service()</span> y los métodos <span class="codigo">doXXX()</span> reciben un <span class="codigo">HttpServletRequest</span> y <span class="codigo">HttpServletResponse</span> y la clase que extiende de <span class="codigo">HttpServlet</span> debe hacer uso de ellos para procesar la respuesta y enviar la respuesta de vuelta al cliente.<br />
<br />
También se usa mucho en comparaciones y ordenamientos usando <span class="codigo">java.util.Comparator#compare()</span>, de hecho este será le mismo uso que nosotros le daremos.<br />
<br />
<br /><h2 class="titulo"><a class="anchorTitle" href="#ejemplo" name="ejemplo">
<svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z" fill-rule="evenodd"></path></svg>
Ejemplo</a></h2>
Para los ejemplos uso Java 17 y Gradle. Hago uso de lambdas en cierta parte del código, por lo que debe funcionar en cualquier versión superior a la 8. También, uso <a href="https://www.javatutoriales.com/2019/03/lombok-escribiendo-menos-codigo-y.html" target="_blank">Lombok</a> para evitar escribir código repetitivo de forma innecesaria.<br />
<br />
Para validar el correcto funcionamiento de la aplicación usaré JUnit y AssertJ. Agrega las siguientes dependencias en el archivo <span class="codigo">build.gradle</span> (si usas Maven, coloca las dependencias correspondientes en el archivo <span class="codigo">pom.xml</span>).<br />
<pre class="brush: groovy;" title="build.gradle.java">
dependencies {
compileOnly 'org.projectlombok:lombok:1.18.22'
annotationProcessor 'org.projectlombok:lombok:1.18.22'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
testImplementation 'org.assertj:assertj-core:3.21.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
}
</pre><br />
<br />
Ahora sí, al código.<br />
<br />
El problema que resolveremos es el siguiente: Crearemos una aplicación que se encargue de ordenar una serie de cadenas. Dependiendo de la cantidad de cadenas que tengamos usaremos <span class="negritas">un algoritmo de ordenamiento diferente</span>, usando la siguiente lógica:
<ol>
<li>Si tenemos menos de 5 cadenas usaremos el algoritmo <span class="codigo">BubbleSort</span>.</li>
<li>Si tenemos entre 5 y 10 cadenas usaremos el algoritmo <span class="codigo">Quicksort</span>.</li>
<li>Si tenemos más de 10 cadenas usaremos el algoritmo <span class="codigo">ContingSort</span>.</li>
</ol><br />
¿Por qué esas implementaciones? En realidad creo que el por qué no es importante para este tutorial, ya que la intención es mostrar como usar el patrón de diseño; pero, me estoy basando en la complejidad (Big-O) de cada uno de los algoritmos tomando la información de este sitio: <a href="https://www.bigocheatsheet.com" target="_blank">https://www.bigocheatsheet.com</a>
<span class="nota">📌 Las implementaciones de los algoritmos de ordenamiento los tomo del siguiente repositorio en GitHub (muy recomendable): <a href="https://github.com/TheAlgorithms/Java" target="_blank">TheAlgorithms/Java</a>
<ul>
<li><a href="https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/BubbleSort.java" target="_blank">BubbleSort</a></li>
<li><a href="https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/QuickSort.java" target="_blank">QuickSort</a></li>
<li><a href="https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/CountingSort.java" target="_blank">CountingSort</a></li>
</ul>
</span><br />
Incluyo el código fuente de los algoritmos en mi proyecto de GitHub, pero dejo el código original con los créditos a los autores originales, y las ligas al código fuente original a lo largo del tutorial.<br />
<br />
Las clases anteriores, convenientemente, ya hacen uso del patrón <span class="codigo">Strategy</span>. Todas implementan una interface común, <span class="codigo"><a href="https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/SortAlgorithm.java" target="_blank">SortAlgorithm</a></span>, esta interface representa a la interface <span class="codigo">Strategy</span> del diagrama del patrón.<br />
<br />
<span class="codigo">SortAlgorithm</span> tiene dos métodos, ambos regresan un objeto de tipo <span class="clase">Comparable</span>, el cual usamos para ordenar la lista usando las funcionalidades nativas de Java. <br />
<br />
El primer método recibe un arreglo de objetos y el segundo una lista. Yo respeto esta misma interface, la cual se ve de la siguiente forma (sin los comentarios del código fuente original):
<pre class="brush: java;" title="SortAlgorithm.java">
/**
* @author Podshivalov Nikita (https://github.com/nikitap492)
*/
public interface SortAlgorithm {
<T extends Comparable<T>> T[] sort(T[] unsorted);
default <T extends Comparable<T>> List<T> sort(List<T> unsorted) {
return Arrays.asList(sort(unsorted.toArray((T[]) new Comparable[unsorted.size()])));
}
}
</pre><br />
Como puedes ver, el segundo método es un método <span class="clase">default</span> que hace uso del primero, así que en realidad en las clases que implementen la interface solo estamos obligados a implementar un método, el que recibe un arreglo.<br />
<br />
Cada una de las implementaciones que usaremos son las estrategias concretas. Como el mostrar el código de las mismas no aporta nada al tutorial, y ocupan algo de espacio, lo omitiré, pero dejo nuevamente las ligas al código fuente original por si quieres revisarlo:
<ul>
<li><a href="https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/BubbleSort.java" target="_blank">BubbleSort</a>, por Varun Upadhyay (https://github.com/varunu28)</li>
<li><a href="https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/QuickSort.java" target="_blank">QuickSort</a>, por Varun Upadhyay (https://github.com/varunu28)</li>
<li><a href="https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/CountingSort.java" target="_blank">CountingSort</a>, por Youssef Ali (https://github.com/youssefAli11997)</li>
</ul>
Lo siguiente es crear la clase <span class="codigo">Contexto</span>. A esta clase la llamaré <span class="clase">OrdenadorCadenas</span>. Esta clase tendrá una referencia a <span class="clase">SortAlgorithm</span>, de la siguiente forma:
<pre class="brush: java; highlight: [2];" title="OrdenadorCadena.java">
public class OrdenadorCadenas {
private SortAlgorithm sortAlgorithm;
}
</pre><br />
Además, tendrá solo un método, <span class="clase">ordena</span>, que recibirá un arreglo de cadenas desordenado y regresará un nuevo arreglo ordenado. La tarea de ordenamiento será delegada a la instancia de <span class="clase">SortAlgorithm</span>, de la siguiente forma:
<pre class="brush: java;" title="OrdenadorCadena.java">
public String[] ordena(String[] arregloDesordenado) {
String[] arregloOrdenado = sortAlgorithm.sort(arregloDesordenado);
return arregloOrdenado;
}
</pre><br />
El último paso consiste en obtener la instancia apropiada de <span class="clase">SortAlgorithm</span> de acuerdo con las reglas que establecimos anteriormente. Para esto haré uso de una <a href="https://www.javatutoriales.com/2021/12/patron-de-diseno-factory-method.html" target="_blank">SimpleFactory</a>. No explico los detalles de la misma ya que tengo un tutorial completo donde hablo de este patrón de diseño. ¿Por qué una <span class="clase">SimpleFactory</span> y no una <span class="clase">FactoryMethod</span>? Bueno, explico las diferencias en este tutorial: <a href="https://www.javatutoriales.com/2021/12/diferencias-simple-factory-vs-factory.html" target="_blank">Diferencias: Simple Factory vs. Factory Method vs. Abstrac Factory </a>.<br />
<br />
<pre class="brush: java;" title=" SortAlgorithmSimpleFactory.java">
public class SortAlgorithmSimpleFactory {
private static final int ELEMENTOS_BUBBLE_SORT = 5;
private static final int ELEMENTOS_QUICK_SORT = 10;
public static SortAlgorithm getSortingAlgorithm(int numeroDeElementos) {
SortAlgorithm sortAlgorithm;
if (numeroDeElementos < ELEMENTOS_BUBBLE_SORT) {
sortAlgorithm = new BubbleSort();
} else if (numeroDeElementos > ELEMENTOS_BUBBLE_SORT && numeroDeElementos <= ELEMENTOS_QUICK_SORT) {
sortAlgorithm = new QuickSort();
} else {
sortAlgorithm = new CountingSort();
}
return sortAlgorithm;
}
}
</pre><br />
<span class="nota">📌 Esta es solo una forma de obtener la instancia de <span class="clase">SortAlgorithm</span>, pero esta instancia puede obtenerse de muchas otras formas. Usa la que sea más conveniente para el problema que estás tratando de resolver.</span><br />
<br />
La clase <span class="clase">OrdenadorCadenas </span>queda de la siguiente forma:<br />
<pre class="brush: java;" title="OrdenadorCadena.java">
public class OrdenadorCadenas {
private SortAlgorithm sortAlgorithm;
public String[] ordena(String[] arregloDesordenado) {
sortAlgorithm = SortAlgorithmSimpleFactory.getSortingAlgorithm(arregloDesordenado.length);
String[] arregloOrdenado = sortAlgorithm.sort(arregloDesordenado);
return arregloOrdenado;
}
}
</pre><br />
Ahora solo queda comprobar que todo funciona correctamente. Para eso haré uso de algunas pruebas unitarias. Usaré tres formas diferentes para validar que los arreglos están correctamente ordenados.<br />
<br />
En la primera prueba creamos un arreglo de 3 cadenas, las ordenamos usando una instancia de <span class="clase">OrdenadorCadena</span> y luego validamos que el arreglo regresado contenga los elementos en las posiciones esperadas:
<pre class="brush: java;" title="OrdenadorCadenaTest.java">
@Test
public void testCadenasOrdenadas_cuandoArregloConTresCadenas() {
String[] arregloDesordenado = {"Hola", "Estoy", "Desordenado"};
String[] arregloOrdenado = ordenadorCadenas.ordena(arregloDesordenado);
assertThat(arregloOrdenado).contains("Desordenado", atIndex(0));
assertThat(arregloOrdenado).contains("Estoy", atIndex(1));
assertThat(arregloOrdenado).contains("Hola", atIndex(2));
}
</pre><br />
Para la segunda prueba usamos 6 cadenas y también validamos que el resultado obtenido sean las mismas cadenas en el orden apropiado:
<pre class="brush: java;" title="OrdenadorCadenaTest.java">
@Test
public void testCadenasOrdenadas_cuandoArregloConSeisCadenas() {
String[] arregloDesordenado = {"Ahora", "Tenemos", "Mas", "Elementos", "Para", "Ordenar"};
String[] arregloOrdenado = ordenadorCadenas.ordena(arregloDesordenado);
assertThat(arregloOrdenado).containsExactly("Ahora", "Elementos", "Mas", "Ordenar", "Para", "Tenemos");
}
</pre><br />
Finalmente, realizaremos la prueba con 15 cadenas:
<pre class="brush: java;" title="OrdenadorCadenaTest.java">
@Test
public void testCadenasOrdenadas_cuandoArregloCon15Cadenas() {
String[] arregloDesordenado = {"Ahora", "Si", "Tenemos", "Muchas", "Cadenas", "Para", "Validar", "El", "Algoritmo", "De", "Ordenamiento", "Seleccionado", "En", "Nuestra", "Aplicacion"};
String[] arregloOrdenado = ordenadorCadenas.ordena(arregloDesordenado);
String[] arregloEsperado = {"Ahora", "Algoritmo", "Aplicacion", "Cadenas", "De", "El", "En", "Muchas", "Nuestra", "Ordenamiento", "Para", "Seleccionado", "Si", "Tenemos", "Validar"};
assertThat(arregloOrdenado).isEqualTo(arregloEsperado);
}
</pre><br />
Y listo, con esto podemos comprobar que la aplicación funciona correctamente.<br />
<br />
El diagrama de nuestra implementación es el siguiente:<br />
<br />
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEjWLcxqDQEJgg484gQNAm46KSu_9BZnBVA0XxQyblCF-MwKCt56Y51IkrfixldybWJhR781kLYUX0DTlKJINA8Ij6V9CdYuXHcLI4k-ZMiE2UnnmkIqc7XTbrsFTTjBrkpiLncxj4QpV-paJuvdhAIztor-B_Te09K0XM2ZLzxLGWOkldDa4r5q0ClWBw" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" data-original-height="532" data-original-width="543" src="https://blogger.googleusercontent.com/img/a/AVvXsEjWLcxqDQEJgg484gQNAm46KSu_9BZnBVA0XxQyblCF-MwKCt56Y51IkrfixldybWJhR781kLYUX0DTlKJINA8Ij6V9CdYuXHcLI4k-ZMiE2UnnmkIqc7XTbrsFTTjBrkpiLncxj4QpV-paJuvdhAIztor-B_Te09K0XM2ZLzxLGWOkldDa4r5q0ClWBw"/></a></div><br />
<br />
<br /><h2 class="titulo"><a class="anchorTitle" href="#ventajasydesventajas" name="ventajasydesventajas">
<svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z" fill-rule="evenodd"></path></svg>
Ventajas y Desventajas</a></h2>
<ul>
<li>➕Es un patrón muy simple de utilizar, eficiente y flexible al hacer uso de la composición por sobre la herencia.</li>
<li>➕Funciona muy bien cuando tenemos muchos comportamientos distintos y de los cuales tenemos diferentes variantes del mismo algoritmo.</li>
<li>➕Ayuda a evitar el exponer estos algoritmos complejos al cliente que hará uso de ellos.</li>
<li>➕Ayuda a eliminar el uso de las sentencias condicionales para poder aplicar el algoritmo deseado.</li>
<li>➖El cliente (o el componente encargado de crear las distintas instancias de Strategy) debe estar al tanto de todas estas instancias (o hacer uso de un patrón más adecuado para la creación de las mismas). Esto significa que debe entender todas las estrategias para saber cuál es la más apropiada para ser usada.</li>
</ul><br />
<br /><h2 class="titulo"><a class="anchorTitle" href="#otrosnombres" name="otrosnombres">
<svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z" fill-rule="evenodd"></path></svg>
Otros Nombres</a></h2>
<ul>
<li>Policy Pattern</li>
</ul><br />
<br /><h2 class="titulo"><a class="anchorTitle" href="#conclusion" name="conclusion">
<svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z" fill-rule="evenodd"></path></svg>
Conclusión</a></h2>
Como viste en este tutorial, <span class="codigo">Strategy</span> es un patrón muy útil, bastante utilizado y con una implementación realmente simple; pienso que de hecho es el más simple de todos los patrones de diseño. También ampliamente utilizado en librerías y frameworks por su conveniencia al momento de estructurar una aplicación.<br />
<br />
Además se mezcla muy bien con otros patrones de diseño como <span class="codigo">FactoryMethod</span> y <span class="codigo">TemplateMethod</span>.<br />
<br />
Como nota adicional, en el repositorio dejo, además del código, el archivo original en draw.io con los esquemas de los patrones.<br />
<br />
Espero que este tutorial les sea de utilidad. Si tienen alguna duda, sugerencia, comentario o aclaración, pueden dejarla en la sección de comentarios o enviar un correo a <a href="mailto:programadorjavablog@gmail.com">programadorjavablog@gmail.com</a> (pueden agregarme al chat de gmail). También puedes seguir JavaTutoriales en las siguientes redes sociales:
<ul>
<li><a href="https://www.facebook.com/JavaTutoriales/" target="_blank">Facebook</a></li>
<li><a href="https://twitter.com/JavaTutoriales" target="_blank">Twitter</a></li>
</ul>
Saludos y gracias.<br /><br />
<span class="negritas">Descarga los archivos de este tutorial desde mi repositorio en GitHub:</span>
<ul>
<li><a href="https://github.com/JavaTutoriales/Strategy" target="_blank">Patrón de diseño Strategy</a>.</li>
</ul>
<br />
<span class="negritas">Entradas relacionadas:</span>
<ul>
<li><a href="https://www.javatutoriales.com/2021/11/introduccion-los-patrones-de-diseno.html" target="_blank">Introducción a los patrones de diseño</a>.</li>
<li><a href="https://www.javatutoriales.com/2021/12/patron-de-diseno-factory-method.html" target="_blank">Patrón de diseño Factory Method</a>.</li>
<li><a href="https://www.javatutoriales.com/2021/12/patron-de-diseno-abstract-factory.html" target="_blank">Patrón de diseño Abstract Factory</a>.</li>
<li><a href="https://www.javatutoriales.com/2021/12/diferencias-simple-factory-vs-factory.html" target="_blank">Diferencias: Simple Factory vs. Factory Method vs. Abstrac Factory</a>.</li>
<li><a href="https://www.javatutoriales.com/2022/01/patron-de-diseno-template-method.html" target="_blank">Patrón de Diseño Template Method</a>.</li>
</ul>
<br />
<br />
</div>Alexhttp://www.blogger.com/profile/06974037481671868076noreply@blogger.comtag:blogger.com,1999:blog-2012232069289957092.post-17016168150481848242022-01-01T03:33:00.005-08:002022-01-07T23:42:02.205-08:00Patrón de Diseño Template Method<div style="text-align: justify;">
<a href="https://www.javatutoriales.com/search/label/patrones%20de%20dise%C3%B1o" target="_blank"><img src="https://img.shields.io/badge/JT-Design%20Patterns-orange" /></a><br />
<br />
<span class="codigo">Template Method</span> es un <a href="https://www.javatutoriales.com/2021/11/introduccion-los-patrones-de-diseno.html" target="_blank">patrón de diseño</a> <span class="negritas">de comportamiento</span> del ámbito de clases. Con este patrón se define <span class="negritas">el esqueleto</span> de un algoritmo en una clase abstracta, y se deja que sean las subclases quienes proporcionen los <span class="negritas">detalles concretos</span> de algunos o todos los pasos de ese algoritmo. Lo interesante de este patrón es que también da la opción a las subclases de redefinir los pasos de acuerdo con sus necesidades, sin cambiar la estructura del algoritmo.<br />
<br />
En este tutorial te explico los detalles de <span class="codigo">Template Method</span>, y te muestro una estrategia de implementación.<br />
<br />
<a name='more'></a>
Este patrón ayuda a definir el esqueleto o estructura de un algoritmo, el cual se asume siempre tendrá los mismos pasos en el mismo orden, pero la forma de realizar estos pasos será diferente dependiendo de la situación particular que se quiera resolver.<br />
<br />
Digamos que queremos construir una casa. Los pasos para construir una casa son siempre los mismos <span class="negritas">sin importar el material o la ubicación en la que esta se encuentra</span>. Así que siempre haremos lo siguiente:
<ol>
<li>Construir los cimientos.</li>
<li>Colocar pilares.</li>
<li>Construir el sótano.</li>
<li>Construir el primer piso.</li>
<li>Construir el segundo piso.</li>
<li>Colocar el techo.</li>
<li>Colocar los acabados.</li>
<li>Pintar.</li>
</ol><br />
Si tenemos una casa de madera en el bosque o una casa de ladrillos en la ciudad siempre realizaremos los mismos pasos. Lo mismo ocurre si tendremos una casa permanente o una casa temporal (digamos que pasaremos una noche en el bosque y queremos construir un refugio para esa noche, o que está nevando y queremos construir un iglú).<br />
<br />
Del párrafo anterior podemos decir lo siguiente: <span class="codigo">Template Method</span> da la estructura del algoritmo a nivel de la clase base, (de construcción de casas en este ejemplo) pero deja que sean las subclases las que proporcionen los de cada uno de esos pasos.<br />
<br />
<ul>
<li><span class="negritas">P:</span> ¿Qué ocurre si en mi ciudad hay grandes lluvias que generan inundaciones y tener un sótano es una mala idea?</li>
<li><span class="negritas">R:</span> Para esa implementación particular podemos omitir (colocar una implementación vacía) en el método de la construcción del sótano. </li>
</ul><br />
<ul>
<li><span class="negritas">P:</span> ¿Qué ocurre si mi casa solo tendrá un piso? </li>
<li><span class="negritas">R:</span> Similar a la respuesta anterior, podemos proporcionar una implementación vacía para la construcción del segundo piso o tener una implementación que reciba el número de pisos que tendrá la casa.</li>
</ul><br />
<ul>
<li><span class="negritas">P:</span> En los puntos anteriores, el paso para construir el primer piso y pintar se colocan al mismo nivel, pero construir un piso lleva mucho más esfuerzos y pasos intermedios que pintar.</li>
<li><span class="negritas">R:</span> Sí, pero eso no modifica el algoritmo. Pintar puede ser solo un método que cambie el color de la casa (o de los distintos elementos de la casa, dependiendo de la implementación), y construir un piso puede tener muchos pasos intermedios o un algoritmo interno por si mismo (colocar las paredes, cableados eléctricos, tuberías, etc) o ser tan sencillo como colocar un conjunto de ramas (si es que nuestra casa será una casa temporal para pasar una noche en el boque). Nuevamente, esto depende de la implementación que hagamos para la casa particular que estemos construyendo en ese momento.</li>
</ul><br />
<br /><h2 class="titulo"><a class="anchorTitle" href="#objetivo" name="objetivo">
<svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z" fill-rule="evenodd"></path></svg>
Objetivo o intención del patrón</a></h2>
Con la explicación anterior, podemos ver que el objetivo de <span class="codigo">Template Method</span> es:
<ul>
<li>Definir el esqueleto de un algoritmo en una operación (o método) y delegar algunos de los pasos a las subclases.</li>
<li>Permitir a las subclases redefinir ciertos pasos del algoritmo sin darles la opción de cambiar la estructura del algoritmo.</li>
</ul><br />
<br /><h2 class="titulo"><a class="anchorTitle" href="#implementacion" name="implementacion">
<svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z" fill-rule="evenodd"></path></svg>
Implementación</a></h2>
La implementación de este patrón, como ya puedes estarte imaginando, es muy sencilla:
<ol>
<li><span class="negritas">Definir una clase abstracta con el esqueleto del patrón</span>. Esta clase será la que contengan los pasos que conforman el algoritmo. </li>
<li><span class="negritas">Definir una serie de métodos abstractos que son los pasos del algoritmo</span>. A estos métodos se les conoce formalmente como <span class="codigo">operaciones primitivas</span>. No necesariamente deben ser métodos abstractos, si de antemano sabemos que sin importar la implementación que usemos, algún o algunos pasos siempre tendrán la misma implementación, podemos proporcionarlos como métodos definidos con lógica implementada. Sin embargo, es importante que estos métodos puedan ser sobre escritos por las clases concretas. Algo que no se recomienda es que todos los métodos sean concretos, ya que se pierde la intención del patrón de que el algoritmo (o los pasos concretos del mismo) sean implementados por las subclases.</li>
<li><span class="negritas">Definir un método <span class="clase">final</span></span> (para evitar ser modificado por las subclases) <span class="negritas">con los pasos que conforman el algoritmo</span>. Este método es donde se invocan las operaciones primitivas. Normalmente este se implementa como una serie de llamadas a métodos, algunas condiciones y ciclos.</li>
<li>Crear una o varias implementaciones concretas de la clase abstracta y proporcionar una implementación para los pasos abstractos del algoritmo.</li>
<li>En el cliente, obtener alguna de estas implementaciones y ejecutar el método que ponga en marcha el algoritmo (el template method).</li>
</ol><br />
Tal vez el paso menos claro sea el paso 3. Este método, que es el corazón del patrón, es quien tiene la plantilla de la secuencia de los pasos del algoritmo. En la descripción menciono que podemos tener condiciones y ciclos, pero es importante que no escribamos en realidad detalles, sino que lo dejemos lo más abstracto posible. Aclaremos un poco esto usando el ejemplo de la construcción de casas:
<pre class="brush: java;">public final Casa construyeCasa(Direccion direccion, Color color){
Casa casa = new Casa(direccion);
construyeCimientos(casa);
colocaPilares(casa);
if(isSotanoNecesario(casa){
construyeSotano(casa);
}
construyePrimerPiso(casa);
if(isSegundoPisoNecesario(casa)){
construyeSegundoPiso(casa);
}
colocaTecho(casa);
colocaAcabados(casa);
pinta(casa, color);
return casa;
}
</pre><br />
El algoritmo anterior tiene muchas oportunidades de mejora pero por ahora sirve bien para ilustrar la idea.<br />
<br />
En el ejemplo anterior, <span class="clase">construyeCasa</span> es nuestro <span class="codigo">Template Method</span>, <span class="clase">construyeCimientos</span>, <span class="clase">colocaPilares</span>, <span class="clase">isSotanoNecesario</span>, <span class="clase">pinta</span>, etc, son los pasos del algoritmo (las operaciones primitivas). Cada uno de estos es un método abstracto que debe ser implementado por las clases concretas.<br />
<br />
Si, por ejemplo, <span class="codigo">pinta</span>, tendrá siempre (o casi siempre) la misma implementación, podemos proporcionarlo como un método concreto similar a:
<pre class="brush: java;">protected void pinta(Casa casa, Color color) {
casa.setColor(color);
}
</pre><br />
El hecho de que <span class="clase">pinta</span> sea <span class="clase">protected</span>, brinda la oportunidad de que cualquiera de sus subclases pueda reimplementarlo en cualquier momento. <br />
<br />
Aquí es importante resaltar una cosa. Ya que el algoritmo, el template method, se encuentra en la clase abstracta y los detalles, las implementaciones de los métodos primitivos, en las clases concretas, es el código de la clase abstracta el que invocará a los métodos de la subclase. A esto se le conoce como <span class="codigo">El principio de Hollywood</span>: “No nos llames, nosotros te llamamos”, al indicar que no son las subclases las que invocan a los métodos de la clase base, sino al revés. Este principio se usa mucho en inversión de control para reducir el acoplamiento entre clases.<br />
<br />
<br /><h2 class="titulo"><a class="anchorTitle" href="#diagrama" name="diagrama">
<svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z" fill-rule="evenodd"></path></svg>
Diagrama</a></h2>
Este es el diagrama del patrón.<br />
<br />
<p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEgqoTU8bRGc6-EiZF47glY0_o_Q6iIG1SGx98mNd4ilNg8oQsKlnSz5OHGoNJG95BKGHWr6Ey86rejNS0Kxu6Sa8b00Fj42R961gnZcrcY0SVxRvK29g2o6nLzirfbWwbiffmN1tkk5fH0_TcmOAv5ejrqM1IzMb3zdDT7IO_SCyYyC-iz9pWRQ4E8wYg=s502" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="502" data-original-width="372" src="https://blogger.googleusercontent.com/img/a/AVvXsEgqoTU8bRGc6-EiZF47glY0_o_Q6iIG1SGx98mNd4ilNg8oQsKlnSz5OHGoNJG95BKGHWr6Ey86rejNS0Kxu6Sa8b00Fj42R961gnZcrcY0SVxRvK29g2o6nLzirfbWwbiffmN1tkk5fH0_TcmOAv5ejrqM1IzMb3zdDT7IO_SCyYyC-iz9pWRQ4E8wYg=s16000" /></a></div><br /> <p></p>
Como puedes ver es muy directo. Tenemos una clase abstracta que contiene al <span class="codigo">templateMethod</span>, y las operaciones primitivas definidas como otra serie de métodos abstractos.<br />
<br />
También, tenemos a la clase (o clases) concretas que extienden den la clase abstracta e implementan los métodos correspondientes a las operaciones primitivas. A diferencia de otros patrones de diseño, en este caso la clase abstracta no puede ser sustituida por una interface, ya que es necesario que proporcione la implementación del <span class="codigo">templateMethod</span>, de otra forma no habría manera de definir esta plantilla del algoritmo.<br />
<br />
<br /><h2 class="titulo"><a class="anchorTitle" href="#ejemplo" name="ejemplo">
<svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z" fill-rule="evenodd"></path></svg>
Ejemplo</a></h2>
Para los ejemplos uso Java 17 y Gradle. Hago uso de lambdas en cierta parte del código, por lo que debe funcionar en cualquier versión superior a la 8. También, uso <a href="https://www.javatutoriales.com/2019/03/lombok-escribiendo-menos-codigo-y.html" target="_blank">Lombok</a> para evitar escribir código repetitivo de forma innecesaria.<br />
<br />
Para validar el correcto funcionamiento de la aplicación usaré <span class="codigo">JUnit</span> y <span class="codigo">AssertJ</span>. Agrega las siguientes dependencias en el archivo <span class="codigo">build.gradle</span> (si usas Maven, coloca las dependencias correspondientes en el archivo <span class="codigo">pom.xml</span>).
<pre class="brush: groovy;" title="build.gradle">dependencies {
compileOnly 'org.projectlombok:lombok:1.18.22'
annotationProcessor 'org.projectlombok:lombok:1.18.22'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
testImplementation 'org.assertj:assertj-core:3.21.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
}
</pre><br />
Ahora sí, al código.<br />
<br />
El problema que resolveremos es el siguiente: Debemos crear una aplicación que pueda recibir una serie de números enteros a través de un canal de datos, los transforme de alguna forma y regresará un único valor. Se puede generar una notificación en caso de que una o una serie de condiciones se cumplan. Para obtener los valores de entrada y regresar el valor de salida debemos usar un canal de comunicación; dependiendo de la implementación de canal puede ser necesario tener que abrirlo (establecer una conexión) y cerrarlo (desconectarse).<br />
<br />
En el párrafo anterior he descrito un problema de manera general que puede resolverse de distintas formas, especialmente la parte que dice “los transforme de alguna forma” y “en caso de que una o una serie de condiciones se cumplan”. Lo único que he hecho es describir los pasos con los que debemos contar. Además, he descrito que se tendrá un canal de datos el cual potencialmente debe abrirse y cerrarse y que en algún punto puede generase una notificación.<br />
<br />
Como puedes ver, si tomamos los elementos del párrafo anterior podemos generar un template method, que en este caso llamaré <span class="clase">convierte</span>, el cual se vea de la siguiente forma:
<pre class="brush: java; highlight: [1, 3];" title="ConversorDatos.java">public abstract class ConversorDatos {
public final int convierte(int[] numeros) {
CanalDatos canalDatos = conecta(numeros);
Dato datoEntrada = selecciona(canalDatos);
Dato datoProcesado = procesa(datoEntrada);
if(isNotificacionNecesaria(datoProcesado)){
notifica(datoProcesado);
}
desconecta(canalDatos);
return datoProcesado.getDatoProcesado();
}
}
</pre><br />
Quiero que veas que en código anterior hay dos cosas importantes. La primera es que he usado una clase <span class="negritas">abstracta</span>, <span class="clase">ConversorDatos</span>, de esta forma me aseguro de que nadie podrá usar directamente esta clase y que se deberá proporcionar al menos una implementación de la misma. La segunda es que el método <span class="clase">convierte</span> es <span class="clase">final</span>. De esta forma me aseguro de que ninguna de las subclases pueda redefinir este método cambiando el algoritmo.<br />
<br />
Para hacer énfasis: el <span class="codigo">template method</span> proporciona los pasos o la estructura del algoritmo, pero sin indicar los detalles de la implementación. Puedes ver que tenemos un <span class="clase">CanalDatos</span> y un <span class="clase">Dato</span>. Estas son algunas clases que generamos siguiendo la especificación descrita. <br />
<br />
Además, tenemos una serie de operaciones o métodos: <span class="clase">conecta</span>, <span class="clase">selecciona</span>, <span class="clase">procesa</span>, <span class="clase">isNotificacionNecesaria</span>, <span class="clase">notifica</span> y <span class="clase">desconecta</span>; estos son las operaciones primitivas que se describen en el paso 2 de la <a href="#implementacion" target="_blank">implementación</a> del patrón. Como queremos que sea alguna subclase la que proporcione los detalles o implementación de los mismos, los definiré como una serie de métodos abstractos:
<pre class="brush: java;" title="ConversorDatos.java">protected abstract CanalDatos conecta(int[] numeros);
protected abstract Dato selecciona(CanalDatos canalDatos);
protected abstract Dato procesa(Dato datoEntrada);
protected abstract boolean isNotificacionNecesaria(Dato dato);
protected abstract void notifica(Dato dato);
protected abstract void desconecta(CanalDatos canalDatos);
</pre><br />
Aquí uso <span class="clase">protected</span> como modificador de acceso ya que estas operaciones primitivas no son métodos que tenga la intención de que puedan ser invocados desde cualquier lado, solo desde el código de la clase base.<br />
<br />
<span class="nota">📌 Nota: Existe una convención para los nombres de las operaciones primitivas. Esta convención dice que los nombres de las operaciones primitivas deben comenzar con “do” en inglés. Si seguimos esta convención en español los nombres de los métodos tendrían que comenzar con “haz”. Yo no lo he hecho porque me parece que gramaticalmente no es correcto. Pero si los nombres de estos métodos fueran en inglés, tendrían que llamarse: <span class="clase">doConnect</span>, <span class="clase">doSelect</span>, <span class="clase">doProcess</span>, <span class="clase">doNotify</span> y <span class="clase">doDisconnect</span>.</span><br />
¿Todas las operaciones primitivas deben ser abstractas? No. Podemos proporcionar una implementación genérica para los pasos para los que tenga sentido. Digamos que por default queremos que el método <span class="clase">isNotificacionNecesaria</span> regrese <span class="clase">false</span>, podemos proporcionar una implementación como la siguiente:
<pre class="brush: java;" title="ConversorDatos.java">protected boolean isNotificacionNecesaria(Dato dato){
return false;
}
</pre><br />
Así estamos indicando que no es necesario generar una notificación; pero además dejamos la posibilidad de que cualquiera de las subclases sobreescriba ese paso si lo necesita.<br />
<br />
La clase <span class="clase">CoversorDatos</span> completa queda de la siguiente forma:
<pre class="brush: java;" title="ConversorDatos.java">public abstract class ConversorDatos {
public final int convierte(int[] numeros) {
CanalDatos canalDatos = conecta(numeros);
Dato datoEntrada = selecciona(canalDatos);
Dato datoProcesado = procesa(datoEntrada);
if(isNotificacionNecesaria(datoProcesado)){
notifica(datoProcesado);
}
desconecta(canalDatos);
return datoProcesado.getDatoProcesado();
}
protected abstract CanalDatos conecta(int[] numeros);
protected abstract Dato selecciona(CanalDatos canalDatos);
protected abstract Dato procesa(Dato datoEntrada);
protected boolean isNotificacionNecesaria(Dato dato){
return false;
}
protected abstract void notifica(Dato dato);
protected abstract void desconecta(CanalDatos canalDatos);
}
</pre><br />
Ahora debemos crear las implementaciones de las clases auxiliares <span class="clase">CanalDatos</span> y <span class="clase">Dato</span>.
<span class="nota">🏆 Buena práctica. Por motivos de espacio y para no alargar el tutorial, crearé las tres clases anteriores como clases concretas; sin embargo, lo mejor sería crearlas como clases abstractas o interfaces y de esa forma podremos también reducir el acoplamiento entre las clases.</span><br />
<span class="clase">CanalDatos</span> nos servirá para leer la información. Como en este ejemplo estamos trabajando con un arreglo de enteros tendremos la siguiente implementación:
<pre class="brush: java;" title="CanalDatos.java">@Data
@AllArgsConstructor
public class CanalDatos {
private int[] datos;
}
</pre><br />
En el caso de <span class="clase">Dato</span>, la usaremos para mantener la lista de enteros originales que estamos recibiendo, y para almacenar el resultado de la conversión de datos:
<pre class="brush: java;" title="Dato.java">@Data
@RequiredArgsConstructor
public class Dato {
private final int[] numerosSeleccionados;
private int datoProcesado;
}
</pre><br />
Lo siguiente es crear una subclase de <span class="clase">ConversorDatos</span> que proporcione implementaciones para cada uno de los métodos abstractos anteriores. En este caso crearé una clase que regrese la suma de los números <span class="negritas">pares</span> que hayan sido proporcionados, si esta suma es <span class="negritas">mayor a 100</span> se creará una notificación. Vamos paso por paso.<br />
<br />
A esta clase la llamaré <span class="clase">ConversorDatosPares</span> y extenderá de <span class="clase">CoversorDatos</span>:
<pre class="brush: java;" title="ConversorDatosPares.java">public class ConversorDatosPares extends ConversorDatos {
}
</pre><br />
Para el primer paso, <span class="clase">conecta</span>, lo que haré será crear un nuevo <span class="clase">CanalDatos</span> con el arreglo de enteros que recibe como parámetros:
<pre class="brush: java;" title="ConversorDatosPares.java">@Override
protected CanalDatos conecta(int[] numeros) {
return new CanalDatos(numeros);
}
</pre><br />
En <span class="clase">selecciona</span>, filtraré los números que sean múltiplos de 2 (los números pares) y crearé un nuevo arreglo que contenga solo estos datos. Luego crearé una instancia de <span class="clase">Dato</span> pasando este nuevo arreglo a su constructor y lo regresaré:<br />
<br />
<span class="nota">📌 Nota: Todo el método siguiente podría quedar en una sola línea de código, pero lo separo en varias para hacer más explícito lo que está ocurriendo.</span><br />
<pre class="brush: java;" title="ConversorDatosPares.java">@Override
protected Dato selecciona(CanalDatos canalDatos) {
int[] numerosPares = Arrays.stream(canalDatos.getDatos()).filter(numero -> numero % 2 == 0).toArray();
Dato dato = new Dato(numerosPares);
return dato;
}
</pre><br />
Para el procesamiento de los datos realizaré la suma de todos los números que se encuentran en el arreglo, esa suma la usaré para crear un nuevo objeto de tipo <span class="clase">Dato</span> el cual tendrá dos atributos; el primero es la lista de los datos que conforman la suma (los números pares obtenidos en el paso anterior) y la suma de estos enteros. Regresaré esa instancia como valor de retorno del método:
<pre class="brush: java;" title="ConversorDatosPares.java">@Override
protected Dato procesa(Dato datoEntrada) {
int resultadoSuma = Arrays.stream(datoEntrada.getNumerosSeleccionados()).sum();
Dato datoSalida = new Dato(datoEntrada.getNumerosSeleccionados());
datoSalida.setDatoProcesado(resultadoSuma);
return datoSalida;
}
</pre><br />
En <span class="clase">isNotificacionNecesaria</span> validaré si la suma total es mayor a 100, en cuyo caso regresaré <span class="clase">true</span>, de lo contrario regresaré <span class="clase">false</span>. Esto ayudará a saber si es necesario enviar una notificación.
<pre class="brush: java;" title="ConversorDatosPares.java">@Override
protected boolean isNotificacionNecesaria(Dato dato) {
return dato.getDatoProcesado() > 100;
}
</pre><br />
Si es necesario enviar una notificación, <span class="clase">notifica</span> debería hacerlo. Para este ejemplo <span class="clase">notifica</span> no hará nada, por lo que el cuerpo del método estará vacío.
<pre class="brush: java;" title="ConversorDatosPares.java">@Override
protected void notifica(Dato dato) {
}
</pre><br />
Finalmente, en la implementación de <span class="clase">desconecta</span> cerraremos el canal de datos. En este caso será tan simple como establecer el arreglo a <span class="clase">null</span>:
<pre class="brush: java;" title="ConversorDatosPares.java">@Override
protected void desconecta(CanalDatos canalDatos) {
canalDatos.setDatos(null);
}
</pre><br />
La implementación completa de <span class="clase">CoversorDatosPares</span> queda de la siguiente forma:
<pre class="brush: java;" title="ConversorDatosPares.java">public class ConversorDatosPares extends ConversorDatos {
@Override
protected CanalDatos conecta(int[] numeros) {
return new CanalDatos(numeros);
}
@Override
protected Dato selecciona(CanalDatos canalDatos) {
int[] numerosPares = Arrays.stream(canalDatos.getDatos()).filter(numero -> numero % 2 == 0).toArray();
Dato dato = new Dato(numerosPares);
return dato;
}
@Override
protected Dato procesa(Dato datoEntrada) {
int resultadoSuma = Arrays.stream(datoEntrada.getNumerosSeleccionados()).sum();
Dato datoSalida = new Dato(datoEntrada.getNumerosSeleccionados());
datoSalida.setDatoProcesado(resultadoSuma);
return datoSalida;
}
@Override
protected boolean isNotificacionNecesaria(Dato dato) {
return dato.getDatoProcesado() > 100;
}
@Override
protected void notifica(Dato dato) {
}
@Override
protected void desconecta(CanalDatos canalDatos) {
canalDatos.setDatos(null);
}
}
</pre><br />
Y listo, la implementación que hemos hecho es así de sencilla. <br />
<br />
El diagrama de nuestra implementación de <span class="codigo">Template Method</span> es el siguiente:<br />
<br />
<p> </p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEhd-aV3UoN0emgf82Go8Kiwx155VeHqiSAMteQYs9vSgt_DqXNLv43qy5BiusjogvPS4rpwkHbnDjAtgirewi8O8on-jHdK7ZdMAmiWaxeuD7pZiNDvtMHStISfx-LhAurBpVnHCrc6mBzhr8qOyjtValH3g6Vd_k8x5e2842Lzrw2Q0wwn-mV3m_5jJg=s692" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="692" data-original-width="432" src="https://blogger.googleusercontent.com/img/a/AVvXsEhd-aV3UoN0emgf82Go8Kiwx155VeHqiSAMteQYs9vSgt_DqXNLv43qy5BiusjogvPS4rpwkHbnDjAtgirewi8O8on-jHdK7ZdMAmiWaxeuD7pZiNDvtMHStISfx-LhAurBpVnHCrc6mBzhr8qOyjtValH3g6Vd_k8x5e2842Lzrw2Q0wwn-mV3m_5jJg=s16000" /></a></div><br /><p></p><br />
<br />
Lo siguiente es validar que todo funciona de forma correcta, para eso crearemos una serie de pruebas unitarias. En la primera verificamos que la suma de los números del 1 al 10 nos regrese un valor de 30. Esto es, tendremos los siguientes datos de entrada:
<pre class="brush: plain;">1, 2, 3, 4, 5, 6, 7, 8, 9, 10
</pre><br />
Filtraremos los números pares, por lo que el arreglo final será:
<pre class="brush: plain;">2, 4, 6, 8, 10
</pre><br />
Y si hacemos la suma de los números anteriores obtendremos:
<pre class="brush: java;">30;
</pre><br />
Por lo que la primera prueba unitaria queda de la siguiente forma:
<pre class="brush: java;" title="ConversorDatosParesTest.java">@Test
public void testConvierteDatos_cuandoValorMenorA100() {
ConversorDatos conversorDatos = new ConversorDatosPares();
assertThat(conversorDatos.convierte(new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})).isEqualTo(30);
}
</pre><br />
Si ejecutas la prueba anterior debes obtener como resultado que la prueba pasa correctamente.<br />
<br />
En la segunda prueba obtendremos la suma de los números pares del 2 al 20; esto es, tenemos los siguientes datos de entrada:
<pre class="brush: plain;">2, 4, 6, 8, 10, 12, 14, 16, 18, 20
</pre><br />
En donde todos los números son pares. Si realizamos la suma obtendremos como resultado el valor de 110:
<pre class="brush: java;" title="ConversorDatosParesTest.java">@Test
public void testConvierteDatos_cuandoValorMayorA100() {
ConversorDatos conversorDatos = new ConversorDatosPares();
assertThat(conversorDatos.convierte(new int[]{2, 4, 6, 8, 10, 12, 14, 16, 18, 20})).isEqualTo(110);
}
</pre><br />
Si ejecutas la prueba, esta también debe ejecutarse de forma correcta, por lo que podemos comprobar que nuestra implementación funciona correctamente.<br />
<span class="nota">🏆 Buena práctica: En este caso solo he creado dos pruebas unitarias que verifican el template method, <span class="clase">convierte</span>, esto para ahorrar un poco de espacio y porque el tutorial no está centrado en pruebas sino en el patrón de diseño. Lo mejor sería crear, además de estas dos pruebas unitarias, otras que validen cada uno de los pasos del algoritmo, de esta forma si hacemos alguna refactorización o cambio en cualquiera de los pasos del algoritmo podremos validar que cada uno sigue funcionando de forma adecuada o de lo contrario podremos saber exactamente en donde está el problema.</span><br />
El patrón tiene un punto de extensión muy interesante que permite agregar algunos métodos adicionales que permiten informar de ciertos eventos de interés dentro del algoritmo y que su implementación es opcional para las subclases. A estos métodos especiales se les conoce como <span class="codigo">hooks</span>, y normalmente se colocan <span class="negritas">antes</span> y <span class="negritas">después</span> de los pasos cruciales del algoritmo. En la clase base se proporciona una implementación vacía de estos <span class="codigo">hooks</span>, con lo que se logra que el invocarlos no tenga ningún efecto, pero se permite que las subclases los implementen.<br />
<br />
Existe una convención de nombres que indica que los nombres de los <span class="codigo">hooks</span> a los que se informan <span class="negritas">antes</span> de que ocurra el evento de interés deben tener el prefijo <span class="codigo">pre</span> y a los que se les informa <span class="negritas">después</span> del evento deben tener el prefijo <span class="negritas">post</span>. Por ejemplo, si el evento de interés es la ejecución del método <span class="clase">convierte</span>, debemos tener dos <span class="codigo">hooks</span>:
<pre class="brush: java;"> preConvierte()
</pre>
y
<pre class="brush: java;"> postConverte()
</pre><br />
y estos se invocarían al inicio y al final de la ejecución del método <span class="clase">convierte</span>:
<pre class="brush: java; highlight: [3, 17]" title="ConversorDatos.java">public final int convierte(int[] numeros) {
preConvierte();
CanalDatos canalDatos = conecta(numeros);
Dato datoEntrada = selecciona(canalDatos);
Dato datoProcesado = procesa(datoEntrada);
if(isNotificacionNecesaria(datoProcesado)){
Notificacion notificacion = notifica(datoProcesado);
}
desconecta(canalDatos);
postConvierte();
return datoProcesado.getDatoProcesado();
}
</pre><br />
Como mencioné antes: en la clase base proporcionamos una implementación <span class="codigo">vacía</span> y, opcionalmente, en la clase concreta podemos sobreescribir estos métodos con una implementación adecuada.<br />
<br />
No es necesario que los métodos reciban parámetros, pero podemos agregarlos si es que lo consideremos importante. Digamos que en <span class="clase">preConvierte</span> queremos pasar como parámetro el arreglo de enteros que recibimos, y en <span class="clase">postConvierte</span> el resultado de la operación de conversión. Estos dos métodos quedarían de la siguiente forma:
<pre class="brush: java;" title="ConversorDatos.java">protected void preConvierte(int[] numeros) {
}
protected void postConvierte(int datoProcesado) {
}
</pre><br />
y los invocaríamos de la siguiente forma:
<pre class="brush: java; highlight: [3, 17]" title="ConversorDatos.java">public final int convierte(int[] numeros) {
preConvierte(numeros);
CanalDatos canalDatos = conecta(numeros);
Dato datoEntrada = selecciona(canalDatos);
Dato datoProcesado = procesa(datoEntrada);
if(isNotificacionNecesaria(datoProcesado)){
Notificacion notificacion = notifica(datoProcesado);
}
desconecta(canalDatos);
postConvierte(datoProcesado.getDatoProcesado());
return datoProcesado.getDatoProcesado();
}
</pre><br />
De forma parecida, si quisiéramos informar antes de la ejecución de alguna de las operaciones primitivas, podemos proporcionar un par de <span class="codigo">hooks</span> de la siguiente forma:
<pre class="brush: java;" title="ConversorDatos.java">preProcesa(datoEntrada);
Dato datoProcesado = procesa(datoEntrada);
postProcesa(datoProcesado);
</pre><br />
¿Debemos proporcionar siempre dos <span class="codigo">hooks</span>, un <span class="codigo">pre</span> y un <span class="codigo">post</span>? Aunque esto es lo normal no es estrictamente necesario. Como siempre digo, solo hazlo si tiene sentido para la implementación particular que estás realizando.<br />
<br />
<br /><h2 class="titulo"><a class="anchorTitle" href="#ventajasydesventajas" name="ventajasydesventajas">
<svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z" fill-rule="evenodd"></path></svg>
Ventajas y Desventajas</a></h2>
<ul>
<li>Abstrae las partes invariantes de un algoritmo y delega a las subclases el que proporcionen los detalles concretos del mismo.</li>
<li>Ayuda a reducir la duplicidad de código.</li>
<li>Tiene puntos de extensión y notificación, conocidos como hooks, que pueden ser implementados en caso de ser necesario.</li>
<li>Solo se pude tener un algoritmo por template method, si se tienen dos algoritmos más o menos parecidos y se quieren poder utilizar a partir de l a misma clase base, es necesario comenzar a agregar más y más condiciones.</li>
<li>Una vez que la estructura del algoritmo está establecido y tenemos algunas subclases funcionando es muy complicado modificar el algoritmo, ya que cualquier cambio nos obligaría a modificar todas las subclases.</li>
</ul><br />
<br /><h2 class="titulo"><a class="anchorTitle" href="#otrosnombres" name="otrosnombres">
<svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z" fill-rule="evenodd"></path></svg>
Otros Nombres</a></h2>
<ul>
<li>Template Pattern</li>
<li>Template</li>
</ul><br />
<br /><h2 class="titulo"><a class="anchorTitle" href="#conclusion" name="conclusion">
<svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z" fill-rule="evenodd"></path></svg>
Conclusión</a></h2>
Este es un patrón de diseño muy simple en concepto e implementación. Se usa mucho especialmente en frameworks y librerías o para controlar el ciclo de vida de elementos de nuestra aplicación.<br />
<br />
El corazón del algoritmo es definir el template method y asegurarse de que:
<ol>
<li>el esqueleto del algoritmo no pueda ser modificado por las subclases, y </li>
<li>proporcionar puntos de extensión a las subclases.</li>
</ol><br />
Como nota adicional, en el repositorio dejo, además del código, el archivo original en draw.io con los esquemas de los patrones.<br />
<br />
Espero que este tutorial les sea de utilidad. Si tienen alguna duda, sugerencia, comentario o aclaración, pueden dejarla en la sección de comentarios o enviar un correo a <a href="mailto:programadorjavablog@gmail.com">programadorjavablog@gmail.com</a> (pueden agregarme al chat de gmail). También puedes seguir JavaTutoriales en las siguientes redes sociales:
<ul>
<li><a href="https://www.facebook.com/JavaTutoriales/" target="_blank">Facebook</a></li>
<li><a href="https://twitter.com/JavaTutoriales" target="_blank">Twitter</a></li>
</ul>
Saludos y gracias.<br /><br />
<span class="negritas">Descarga los archivos de este tutorial desde mi repositorio en GitHub:</span>
<ul>
<li><a href="https://github.com/JavaTutoriales/TemplateMethod" target="_blank">Patrón de diseño Template Method</a>.</li>
</ul>
<br />
<span class="negritas">Entradas relacionadas:</span>
<ul>
<li><a href="https://www.javatutoriales.com/2021/11/introduccion-los-patrones-de-diseno.html" target="_blank">Introducción a los patrones de diseño</a>.</li>
<li><a href="https://www.javatutoriales.com/2021/12/patron-de-diseno-factory-method.html" target="_blank">Patrón de diseño Factory Method</a>.</li>
<li><a href="https://www.javatutoriales.com/2021/12/patron-de-diseno-abstract-factory.html" target="_blank">Patrón de diseño Abstract Factory</a>.</li>
<li><a href="https://www.javatutoriales.com/2021/12/diferencias-simple-factory-vs-factory.html" target="_blank">Diferencias: Simple Factory vs. Factory Method vs. Abstrac Factory</a>.</li>
</ul>
<br />
<br />
</div>Alexhttp://www.blogger.com/profile/06974037481671868076noreply@blogger.comtag:blogger.com,1999:blog-2012232069289957092.post-58375514993530259862021-12-25T18:30:00.008-08:002022-01-07T23:43:11.213-08:00Diferencias: Simple Factory vs. Factory Method vs. Abstrac Factory<div style="text-align: justify;">
<a href="https://www.javatutoriales.com/search/label/patrones%20de%20dise%C3%B1o" target="_blank"><img src="https://img.shields.io/badge/JT-Design%20Patterns-orange" /></a><br />
<br />
En los tutoriales anteriores vimos tres patrones de diseño que son muy parecidos en nombre y concepto: <span class="codigo">Simple Factory</span> (conocida también como <span class="codigo">Factory</span>, a secas), <span class="codigo">Factory Method </span>y <span class="codigo">Abstract Factory</span>. La finalidad de los tres patrones es la misma: <span class="negritas">encapsular la instanciación de objetos</span>. Sin embargo, cada uno de los patrones lo hace de una forma diferente dependiendo del problema que estemos tratando de resolver.<br />
<br />
Al tener nombres tan parecidos, estos tres patrones generan mucha confusión entre los desarrolladores.<br />
<br />
En este tutorial te explico las diferencias entre estos tres patrones y te doy algunas recomendaciones de cuándo debes usar uno u otro.<br />
<br />
<a name='more'></a>
Si no conoces ninguno de los tres patrones que menciono en la introducción, es importante que primero leas las definiciones en los artículos respectivos. Hablo de <a href="https://www.javatutoriales.com/2021/12/patron-de-diseno-factory-method.html" target="_blank">Factory y Factory Method</a> en un tutorial, y de <a href="https://www.javatutoriales.com/2021/12/patron-de-diseno-abstract-factory.html" target="_blank">Abstract Factory</a> en otro tutorial.<br />
<br />
Este será un tutorial muy corto ya que solo me enfocaré en las diferencias entre estos patrones. Comencemos. <br />
<br />
<br /><h3 class="subtitulo"><a class="anchorSubtitle" href="#comun" name="comun">
<svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z" fill-rule="evenodd"></path></svg>
¿Qué tienen en común estos patrones?</a></h3>
<ol>
<li>Se clasifican como <span class="negritas">patrones creacionales</span>; o sea, que su objetivo es crear un objeto y regresarlo a la clase que los invoca. </li>
<li>Ocultan la lógica de la instanciación e inicialización. Es a esto a lo que me refiero cuando digo que encapsulan los detalles de la instanciación de objetos.</li>
<li>Todos tienen una clase "Factory" responsable de crear estos nuevos objetos, ya sea por ella misma o delegando esta tarea a una subclase concreta.</li>
</ol><br />
<br /><h3 class="subtitulo"><a class="anchorSubtitle" href="#diferencias" name="diferencias">
<svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z" fill-rule="evenodd"></path></svg>
¿Cuáles son las diferencias?</a></h3>
<table>
<thead>
<tr><th><span class="codigo">Simple Factory</span></th><th><span class="codigo">Factory Method</span></th><th><span class="codigo">Abstract Factory</span></th></tr>
</thead>
<tbody>
<tr>
<td>Una clase es responsable de crear objetos que extienden de la misma clase base.</td>
<td>Define una clase abstracta o interface para que sus subclases creen objetos que extienden de la misma clase base.</td>
<td>Define una clase abstracta o interface para que sus subclases creen objetos que pertenecen a una familia de diferentes objetos relacionados entre sí.</td>
</tr>
<tr>
<td>Es una clase plana que no implementa ninguna interface ni extiende de ninguna clase abstracta.</td>
<td>Los objetos específicos son creados por una o más clases concretas que extienden de la clase abstracta.</td>
<td>Es una fábrica de fábricas, los objetos específicos son creados por una o más clases concretas que extienden de la clase abstracta.</td>
</tr>
<tr>
<td>La fábrica decide directamente qué objeto concreto crear basado en los parámetros que recibe.</td>
<td>La fábrica delega el trabajo de la creación del objeto concreto a una de sus subclases, la cual puede o no hacer uso de un parámetro.</td>
<td>Cada fábrica se especializa en crear un tipo de objeto.</td>
</tr>
<tr>
<td>Una sola fábrica generalizada es capaz de producir uno o más tipo de objetos.</td>
<td>Cada fábrica concreta se especializa en crear solo un tipo concreto de objeto.</td>
<td>Una fábrica generalizada contiene una o más fábricas especializadas donde cada una produce un tipo concreto de objeto.</td>
</tr>
<tr>
<td><span class="negritas">Caso de uso</span>: Crear un objeto para un propósito específico, sin que el tipo concreto del objeto importe y en donde solo hay una forma de crear e inicializar los objetos.</td>
<td><span class="negritas">Caso de uso</span>: Crear un objeto que tiene muchas dependencias o configuración que debe hacerse al inicializarlo, o en donde hay diferentes formas o algoritmos para crear le objeto.</td>
<td><span class="negritas">Caso de uso</span>: Crear múltiples objetos de distintos tipos pero que tienen una relación o dependencia con otros; esto es, crear una familia de objetos.</td>
</tr>
</tbody>
</table><br />
Los diagramas de los patrones también nos pueden ayudar mejor a entender las diferencias entre ellos: </div><div style="text-align: justify;"> </div><div style="text-align: justify;"><span class="codigo">Simple Factory</span>:</div><div style="text-align: justify;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEjZW9GPhBM5HrwtsPbsdkdyIXgFiOlT76UISvU9OCJT7iicC9g0DGMQYfI76E8yLrf4C_3bGApBz0JR2DF8Ibw9LqgctobmprkUXIDkaNXoGEPbnxA6hvnvU-KaNNsrd2dCyjt7OdJLCouq0zUfBIPhtUrROJOBKMHg5K53IoaDV2uOBraF25vkm9jI4g=s762" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="552" data-original-width="762" height="290" src="https://blogger.googleusercontent.com/img/a/AVvXsEjZW9GPhBM5HrwtsPbsdkdyIXgFiOlT76UISvU9OCJT7iicC9g0DGMQYfI76E8yLrf4C_3bGApBz0JR2DF8Ibw9LqgctobmprkUXIDkaNXoGEPbnxA6hvnvU-KaNNsrd2dCyjt7OdJLCouq0zUfBIPhtUrROJOBKMHg5K53IoaDV2uOBraF25vkm9jI4g=w400-h290" width="400" /></a></div> </div><div style="text-align: justify;"> </div><div style="text-align: justify;"> </div>
<div style="text-align: justify;"><span class="codigo">Factory Method</span>:</div><div style="text-align: justify;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEi94Bvg1JAH2xYtgJZImdoqzHLCdUKgCO4UpgrqSbx4OEKWLRUVp3DbkrZ7V5vamWS0n01yAwGHuloaitDiyBLJqy8xOALFHOZWMwPLtN06xdW8zfIq37nohhQvLE8Y9Gf6x5dsOR44pCeBKbZHkBYEMrNcTxCslGugZDeagARthSdsBG1d51EXDSfPww=s944" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="552" data-original-width="944" height="234" src="https://blogger.googleusercontent.com/img/a/AVvXsEi94Bvg1JAH2xYtgJZImdoqzHLCdUKgCO4UpgrqSbx4OEKWLRUVp3DbkrZ7V5vamWS0n01yAwGHuloaitDiyBLJqy8xOALFHOZWMwPLtN06xdW8zfIq37nohhQvLE8Y9Gf6x5dsOR44pCeBKbZHkBYEMrNcTxCslGugZDeagARthSdsBG1d51EXDSfPww=w400-h234" width="400" /></a></div> </div><div style="text-align: justify;"> </div><div style="text-align: justify;"> </div>
<div style="text-align: justify;"><span class="codigo">Abstract Factory</span>: <br /></div><div style="text-align: justify;"> </div><div style="text-align: justify;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEhe6MRKAszL1p_ykrHKAKxSTgWJlcldQ5N3pOEvifWNfsYXr4tmTE_R8ZVBYLmIXSXR0Mh5W0mo4Co3lFDIq5dl7qdcoyR1lt_pix1HXog0H-MTFU-dvejIkvE7wvxKypQwOaEAkncaJXJobkyJ0yD-rvzbUwyF2EJJSXv4AKAWO3zJcHEoESOf0khp6Q=s1215" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1052" data-original-width="1215" height="554" src="https://blogger.googleusercontent.com/img/a/AVvXsEhe6MRKAszL1p_ykrHKAKxSTgWJlcldQ5N3pOEvifWNfsYXr4tmTE_R8ZVBYLmIXSXR0Mh5W0mo4Co3lFDIq5dl7qdcoyR1lt_pix1HXog0H-MTFU-dvejIkvE7wvxKypQwOaEAkncaJXJobkyJ0yD-rvzbUwyF2EJJSXv4AKAWO3zJcHEoESOf0khp6Q=w640-h554" width="640" /></a></div><br /><br /><br /> </div><div style="text-align: justify;"></div><div style="text-align: justify;"></div><div style="text-align: justify;"></div><div style="text-align: justify;"></div><div style="text-align: justify;"></div><div style="text-align: justify;"></div><div style="text-align: justify;"><br /><h2 class="titulo"><a class="anchorTitle" href="#conclusion" name="conclusion">
<svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z" fill-rule="evenodd"></path></svg>
Conclusión</a></h2>
Como puedes ver, <span class="codigo">Simple Factory</span> es solo una clase y esta se encarga de decidir qué objeto concreto se creará.<br />
<br />
En <span class="codigo">Factory Method</span> tenemos una clase o interface base para la fábrica y tenemos varias implementaciones de la misma. Cada implementación tiene la responsabilidad de crear los objetos. El tener una clase base para la fábrica nos brinda la flexibilidad para cambiar la fábrica concreta que usamos en cada momento, así podemos tener varias formas de crear los mismos objetos usando una lógica que puede ser diferente. Esto último no es obligatorio, podríamos en nuestra aplicación siempre tener una única fábrica concreta.<br />
<br />
En <span class="codigo">Abstract Factory</span>, a diferencia de los dos patrones anteriores, el objetivo no es crear solo un objeto sino un conjunto o una familia de objetos (objetos que tienen una relación o dependencia entre ellos), por lo que tenemos una fábrica abstracta y varias fábricas concretas. Cada fábrica concreta se encarga de crear los distintos objetos de la familia.<br />
<br />
Como vemos, los distintos niveles de abstracción van aumentando en cada una de las tres fábricas anteriores. Toda la explicación he intentado hacerla yendo de la implementación y el concepto más simple hasta el más complicado.<br />
<br />
Recuerda que las tres fábricas son patrones de diseño diferentes y que cada una puede, además, tener nombres diferentes. En los respectivos tutoriales puedes ver más información sobre esto.<br />
<br />
Y listo, eso es todo. Este fue un tutorial muy corto solo para mostrar las diferencias entre cada uno de estos tres patrones que siempre generan confusión.<br />
<br />
Espero que este tutorial les sea de utilidad. Si tienen alguna duda, sugerencia, comentario o aclaración, pueden dejarla en la sección de comentarios o enviar un correo a <a href="mailto:programadorjavablog@gmail.com">programadorjavablog@gmail.com</a> (pueden agregarme al chat de gmail). También pueden seguir JavaTutoriales en las siguientes redes sociales:
<ul>
<li><a href="https://www.facebook.com/JavaTutoriales/" target="_blank">Facebook</a></li>
<li><a href="https://twitter.com/JavaTutoriales" target="_blank">Twitter</a></li>
</ul>
Saludos y gracias.<br /><br />
<span class="negritas">Entradas relacionadas:</span>
<ul>
<li><a href="https://www.javatutoriales.com/2021/11/introduccion-los-patrones-de-diseno.html" target="_blank">Introducción a los patrones de diseño</a>.</li>
<li><a href="https://www.javatutoriales.com/2021/12/patron-de-diseno-factory-method.html" target="_blank">Patrón de diseño Factory Method</a>.</li>
<li><a href="https://www.javatutoriales.com/2021/12/patron-de-diseno-abstract-factory.html" target="_blank">Patrón de diseño Abstract Factory</a>.</li>
</ul>
<br />
<br />
<br />
</div>Alexhttp://www.blogger.com/profile/06974037481671868076noreply@blogger.comtag:blogger.com,1999:blog-2012232069289957092.post-15625695653840448872021-12-23T02:19:00.006-08:002022-01-07T23:42:28.253-08:00Patrón de Diseño Abstract Factory<div style="text-align: justify;">
<a href="https://www.javatutoriales.com/search/label/patrones%20de%20dise%C3%B1o" target="_blank"><img src="https://img.shields.io/badge/JT-Design%20Patterns-orange" /></a><br />
<br />
<span class="codigo">Abstract Factory</span> es un <a href="https://www.javatutoriales.com/2021/11/introduccion-los-patrones-de-diseno.html" target="_blank">patrón de diseño</a> <span class="negritas">creacional</span> del ámbito de objetos. Este patrón ayuda a crear <span class="negritas">grupos o familias de objetos</span> que, aunque en esencia son diferentes, tienen una relación de herencia o dependencia entre ellos a niveles de padres e hijos, pero también a nivel de hermanos. De esta forma ayudan a que la construcción de esa familia de objetos mantenga una lógica y coherencia, manteniendo al mismo tiempo un alto nivel de abstracción y bajo acoplamiento a través del polimorfismo.<br />
<br />
En algunas ocasiones este patrón es descrito como una <span class="negritas">fábrica de fábricas</span> lo cual, en parte, tiene razón, pero esta descripción se queda corta con respecto al potencial que ofrece.<br />
<br />
En este tutorial te explicaré más en detalle en qué consiste este patrón de diseño, así como una estrategia de implementación.<br />
<br />
<a name='more'></a>
La esencia de <span class="codigo">Abstract Factory</span> es similar a la de <span class="codigo"><a href="https://www.javatutoriales.com/2021/12/patron-de-diseno-factory-method.html" target="_blank">Factory Method</a></span> en el sentido de que lo que buscamos conseguir es abstraer la forma en la que creamos los objetos de cierto tipo sin preocuparnos de los detalles de cómo se crear e inicializan, pero también sin preocuparnos del tipo concreto de estos objetos en ejecución. <br />
<br />
La diferencia principal es que <span class="codigo">Factory Method</span> se encarga de la creación de objetos únicos; es decir, se crea un objeto de varias posibles clases candidatas. En el caso de <span class="codigo">Abstract Factory</span>, lo que queremos es crear familias de objetos que mantengan una relación entre ellos.<br />
<br />
Veamos un ejemplo sencillo para entender el concepto anterior: <span class="negritas">Muebles</span>.<br />
<br />
Digamos que somos fabricantes de muebles y nos especializamos en hacer los siguientes tipos:
<ul>
<li>Mesas</li>
<li>Sillas</li>
<li>Sofás</li>
<li>Escritorios</li>
</ul><br />
Al inicio solo nos dedicábamos a estos tipos de muebles usando un solo estilo: muebles de madera de color natural, sin adornos ni accesorios adicionales. Cuando un cliente llega a nuestra tienda selecciona qué tipo de mueble quiere y compra una unidad de este mueble. Para la construcción del mueble nos basta con una <span class="codigo">Factory Method</span> que recibe como parámetro el tipo de mueble que el cliente quiere y regresamos un <span class="codigo">Mueble</span>. Una vez que tenemos esa instancia de <span class="codigo">Mueble</span>, la empaquetamos y se la enviamos al cliente. Tan simple como eso. Todos los muebles los tratamos de la misma forma sin importar el tipo de mueble, por lo que nuestra abstracción funciona bien.<br />
<br />
Como el negocio ha crecido y muchas personas han recomendado nuestros muebles de alta calidad, una gran empresa ha puesto sus ojos en nosotros y quiere comprar nuestra fábrica de muebles. La oferta que han hecho es bastante generosa pero solo nos ponen una condición: como sus clientes son normalmente dueños de enormes casas o mansiones, compran <span class="negritas">conjuntos de muebles del mismo estilo</span> para las diferentes secciones de sus casas, por lo que no están interesados en la venta de muebles individuales, sino en conjuntos completos (una colección o familia de muebles). Así que nos piden ampliar nuestra producción, manteniendo los tipos de muebles que hacemos (al que los genios de marketing han llamado la “línea classic”) pero nos piden que los fabriquemos también en diferentes estilos. <br />
<br />
Así que ahora tenemos que fabricar mesas, sillas, sofás y escritorios en estilo minimalista, rústico, y steampunk.<br />
<br />
Claro, no es posible mezclar muebles de diferentes estilos en un solo conjunto, así que nunca tendremos una mesa minimalista con sillas rústicas y un sofá steampunk. Sino que el cliente seleccionará un estilo y nosotros fabricaremos los muebles.<br />
<br />
Así que ahora nuestro <span class="codigo">Factory Method</span> no es suficiente ya que no nos permite seleccionar el estilo de mueble que queremos, ni asegurar que todos los muebles serán del mismo estilo (o familia). Por lo tanto, debemos agregar un nivel extra de abstracción para cumplir con todos los requerimientos.<br />
<br />
En este ejemplo, tenemos que cada uno de los estilos de muebles será creado con una fábrica diferente. Tendremos una especializada en muebles rústicos, otra en muebles minimalistas, otra en muebles steampunk, etc.<br />
<br />
Otro ejemplo clásico es el de la interfaz de usuario. Si vamos a proporcionar una interfaz en modo obscuro y otra en modo claro, todos los componentes visuales de cada uno de los modos deben tener una coherencia entre ellos (todos claros o todos obscuros). O si vamos a tener una interfaz tipo Windows y otra tipo Mac, todos los controles de la familia Windows serán iguales entre ellos y todos los de la familia Mac también serán iguales entre ellos, aunque entre familias serán diferentes.<br />
<br />
<br /><h2 class="titulo"><a class="anchorTitle" href="#objetivo" name="objetivo">
<svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z" fill-rule="evenodd"></path></svg>
Objetivo o intención del patrón</a></h2>
Con la explicación anterior podemos entender que lo que buscamos lograr con este patrón es:<br />
<ul>
<li>Tener una interface para crear una familia de objetos relacionados o dependientes, sin especificar sus clases de forma explícita.</li>
</ul>
En el ejemplo anterior, las clases que buscamos crear son <span class="clase">Silla</span>, <span class="clase">Mesa</span>, <span class="clase">Sillón</span>, etc. Y las clases concretas son <span class="clase">SillaMinimalista</span>, <span class="clase">SillaRustica</span>, <span class="clase">SillaSteampunk</span>, etc. <br />
<br />
Esto ayudará a que cuando tengamos que integrar un nuevo estilo de muebles lo podamos hacer sin necesidad de modificar ninguno de los estilos que ya tenemos.<br />
<br />
<br /><h2 class="titulo"><a class="anchorTitle" href="#implementacion" name="implementacion">
<svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z" fill-rule="evenodd"></path></svg>
Implementación</a></h2>
Antes de ver los detalles en código, un poco de teoría de cómo implementar el patrón.<br />
<ol>
<li><span class="negritas">Crear una familia de clases base</span>. Estas serán las clases que representen los tipos regresados por la fábrica. Pueden ser clases abstractas o interfaces. En el ejemplo de los muebles, la familia de clases base serían <span class="clase">Silla</span>, <span class="clase">Mesa</span>, <span class="clase">Escritorio</span>, etc.</li>
<li><span class="negritas">Crear una serie de clases hija que extiendan (o implementen) la clase base</span>. Debe haber una implementación por cada fábrica que tengamos. Estas clases serán los objetos creados por nuestras fábricas. Así que, si tenemos 5 tipos de muebles y 2 estilos, tendremos 10 clases hija concretas.</li>
<li><span class="negritas">Crear una clase base abstracta Factory</span>. Esta puede ser una clase abstracta o una interface, con un método que regrese una instancia de cada uno de los tipos de la familia de clases. Eso quiere decir que habrá un método para obtener una <span class="clase">Silla</span>, otro para una <span class="clase">Mesa</span>, otro para un <span class="clase">Escritorio</span>, etc. Hay que mencionar que la fábrica abstracta solo declara la interface para la creación de los productos, y son las fábricas concretas las responsables de la creación de los objetos concretos.</li>
<li><span class="negritas">Crear una o más implementaciones de la clase Abstract Factory</span>. Estas implementaciones serán quienes regresen los objetos concretos creados por la fábrica. Las clases concretas se encargarán de regresar los objetos de la familia.</li>
<li><span class="negritas">Crear una clase que se encargue de decidir qué instancia particular de la Abstract Factory se usará</span>. A esta clase la conocemos como <span class="codigo">Factory Maker</span> o <span class="codigo">Factory Producer</span>. Esta clase puede recibir parámetros si es necesario para que decida cuál es la instancia que se debe regresar.</li>
<li><span class="negritas">Crear los objetos se regresarán al cliente</span>. Las instancias pueden ser creada de inmediato, tomada de un pool de objetos, u obtenido de cualquier otra forma que sea necesaria.
</li><li>Regresar al cliente las instancias como referencias del tipo de la clase base.</li>
</ol><br />
El paso 5 es opcional, pero facilita mucho el seleccionar estas fábricas concretas.<br />
<br />
<br /><h2 class="titulo"><a class="anchorTitle" href="#diagrama" name="diagrama"><svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z" fill-rule="evenodd"></path></svg>
Diagrama</a></h2>
Este es el diagrama general del patrón:<br />
<br />
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEgJ_GkBLc-oUPBJvVaGCua2TAcIAepC9CcbQx3GoWEsiDDPmChWiFmrzqHwXO-3bSvKqo3G-zz9gN4nZCscXYbl-jvBbtK0xIiAHVUDmMRVh0FvSO1OxkdDzsUIvmCrk-m8b2qHgyqJ-UuCCo6MQhULHaopSWxng13ZrWL6oKqAewAqdNvuVKMpzZh3DQ=s1215" style="display: block; padding: 1em 0px; text-align: center;"><img alt="" border="0" data-original-height="1052" data-original-width="1215" src="https://blogger.googleusercontent.com/img/a/AVvXsEgJ_GkBLc-oUPBJvVaGCua2TAcIAepC9CcbQx3GoWEsiDDPmChWiFmrzqHwXO-3bSvKqo3G-zz9gN4nZCscXYbl-jvBbtK0xIiAHVUDmMRVh0FvSO1OxkdDzsUIvmCrk-m8b2qHgyqJ-UuCCo6MQhULHaopSWxng13ZrWL6oKqAewAqdNvuVKMpzZh3DQ=s600" width="600" /></a></div><br />
<br />
Como puedes ver es muy directo, nuestro <span class="clase">Cliente</span> usa objetos de tipo <span class="clase">ClaseBaseA</span> y <span class="clase">ClaseBaseB</span>. Cada una de las fábricas se encarga de crear los objetos concretos; en el diagrama la <span class="clase">FabricaConcreta1</span> crea objetos de tipo <span class="clase">ClaseHijaA1</span> y <span class="clase">ClaseHijaB1</span> y la <span class="clase">FabricaConcreta2</span> creo objetos de tipo <span class="clase">ClaseHijaA2</span> y <span class="clase">ClaseHijaB2</span>.<br />
<br />
El Cliente obtiene la instancia correspondiente de la <span class="codigo">AbstractFactory </span>a través del <span class="clase">FactoryMaker</span>.<br />
<br />
Para el ejemplo de los Muebles, el diagrama es:<br />
<br />
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEgecgj_WCGcV-Qortt8y2EbnGdaxN54gLRWTjSKJerj91HFB9uLmGPTOZoUFaBfJ_EkdIXJW-Lzc6eRo5fb8zDVzfLw-U1JooO3vqayxUpRNU0UIi8whU55w_hCa8GkZ1RFUwORMipTXheFl-brA5dcplYTL-FmdwK6dDRaLv6GXf90E_W8GO1-THr2qQ=s2122" style="display: block; padding: 1em 0px; text-align: center;"><img alt="" border="0" data-original-height="2122" data-original-width="1533" height="600" src="https://blogger.googleusercontent.com/img/a/AVvXsEgecgj_WCGcV-Qortt8y2EbnGdaxN54gLRWTjSKJerj91HFB9uLmGPTOZoUFaBfJ_EkdIXJW-Lzc6eRo5fb8zDVzfLw-U1JooO3vqayxUpRNU0UIi8whU55w_hCa8GkZ1RFUwORMipTXheFl-brA5dcplYTL-FmdwK6dDRaLv6GXf90E_W8GO1-THr2qQ=s600" /></a></div><br />
<br />
Cada una de las fábricas tiene métodos para cada una de las clases concretas.<br />
<br />
Este patrón no tiene ninguna variación real. Existe una modificación que en algunos lugares podrás encontrar como Abstract Factory (cuando en realidad no lo es), y que se representa con el siguiente diagrama.<br />
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEgI4SfIEA-v9QaqiQbDeFbJ1AX4TY1I5newgwV2uvtZIUSZtJNbK0wZKiAFMl2qct7g4p3Qa4CV1LH_N9kEXKcFn3unAzN65BuAPcEmeO1QWHIEOCeioatLYeZpzyrFk-ZyDRMPzp1L2uyCB6eoIZS5kSgvEypKH-hrMt3AC7Eme4IC4ArsN9hrwymCUA=s560" style="display: block; padding: 1em 0px; text-align: center;"><img alt="" border="0" data-original-height="437" data-original-width="560" src="https://blogger.googleusercontent.com/img/a/AVvXsEgI4SfIEA-v9QaqiQbDeFbJ1AX4TY1I5newgwV2uvtZIUSZtJNbK0wZKiAFMl2qct7g4p3Qa4CV1LH_N9kEXKcFn3unAzN65BuAPcEmeO1QWHIEOCeioatLYeZpzyrFk-ZyDRMPzp1L2uyCB6eoIZS5kSgvEypKH-hrMt3AC7Eme4IC4ArsN9hrwymCUA=s600" width="600" /></a></div><br />
<br />
¿Por qué esta no es una variación correcta del patrón? Si observas, <span class="clase">ShapeFactory</span> y <span class="clase">ColorFactory</span> son las dos fábricas concretas que extienden de la <span class="clase">AbstractFactory</span> y cada una se encarga de crear elementos de una familia de clases; hasta aquí todo bien. Sin embargo, en realidad ninguna implementa todos los métodos de la <span class="clase">AbstractFactory</span>, sino que implementan el método correspondiente a su familia de clases, <span class="clase">ShapeFactory</span> implementa <span class="clase">getShape</span> y <span class="clase">ColorFactory</span> implementa <span class="clase">getColor</span>. Esto ocurre así porque no tiene sentido que <span class="clase">ShapeFactory</span> implemente <span class="clase">getColor</span> y viceversa. Por lo tanto, en realidad no es una forma correcta de usar <span class="codigo">Abstract Factory</span>.<br />
<br />
El diagrama anterior tendría más sentido usando una relación de composición y un par de <span class="codigo">Factory Methods</span>.<br />
<br />
¿Dónde se usa <span class="codigo">Abstract Factory</span>? Dentro del JDK podemos verlo en <span class="codigo"><a href="https://github.com/AdoptOpenJDK/openjdk-jdk11/blob/master/src/java.xml/share/classes/javax/xml/parsers/DocumentBuilderFactory.java" target="_blank">DocumentBuilderFactory</a></span>. El método <span class="clase"><a href="https://github.com/AdoptOpenJDK/openjdk-jdk11/blob/master/src/java.xml/share/classes/javax/xml/parsers/DocumentBuilderFactory.java#L139" target="_blank">newInstance()</a></span> busca y regresa la instancia apropiada de una subclase concreta que extienda <span class="clase">DocumentBuilderFactory</span>.<br />
<br />
En este caso <span class="clase">DocumentBuilderFactory</span> funciona, además de como la <span class="codigo">AbstractFactory</span>, como el <span class="codigo">FactoryMaker</span> ya que ella misma se encarga de encontrar la instancia que regresará en lugar de delegarlo a una clase externa.<br />
<br />
<span class="nota">📌Muchas de las clases del JDK y de frameworks populares como Spring hacen esto mismo fusionando estos dos elementos en uno solo y usando reflexión para decidir en tiempo de ejecución qué instancia concreta crearán y regresarán.</span><br />
<br />
<br /><h2 class="titulo"><a class="anchorTitle" href="#ejemplo" name="ejemplo"><svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z" fill-rule="evenodd"></path></svg>
Ejemplo</a></h2>
En el ejemplo no usaremos los muebles sino algo más interesante. Antes de saltar a la explicación unos detalles técnicos sobre el proyecto.<br />
<br />
Para los ejemplos uso Java 17 y Gradle. No uso ninguna característica particular de esta versión de Java, por lo que debe funcionar en cualquier versión (incluso en la 5). También, uso <a href="https://www.javatutoriales.com/2019/03/lombok-escribiendo-menos-codigo-y.html" target="_blank">Lombok</a> para evitar escribir código repetitivo de forma innecesaria.<br />
<br />
Para validar el correcto funcionamiento de la aplicación usaré JUnit y AssertJ. Agrega las siguientes dependencias en el archivo <span class="codigo">build.gradle</span> (si usas Maven, coloca las dependencias correspondientes en el archivo <span class="codigo">pom.xml</span>)
<pre class="brush: groovy;" title="build.gradle">dependencies {
compileOnly 'org.projectlombok:lombok:1.18.22'
annotationProcessor 'org.projectlombok:lombok:1.18.22'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
testImplementation 'org.assertj:assertj-core:3.21.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
}
</pre>
Ahora sí, al código.<br />
<br />
El problema que resolveremos es el siguiente: Tenemos una aplicación la cual distribuiremos a diferentes clientes. Una de las ventajas de nuestra aplicación es que los clientes pueden decidir el tipo de base de datos que quieren usar. Esto quiere decir que para nuestra aplicación da igual si usan MongoDB o si usan PostgreSQL. <br />
<br />
Para que la lógica de nuestra aplicación no se preocupe de los detalles de la implementación del mecanismo de persistencia, hemos decidido que el acceso a datos se hará a través de una serie de clases abstractas <span class="codigo">Repository</span> (o <span class="codigo">DAO</span> si lo prefieres, pero para este ejemplo particular no importan los detalles sobre las diferencias entre estos dos). <br />
<br />
Tenemos tres tablas (o entidades) principales: <span class="codigo">Usuario</span>, <span class="codigo">Producto</span> y <span class="codigo">Compra</span>; así que tendremos un <span class="clase">UsuarioRepository</span>, un <span class="clase">ProductoRepository</span> y un <span class="clase">CompraRepository</span>. Estos repositorios representan nuestra familia de clases.<br />
<br />
Para no complicar la implementación de los repositorios (ya que el objetivo del tutorial es aprender sobre el patrón de diseño, y no sobre el trabajo con la capa de persistencia) solo agregaremos un método para guardar la "entidad" correspondiente (la cual representaremos como una String) y regresaremos también una String que contendrá un texto indicando que la instancia correspondiente se guardó. Por lo tanto, tenemos las siguientes tres interfaces:<br />
<pre class="brush: java;" title="CompraRepository.java">public interface CompraRepository {
String guardaCompra(String compra);
}
</pre>
<pre class="brush: java;" title="ProductoRepository.java">public interface ProductoRepository {
String guardaProducto(String producto);
}
</pre>
<pre class="brush: java;" title="UsuarioRepository.java">public interface UsuarioRepository {
String guardaUsuario(String usuario);
}
</pre>
<br />
En la primera implementación proporcionaremos la habilidad de seleccionar entre dos almacenes diferentes una base datos relacional y MongoDB. Por lo tanto, tendremos una fábrica abstracta que tendrá tres métodos, cada uno de los cuales regresará el Repository correspondiente:
<pre class="brush: java;" title="AbstractRepositoryFactory.java">public interface AbstractRepositoryFactory {
CompraRepository getCompraRepository();
ProductoRepository getProductoRepository();
UsuarioRepository getUsuarioRepository();
}
</pre><br />
Además, tendremos dos implementaciones de la <span class="codigo">Abstract Factory</span>, una para una base de datos relacional y otra para MongoDB:
<pre class="brush: java;" title="RelacionalRepositoryFactory.java">public class RelacionalRepositoryFactory implements AbstractRepositoryFactory {
@Override
public CompraRepository getCompraRepository() {
return new CompraRelacionalRepository();
}
@Override
public ProductoRepository getProductoRepository() {
return new ProductoRelacionalRepository();
}
@Override
public UsuarioRepository getUsuarioRepository() {
return new UsuarioRelacionalRepository();
}
}
</pre>
<pre class="brush: java;" title="MongoRespositoryFactory.java">public class MongoRespositoryFactory implements AbstractRepositoryFactory {
@Override
public CompraRepository getCompraRepository() {
return new CompraMongoRepository();
}
@Override
public ProductoRepository getProductoRepository() {
return new ProductoMongoRepository();
}
@Override
public UsuarioRepository getUsuarioRepository() {
return new UsuarioMongoRepository();
}
}
</pre>
<span class="nota">📌 Un punto importante que hay que mencionar, es que la implementación de las fábricas normalmente es muy sencilla y podemos mejorar su implementación si las creamos como <span class="codigo">Singleton</span>s o usando el patrón <span class="codigo">Prototype</span>.</span><br />
<br />
Lo siguiente es crear las implementaciones de estas interfaces. Tendremos dos por interfaz, una para la base relacional y para MongoDB, así que en total tendremos 6 clases concretas. Estas son las implementaciones para la base relacional
<pre class="brush: java;" title="CompraRelacionalRepository.java">public class CompraRelacionalRepository implements CompraRepository {
@Override
public String guardaCompra(String compra) {
return String.format("Guardando %s en una base de datos relacional", compra);
}
}
</pre>
<pre class="brush: java;" title="ProductoRelacionalRepository.java">public class ProductoRelacionalRepository implements ProductoRepository {
@Override
public String guardaProducto(String producto) {
return String.format("Guardando %s en una base de datos relacional", producto);
}
}
</pre>
<pre class="brush: java;" title="TarjetaBronce.java">public class UsuarioRelacionalRepository implements UsuarioRepository {
@Override
public String guardaUsuario(String usuario) {
return String.format("Guardando %s en una base de datos relacional", usuario);
}
}
</pre><br />
<br />
Y estas para la MongoDB.
<pre class="brush: java;" title="CompraMongoRepository.java">public class CompraMongoRepository implements CompraRepository {
@Override
public String guardaCompra(String compra) {
return String.format("Guardando %s en MongoDB", compra);
}
}
</pre>
<pre class="brush: java;" title="ProductoMongoRepository.java">public class ProductoMongoRepository implements ProductoRepository {
@Override
public String guardaProducto(String producto) {
return String.format("Guardando %s en MongoDB", producto);
}
}
</pre>
<pre class="brush: java;" title="UsuarioMongoRepository.java">public class UsuarioMongoRepository implements UsuarioRepository {
@Override
public String guardaUsuario(String usuario) {
return String.format("Guardando %s en MongoDB", usuario);
}
}
</pre><br />
<br />
Como puedes ver el código es muy sencillo. El valor de retorno de cada uno de los métodos nos ayudará a verificar que nuestra fábrica funciona de forma correcta cuando creemos nuestras pruebas unitarias.<br />
<br />
Lo siguiente es una pieza esencial para este patrón y que puede implementarse de dos formas. Me refiero, por supuesto, al <span class="codigo">FactoryMaker</span>. Esta es la clase que se encarga de seleccionar cuál fábrica concreta se regresará al cliente y desde la cual se crearán los repositorios correspondientes. La implementación de este también es muy sencilla. Usaremos un parámetro para indicar qué tipo de repositorio queremos: <span class="clase">RELACIONAL</span> o <span class="clase">MONGO_DB</span>. Colocaremos estos dos valores en una enumeración y usaremos una constante de esa enumeración para indicarle al <span class="clase">FactoryMaker</span> qué implementación debe regresar.<br />
<br />
Así que primero crearemos la enumeración correspondiente:<br />
<pre class="brush: java;" title="TarjetaBronce.java">public enum TipoRepositorio {
RELACIONAL,
MONGO_DB
}
</pre><br />
<br />
Y ahora el <span class="clase">FactoryMaker</span>, el cual tendrá un solo método estático que recibirá como parámetro una constante de tipo <span class="clase">TipoRepositorio</span> y regresará un <span class="clase">AbstractRepositoryFactory</span>. Usaremos como valor por default <span class="clase">RelacionalRepositoryFactory</span>. Por lo tanto, la implementación queda de la siguiente forma:<br />
<br />
<pre class="brush: java;" title="TarjetaBronce.java">public class RepositoryFactoryMaker {
public static AbstractRepositoryFactory getRepositoryFactory(TipoRepositorio tipo) {
if (TipoRepositorio.MONGO_DB.equals(tipo)) {
return new MongoRespositoryFactory();
}
return new RelacionalRepositoryFactory();
}
}
</pre><br />
<br />
Y listo, ya tenemos todos los elementos de nuestra aplicación.<br />
<br />
El diagrama de nuestra implementación se ve así:<br />
<br />
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEhreLbPVJ_ZnHp5aNh6konuO0HvsMq8FnwTW_uoSIHAuxfXoef4kWMBOkDi-k1tKfZwXywAY4OACzo4w3G1wgg3K5OAGybtubRqVPlmsACx0SUftrZiEEcpcoURj3pmq4sO4OPuR2vRcx14_Yr10bHt_h61sm70kCpMvl9v1RKkar3LfgtMsf0kzTEGUg=s1443" style="display: block; padding: 1em 0px; text-align: center;"><img alt="" border="0" data-original-height="1412" data-original-width="1443" src="https://blogger.googleusercontent.com/img/a/AVvXsEhreLbPVJ_ZnHp5aNh6konuO0HvsMq8FnwTW_uoSIHAuxfXoef4kWMBOkDi-k1tKfZwXywAY4OACzo4w3G1wgg3K5OAGybtubRqVPlmsACx0SUftrZiEEcpcoURj3pmq4sO4OPuR2vRcx14_Yr10bHt_h61sm70kCpMvl9v1RKkar3LfgtMsf0kzTEGUg=s600" width="600" /></a></div><br />
<br />
Como nota adicional, hace unos momentos comenté que el <span class="codigo">FactoryMaker</span> podíamos implementarlo de dos formas; la primera es la que acabamos de ver, como una clase <span class="clase">public</span> que podemos usar de forma independiente. En la segunda implementación podemos hacer algo parecido a lo que hace <span class="clase">DocumentBuilderFactory</span>, en donde esta misma clase es quien proporciona la clase concreta correspondiente. <span class="clase">DocumentBuilderFactory</span> hace uso de una clase, <span class="clase">FactoryFinder</span>, que es quien se encarga de obtener y regresar la clase concreta.<br />
<br />
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEh6sagjHcC3UN7Hzkzqbt4_fJmYdFAgDPC7utFumktCt5RuykI8aSD25OcHX-97aHS8UiE3jQO7aA206QFhlBdMSYDuu54W00PfbbAFEbYo8uqqjiKzfbEcrln0NleJ-1T1tkmWE_kEnbrZVCGHkLR1OPKV_YOUL6bhp3aDL_4leiStm0p0XDBXY1nlig" style="display: block; padding: 1em 0px; text-align: center;"><img alt="" border="0" data-original-height="398" data-original-width="630" src="https://blogger.googleusercontent.com/img/a/AVvXsEh6sagjHcC3UN7Hzkzqbt4_fJmYdFAgDPC7utFumktCt5RuykI8aSD25OcHX-97aHS8UiE3jQO7aA206QFhlBdMSYDuu54W00PfbbAFEbYo8uqqjiKzfbEcrln0NleJ-1T1tkmWE_kEnbrZVCGHkLR1OPKV_YOUL6bhp3aDL_4leiStm0p0XDBXY1nlig" /></a></div><br />
<br />
Siguiendo esta misma idea, <span class="clase">RepositoryFactoryMaker</span> podría ser una clase con visibilidad por default (sin modificador de acceso), con lo que se limitaría su uso al mismo paquete en el que se encuentra <span class="clase">AbstractFactoryRepository</span>, y esta última clase sería quién tuviera un método estático que internamente usara a <span class="clase">RepositoryFactoryMaker</span> para regresar la instancia correspondiente. Nuevamente, muy parecido a como lo hace <span class="clase">DocumentBuilderFactory</span>. Esta segunda implementación sería algo como esto:
<pre class="brush: java;" title="TarjetaBronce.java">public static AbstractRepositoryFactory(TipoRepositorio tipo){
return RepositoryFactoryMaker.getRepositoryFactory(tipo);
}
</pre><br />
<span class="nota">📌 Para que lo anterior funcione <span class="clase">AbstractFactoryRepository</span> tendría que ser una clase abstracta y no una interface, que es como yo la implemente. Esto porque las interfaces no pueden tener métodos estáticos.</span>
¿Cuál implementación es mejor? La que le haga más sentido a tu aplicación y la que mejor se acomode a tu forma de programar. No hay mucha diferencia entre ambas, así que no te preocupes mucho por esto.<br />
<br />
La implementación que acabamos de hacer de <span class="clase">FactoryMaker</span> tiene un punto débil que vale la pena mencionar: cada vez que creemos una nueva implementación de <span class="clase">AbstractFactoryRepository</span> debemos actualizar dos clases: <span class="clase">TipoRepository</span> (agregando una nueva constante) y la propia <span class="codigo">FactoryMaker</span> para agregar una nueva condición para regresar la implementación correspondiente de <span class="clase">AbstractFactoryRepository</span>.<br />
<br />
¿Cómo podemos evitar esto? Realmente las mejores implementaciones que he visto de <span class="codigo">FactoryMaker</span> hacen uno del mecanismo de reflección (<a href="https://www.oracle.com/technical-resources/articles/java/javareflection.html" target="_blank">reflection</a>) de Java para encontrar y crear la instancia adecuada. <br />
<br />
Si alguien está interesado en esto, podemos hacer un tutorial que hable más de este tema, mezclando reflection y anotaciones para una selección dinámica de las instancias y que no requiera de modificaciones al código de <span class="codigo">FactoryMaker</span>.<br />
<br />
Para terminar, validaremos el funcionamiento de la aplicación con un par de pruebas unitarias muy sencillas. En la primera solicitaremos repositorios para una base de datos relacional, y comprobaremos que los repositorios regresan el mensaje correspondiente al momento de invocar el método de guardado:<br />
<br />
<pre class="brush: java;" title="AbstractRepositoryFactoryTest.java">@Test
public void testRelacionalRepository_cuandoTipoEsRelacional() {
AbstractRepositoryFactory repositoryFactory = RepositoryFactoryMaker.getRepositoryFactory(TipoRepositorio.RELACIONAL);
CompraRepository compraRepository = repositoryFactory.getCompraRepository();
ProductoRepository productoRepository = repositoryFactory.getProductoRepository();
UsuarioRepository usuarioRepository = repositoryFactory.getUsuarioRepository();
assertThat(compraRepository.guardaCompra("compra sencilla")).isEqualTo("Guardando compra sencilla en una base de datos relacional");
assertThat(productoRepository.guardaProducto("producto elegante")).isEqualTo("Guardando producto elegante en una base de datos relacional");
assertThat(usuarioRepository.guardaUsuario("usuario importante")).isEqualTo("Guardando usuario importante en una base de datos relacional");
}
</pre><br />
Para la segunda validación haremos básicamente lo mismo, pero solicitaremos repositorios para MongoDB:<br />
<br />
<pre class="brush: java;" title="AbstractRepositoryFactory.java">@Test
public void testMongoRepository_cuandoTipoEsMongo() {
AbstractRepositoryFactory repositoryFactory = RepositoryFactoryMaker.getRepositoryFactory(TipoRepositorio.MONGO_DB);
CompraRepository compraRepository = repositoryFactory.getCompraRepository();
ProductoRepository productoRepository = repositoryFactory.getProductoRepository();
UsuarioRepository usuarioRepository = repositoryFactory.getUsuarioRepository();
assertThat(compraRepository.guardaCompra("compra sencilla")).isEqualTo("Guardando compra sencilla en MongoDB");
assertThat(productoRepository.guardaProducto("producto elegante")).isEqualTo("Guardando producto elegante en MongoDB");
assertThat(usuarioRepository.guardaUsuario("usuario importante")).isEqualTo("Guardando usuario importante en MongoDB");
}
</pre><br />
Y esto es todo; al momento de ejecutar las pruebas unitarias podemos ver que ambas se ejecutan de forma exitosa, y por lo tanto nuestra <span class="codigo">Abstract Factory</span> funciona de forma correcta.<br />
<br />
<br /><h2 class="titulo"><a class="anchorTitle" href="#ventajasydesventajas" name="ventajasydesventajas">
<svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z" fill-rule="evenodd"></path></svg>
Ventajas y Desventajas</a></h2>
<ol>
<li>➕ Aísla la creación la creación de los objetos y la oculta al cliente que necesita los objetos, dándole acceso a los mismos a través de una interface, lo que hace que manipularlos sea más fácil.</li>
<li>➕ Cambiar de una familia a otra es fácil, ya que solo hace falta cambiar la fábrica concreta que estamos usando.</li>
<li>➕ Si la fábrica está bien implementada, asegura que los objetos creados corresponden siempre a la misma familia.</li>
<li>➖ Agregar nuevos productos o clases derivadas a familias existentes es difícil, ya que hay que proporcionar una implementación para cada una de las fábricas. Esto muchas veces obliga a realizar cambios en la fábrica abstracta y en todas las implementaciones concretas. Además de que todas las familias deben tener una implementación de cada producto, aún si no hace mucho sentido para esa familiar en particular (¿qué pasa si uno de los estilos de nuestros muebles no requiere de un Sofá?)</li>
<li>➖ Aunado al punto anterior; se necesita mucho código para implementar la fábrica de forma correcta. Podemos ver en los diagramas del ejemplo de muebles, como al agregar un nuevo mueble o estilo nos obliga a generar muchas clases nuevas.</li>
</ol>
<br /><h2 class="titulo"><a class="anchorTitle" href="#otrosnombres" name="otrosnombres"><svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z" fill-rule="evenodd"></path></svg>
Otros nombres</a></h2>
<ol>
<li>Factory Kit o simplemente Kit</li>
</ol>
<br /><h2 class="titulo"><a class="anchorTitle" href="#conclusion" name="conclusion"><svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z" fill-rule="evenodd"></path></svg>
Conclusión</a></h2>
<span class="codigo">Abstract Factory</span> es un patrón muy útil y fácil de implementar y que ayuda a mantener una consistencia entre los objetos creados por cada una de las fábricas. Además de que facilita el intercambio de fábricas con tan solo la modificación de un parámetro. Con esto logramos tener un código más flexible y fácil de mantener.<br />
<br />
Como nota adicional, en el repositorio dejo, además del código, el archivo original en draw.io con los esquemas de los patrones.<br />
<br />
Espero que este tutorial les sea de utilidad. Si tienen alguna duda, sugerencia, comentario o aclaración, pueden dejarla en la sección de comentarios o enviar un correo a <a href="mailto:programadorjavablog@gmail.com">programadorjavablog@gmail.com</a> (pueden agregarme al chat de gmail). También pueden seguir JavaTutoriales en las siguientes redes sociales:
<ul>
<li><a href="https://www.facebook.com/JavaTutoriales/" target="_blank">Facebook</a></li>
<li><a href="https://twitter.com/JavaTutoriales" target="_blank">Twitter</a></li>
</ul>
Saludos y gracias.<br /><br />
<span class="negritas">Descarga los archivos de este tutorial desde mi repositorio en GitHub</span>
<ul>
<li><a href="https://github.com/JavaTutoriales/AbstractFactory" target="_blank">Patrón de diseño Abstract Factory</a>.</li>
</ul>
<br />
<span class="negritas">Entradas relacionadas</span>
<ul>
<li><a href="https://www.javatutoriales.com/2021/11/introduccion-los-patrones-de-diseno.html" target="_blank">Introducción a los patrones de diseño</a>.</li>
<li><a href="https://www.javatutoriales.com/2021/12/patron-de-diseno-factory-method.html" target="_blank">Patrón de diseño Factory Method</a>.</li>
<li><a href="https://www.javatutoriales.com/2021/12/diferencias-simple-factory-vs-factory.html" target="_blank">Diferencias: Simple Factory vs. Factory Method vs. Abstrac Factory</a>.</li>
</ul>
<br />
<br />
</div>Alexhttp://www.blogger.com/profile/06974037481671868076noreply@blogger.comtag:blogger.com,1999:blog-2012232069289957092.post-19548035526365123972021-12-08T05:44:00.024-08:002022-01-07T23:42:44.031-08:00Patrón de diseño Factory Method<div style="text-align: justify;">
<a href="https://www.javatutoriales.com/search/label/patrones%20de%20dise%C3%B1o" target="_blank"><img src="https://img.shields.io/badge/JT-Design%20Patterns-orange" /></a><br />
<br />
El patrón de diseño <span class="codigo">Factory Method</span> es un patrón de diseño <span class="negritas">creacional</span> del ámbito de los objetos; es el único patrón de este tipo de los <a href="https://www.javatutoriales.com/2021/11/introduccion-los-patrones-de-diseno.html" target="_blank">24 definidos originalmente por la GoF</a>.<br />
<br />
Este patrón se usa mucho dentro del JDK y en frameworks como Spring, además de en un sin número de librerias y especificaciones, ya que uno de sus principales objetivos es poder ocultar los detalles de la implementación a través de una clase abstracta o interface para definir y mantener relaciones entre objetos.<br />
<br />
En este tutorial veremos varias estrategias de implementación y una variación a las cual se les conoce simplemente como <span class="codigo">Factory</span> o como <span class="codigo">Simple Factory</span> y sus diferencias con <span class="codigo">Factory Method</span>.<br />
<br />
<a name='more'></a>
<span class="negritas">Factory</span> puede aplicarse cuando tenemos varias clases diferentes que tienen elementos en común o las tratamos de la misma forma, pero con pequeñas diferencias. En estos casos hacemos uso de la <span class="negritas">herencia</span> para crear una clase base (normalmente abstracta, aunque esto no es necesario), o una interface, de la cual heredan el resto de las clases, y, dependiendo de ciertas condiciones, queremos obtener una instancia una de estas clases sin que realmente nos importen los detalles de la clase concreta que se está usando o como se instanció.<br />
<br />
Este patrón toma la responsabilidad de instanciar dicha clase (o sea, de crear el objeto concreto que necesitamos) y regresarnos la instancia sin que nos preocupemos de los detalles de cómo hizo la selección y como creó el objeto.<br />
<br /><h2 class="titulo"><a class="anchorTitle" href="#objetivo" name="objetivo">
<svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z" fill-rule="evenodd"></path></svg>
Objetivo o intención del patrón</a></h2>
Con la breve explicación anterior podemos decir que el objetivo o intención de este patrón es:
<ol>
<li>Crear objetos sin exponer al cliente la lógica de cómo se crean esas instancias (este en realidad corresponde a <span class="codigo">Factory</span>, no a <span class="codigo">Factory Method</span>, pero me parece que ambos lo cumplen a la perfección).</li>
<li>Hacer referencia al objeto de reciente creación usando una interface común.</li>
<li>Definir una interface para crear objetos, pero dejar que sean las subclases quienes decidan qué clase concreta instanciar.</li>
</ol>
En las líneas anteriores, <span class="negritas">cliente</span> se refiere a la parte de la aplicación que hace uso del patrón Factory Method para obtener la instancia que necesita.<br />
<br />
También, esto ayuda a que el cliente no se preocupe de los detalles de cómo se inicializan los objetos que recibe; en ocasiones puede ser necesario conectarse a una base de datos, o a un servicio externo, si las regresa de un caché o un pool, si regresa la misma instancia cada vez que lo necesitamos o si siempre crea una nueva instancia y la devuelve; estos son detalles que no son importantes para el cliente. Al cliente solo le interesa obtener una instancia de una subclase (incluso, no es importante cuál es la clase concreta que se está usando). <br />
<br />
<span class="nota">📌 Lo anterior ayuda a genera un bajo acoplamiento al aplicar de forma correcta el polimorfismo.<br /></span>
<br />
¿Qué pasa si tu cliente necesita conocer los detalles de qué subclase se está usando? Digamos que quieres que, si la instancia es de un tipo particular hacer algo diferente como invocar un método o establecer el valor de una variable. Esto solo lo necesitas para este tipo particular pero no para los demás. <br />
<br />
La anterior es en realidad es una pregunta con truco pero que he escuchado muchas veces. La respuesta tiene dos partes:
<ol>
<li>La pregunta no tiene nada que ver con el patrón. El alcance del patrón está relacionado con <span class="negritas">la creación del objeto</span>, no con lo que el cliente hace con ese objeto una vez que lo tiene. </li>
<li>Hablando de "<span class="negritas">buenas prácticas</span>", si necesitas conocer qué implementación particular estás usando para hacer algo diferente entonces estás aplicando mal los conceptos de herencia, polimorfismo y abstracción. Posiblemente necesites revisar y ajustar tu diseño o tu algoritmo.</li>
</ol>
Es válido refactorizar tu clase base para agregar métodos que necesites, aunque algunas implementaciones dejen ese método vacío. Podemos cambiar esto:
<pre class="brush: java; highlight: [8]">public abstract class Computadora{
public int horasContinuasDeUso;
public void incrementaHorasDeUso() {
horasDeUso++;
}
public abstract void hazCosasDeComputadora();
}
public class Servidor extends Computadora {
@Overrride
public void hazCosasDeComputadora(){
//cálculos muy complejos, multi hilos, trabajo de red, etc.
incrementaHorasDeUso();
}
public boolean isMantenimientoNecesario() {
return horasDeUso <= 1000;
}
public boolean darMantenimiento(){
horasDeUso = 0;
}
}
public class Laptop extends Computadora {
public void hazCosasDeComputadora(){
//Hojas de cálculo, presentaciones, documentos y memes
incrementaHorasDeUso();
}
}
public class Cliente {
public static void main(String... args) {
Computadora computadora = ComputadoraFactory.getComputadora(TIPO.TRABAJO_DE_HOY);
if(computadora instanceOf Servidor servidor) { //A partir de Java 14 podemos usar esta notación
if(servidor.isMantenimientoNecesario()){
servidor. darMantenimiento();
}
}
computadora. hazCosasDeComputadora();
}
}
</pre>
<br />
Por esto:
<pre class="brush: java; highlight: [8,9,10]">public abstract class Computadora {
public int horasContinuasDeUso;
public void incrementaHorasDeUso() {
horasDeUso++;
}
public abstract void hazCosasDeComputadora();
public abstract boolean isMantenimientoNecesario();
public abstract boolean darMantenimiento();
}
public class Servidor extends Computadora {
@Overrride
public void hazCosasDeComputadora(){
//cálculos muy complejos, multi hilos, trabajo de red, etc.
incrementaHorasDeUso();
}
@Override
public boolean isMantenimientoNecesario() {
return horasDeUso <= 1000;
}
@Override
public boolean darMantenimiento(){
horasDeUso = 0;
}
}
public class Laptop extends Computadora {
public void hazCosasDeComputadora(){
//Hojas de cálculo, presentaciones, documentos y memes
incrementaHorasDeUso();
}
@Override
public boolean isMantenimientoNecesario() {
return false;
}
@Override
public boolean darMantenimiento(){
}
}
public class Cliente {
public static void main(String... args) {
Computadora computadora = ComputadoraFactory.getComputadora(TIPO.TRABAJO_DE_HOY);
if(computadora.isMantenimientoNecesario()){
computadora.darMantenimiento();
}
computadora.hazCosasDeComputadora();
}
}
</pre>
Así, <span class="clase">Cliente</span> no necesita conocer detalles de la implementación y si en algún momento decides que <span class="clase">Laptop</span> también necesita mantenimiento, no tendrás que hacer ningún cambio en el código de <span class="clase">Cliente</span>, solo directamente en la clase <span class="clase">Laptop</span> o en alguna subclase especializada.<br />
<br />
Nuevamente, esto no tiene nada qué ver con el patrón, pero es una pregunta que siempre sale al explicarlo.<br />
<br />
<br /><h2 class="titulo"><a class="anchorTitle" href="#implementacion" name="implementacion">
<svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z" fill-rule="evenodd"></path></svg>
Implementación</a></h2>
Antes de ver los detalles en código, un poco de teoría de cómo implementar el patrón.
<ol>
<li><span class="negritas">Crear una clase base</span>. Esta representa a los tipos de datos regresados por la fábrica. La clase puede ser concreta, pero lo más común y recomendable es que se use una <span class="negritas">clase abstracta o una interface</span> para realmente facilitar un bajo acoplamiento.</li>
<li><span class="negritas">Crear una serie de clases hija que extiendan (o implementen) la clase base</span>. Claro que al final las clases tendrán detalles diferentes en la implementación. Estas clases serán los objetos creados por nuestras fábricas.</li>
<li><span class="negritas">Crear una clase base Factory Method</span>. Esta puede ser una clase abstracta o una interface, con un método que regrese una instancia de la clase base. Este método puede recibir parámetros si es necesario para que decida cuál es la instancia que se debe regresar.</li>
<li><span class="negritas">Crear una o más implementaciones de la clase Factory</span>. Estas implementaciones serán quienes regresen los objetos concretos creados por la fábrica. Si necesitamos más de una forma de crear los objetos, podemos crear también más de una implementación de la fábrica. Sé que esto es poco claro o puede ser confuso, pero lo explicaré en un momento.</li>
<li><span class="negritas">Obtener la instancia que se regresará al cliente</span>. La instancia puede ser creada de inmediato, tomada de un pool de objetos, u obtenido de cualquier otra forma que sea necesaria. La manera en la que el <span class="codigo">Factory Method</span> cree la instancia dependerá de las necesidades particulares de tu aplicación.</li>
<li>Regresar al cliente la instancia como una referencia del tipo de la clase base.</li>
</ol>
Los pasos 3 y 4 necesitan un poco de atención adicional. ¿Es necesario hacer una clase fábrica base y luego una implementación?, ¿podemos hacer una sola clase y olvidarnos de lo demás? Sí... y no.<br />
<br />
Explicaré la respuesta anterior con un ejemplo reutilizando la fábrica de computadoras. <br />
<br />
Si diagramamos los objetos del ejemplo anterior tendremos algo parecido a esto:<br />
<br />
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEgnm9Ch6ogRYnZL61xNt1elfj84qOIajOXCbuYAhmaCGWF2BvdSCoR1D2qALkDCLQhqXyLGXm1tp_T9VQcO_LQBwpQQl0ZGziFg-wVKfXE5hpsAcXD5LOA2E5kHMme8Va11M6TvJn7yjeFseAT3KxYhsEhk5RMZ0VL4-arytRPwNK6LIua3OG79vpq-qQ=s762" style="display: block; padding: 1em 0px; text-align: center;"><img alt="" border="0" data-original-height="552" data-original-width="762" height="464" src="https://blogger.googleusercontent.com/img/a/AVvXsEgnm9Ch6ogRYnZL61xNt1elfj84qOIajOXCbuYAhmaCGWF2BvdSCoR1D2qALkDCLQhqXyLGXm1tp_T9VQcO_LQBwpQQl0ZGziFg-wVKfXE5hpsAcXD5LOA2E5kHMme8Va11M6TvJn7yjeFseAT3KxYhsEhk5RMZ0VL4-arytRPwNK6LIua3OG79vpq-qQ=w640-h464" width="640" /></a></div>
<br />
<br />
Observa que toda la lógica de la creación de las instancias de <span class="clase">Computadora</span>, está centrada en una sola clase: <span class="clase">ComputadoraFactory</span>. No nos interesa cómo es que esta clase obtiene las instancias de <span class="clase">Computadora</span>. Digamos que nuestro <span class="clase">Cliente</span> simplemente nos pide una computadora y no le interesa si vamos a comprar una nueva, si le vamos a dar una usada que esté en la bodega, si se la vamos a quitar a alguien más para dársela a él, etc. Lo único que le interesa es obtener una <span class="clase">Computadora</span>. Hasta aquí todo bien. La responsabilidad de obtener la <span class="clase">Computadora</span> es de <span class="clase">ComputadoraFactory</span>. Esto funciona bastante bien.<br />
<br />
Lo anterior es algo que los estudiosos llaman: <span class="codigo">Simple Factory</span> (o <span class="codigo">Factory</span> a secas), una sola clase que se encarga de la creación de estos objetos. Pero, estos mismos estudiosos, dicen que <span class="codigo">Simple Factory</span> NO es un patrón de diseño porque no es suficientemente flexible. Solo tenemos una forma de crear los objetos (una sola regla), si esa regla cambia entonces hay que modificar la lógica dentro de la clase, lo cual viola algunos de los principios de diseño <span class="negritas"><a href="https://es.wikipedia.org/wiki/SOLID" target="_blank">S.O.L.I.D.</a></span><br />
<br />
En mi opinión, <span class="codigo">Simple Factory</span> es útil, sea defina o no como un patrón de diseño (tan útil es que se tomaron la molestia de darle un nombre).
Entonces, lo que indican los pasos 3 y 4 es que, para tener una Factory que cumpla con el patrón, <span class="clase">ComputadoraFactory</span> debe ser abstracta y debe haber por lo menos una implementación concreta. Y no, aunque tenemos una Fabrica y esta es Abstracta, no es el patrón <span class="codigo">Abstract Factory</span>. En el siguiente tutorial hablaré de la diferencia entre estos patrones.<br />
<br />
Por cierto, yo sí considero a <span class="codigo">Simple Factory</span> como un patrón de diseño.<br />
<br />
Dentro de los siguientes ejemplos del tutorial no hablaré de <span class="codigo">Simple Factory</span> ni daré un ejemplo... porque ya hablé de él y di un ejemplo 😉. Pero a lo que me refiero es que no proporcionaré un ejemplo de código.<br /><br /><br />
<br /><h2 class="titulo"><a class="anchorTitle" href="#diagrama" name="diagrama">
<svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z" fill-rule="evenodd"></path></svg>
Diagrama</a></h2>
Este es el diagrama de <span class="codigo">Factory Method</span>:<br />
<br />
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEg8bzSzgDMeCZ1Tx4M_Qezz2EsQbcskDqNKYLrGl0n755lGk7ynLGTvgEY3jzApRd2xDEHBDl-y1bRiLSHoVavYX23fu_G-JJjxqEL4R8dzpI8bHXcISzV86z7cbowEjIQLeHF27BphYlJyNeFuHVXVI0wsDEOpgcUO2AwJPtnz-372Q2ez83dXAOyKIA=s944" style="display: block; padding: 1em 0px; text-align: center;"><img alt="" border="0" data-original-height="552" data-original-width="944" height="374" src="https://blogger.googleusercontent.com/img/a/AVvXsEg8bzSzgDMeCZ1Tx4M_Qezz2EsQbcskDqNKYLrGl0n755lGk7ynLGTvgEY3jzApRd2xDEHBDl-y1bRiLSHoVavYX23fu_G-JJjxqEL4R8dzpI8bHXcISzV86z7cbowEjIQLeHF27BphYlJyNeFuHVXVI0wsDEOpgcUO2AwJPtnz-372Q2ez83dXAOyKIA=w640-h374" width="640" /></a></div><br />
<br />
Como ves, el patrón es muy sencillo de implementar. Para el problema particular que quieres resolver necesitas adecuar la plantilla anterior; la cual, por cierto, te dejo en GitHub junto con el código del ejemplo. Puedes abrir la plantilla usando <a href="https://app.diagrams.net/" target="_blank">draw.io.</a><br />
<br />
En muchos lugares verás que lo que yo llamo <span class="codigo">ClaseBase</span> lo presentan como <span class="codigo">Producto</span> y <span class="codigo">ClaseHijaX</span> lo represetan como <span class="codigo">ProductoConcreto</span>; de forma parecida <span class="codigo">ClaseBaseAbstractFactory</span> lo representan como <span class="codigo">Creador</span>. Los nombres cambian pero la idea es exactamente la misma.<br />
<br />
Existen tres variaciones o estrategias de implementación para este patrón (seguro hay más, pero estas son las más comunes). A la estrategia presentada anteriormente le llamaré <span class="negritas">Estrategia A</span>. <br />
<br />
En esta estrategia podemos tener una o más implementaciones de la Factory, cada una con una lógica diferente para crear los objetos concretos. Si en algún momento queremos cambiar la lógica solo debemos crear una nueva implementación de la fábrica y así no debemos tocar nada del código que ya existe. Incluso podemos definir algunas condiciones bajo las cuales se use una u otra fábrica.<br />
<br />
Digamos que queremos dar un Regalo a los empleados de una empresa. Con una implementación de la fábrica podríamos hacer que el regalo sea seleccionado con base a la antigüedad del empleado, con otra fábrica podemos generarlo tomando en cuenta las horas de trabajo, y con otra podríamos generarlos completamente al azar.<br />
<br />
En la <span class="negritas">Estrategia B</span> tenemos un diagrama muy parecido al anterior, pero en donde la Factory base es abstracta y se tienen clases concretas que crean cada uno de los tipos concretos. La diferencia con las estrategia anterior es que cada fábrica concreta se encarga de genera solamente un tipo de objeto concreto. Así, por ejemplo, podemos tener una fábrica de teléfonos celulares, en donde una fábrica solo cree teléfonos Samsung y la otra solo teléfonos Motorola. Aunque la forma de fabricación pude ser parecida, los materiales y procesos seguro que son diferentes.<br />
<br />
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEg2tq7V9_pEucto9F9wzaa9QI2mMJna3CfCTI8jne7eicKorecaHOXWN4Vhqf3nfOFxzzZCZSYDUoYPCTncd5oYQXsUq707lc1bS23cngxLKFqNsQSw_ajlm3Y2miG6QxKdTVnfRTkmddNEcN7T3fiA_iSF4qM-K9zYuNmIEdP1CncpYO0qHy9mMyS9nQ=s944" style="display: block; padding: 1em 0px; text-align: center;"><img alt="" border="0" data-original-height="552" data-original-width="944" src="https://blogger.googleusercontent.com/img/a/AVvXsEg2tq7V9_pEucto9F9wzaa9QI2mMJna3CfCTI8jne7eicKorecaHOXWN4Vhqf3nfOFxzzZCZSYDUoYPCTncd5oYQXsUq707lc1bS23cngxLKFqNsQSw_ajlm3Y2miG6QxKdTVnfRTkmddNEcN7T3fiA_iSF4qM-K9zYuNmIEdP1CncpYO0qHy9mMyS9nQ=s600" width="600" /></a></div><br />
<br />
En la <span class="negritas">Estrategia C</span> la <span class="codigo">ClaseBase</span> también es la fábrica y es ella misma quien determina qué clase Hija concreta regresar. <br />
<br />
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEghuiBcmzxcQwPhAvn4Zzlx79c_PMIFSV7RFJyDBLiimygOh31Zv86KgHM6goAnUBr9nvNNPLMGp0XtnLIEUmmVFpJr_vjbTFOKVN_mToFZpSh5E5rxF6pt592DtsKO2U8QzoJSovs9sRCWIXypE56rDXpSH92KrJgKc7Hv0AcVsXbKPAOVcYgm1QB37w=s612" style="display: block; padding: 1em 0px; text-align: center;"><img alt="" border="0" data-original-height="612" data-original-width="420" height="400" src="https://blogger.googleusercontent.com/img/a/AVvXsEghuiBcmzxcQwPhAvn4Zzlx79c_PMIFSV7RFJyDBLiimygOh31Zv86KgHM6goAnUBr9nvNNPLMGp0XtnLIEUmmVFpJr_vjbTFOKVN_mToFZpSh5E5rxF6pt592DtsKO2U8QzoJSovs9sRCWIXypE56rDXpSH92KrJgKc7Hv0AcVsXbKPAOVcYgm1QB37w=w275-h400" width="275" /></a></div><br />
<br />
Puede sonar un poco raro, pero de hecho esta es la estrategia del patrón <span class="codigo">Factory Method</span> original definida por GoF. ¿La <span class="negritas">Estrategia C</span> se usa en el mundo real? Sí, y mucho, sobre todo en la implementación del JDK y OpenJDK. El ejemplo más conocido es la clase <a href="https://github.com/openjdk-mirror/jdk7u-jdk/blob/f4d80957e89a19a29bb9f9807d2a28351ed7f7df/src/share/classes/java/util/Calendar.java#L1016" target="_blank">Calendar</a>. Esta es una clase abstracta que tiene una serie de métodos <span class="clase">getInstance</span> (línea <a href="https://github.com/openjdk-mirror/jdk7u-jdk/blob/master/src/share/classes/java/util/Calendar.java#L966" target="_blank">966</a> en la liga anterior) que a su vez usa un método privado <span class="clase">createCalendar</span> (línea <a href="https://github.com/openjdk-mirror/jdk7u-jdk/blob/f4d80957e89a19a29bb9f9807d2a28351ed7f7df/src/share/classes/java/util/Calendar.java#L1016" target="_blank">1016</a>) y regresa una de 3 instancias.<br />
<br />
El diagrama de implementación de <span class="clase">Calendar</span> sería algo parecido al siguiente.<br />
<br />
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEjeVhUYefW-McPe57p2pQhMdezyOd5sNe8WI2yrwMP7bLH0PYEBAriUCmdarac9gJdG4XG1wrdSS-P1WFIY-w08AJgw8IVrq0Seplqtv5zZgtO2202qbL88UOhopFFFA_p2fljSAI7l_2rR5C7axFWyyBzJs2zjPY897BYVQHj1wBaS-S12XAWeLC5Lxw=s521" style="display: block; padding: 1em 0px; text-align: center;"><img alt="" border="0" data-original-height="515" data-original-width="521" height="395" src="https://blogger.googleusercontent.com/img/a/AVvXsEjeVhUYefW-McPe57p2pQhMdezyOd5sNe8WI2yrwMP7bLH0PYEBAriUCmdarac9gJdG4XG1wrdSS-P1WFIY-w08AJgw8IVrq0Seplqtv5zZgtO2202qbL88UOhopFFFA_p2fljSAI7l_2rR5C7axFWyyBzJs2zjPY897BYVQHj1wBaS-S12XAWeLC5Lxw=w400-h395" width="400" /></a></div><br />
<br />
También, se usa en la clase <span class="clase">NumberFormat</span>. Esta clase tiene una serie de métodos <span class="clase">getInstance</span> que regresan la instancia correspondiente para dar formato a los número. En el caso de <span class="clase">NumberFormat</span> usa una serie de <span class="codigo">Factory Methods</span> por lo que es algo más complejo que el ejemplo anterior.<br />
<br />
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEhwTYzGSd2abRJZdBR_Jg1r0MHYq54wDm3nS8Sn-4voh1-tP20OyOPYtnAt-dffJJ_BLQX29D1CzDL9zCXgvtIcZZogrP7qyBlTydAJdpY4lHB0ONIiRggJ5VSaIs5OlWLnslSD7MK5ttm1HweV4JhGftN4aa3IUGXA1vUcKed62Ci7XSzbP6lEIyjsgg=s595" style="display: block; padding: 1em 0px; text-align: center;"><img alt="" border="0" data-original-height="509" data-original-width="595" src="https://blogger.googleusercontent.com/img/a/AVvXsEhwTYzGSd2abRJZdBR_Jg1r0MHYq54wDm3nS8Sn-4voh1-tP20OyOPYtnAt-dffJJ_BLQX29D1CzDL9zCXgvtIcZZogrP7qyBlTydAJdpY4lHB0ONIiRggJ5VSaIs5OlWLnslSD7MK5ttm1HweV4JhGftN4aa3IUGXA1vUcKed62Ci7XSzbP6lEIyjsgg=s600" width="600" /></a></div><br />
<br />
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEinFMhMPtVpdVpoCyinU3HQMAWyDVt7PdcsxE8W6uvceI-SnisgszPKCTSC-whZ31GXOOM3oUo1HQLL1COFPGmdSIrccbHweBZ9JXZOixbEXgVIk-nQJmRu3pqOxYoXRYDUHM1eEjP2HcwbzfSYVeCHIGIb5oiCMI422eLYDqGRWQPN7Et8dplf9iFsnQ=s603" style="display: block; padding: 1em 0px; text-align: center;"><img alt="" border="0" data-original-height="311" data-original-width="603" src="https://blogger.googleusercontent.com/img/a/AVvXsEinFMhMPtVpdVpoCyinU3HQMAWyDVt7PdcsxE8W6uvceI-SnisgszPKCTSC-whZ31GXOOM3oUo1HQLL1COFPGmdSIrccbHweBZ9JXZOixbEXgVIk-nQJmRu3pqOxYoXRYDUHM1eEjP2HcwbzfSYVeCHIGIb5oiCMI422eLYDqGRWQPN7Et8dplf9iFsnQ=s600" width="600" /></a></div><br />
<br />
Si quieres divertirte un rato, puedes ver el código fuente de <a href="https://github.com/AdoptOpenJDK/openjdk-jdk12u/blob/master/src/java.base/share/classes/java/text/NumberFormat.java" target="_blank">NumberFormat</a> aquí: <a href="https://github.com/AdoptOpenJDK/openjdk-jdk12u/blob/master/src/java.base/share/classes/java/text/NumberFormat.java" target="_blank">https://github.com/AdoptOpenJDK/openjdk-jdk12u/blob/master/src/java.base/share/classes/java/text/NumberFormat.java</a><br />
<br /><br />
<br /><h2 class="titulo"><a class="anchorTitle" href="#ejemplo" name="ejemplo">
<svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z" fill-rule="evenodd"></path></svg>
Ejemplo</a></h2>
En este tutorial me centraré en la implementación de la <span class="negritas">Estrategia A</span>, pero en el repositorio te dejo el código que también incluye la <span class="negritas">Estrategia B</span>. No lo explico aquí ya que este tutorial se volvería eterno; pero con la explicación de la <span class="negritas">Estrategia A</span> es suficiente para que entiendas la implementación de la <span class="negritas">Estrategia B</span>. Hago lo mismo con la <span class="negritas">Estrategia C</span>.<br />
<br />
<span class="nota">
<span class="negritas">📌 Nota:</span> Estos nombres (estrategia A, B y C) son nombres que yo le estoy dando para diferenciar las formas de implementar el patrón, no son nombres "oficiales" de las diferentes estrategias.
</span><br />
Para los ejemplos uso Java 17 y Gradle. No uso ninguna característica particular de esta versión de Java, por lo que debe funcionar en cualquier versión (incluso en la 5). También, uso <a href="https://www.javatutoriales.com/2019/03/lombok-escribiendo-menos-codigo-y.html" target="_blank">Lombok</a> para evitar escribir código repetitivo de forma innecesaria.<br />
<br />
Para validar el correcto funcionamiento de la aplicación usaré JUnit y AssertJ. Agrega las siguientes dependencias en el archivo <span class="clase">build.gradle</span> (si usas Maven, coloca las dependencias correspondientes en el archivo <span class="clase">pom.xml</span>)
<pre class="brush: groovy;" title="build.gradle">dependencies {
compileOnly 'org.projectlombok:lombok:1.18.22'
annotationProcessor 'org.projectlombok:lombok:1.18.22'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
testImplementation 'org.assertj:assertj-core:3.21.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
}
</pre>
Ahora sí, al código. <br />
<br /><br />
El problema que resolveremos es el siguiente: Tenemos una cafetería y el dueño ha decidido lanzar una nueva tarjeta de lealtad. Habrá tres tipos de tarjetas de lealtad: Bronce, Plata y Oro. Cada tarjeta otorgará un descuento diferente:<br />
<br />
<ul>
<li><span class="negritas">Bronce: 0%</span>. Solo se usa para almacenar el número de compras que el cliente ha realizado en el establecimiento.</li>
<li><span class="negritas">Plata: 5%</span> de descuento sobre el costo total de la compra.</li>
<li><span class="negritas">Oro</span>: Si el total de la compra es menor o igual a <span class="negritas">$50</span>, se considera una compra baja y se otorga un <span class="negritas">10%</span> de descuento; si es mayor a $50 se otorga un <span class="negritas">5%</span> de descuento.</li>
</ul><br />
El dueño aún no está seguro de cuál es el mejor criterio para otorgar una tarjeta a un cliente, si con base en el número de compras que ha realizado, la suma del total de estas compras, la edad del cliente, etc. Sin embargo, y como sabe que hay que iniciar por algún lado, quiere ver qué tan bien funciona el otorgar las tarjetas dependiendo del número de compras que ha realizado el cliente. Todos los clientes iniciarán sus compras en 0, ya que antes no se llevaba este registro. Las tarjetas se les darán a los clientes al momento de llegar al número de compras indicado y no es necesario que la soliciten (y tampoco pueden rechazarla).<br />
<br />
Las tarjetas se otorgarán usando el siguiente criterio:
<ul>
<li>En la primera compra se les otorgará una tarjeta de Bronce.</li>
<li>Al tener 5 compras acumuladas se les otorgará la tarjeta de Plata.</li>
<li>Al tener 10 compras se les otorgará la tarjeta de Oro.</li>
</ul>
Si este criterio no funciona el dueño considerará una nueva forma de otorgar las tarjetas.<br />
<br />
Como sabemos que en este caso la forma en la que se crearán las tarjetas (la lógica de creación de los objetos) puede cambiar en cualquier momento, queremos que esta parte de la aplicación sea flexible y que modificar la misma no sea difícil en caso de que el criterio cambie. Esto por varios motivos:
<ul>
<li>Cuando el criterio cambie el dueño querrá que se haga prácticamente al instante, sin esperar algunos días para que realicemos los cambios necesarios en la aplicación y volvamos a probar todo.</li>
<li>En general es una buena práctica el encapsular y aislar esta lógica de creación de los objetos de forma que, si cambia, solo tengamos que hacer ajustes en un archivo de código (o aún mejor, en algún archivo de configuración).</li>
<li>No queremos volver a probar toda la aplicación para verificar que el cambio realizado no ha tenido impactos no deseados en otras partes de la aplicación. </li>
</ul>
Comencemos entonces con el desarrollo de la aplicación.<br />
<br />
Lo primero será modelar nuestra tarjeta de lealtad. La única función de la tarjeta será el calcular el descuento de la compra, y este descuento varía en función del tipo de tarjeta. Esto podemos hacerlo de dos formas:
<ol>
<li>Modelando la tarjeta de lealtad como una clase abstracta con un método abstracto que obtenga el total del descuento.</li>
<li>Modelando la tarjeta como una interface, con un método que obtenga el total del descuento.</li>
</ol>
Como no tenemos en realidad ningún atributo ni funcionalidad o métodos compartidos por los diferentes tipos de tarjetas, usaré la segunda forma. La clase base (interface base) <span class="clase">TarjetaLealtad</span> queda de la siguiente forma:
<pre class="brush: java;" title="TarjetaLealtad.java">public interface TarjetaLealtad {
float calculaDescuento(float totalVenta);
}
</pre>
Y las implementaciones de la siguiente forma:<br />
<pre class="brush: java;" title="TarjetaBronce.java">public class TarjetaBronce implements TarjetaLealtad {
@Override
public float calculaDescuento(float totalVenta) {
return 0;
}
}
</pre>
<pre class="brush: java;" title="TarjetaPlata.java">public class TarjetaPlata implements TarjetaLealtad {
private static final float PORCENTAJE_DESCUENTO = 5f / 100f; // 5% de descuento
@Override
public float calculaDescuento(float totalVenta) {
return totalVenta * PORCENTAJE_DESCUENTO;
}
}
</pre>
<pre class="brush: java;" title="TarjetaOro.java">public class TarjetaOro implements TarjetaLealtad {
private static final float PORCENTAJE_DESCUENTO_PRECIO_BAJO = 10f / 100f; // 10% de descuento
private static final float PORCENTAJE_DESCUENTO_PRECIO_ALTO = 5f / 100f; // 5% de descuento
private static final float UMBRAL_PRECIO_ALTO = 50f;
@Override
public float calculaDescuento(float totalVenta) {
return totalVenta > UMBRAL_PRECIO_ALTO ? totalVenta * PORCENTAJE_DESCUENTO_PRECIO_ALTO : totalVenta * PORCENTAJE_DESCUENTO_PRECIO_BAJO;
}
}
</pre>
<br />
Y el cliente queda representado de la siguiente forma (uso algunas anotaciones de Lombok para simplificar la escritura del código):
<pre class="brush: java;" title="Cliente.java">@Data
@Builder
public class Cliente {
private int edad;
private int numeroCompras;
private float totalCompras;
private TarjetaLealtad tarjetaLealtad;
public void compra(float montoCompra) {
totalCompras += montoCompra;
numeroCompras++;
}
}
</pre><br />
Ahora sí veremos la implementación de la <span class="negritas">Estrategia A</span> del patrón.<br />
<br />
Sabemos que podemos tener diferentes formas de crear las instancias las tarjetas de lealtad, usando diferentes criterios, por lo que queremos encapsular esa lógica. Vemos nuevamente el diagrama de la Estrategia A:.<br />
<br />
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEg8bzSzgDMeCZ1Tx4M_Qezz2EsQbcskDqNKYLrGl0n755lGk7ynLGTvgEY3jzApRd2xDEHBDl-y1bRiLSHoVavYX23fu_G-JJjxqEL4R8dzpI8bHXcISzV86z7cbowEjIQLeHF27BphYlJyNeFuHVXVI0wsDEOpgcUO2AwJPtnz-372Q2ez83dXAOyKIA=s944" style="display: block; padding: 1em 0px; text-align: center;"><img alt="" border="0" data-original-height="552" data-original-width="944" height="374" src="https://blogger.googleusercontent.com/img/a/AVvXsEg8bzSzgDMeCZ1Tx4M_Qezz2EsQbcskDqNKYLrGl0n755lGk7ynLGTvgEY3jzApRd2xDEHBDl-y1bRiLSHoVavYX23fu_G-JJjxqEL4R8dzpI8bHXcISzV86z7cbowEjIQLeHF27BphYlJyNeFuHVXVI0wsDEOpgcUO2AwJPtnz-372Q2ez83dXAOyKIA=w640-h374" width="640" /></a></div><br />
<br />
La <span class="codigo">ClaseBase</span> es representada por <span class="clase">TarjetaLealtad</span>, y las clases hijas representadas por <span class="clase">TarjetaOro</span>, <span class="clase">TarjetaPlata</span> y <span class="clase">TarjetaBronce</span>.<br />
<br />
Lo que haremos ahora será crear la representación de <span class="codigo">ClaseBaseAbstractFactory</span>. Para ello crearemos una clase abstracta que tendrá un solo método, llamado <span class="clase">getTarjetjetaLealtad</span>, el cual recibirá la instancia de <span class="clase">Cliente</span> al que asignaremos la tarjeta. Siguiendo la misma lógica que usé para <span class="clase">TarjetaLealtad</span>, como esta será una clase abstracta sin atributos ni métodos concretos, solo métodos abstractos, cambiaré de una clase abstracta a una interface:
<pre class="brush: java;" title="TarjetaLealtadFactory.java">public interface TarjetaLealtadFactory {
TarjetaLealtad getTarjetaLealtad(Cliente cliente);
}
</pre><br />
El último paso será crear una implementación de esta interface que cumpla con los deseos actuales del dueño; esta será la representación de la <span class="codigo">FabricaConcreta</span> en el diagrama del patrón. Como el criterio que usaré es el número de compras, llamaré a esta clase "<span class="clase">TarjetaLealtadFactoryNumeroCompras</span>":
<pre class="brush: java;" title="TarjetaLealtadFactoryNumeroCompras.java">public class TarjetaLealtadFactoryNumeroCompras implements TarjetaLealtadFactory {
}
</pre><br />
Mantendremos el número de compras mínimo para obtener cada tarjeta en constantes para darles un significado más claro:
<pre class="brush: java;">private static final int MINIMO_COMPRAS_PLATA = 5;
private static final int MINIMO_COMPRAS_ORO = 10;
</pre><br />
Finalmente, sobre escribiremos el método <span class="clase">getTarjetaLealtad</span> usando la lógica descrita anteriormente:
<pre class="brush: java;" title="TarjetaLealtadFactoryNumeroCompras.java">@Override
public TarjetaLealtad getTarjetaLealtad(Cliente cliente) {
if (cliente.getNumeroCompras() >= MINIMO_COMPRAS_ORO) {
return new TarjetaOro();
}
if (cliente.getNumeroCompras() >= MINIMO_COMPRAS_PLATA) {
return new TarjetaPlata();
}
return new TarjetaBronce();
}
</pre><br />
Y esto es todo. Dependiendo del número de compras del cliente le asignaremos una tarjeta de Oro, Plata o Bronce.<br />
<br />
El diagrama de la implementación queda de la siguiente forma:<br />
<br />
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEigPmtD2-FmZPz1WIlFzvU5CBvow5w4uBJlPuOrmw3f_C3PxP_XfIZSL4TpcOAw_ZUYFhVQHzohk0gfiQTkRZ-QuGNXZbA8X9WlWMNmsW2Vv6bB83-6FRTMaEa3Qm8KUu7lKZlrk9pb5DTOwDfYcu1ABUbKwNi05lMkHmU417SAcKKZyknTEkSt5cUl2Q=s902" style="display: block; padding: 1em 0px; text-align: center;"><img alt="" border="0" data-original-height="612" data-original-width="902" src="https://blogger.googleusercontent.com/img/a/AVvXsEigPmtD2-FmZPz1WIlFzvU5CBvow5w4uBJlPuOrmw3f_C3PxP_XfIZSL4TpcOAw_ZUYFhVQHzohk0gfiQTkRZ-QuGNXZbA8X9WlWMNmsW2Vv6bB83-6FRTMaEa3Qm8KUu7lKZlrk9pb5DTOwDfYcu1ABUbKwNi05lMkHmU417SAcKKZyknTEkSt5cUl2Q=s600" width="600" /></a></div><br />
<br />
Ahora probemos que la implementación funciona. Para eso haré uso de pruebas unitarias usando Junit 5 y AssertJ. Primero, el esqueleto de la clase para pruebas unitarias:
<pre class="brush: java;" title="TarjetaLealtadFactoryNumeroComprasTest.java">public class TarjetaLealtadFactoryNumeroComprasTest {
TarjetaLealtadFactory tarjetaLealtadFactory = new TarjetaLealtadFactoryNumeroCompras();
}
</pre><br />
Comencemos validando que la tarjeta de Bronce se asigna desde la primera compra (cuando el total de compras aún es 0). Para validar que estamos obteniendo la tarjeta de bronce lo haremos de dos formas: la primera comprobando que efectivamente estamos obteniendo una instancia de la clase <span class="clase">TarjetaBronce</span>; la segunda es comprobando que no se está otorgando ningún descuento a la compra.
<pre class="brush: java;" title="TarjetaLealtadFactoryNumeroComprasTest.java">@Test
public void testTarjetaBronce_cuandoComprasEsCero() {
Cliente cliente = Cliente.builder().edad(15).numeroCompras(0).totalCompras(0).build();
TarjetaLealtad tarjetaLealtad = tarjetaLealtadFactory.getTarjetaLealtad(cliente);
final float montoCompra = 25f;
assertThat(tarjetaLealtad).isOfAnyClassIn(TarjetaBronce.class);
assertThat(tarjetaLealtad.calculaDescuento(montoCompra)).isEqualTo(0);
}
</pre><br />
Ahora, verificaremos que seguimos teniendo la tarjeta de bronce y el descuento de 0% en la cuarta compra:
<pre class="brush: java;" title="TarjetaLealtadFactoryNumeroComprasTest.java">@Test
public void testTarjetaBronce_cuandoComprasEsCuatro() {
Cliente cliente = Cliente.builder().edad(16).numeroCompras(4).totalCompras(120f).build();
TarjetaLealtad tarjetaLealtad = tarjetaLealtadFactory.getTarjetaLealtad(cliente);
final float montoCompra = 35.5f;
assertThat(tarjetaLealtad).isOfAnyClassIn(TarjetaBronce.class);
assertThat(tarjetaLealtad.calculaDescuento(montoCompra)).isEqualTo(0);
}
</pre><br />
A partir de la quinta compra debemos tener la tarjeta de plata y un descuento de 5%:
<pre class="brush: java;" title="TarjetaLealtadFactoryNumeroComprasTest.java">@Test
public void testTarjetaPlata_cuandoComprasEsCinco() {
Cliente cliente = Cliente.builder().edad(20).numeroCompras(5).totalCompras(155.5f).build();
TarjetaLealtad tarjetaLealtad = tarjetaLealtadFactory.getTarjetaLealtad(cliente);
final float montoCompra = 40f;
assertThat(tarjetaLealtad).isOfAnyClassIn(TarjetaPlata.class);
assertThat(tarjetaLealtad.calculaDescuento(montoCompra)).isEqualTo(2f);
}
</pre><br />
Para la tarjeta de oro, validaremos los dos casos: cuando una compra es menor o igual a $50 y cuando es mayor:
<pre class="brush: java;" title="TarjetaLealtadFactoryNumeroComprasTest.java">@Test
public void testTarjetaOrocuandoComprasEs10YPrecioEsBajo() {
Cliente cliente = Cliente.builder().edad(27).numeroCompras(10).totalCompras(255.5f).build();
TarjetaLealtad tarjetaLealtad = tarjetaLealtadFactory.getTarjetaLealtad(cliente);
final float montoCompra = 40f;
assertThat(tarjetaLealtad).isOfAnyClassIn(TarjetaOro.class);
assertThat(tarjetaLealtad.calculaDescuento(montoCompra)).isEqualTo(4f);
}
@Test
public void testTarjetaOrocuandoComprasEs10YPrecioEsAlto() {
Cliente cliente = Cliente.builder().edad(27).numeroCompras(10).totalCompras(300f).build();
TarjetaLealtad tarjetaLealtad = tarjetaLealtadFactory.getTarjetaLealtad(cliente);
final float montoCompra = 80f;
assertThat(tarjetaLealtad).isOfAnyClassIn(TarjetaOro.class);
assertThat(tarjetaLealtad.calculaDescuento(montoCompra)).isEqualTo(4f);
}
</pre><br />
Y listo, con esto podemos comprobar que la lógica de generación de tarjetas y de cálculo de descuentos funciona correctamente (si quieres, puedes generar una clase <span class="codigo">main</span> para hacer alguna comprobación adicional).<br />
<br />
¿Qué ocurre si el dueño decide que el número de compras no fue el criterio correcto para seleccionar la tarjeta de lealtad que se le otorgará al cliente? Fácil, solo tenemos que crear una nueva implementación de <span class="clase">TarjetaLaltadFactory</span> que contenga la nueva lógica de creación de tarjetas.<br />
<br />
¿Podemos simplemente modificar la lógica que ya existe en <span class="clase">TarjetaLealtadFactoryNumeroCompras</span>? Sí... y no.<br />
<br />
Explico la respuesta anterior. No hay algo que impida que hagamos una modificación de la clase ya existente, salvo las recomendaciones de los expertos. Existe una serie de guías y principios de diseño, de los cuales hablaré en otro tutorial, que indican que una vez que una clase está terminada y funcionando, esta no debería de ser modificada a menos que sea para corregir errores o realizar mejoras. El hacer un cambio completo de la lógica no es ni una corrección de errores ni una mejora. Es un algoritmo diferente que se usará para conseguir el mismo fin, obtener una tarjeta de lealtad, pero con un criterio diferente. Esto es una simplificación de los problemas reales que tenemos cuando empezamos a mover y a cambiar cosas de esta forma. Recuerda que además ya tenemos una serie de pruebas que validan el funcioamiento correcto de este algoritmo.<br />
<br />
Por lo tanto, es mejor y más flexible crear una nueva clase con la nueva lógica, pero que implemente la misma interface.<br />
<br />
¿Qué ocurre si el dueño decide que la nueva forma es aún peor que la anterior y quiere que regresemos de inmediato a al proceso anterior? Si simplemente modificamos la clase existente, no podremos hacer esto.<br />
<br />
Por lo tanto, la implementación de este patrón ofrece una serie de ventajas en este caso.<br />
<br />
Ocurre lo mismo con las estrategias B y C. No entraré en detalles de estas estrategias, pero creo que es importante explicar un poco lo que verás en el código.<br />
<br />
En la <span class="negritas">Estrategia B</span>, uso una <span class="codigo">Factory Method</span> para <span class="clase">Computadora</span>. Tenemos dos tipos de computadoras: <span class="clase">Laptop</span> y <span class="clase">Servidor</span>, lo que queremos es obtener nuevas instancias de alguna de los dos tipos, pero con diferentes características de memoria RAM, velocidad del procesador, y Sistema Operativo. La fábrica se encargará de crear e inicializar la <span class="clase">Computadora</span> del tipo correspondiente, establecer los valores de sus atributos, y regresarlas al código que la necesita. Tendremos dos fábricas concretas, una para Laptops y otra para Servidores.<br />
<br />
Recordemos que este es el diagrama de la estrategia B:<br />
<br />
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEg2tq7V9_pEucto9F9wzaa9QI2mMJna3CfCTI8jne7eicKorecaHOXWN4Vhqf3nfOFxzzZCZSYDUoYPCTncd5oYQXsUq707lc1bS23cngxLKFqNsQSw_ajlm3Y2miG6QxKdTVnfRTkmddNEcN7T3fiA_iSF4qM-K9zYuNmIEdP1CncpYO0qHy9mMyS9nQ=s944" style="display: block; padding: 1em 0px; text-align: center;"><img alt="" border="0" data-original-height="552" data-original-width="944" src="https://blogger.googleusercontent.com/img/a/AVvXsEg2tq7V9_pEucto9F9wzaa9QI2mMJna3CfCTI8jne7eicKorecaHOXWN4Vhqf3nfOFxzzZCZSYDUoYPCTncd5oYQXsUq707lc1bS23cngxLKFqNsQSw_ajlm3Y2miG6QxKdTVnfRTkmddNEcN7T3fiA_iSF4qM-K9zYuNmIEdP1CncpYO0qHy9mMyS9nQ=s600" width="600" /></a></div><br />
<br />
Y este es el de la implementación particular que estamos realizando.<br />
<br />
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEjFgHtZaYonfc7gBBI71nBRkb6R2ZTEcNpagaq1eVXsk_A8HoQ5x7rijJ97oKZ5GxKhELMrazlpoqNZHxyji6qHED3hpL1R1kFBdzsYDJ8ABtZ4FZpLK4jZLKoiej5FEJCWFlm94AhF6AErQ9ixAIMK_5F98wvnhCrUjMi1fNzVSFqjhP__Qk9s9qxeZQ=s944" style="display: block; padding: 1em 0px; text-align: center;"><img alt="" border="0" data-original-height="552" data-original-width="944" src="https://blogger.googleusercontent.com/img/a/AVvXsEjFgHtZaYonfc7gBBI71nBRkb6R2ZTEcNpagaq1eVXsk_A8HoQ5x7rijJ97oKZ5GxKhELMrazlpoqNZHxyji6qHED3hpL1R1kFBdzsYDJ8ABtZ4FZpLK4jZLKoiej5FEJCWFlm94AhF6AErQ9ixAIMK_5F98wvnhCrUjMi1fNzVSFqjhP__Qk9s9qxeZQ=s600" width="600" /></a></div><br />
<br />
Para la <span class="negritas">Estrategia C</span> tengo una clase base abstracta <span class="clase">Documento</span> y dos clases derivadas: <span class="clase">Reporte</span> y <span class="clase">Resumen</span>. <span class="clase">Documento</span> sirve como la fábrica y regresará uno de los dos subtipos, de acuerdo con un parámetro proporcionado en el método. Cada tipo de documento contiene diferentes secciones o <span class="clase">Pagina</span>s.<br />
<br />
Este es el diagrama general de la estrategia C:<br />
<br />
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEghuiBcmzxcQwPhAvn4Zzlx79c_PMIFSV7RFJyDBLiimygOh31Zv86KgHM6goAnUBr9nvNNPLMGp0XtnLIEUmmVFpJr_vjbTFOKVN_mToFZpSh5E5rxF6pt592DtsKO2U8QzoJSovs9sRCWIXypE56rDXpSH92KrJgKc7Hv0AcVsXbKPAOVcYgm1QB37w=s612" style="display: block; padding: 1em 0px; text-align: center;"><img alt="" border="0" data-original-height="612" data-original-width="420" height="400" src="https://blogger.googleusercontent.com/img/a/AVvXsEghuiBcmzxcQwPhAvn4Zzlx79c_PMIFSV7RFJyDBLiimygOh31Zv86KgHM6goAnUBr9nvNNPLMGp0XtnLIEUmmVFpJr_vjbTFOKVN_mToFZpSh5E5rxF6pt592DtsKO2U8QzoJSovs9sRCWIXypE56rDXpSH92KrJgKc7Hv0AcVsXbKPAOVcYgm1QB37w=w275-h400" width="275" /></a></div><br />
<br />
Y este el de nuestra implementación particular.<br />
<br />
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEjPUib4Bis4u1oR2y8m1Fd0ahupRVM8s0s3eco0o3RwR42lRPmTdNdyHoGniyL2quEvEtU9Z2mb9f3TsugsiSroOZmHFrv6gQavIRbZhExHUn1S7p3Ef7NEdLkU-eBD6smjytNAeHseP64BT7uz21C_1Ur88Tsi0tIyF8TRKeDKqnQEq_E5VgJ7fzYK4A=s612" style="display: block; padding: 1em 0px; text-align: center;"><img alt="" border="0" data-original-height="612" data-original-width="412" height="400" src="https://blogger.googleusercontent.com/img/a/AVvXsEjPUib4Bis4u1oR2y8m1Fd0ahupRVM8s0s3eco0o3RwR42lRPmTdNdyHoGniyL2quEvEtU9Z2mb9f3TsugsiSroOZmHFrv6gQavIRbZhExHUn1S7p3Ef7NEdLkU-eBD6smjytNAeHseP64BT7uz21C_1Ur88Tsi0tIyF8TRKeDKqnQEq_E5VgJ7fzYK4A=s400" /></a></div></div><div style="text-align: justify;"><br />
<br /><h2 class="titulo"><a class="anchorTitle" name="ventajasydesventajas" href="#ventajasydesventajas">
<svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg>
Ventajas y Desventajas</a></h2>
<ul><li>➕Separa el código con la lógica de la construcción de objetos que
pertenecen a una misma familiar, del código que hace uso de esos objetos
y la encapsula en un solo punto. Esto ayuda a tener un bajo
acomplamiento entre las clases, lo que beneficia al mantenimiento; si
agregamos nuevas subclases, solo debemos agregar la lógica de la
creación en un solo lugar.</li><li>➕El cliente no necesita conocer los detalles de la subclase que está
recibiendo, lo único que necesita conocer es la clase base. Esto ayuda a
que proporcionemos una instancia diferente en caso de ser necesario o
como parte de una mejora, sin que el código deba sufrir modificaciones
por este cambio.</li><li>➕Genera un bajo acoplamiento al hacer uso del polimorfismo.</li><li>➖Debe ser usada para una familia de objetos. Si las clases no extienden una clase base común no pueden ser creadas usando <span class="codigo">Factory Method</span>.</li></ul>
</div><div style="text-align: justify;"></div><div style="text-align: justify;"></div><div style="text-align: justify;"></div><div style="text-align: justify;"></div><div style="text-align: justify;"><br />
<br /><h2 class="titulo"><a class="anchorTitle" name="otrosnombres" href="#otrosnombres"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg>
Otros nombres</a></h2>
Este patrón también podemos encontrarlo con los siguientes nombres:
<ol>
<li>Factory (sin el Method).</li>
<li>Simple Factory</li>
<li>Factory Pattern.</li>
<li>Virtual Constructor.</li>
</ol>
Recuerda que Factory (o Simple Factory) hacen referencia a una variación del patrón que expliqué al inicio del tutorial; pero muchas veces se usan (erróneamente) como sinónimos de <span class="codigo">Factory Method</span>.<br /><br /><br />
<br /><h2 class="titulo"><a class="anchorTitle" name="conclusion" href="#conclusion"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg>
Conclusión</a></h2>
Como pudiste ver, este es un patrón de diseño muy fácil de implementar y muy útil. Al implementar cualquiera de las estrategias que vimos realmente ayuda a mantener el código de una forma más estructurado, flexible y menos acoplado.<br />
<br />
Como nota adicional, en el repositorio dejo, además del código, el archivo original en draw.io con los esquemas de los patrones.<br />
<br />
Espero que este tutorial les sea de utilidad. Si tienen alguna duda, sugerencia, comentario o aclaración, pueden dejarla en la sección de comentarios o enviar un correo a <a href="mailto:programadorjavablog@gmail.com">programadorjavablog@gmail.com</a> (pueden agregarme al chat de gmail). También pueden seguir JavaTutoriales en las siguientes redes sociales:
<ul>
<li><a href="https://www.facebook.com/JavaTutoriales/" target="_blank">Facebook</a></li>
<li><a href="https://twitter.com/JavaTutoriales" target="_blank">Twitter</a></li>
</ul>
Saludos y gracias.<br /><br />
<span class="negritas">Descarga los archivos de este tutorial desde mi repositorio en GitHub</span>
<ul>
<li><a href="https://github.com/JavaTutoriales/FactoryMethod" target="_blank">Patrón de diseño Factory Method</a>.</li>
</ul>
<br />
<span class="negritas">Entradas relacionadas</span>
<ul>
<li><a href="https://www.javatutoriales.com/2021/11/introduccion-los-patrones-de-diseno.html" target="_blank">Introducción a los patrones de diseño</a>.</li>
<li><a href="https://www.javatutoriales.com/2021/12/patron-de-diseno-abstract-factory.html" target="_blank">Patrón de diseño Abstract Factory</a>.</li>
<li><a href="https://www.javatutoriales.com/2021/12/diferencias-simple-factory-vs-factory.html" target="_blank">Diferencias: Simple Factory vs. Factory Method vs. Abstrac Factory</a>.</li>
</ul>
<br />
</div>Alexhttp://www.blogger.com/profile/06974037481671868076noreply@blogger.comtag:blogger.com,1999:blog-2012232069289957092.post-57486628343658333262021-11-26T19:26:00.002-08:002022-01-01T01:40:26.175-08:00Recomendaciones de qué debe incluir un buen mensaje de commit al repositorio de código<div style="text-align: justify;">
<img src="https://img.shields.io/badge/JT-Git-blue" /><br />
<br />
Hoy en día, al escribir una aplicación, ya sea solo o en equipo, lo más normal es que subamos nuestro código a un repositorio (publico o privado).</div><div style="text-align: justify;"> </div><div style="text-align: justify;">Git es actualmente la opción más popular, y algunos sitios como <a href="https://github.com/" target="_blank">GitHub </a>y <a href="https://about.gitlab.com/" target="_blank">GitLab </a>ofrecen espacio gratuito de almacenamiento (con ciertas restricciones y condiciones, pero gratuito).</div><div style="text-align: justify;"> </div><div style="text-align: justify;"> Al momento de inicializar un repositorio, o hacer alguna modificación, hay un paso importante y al que normalmente no le prestamos mucha atención: Escribir un mensaje de las modificaciones que hemos hecho a la aplicación y que van incluidas en ese <i>commit</i> particular. Algunas veces lo dejamos de lado (escribimos cualquier cosa), porque pensamos que es un mensaje que no volveremos a leer nunca más. Sin embargo, cuando ocurre un problema o queremos saber por qué se hizo una modificación en particular, el escribir un buen mensaje puede ser la diferencia entre pasar <b>horas </b>revisando el código antes y después del <i>commit</i>, o el pasar solo unos <b>segundos </b>leyendo el mensaje para entender qué fue lo que ocurrió.</div><div style="text-align: justify;"><br /></div><div style="text-align: justify;">En este artículo te daré algunas recomendaciones de qué debe incluir y cómo estructurar ese mensaje para que, además de ser útil, no pases una vida pensando en qué escribir.</div><div style="text-align: justify;"><br /></div>
<a name='more'></a>
<div style="text-align: justify;">Antes de comenzar, debo decir que estas no son ideas mías, sino que me baso en otros artículos que he visto y en la forma en la que algunos repositorios de proyectos populares recomiendan escribir estos mensajes. Principalmente en <a href="https://chris.beams.io/posts/git-commit/" target="_blank">este artículo</a> escrito por Chris Beams en 2014, y el cual ha sido descaradamente copiado y traducido muchas veces sin dar crédito al autor original. </div><div style="text-align: justify;"><br /></div><div style="text-align: justify;">Dentro del artículo Chris señala algunos ejemplos de repositorios (como el Kernel de Linux o Spring Boot), los cuales tienen muy buenos comentarios en sus <i>commits</i>; por lo que, aunque no estés involucrado en el proyecto, puedes entender qué cambios se están incluyendo. Te dejo un par de ejemplos de los mismos, tomados directamente de los repositorios.</div><div style="text-align: justify;"><br /></div><div style="text-align: justify;"></div><div style="text-align: justify;"></div><div style="text-align: justify;"></div><div style="text-align: justify;"></div><div style="text-align: justify;"></div><div style="text-align: justify;"></div><div style="text-align: justify;"></div><div style="text-align: justify;"></div><div style="text-align: justify;"></div><div style="text-align: justify;"></div><div class="separator" style="clear: both; text-align: center;"></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh8xEX5pf_v3yHV17DT4yfG0YBDwC8DhiwmucGhGtRF8yTfr3P0Qzc6U-chkt1ma7FX5kgrjsl2JcpUS-WWFAvdrPThUSkGUFgbgOkFxQK14pAxOup1_d6NKrDGmKy-UFANIspFIx-HOt1L/s528/ejemplocommit2.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="185" data-original-width="528" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh8xEX5pf_v3yHV17DT4yfG0YBDwC8DhiwmucGhGtRF8yTfr3P0Qzc6U-chkt1ma7FX5kgrjsl2JcpUS-WWFAvdrPThUSkGUFgbgOkFxQK14pAxOup1_d6NKrDGmKy-UFANIspFIx-HOt1L/s16000/ejemplocommit2.png" /></a></div><br /><div class="separator" style="clear: both; text-align: center;"></div><div class="separator" style="clear: both; text-align: center;"></div><div class="separator" style="clear: both; text-align: center;"></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg79qHFh4WL19ZXURWiEALa8jtQKHljGTxQ3JnhYIRL3FGV9_P1VyC4-XAe7erNyCRR4cyG_lM_F6OJ6lwZV9e6HzeoxV9tR04A_N2FqamYXZHgl6SzLVsI3velfJjDxRQHI3n6FwlXf_Il/s581/ejemplocommit1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="155" data-original-width="581" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg79qHFh4WL19ZXURWiEALa8jtQKHljGTxQ3JnhYIRL3FGV9_P1VyC4-XAe7erNyCRR4cyG_lM_F6OJ6lwZV9e6HzeoxV9tR04A_N2FqamYXZHgl6SzLVsI3velfJjDxRQHI3n6FwlXf_Il/s16000/ejemplocommit1.png" /></a></div><br /><div style="text-align: justify;"><br />
Como puedes ver, ambos comentarios tienen una estructura muy parecida, que consta de los siguientes elementos:</div><div style="text-align: justify;"><br /></div><ol style="text-align: left;"><li><b>Título</b>. El cual es una descripción breve, se recomiendan menos de 50 caracteres, y proporcione un contexto de qué es lo que se incluye en el <i>commit</i>.</li><li><b>Cuerpo</b>. Aquí, se explica el<b> por qué</b> o <b>para qué</b> del cambio, <b>NO el cómo</b>. Este es un error muy frecuente, explicamos todos los cambios que hicimos en el código pero nunca decimos para qué hicimos esos cambios. También, se debe indicar si este cambio genera algunos efectos secundarios o alguna otra consecuencia que no sea obvia.<br /><br />Si necesitas explicar una serie de cosas o listar algunos elementos, se recomienda usar guiones medios o asteriscos.<br /><br /></li><li><b>Pie</b>. Información adicional o meta información. Si estás usando algún sistema de seguimiento de tareas como Jira, este es el lugar para indicar el número de los elementos de trabajo o <i>tickets </i>que se afectan o cierran con este <i>commit</i>. </li></ol><div style="text-align: justify;"><br /></div><div style="text-align: justify;">Como puedes ver en el ejemplo anterior, aquí también puedes indicar quién está validando los cambios (o haciendo revisiones de código).</div><div style="text-align: justify;"><br /></div><div style="text-align: justify;">Chris da 7 recomendaciones sobre el estilo "gráfico" del mensaje:</div><div style="text-align: justify;"><br /></div><div style="text-align: justify;"><ol><li>Separar el título del cuerpo del mensaje usando una línea en blanco. </li><li>Limitar el título a 50 caracteres.</li><li>Colocar la primera línea del título en mayúscula.</li><li>No terminar el título con punto.</li><li>Escribir el título usando el tiempo imperativo.</li><li>Escribe el cuerpo usando líneas de 72 caracteres.</li><li>Usa el cuerpo para explicar el qué y por qué y no el cómo.</li></ol><p> Si el título es suficiente para entender los cambios en el <i>commit</i>, el cuerpo se puede excluir.</p><p> Estos son algunos ejemplos de mensajes de <i>commit </i>que siguen estas recomendaciones:</p><p> </p><span style="color: #0b5394;"><span style="font-family: courier;"><span style="font-size: medium;"><b>Simplifica el proceso de validación de información del usuario<br /> <br />Elimina la información innecesaria para crear la cuenta del usuario.<br />Con este cambio, la información obligatoria se limita a:<br />- Nombre <br />- Correo electrónico<br />- Avatar o Foto<br />Si se requiere información adicional esta se solicita en el momento <br />que se use, si es que no existe previamente. A los usuarios que <br />completen toda la información (obligatoria y opcional), se les <br />premia con 10 puntos de recompensa.<br /> <br />Resuelve: #1234<br />Impacta en: #567 y #890</b></span></span></span><p><br /></p><p>Claro, las anteriores son solo <b><u>recomendaciones </u></b>que deben ser ajustadas dependiendo de las necesidades particulares del equipo de trabajo y no deben ser tomadas como reglas o limitantes. Por ejemplo, el problema más típico que me he topado es con las recomendaciones del número de caracteres. En general, el español es un idioma en el que las palabras son más largas que en inglés y donde no usamos tantas abreviaturas, así que si 50 no es suficiente, no temas aumentar este límite a, por ejemplo, 60 0 65.</p><p>Un ejemplo de esto último:</p><p><span style="font-size: medium;"><b><span style="color: #0b5394;"><span style="font-family: courier;">Add validations in the mandatory user account fields</span></span></b></span> -> 53 caracteres.</p><p><span style="font-size: medium;"><span style="color: #0b5394;"><span style="font-family: courier;"><b>Agrega validaciones en los campos obligatorios de la cuenta del usuario</b></span></span></span> -> 72 caracteres</p><p> </p><p>Chris explica el por qué de estas reglas dentro de su artículo. </p><p><br /></p><p>Además de esto, en algunos repositorios, como el de <a href="https://github.com/angular/angular/blob/22b96b9/CONTRIBUTING.md#-commit-message-guidelines" target="_blank">Angular</a>, los lineamientos indican dividir el título en dos partes:</p><p>1. El <b>tipo </b>de cambio en el <i>commit</i>. Usar un prefijo para indicar si estamos agregando, eliminando, refactorizando, etc.</p><p>2. El <b>alcance </b>del cambio. Animaciones, documentos, formularios, etc.</p><p><br />Esto es algo que se conoce como <i>commits</i> o títulos <b>semánticos </b><br /><br />Así, el mensaje título del mensaje anterior podrá quedar:</p><p><b><span style="color: #0b5394;"><span style="font-family: courier;"><span style="font-size: medium;">Agrega (formulario): validaciones en los campos obligatorios de la cuenta del usuario</span></span></span></b></p><p><br /></p><p>Si en tu equipo de trabajo deciden que el tipo debe escribirse, por ejemplo, en inglés y mayúsculas, el título puede quedar así:</p><p><span style="font-family: courier;"><span style="font-size: medium;"><span style="color: #0b5394;"><b>ADD (form): validaciones en los campos obligatorios de la cuenta del usuario</b></span></span></span></p><p> </p><p>En algunos repositorios incluso agregan algunos emoji para hacer aún más rápida la identificación del tipo de cambio:</p><p>➕ <b><span style="color: #0b5394;"><span style="font-family: courier;"><span style="font-size: medium;">ADD (form): valida los campos obligatorios de la cuenta del usuario</span></span></span></b></p><p>⚙ <span style="color: #0b5394;"><span style="font-family: courier;"><span style="font-size: medium;"><b>CONF: mueve la configuración de archivos a base de datos</b></span></span></span></p><p>📕<span style="font-size: medium;"><span style="font-family: courier;"><span style="color: #0b5394;"><b>DOC (cart): actualiza la documentación del servicio de carro de compra </b></span></span></span></p><p><br /></p><p>En otros repositorios, la convención es colocar en la primera parte del título el número del ticket o elemento de trabajo, y despúes una descripción:</p><p><span style="color: #0b5394;"><span style="font-family: courier;"><span style="font-size: medium;"><b>#879: Agrega validaciones en el los campos obligatorios de la cuenta del usuario</b></span></span></span><br /></p><p>Nuevamente, esto puedes ajustarlo de acuerdo con las convenciones definidas por tu equipo de trabajo.</p><p>Para terminar, y esto ya no está relacionado directamente con estas sugerencias. Si aún estás aprendiendo o quieres mejorar tus habilidades en Git, recuerda que el libro <a href="https://git-scm.com/book/en/v2" target="_blank"><i>Pro Git</i></a> es la fuente oficial y actualizada sobre el uso de Git (además de que es público y gratuito).<br /></p><p> </p><p>Espero que estas sugerencias te sean útiles y ayuden a escribir cada día mejores mensajes para tus <i>commits</i>, y en general para la información de los cambios en tu proyecto.<br /></p><p>¿Tienes alguna otra recomendación que te haya funcionado y quieras compartir? Puedes hacerlo aquí o colocarlo en mis redes sociales para compartirlo con la comunidad.</p><p> Espero que este tutorial les sea de utilidad. Si tienen alguna duda,
sugerencia, comentario o aclaración, pueden dejarla en la sección de
comentarios o enviar un correo a <a href="mailto:programadorjavablog@gmail.com">programadorjavablog@gmail.com</a> (pueden agregarme al chat de gmail). También pueden seguir <span class="codigo">JavaTutoriales</span> en las siguientes redes sociales:<br />
</p><ul><li><span class="codigo"><a href="https://www.facebook.com/JavaTutoriales/" target="_blank">Facebook</a></span></li><li><span class="codigo"><a href="https://twitter.com/JavaTutoriales" target="_blank">Twitter</a></span></li></ul><br />
Saludos y gracias.<p> </p><p><br /></p><p> </p></div>
Alexhttp://www.blogger.com/profile/06974037481671868076noreply@blogger.comtag:blogger.com,1999:blog-2012232069289957092.post-53610244029154228002021-11-13T20:07:00.022-08:002022-03-03T03:10:34.299-08:00Introducción a los patrones de diseño<a href="https://www.javatutoriales.com/search/label/patrones%20de%20dise%C3%B1o" target="_blank"><img src="https://img.shields.io/badge/JT-Design%20Patterns-orange" /></a><br />
<br />
Con este artículo comienza una nueva serie, que será el inicio de varias otras nuevas series, las cuales hablarán de diversas formas de mejorar la calidad de nuestro código, con el objetivo de hacerlo más mantenible, flexible, entendible, y mejor estructurado.<br />
<br />
Con estas series busco brindarte una base formal que te ayude a afrontar problemas complejos y te prepare mejor para desarrollar tu carrera profesional.<br /><br />
En esa serie te explicaré qué son los <span class="negritas">patrones de diseño</span> y hablaré de los patrones de diseño más comunes, y te mostraré diversas formas o estratégias para implementar cada uno de ellos. <br />
<br />
En este primer artículo explicaré rápidamente qué son los patrones de diseño, cómo se clasifican, junto con una lista que se irá actualizando conforme vaya agregando más artículos. <br />
<br />
Sin más, vamos al tema.<br /><br />
<br />
<a name='more'></a>
<br /><h2 class="titulo"><a class="anchorTitle" href="#introduccion" name="introduccion">
<svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z" fill-rule="evenodd"></path></svg>
¿Qué son los patrones de diseño y por qué son importantes?</a></h2>
Cuando nos enfrentamos a un problema, pensamos en distintas soluciones para resolverlo, incluyendo <span class="negritas">soluciones exitosas que hemos aplicado en el pasado</span> para el mismo o un problema similar. <br />
<br />
Lo primero que debes saber cuando tratas de resolver un problema es que es muy probable que alguien más ya haya resuelto ese problema. No solo eso, sino que es probable que muchas personas ya hayan resuelto ese problema, hayan hablado de eso con sus colegas, en reuniones, en la cafetería, en foros, eventos especializados, etc.;que al final, se hayan dado cuenta de que es un problema común, hayan intercambiado distintas experiencias sobre cómo fue resuelto, esas experiencias se hayan documentado, recopilado, que varios otros miembros de la comunidad haya descartado algunas de esas soluciones, hayan probado varias de las soluciones no descartadas, y que al final la comunidad haya decidido cual o cuales son las mejores formas de resolver ese problema; se le haya dado un nombre, se haya clasificado junto con otros problemas similares, se hayan escrito libros, dado conferencias y cursos sobre estas mejores formas de resolver los problemas, se hayan hecho una o dos películas y documentales, etc., etc.<br />
<br />
Los patrones de diseño entran dentro de la larga explicación del párrafo anterior (excepto, obviamente, por las películas y documentales; y sí, a mí también me frustra que haya <a href="https://www.imdb.com/title/tt4877122/" target="_blank">una película sobre emojis</a>, pero no sobre patrones de diseño). La gran ventaja que esto nos da es que no tenemos que empezar a experimentar desde cero una nueva solución, podemos buscar nuestro problema dentro de un catálogo y usar la solución que mejor se adapte a nuestro contexto particular. De esta forma reutilizamos la sabiduría de los que se enfrentaron al problema antes de nosotros. <br />
<br />
Para explicarlo de una forma más sencilla: Los patrones de diseño son la <span class="negritas">abstracción de soluciones a alto nivel</span>, probadas y reutilizables para problemas comunes en programación. El aplicarlos nos evitan el tener todos los problemas que conlleva reinventar la rueda. En resumen: los patrones de diseño son soluciones reutilizables a problemas que ocurren con frecuencia cuando se diseña y desarrolla una aplicación. Cuando digo que son una abstracción de una solución a alto nivel me refiero a que podemos considerarlos unos planos pre-creados que se aplican para solucionar un problema particular; no son piezas de código terminadas que puedan ser aplicadas directamente en nuestra aplicación, sino como plantillas o descripciones que pueden darnos una idea (o la inspiración) de cómo resolver el problema. Es por esto que un mismo patrón aplicado a dos escenarios diferentes resultará en que la implementación del patrón sea diferente.<br />
<br />
Los patrones de diseño no han dejado de ser vigentes desde que fueron definidos hace ya muchos años, ya que su uso nos ayuda a escribir código de forma más limpia y modular. Cada patrón de diseño gira al rededor de un concepto fundamental que es la base para lograr un buen diseño: <span class="negritas">no modificar el código existente</span>. Una vez que modificamos código que ya funciona, existe una alta posibilidad de que introduzcamos nuevos defectos y resultados no deseados. Lo que resulta en ciclos de pruebas más largos y exhaustivos para asegurar que nada se ha roto.<br />
<br />
Estos patrones ayudan a minimizar la duplicación de código, prevenir el acoplamiento entre clases y estandarizar la forma de escribir código.Con estos principios en mente se busca <span class="negritas">escribir código flexible y reutilizable</span>.
<br />
<br />
<br /><h2 class="titulo"><a class="anchorTitle" href="#historia" name="historia">
<svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z" fill-rule="evenodd"></path></svg>
Un poco de historia</a></h2>
La historia de los patrones de diseño comienza en 1977, pero no en el ámbito de software, sino en el de la ingeniería civil. En este año Christopher Alexander publica el libro "<a href="https://www.amazon.com/Pattern-Language-Buildings-Construction-Environmental/dp/0195019199" target="_blank">A Pattern Language Towns, Buildings, Construction</a>". En este libro en lugar de hablar sobre los detalles de la construcción, Alexander se centra en los problemas comunes que enfrenta un trabajo de arquitectura. Estos problemas los explica de la forma más general posible, capturando solo su esencia. Con esto, logró que el libro hable de problemas generales y atemporales. En este libro también habla de las relaciones entre estos problemas. <br />
<br />
¿Qué tiene que ver esto con el software? Pues bien, en 1994 un grupo de 4 sujetos: Erich Gamma, Richard Helm, Ralph Johnson, y John Vlissides tomaron esta idea de patrones para poder aplicarla al mundo del desarrollo de software y publicaron uno de los libros más influyentes en la historia de la programación: "<a href="https://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612" target="_blank">Design Patterns: Elements of Reusable Object-Oriented Software</a>". Cariñosamente nos referimos a este grupo de autores con el nombre de “La banda de los 4” (Gang of Four, en inglés) o simplemente GoF.<br />
<br />
Este libro presenta un catálogo de <span class="negritas">23 problemas</span> que aparecen de forma recurrente cuando construimos aplicaciones usando un lenguaje orientado a objetos; además ofrece una solución general para cada uno de estos problemas. A cada una de estas soluciones, o patrones, les da un nombre y las clasifica dentro de uno de tres tipos. Facilitando con eso la creación de un lenguaje común para todos los programadores.<br />
<br />
De acuerdo con los autores, todos los patrones de diseño se basan en los siguientes principios del diseño orientado a objetos:
<ul>
<li>Programar hacia una interface, no a una implementación.</li>
<li>Favorecer la composición de objetos frente a la herencia de clases.</li>
<li>Determinar qué parte del problema es común y qué parte es variable.</li>
<li>Permitir el reemplazo de la parte variable mediante la interfaz común.</li>
</ul>
Hay que recordar que estos autores no tomaron solo su experiencia para escribir estos patrones. Sino que recopilaron la experiencia de los problemas comunes que observaron en la comunidad y los concentraron en una sola fuente de información.<br />
<br />
Los patrones de diseño se clasifican en 3 grupos:<br />
<ul>
<li><span class="negritas">Creacionales</span>. Diferentes maneras de crear objetos o grupos de objetos. Sí, todos los objetos en Java se crean usando la palabra reservada <span class="clase">new</span>, pero podemos envolver este llamado en otras estructuras que faciliten que otros objetos obtengan una instancia apropiada e inicializada de forma correcta. Esto ayuda a ocultar los detalles de cómo los objetos son creados o inicializados.</li>
<li><span class="negritas">Estructurales</span>. Diferentes formas de crear la estructura de las clases; por ejemplo, usando herencia y composición para crear objetos grandes y complejos a través de otros más simples.</li>
<li><span class="negritas">De comportamiento</span>. Formas en las que podemos lograr una mejor interacción entre objetos para lograr un bajo acoplamiento y flexibilidad.</li>
</ul>
Cado uno de los grupos anteriores se divide en dos diferentes ámbitos: Clases y objetos. <br />
<br />
Los patrones de diseño con ámbito de <span class="negritas">Clase</span> indican la forma en que las clases se relacionan con sus subclases a través de <span class="negritas">la herencia</span>. En estos patrones los elementos que usará la aplicación se conocen o resuelven en tiempo de <span class="negritas">compilación</span>; esto quiere decir que no se modifican a lo largo de la ejecución del programa ya que su estructura es estática. Puedes recordar este alcance usando la frase: <span class="negritas">Los elementos se conocen en tiempo de compilación</span>. <br />
<br />
En los patrones de diseño con ámbito de <span class="negritas">Objeto</span> se solucionan problemas usando instancias de diferentes clases polimórficas que pueden ser intercambiadas en tiempo de ejecución. Esto quiere decir que podemos usar una instancia en la ejecución de la aplicación, y luego cambiarla por una instancia de una clase diferente (que tenga la misma clase base que la anterior) en otro momento; por lo tanto, no todos los elementos se resuelven en tiempo de compilación; la estructura de estos patrones es dinámica en ejecución y se logra a través de la <span class="negritas">composición</span>. Puedes recordar este alcance usando la frase: <span class="negritas">Pueden ser cambiandos en tiempo de ejecución</span>.<br />
<br />
Existe un patrón que tiene los dos ámbitos; esto quiere decir que dependiendo de cómo lo implementemos podemos usarlo a nivel de clase o a nivel de objeto.<br />
<br />
En la lista de patrones que está más abajo podrás ver que la mayoría de los patrones trabajan en el ámbito de los objetos. El entender el álcance o ámbito de un patrón nos ayuda también a tener más elementos para saber si un patrón particular los puede ayudar a no a resolver el problema que estemos enfrentando.<br />
<br />
Con esto, logramos tener 6 subclasificaciones:
<ul>
<li>Creacionales</li>
<ul>
<li>Clases</li>
<li>Objetos</li>
</ul>
<li>Estructurales</li>
<ul>
<li>Clases</li>
<li>Objetos</li>
</ul>
<li>De Comportamiento</li>
<ul>
<li>Clases</li>
<li>Objetos</li>
</ul>
</ul><p> A continuación, te dejo una lista de los patrones contenidos en este libro. A lo largo de esta serie iremos desarrollando la explicación de cada uno de estos patrones.<br /><br />
</p><table>
<thead>
<tr><th>Clasificación</th><th>Clase</th><th>Objeto</th></tr>
</thead><tbody>
<tr>
<td>Creacionales</td>
<td>
<ul>
<li><a href="https://www.javatutoriales.com/2021/12/patron-de-diseno-factory-method.html" target="_blank">Factory Method</a></li>
</ul>
</td>
<td>
<ul>
<li><a href="https://www.javatutoriales.com/2021/12/patron-de-diseno-abstract-factory.html" target="_blank">Abstract Factory</a></li>
<li><a href="https://www.javatutoriales.com/2022/03/patron-de-diseno-builder.html" target="_blank">Builder</a></li>
<li>Prototype</li>
<li>Singleton</li>
</ul>
</td>
</tr>
<tr>
<td>
Estructurales
</td>
<td>
<ul>
<li><a href="https://www.javatutoriales.com/2022/01/patron-de-diseno-adapter.html" target="_blank">Adapter</a></li>
</ul>
</td>
<td>
<ul>
<li><a href="https://www.javatutoriales.com/2022/01/patron-de-diseno-adapter.html" target="_blank">Adapter</a> (sí, este patrón puede usarse en los dos ámbitos)</li>
<li>Bridge</li>
<li>Composite</li>
<li>Decorator</li>
<li>Facade</li>
<li>Flyweight</li>
<li>Proxy</li>
</ul>
</td></tr>
<tr>
<td>
De comportamiento
</td>
<td>
<ul>
<li>Interpreter</li>
<li><a href="https://www.javatutoriales.com/2022/01/patron-de-diseno-template-method.html" target="_blank">Template Method</a></li>
</ul>
</td>
<td>
<ul>
<li>Chain of Resposibility</li>
<li>Commander</li>
<li>Iterator</li>
<li>Mediator</li>
<li>Memento</li>
<li>Observer</li>
<li>State</li>
<li><a href="https://www.javatutoriales.com/2022/01/patron-de-diseno-strategy.html" target="_blank">Strategy</a></li>
<li>Visitor</li>
</ul>
</td>
</tr>
</tbody>
</table><p><br />
<span class="negritas">¿Son estos los únicos patrones que existen?</span> No, cada tecnología, lenguaje de programación, o incluso framework, puede definir sus propios patrones de diseño. En esta serie veremos los patrones más comunes y, dependiendo de la respuesta y necesidades, veré si es necesario que entremos en patrones más específicos. También es importante entender que esos patrones de diseño se usan en aplicaciones que usan el paradigma orientado a objetos. Diferentes paradigmas tienen una serie diferente de patrones.<br />
<br />
Si quieres ver una lista más o menos completa de patrones de diseño aplicables a Java, puedes consultarlo en esta liga: <a href="https://java-design-patterns.com/patterns/" target="_blank">https://java-design-patterns.com/patterns/</a><br />
<br />
<span class="negritas">¿Son estas las únicas formas de clasificar los patrones de diseño?</span> Igual que la respuesta anterior: No. Existen varias formas de clasificarlos; la anterior es la forma más común o popular, pero no es la única. Para da un breve ejemplo, en JavaEE (ahora JakartaEE) los patrones se dividen dependiendo de la capa de la aplicación en donde pueden usarse.<br />
<br />
Para dar un ejemplo rápido de lo anterior, en JavaEE las aplicaciones se dividen en 5 capas, y se tiene un catálogo de 21 patrones de diseño que aplican para las tres capas intermedias.</p><p> </p><p><img alt="" src="data:image/png;base64,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zbRjpVX64nGUdq6sv0uz8sIpyy/tePfu3ctMX48ePQCoUaMGbm5upcZVnn+VXX5PCyBFhUTysIAUFVUGkKLiUfMrMTERRVFYsWIFubm53L17lxs3bvDxxx+jKAonT54UdWvfvn1ERkaSn58PICp8VFQUkZGRREZGcv78eZH3AMePHxfntN+YTCaioqI4e/asGLx3+vRp9uzZQ0pKiogjMjKS9PR0AK5evSrCycrKsok/OjqayMhICgsLS7y08vLyyMnJITc3lxYtWtCiRQtyc3PJyckhLy8PVVXZvHkzx48fF+GlpqYSGRlJQkKCiOfy5cs2fmVmZsoX5CPUMykqJJKHA6SoqDKAFBWPml+aqCi+noejoyOKonDs2DEAwsPDxRf+F198ISr7jBkzbL7+X331VX755RfAIkKsz40aNQqwzAZQFEW0DgAMGjQInU7H8uXLURSFjh07oigKW7du5fbt2zRt2lSE8/7773Pz5k0AQkNDxXEnJyfAtsvG2t59913efffdEveM9W9TU1Np0aIFiqLwwgsvEB8fD8C8efNQFIXPP/8cRVGESKrsMnwaACkqJJKHBaSoqDKAFBWPml+aqKhVqxb16tWjcePGNG/enOeff55x48aRl5fHtm3bUBSFZcuWERERQZ06dejduzcAOp2OFi1aEBERwY4dO3jppZf46aefSE5O5q9//Su1a9fm6NGjuLi4oCgKU6ZMQVVVFEXB09NT1NuhQ4fy0ksvERISgqIo1K9fn6NHj5KRkUHz5s1RFIXNmzcTFBSEoiikpaWxZcsWFEVh1apVREREUKtWLQYNGlRq2QO88847vPPOO+K8dsMqisKQIUMAqF+/Ph07diQiIgJPT09q1arF7du3+f7771EUhcaNG3P06FGys7MrvfyeFuDBoqIsq2zfKyr9lZ2WquCD5OHLSoqKKgJIUfGo+aWJirfffpuOHTtSq1YtFEXh559/FnVKaz2w5o033gDA3d1dHFu6dKnoLtmzZw+KorB//34RTs2aNcV037JExapVq1AURXSj5OfnoygKEydOFNcajUYAFixYUMKvli1bllr2UL6o+Prrr8X/i3Pjxg1WrFiBoiikpqYKPyq7/J4WoHxRoaoqeXl5hISE8N133wkyMjKe+nxWVZWkpCTOnDnzQEH1e/oQFxfHlStXZEvRUwBIUVFlACkqHjW/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" /> <br /></p>
También tenemos otros tipos de patrones, como los <span class="negritas">arquitectónicos</span> (<span class="codigo">MVC</span>, <span class="codigo">MVVM</span>, <span class="codigo">VIPER</span>, etc). Estos tienen un alcance más amplio ya que consideran la forma de organizar las capas de un sistema. Dependiendo del patrón estas capas variarán en número y objetivo.<br />
<br />
<p><span class="negritas">¿Existe una sola forma de implementar un patrón?</span> A esta altura ya sabes la respuesta: No. Existen diversas formas correctas de implementar un patrón de diseño, a cada una de estas formas se les da el nombre de <span class="negritas">Estrategia</span>.<br /><br />
</p>
<br /><h2 class="titulo"><a class="anchorTitle" href="#definicion" name="definicion">
<svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z" fill-rule="evenodd"></path></svg>
Definiendo un patrón</a></h2>
Recordemos que el objetivo de los patrones de diseño es recopilar problemas y sus soluciones; pero es importante entender dos cosas:
<ol>
<li>No todos los problemas se pueden resolver aplicando algún patrón de diseño. Existen problemas para los cuales no existe, aún, algún patrón de diseño. Esto es muy importante ya que una aplicación no puede ser desarrollada usando <span class="negritas">solo</span> patrones de diseño. Debe ser una combinación entre soluciones generales y soluciones particulares.</li>
<li>Los patrones de diseño <span class="negritas">tienen un contexto donde se pueden aplicar y una solución</span>. Si tratamos de aplicar esta solución en un contexto diferente para el cual el patrón fue concebido, es posible que terminemos con más problemas de los que teníamos inicialmente. ¿Esto quiere decir que el patrón no sirve? No, quiere decir que estamos aplicando un patrón para un problema diferente al que resuelve; dicho de otra manera, lo estamos sacando de su contexto de aplicación.</li>
</ol>
De los dos párrafos anteriores podemos concluir que: Un patrón de diseño representa la relación entre tres elementos: un <span class="negritas">contexto</span>, un <span class="negritas">problema</span> y una <span class="negritas">solución</span>.<br />
<br />
Cuando se explica un patrón de diseño, esta explicación normalmente incluye:
<ul>
<li>Descripción </li>
<li>Escenario de Uso </li>
<li>Solución concreta </li>
<li>Las consecuencias de utilizar este patrón </li>
<li>Ejemplos de implementación </li>
<li>Lista de patrones relacionados</li>
</ul>
<br />
<br /><h2 class="titulo"><a class="anchorTitle" href="#conclusion" name="conclusion">
<svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z" fill-rule="evenodd"></path></svg>
Palabras finales de advertencia sobre el uso (y abuso) de los patrones de diseño</a></h2>
<p>
Aunque esto pueda sonar un poco contradictorio después de toda la explicación anterior, hay que recordar que, aunque el uso de los patrones de diseño es una buena práctica que ayuda a solucionar problemas, el abusar de ellos y no aplicarlos de forma correcta puede traer muchos problemas en el futuro. <br />
<br />
Incluso existe un término para esas personas que intentan aplicar patrones de diseño en todo momento, aún fuera del contexto en el que funcionan, logrando con esto que no se obtenga ningún valor y abusar del uso de patrones de diseño. Este término es: <span class="negritas">Pattern Happy</span>.<br />
<br />
Este término también se aplica a personas que apenas aprender un patrón tratan de aplicarlo a un proyecto sin haber terminado de entender de todo el patrón y en donde no agrega ningún valor.<br />
<br />
Muchas veces se trata a los patrones de diseño como verdades universales. Esto es incorrecto.<br />
<br />
Martin Fowler nos advierte desde 1997 en su libro "<a href="https://martinfowler.com/books/ap.html" target="_blank">Analysis Patterns: Reusable Object Models</a>" otros dos puntos importantes que debemos tener en mente en todo momento al utilizar patrones de diseño: <br /></p><ul>
<li>Los patrones son un punto de partida, no un destino.</li>
<li>Los modelos no están bien o mal, sino que son más útiles o menos útiles.</li>
</ul><p></p><p> Creo que esto es suficiente para una rápida introducción. En el siguiente artículo comenzaremos la aventura de aprender a usar nuestro primer patrón.</p><br />
<span class="negritas">Entradas relacionadas</span>
<ul>
<li><a href="https://www.javatutoriales.com/2021/12/patron-de-diseno-factory-method.html" target="_blank">Patrón de diseño Factory Method</a>.</li>
<li><a href="https://www.javatutoriales.com/2021/12/patron-de-diseno-abstract-factory.html" target="_blank">Patrón de diseño Abstract Factory</a>.</li>
<li><a href="https://www.javatutoriales.com/2021/12/diferencias-simple-factory-vs-factory.html" target="_blank">Diferencias: Simple Factory vs. Factory Method vs. Abstrac Factory</a>.</li>
<li><a href="https://www.javatutoriales.com/2022/01/patron-de-diseno-template-method.html" target="_blank">Patrón de Diseño Template Method</a>.</li>
<li><a href="https://www.javatutoriales.com/2022/01/patron-de-diseno-strategy.html" target="_blank">Patrón de Diseño Strategy</a>.</li>
<li><a href="https://www.javatutoriales.com/2022/01/patron-de-diseno-adapter.html" target="_blank">Patrón de Diseño Adapter</a>.</li>
</ul>
<br />
<br />
<br />Alexhttp://www.blogger.com/profile/06974037481671868076noreply@blogger.comtag:blogger.com,1999:blog-2012232069289957092.post-17733431578673922142019-03-20T05:53:00.002-07:002022-01-01T17:01:21.628-08:00Spring Boot Parte 2 - Externalizando la Configuración de la Aplicación<div style="text-align: justify;">
<img src="https://img.shields.io/badge/JT-Spring-brightgreen" /><br />
<br />
Algo muy importante y fácil de olvidar cuando creamos una aplicación, es que (si todo sale bien), esta vera la luz en un mundo productivo y será ahí cuando comenzará su vida útil, en un mundo fuera de la computadora del desarrollador.<br />
<br />
Antes de llegar a su destino final (el ambiente de producción) una aplicación sana debería pasar por al menos dos ambientes. El primero es <span class="negritas">el ambiente del desarrollador</span>, la computadora o computadoras en las que nunca nada falla ni sale mal :). El segundo ambiente debería ser <span class="negritas">el ambiente de integración continua</span>; aquí será una computadora configurada en la que la aplicación se compilará y se ejecutarán una serie de pruebas automáticas que van desde las pruebas funcionales, hasta pruebas de carga y estrés. El tercer ambiente es un <span class="negritas">ambiente de testing</span>, aquí un profesional altamente capacitado (el tester) hará su mayor esfuerzo por hacer que nuestra aplicación falle o "se rompa" en formas que nunca imaginamos al momento de escribir nuestro código productivo o de pruebas unitarias.<br />
<br />
Una vez que nuestra aplicación sobrevive en los ambientes anteriores, llegará finalmente <span class="negritas">al ambiente productivo</span>. En este ambiente es donde pasará su vida útil sirviendo a usuarios que no estuvieron involucrados dentro del proceso de construcción. <br />
Como podemos ver, nuestra aplicación pasara a través de varios ambientes, algunos de los cuales no tendremos control y serán distintos al ambiente creado dentro de las máquinas de desarrollo. Debido a esto es muy importante tener un mecanismo que nos permita externalizar la configuración de nuestras aplicaciones <span class="codigo">Spring Boot</span> y que se ejecute de la misma forma sin importar el ambiente en el que se encuentre. Algunos elementos que comúnmente cambian dependiendo del ambiente en el que ejecutemos la aplicación son: <span class="negritas">rutas</span>, <span class="negritas">usuarios de conexiones a base de datos</span>, <span class="negritas">puertos de funcionamiento</span>, <span class="negritas">nombres de servidores</span>, etc.<br />
<br />
En este tutorial aprenderemos las formas que <span class="codigo">Spring Boot</span> proporciona para colocar esta información de configuración fuera del código fuente de la aplicación (evitando con esto que tengamos que recompilarla cada vez que vayamos a moverla de ambiente), y las prioridades o precedencias que <span class="codigo">Spring Boot</span> asigna a cada una de estas formas.<br />
<br />
<a name='more'></a>Para realizar esta configuración <span class="codigo">Spring Boot</span> proporciona varios mecanismos como son: archivos de propiedades, archivos <a href="https://es.wikipedia.org/wiki/YAML" target="_blank">YAML</a>, variables de entorno y argumentos de líneas de comando. El externalizar esta configuración es una muy buena práctica, ya que evita que tengamos que recompilar nuestra aplicación cada vez que cambiemos de ambiente; hacer esta nueva configuración será tan fácil como agregar una nueva opción dentro de un archivo, indicar que se usará un perfil de aplicación diferente, o tener un valor distinto en una variable de entorno. Nuestra aplicación debe estar preparada para leer estas fuentes de configuración en vez de tener los valores escritos directamente en el código fuente de la misma.<br />
<br />
<br />
<h2 class="titulo"><a name="listaPrecedencias">Lista de Precedencias de Fuentes de Configuración en Spring Boot</a></h2><span class="codigo">Spring Boot</span> leerá cada uno de los valores de configuración de distintas fuentes, que se muestran a continuación, en el orden que están indicados. Esto quiere decir que, si colocamos el mismo valor en dos de las fuentes el valor de la fuente que se encuentra más abajo en la lista sobreescribirá el valor de la fuente que se encuentra más arriba. <span class="codigo">Spring Boot</span> lee los valores de los elementos de configuración en el siguiente orden:<br />
<ol><li>Valores por default de las propiedades ( "<span class="codigo">SpringApplication.setDefaultProperties</span>").</li>
<li>Anotaciones "<span class="codigo">@PropertySource</span>" en clases decoradas con "<span class="codigo">@Configuration</span>".</li>
<li>Valores en el archivo "<span class="codigo">application.properties</span>" o archivo "<span class="codigo">application.yaml</span>" que se encuentren <span class="negritas">dentro el jar de nuestra aplicación</span>.</li>
<li>Valores en el archivo "<span class="codigo">application.properties</span>" o archivo "<span class="codigo">application.yaml</span>" que se encuentren <span class="negritas">fuera del jar de la aplicación</span>.</li>
<li>Propiedades específicas al perfil de la aplicación que se encuentren en archivo de propiedades o <span class="codigo">YAML</span> <span class="negritas">dentro del jar de la aplicación</span> ("<span class="codigo">application-{profile}.properties</span>").</li>
<li>Propiedades específicas al perfil de la aplicación que se encuentren en archivo de propiedades o <span class="codigo">YAML</span> <span class="negritas">fuera del jar de la aplicación</span> ("<span class="codigo">application-{profile}.properties</span>").</li>
<li>Variables de entorno del Sistema Operativo.</li>
<li>Propiedades Java de Sistema (las que se obtienen con "<span class="codigo">System.getProperties()</span>").</li>
<li>Atributos <span class="codigo">JNDI</span> de "<span class="codigo">java:comp/env</span>".</li>
<li>Parámetros de inicialización en "<span class="codigo">ServletContext</span>".</li>
<li>Parámetros de inicialización en "<span class="codigo">ServletConfig</span>".</li>
<li>Propiedades de "<span class="codigo">SPRING_APPLICATION_JSON</span>" (un JSON embebido en una variable de entorno o propiedad del sistema).</li>
<li>Argumentos de línea de comandos.</li>
<li>Atributos "<span class="codigo">properties</span>" de nuestras pruebas.</li>
<li>Anotaciones "<span class="codigo">@TestPropertySource</span>" en nuestras pruebas.</li>
<li>Propiedades de configuración globales de las Devtools en nuestro directorio home ("<span class="codigo">~/.spring-boot-devtools.properties</span>") cuando devtools está activo.</li>
</ol>Como vemos <span class="codigo">Spring Boot</span> tiene muchas fuentes de propiedades, y el conocer este orden es importante ya que si queremos sobreescribir el valor de una fuente (como el archivo "<span class="codigo">application.properties</span>") solo deberemos escribirlo en una fuente que se encuentre más abajo en la lista (como una variable de ambiente).<br />
<br />
Para no hacer el tutorial largo y aburrido, veremos algunas de estas fuentes en este tutorial y algunas otras las dejaremos para otros a lo largo de esta serie.<br />
<br />
Comencemos pues con el código.<br />
<br />
Lo primero que haremos será crear un proyecto usando el sitio de <a href="https://start.spring.io/" target="_blank"><span class="codigo">Spring Initializr</span></a> (que por cierto acaba de actualizarse hace unos días) justo como lo hicimos en <a href="https://www.javatutoriales.com/2019/02/spring-boot-parte-1-introduccion-y-hola.html" target="_blank">el primer tutorial de la serie</a>. En mi caso colocaré la siguiente configuración, donde la parte más importante es que seleccionaré como dependencias "<span class="codigo">Web</span>" (igual que en el primer tutorial de la serie) y "<span class="codigo"><a href="https://www.javatutoriales.com/2019/03/lombok-escribiendo-menos-codigo-y.html" target="_blank">Lombok</a></span>" que es una herramienta para simplificar la escritura de código y que vimos en <a href="https://www.javatutoriales.com/2019/03/lombok-escribiendo-menos-codigo-y.html" target="_blank">el último tutorial</a>.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh53sMpQxbCRKgCucEE1zHFE3utqLR8gLTES9zFhq8I7Jagw1jq42_k8-uczGeA7OCJnSP9it7HxwZ0UGqYS_efDKxVTGdo04p93a1S4Ccd8JQRbpKVcJ1BbRS0xeXbsIDR30A7JrCg6rwh/s1600/SB2_1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="670" data-original-width="1006" height="425" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh53sMpQxbCRKgCucEE1zHFE3utqLR8gLTES9zFhq8I7Jagw1jq42_k8-uczGeA7OCJnSP9it7HxwZ0UGqYS_efDKxVTGdo04p93a1S4Ccd8JQRbpKVcJ1BbRS0xeXbsIDR30A7JrCg6rwh/s640/SB2_1.png" width="640" /></a></div><br />
<br />
Hacemos clic en el botón "<span class="codigo">Generate Project</span>", descargamos el <span class="codigo">zip</span> del proyecto y lo importamos en nuestro IDE, en mi caso <span class="codigo">Eclipse</span>. Una vez que nuestro código esté en el IDE podemos comenzar.<br />
<br />
Lo primero será comprobar que efectivamente la precedencia anterior es correcta. <span class="codigo">Spring Boot</span> tiene básicamente tres formas de inyectar los valores de las propiedades en nuestros beans; la primera es usando la anotación "<span class="codigo">@Value</span>" en la propiedad en la que queremos que quede el valor del parámetro; la segunda es acceder al valor a través del objeto "<span class="codigo">Environment</span>" de <span class="codigo">Spring</span>; y la tercera es usando algo llamado "<span class="negritas">objetos estructurados</span>" usando la anotación "<span class="codigo">@ConfigurationProperties</span>". Para mantener las cosas simples iniciaremos inyectando las propiedades con "<span class="codigo">@Value</span>" y al final del tutorial veremos las otras dos formas.<br />
<br />
Lo primero será crear los paquetes con los que estaremos trabajando, crearemos dos paquetes, un paquete para las clases de configuración ("<span class="codigo">config</span>") y un paquete con los controladores ("<span class="codigo">controllers</span>"), estos últimos serán los componentes de <span class="codigo">Spring</span> que usarán los valores de las propiedades inyectadas. Crearemos estos dos paquetes debajo del paquete principal "<span class="codigo">com.javatutoriales.springboot.configuracion</span>", de la siguiente forma:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjmdC5W7VBwwHSV2XMZB2Rd1OvnJa7V4u8VgFzKuAG6OxJ2YgKgr5AoaUUskse9vRpY9o2XE1GapRGBnYx5NpmNUvV5xhd5gT_Hy42M02MLyvJQvk16Ye5bbDG5wGHP3Ny63T08ZBVvV-dL/s1600/SB2_2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="109" data-original-width="349" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjmdC5W7VBwwHSV2XMZB2Rd1OvnJa7V4u8VgFzKuAG6OxJ2YgKgr5AoaUUskse9vRpY9o2XE1GapRGBnYx5NpmNUvV5xhd5gT_Hy42M02MLyvJQvk16Ye5bbDG5wGHP3Ny63T08ZBVvV-dL/s1600/SB2_2.png" /></a></div><br />
<br />
Dentro del paquete "<span class="codigo">config</span>" crearemos una clase llamada "<span class="codigo">EjemploConfig</span>".<br />
<br />
En la lista de precedencias podemos ver que la primera son los valores por default de <span class="codigo">Spring Boot</span> y la segunda son los valores que provengan de anotaciones "<span class="codigo"><a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/PropertySource.html" target="_blank">@PropertySource</a></span>" en clases decoradas con "<span class="codigo"><a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Configuration.html" target="_blank">@Configuration</a></span>"; esto será lo que probaremos.<br />
<br />
<br />
<h2 class="subtitulo"><a name="propertySource">Configurando con @PropertySource</a></h2>Decoramos nuestra clase con la anotación "<span class="codigo">@Configuration</span>", esta le dice al contenedor de beans de <span class="codigo">Spring</span> que esta clase contiene elementos de configuración, y que por lo tanto debe procesarla antes de iniciar la aplicación. También decoraremos la clase con la anotación "<span class="codigo">@PropertySource</span>", esta anotación es un complemento de la anterior, y le indica a <span class="codigo">Spring</span> en dónde se encuentran los valores que se usarán, lo normal es que estos se encuentren en un archivo, ya sea dentro del jar de nuestra aplicación o en algún directorio de la computadora en la que se ejecutará la aplicación. Nosotros indicamos que el archivo se encuentra en el classpath de la aplicación, en un archivo llamado "<span class="codigo">tutorial.properties</span>" dentro de un directorio llamado "<span class="codigo">config</span>", este último debe estar en el directorio "<span class="codigo">src/main/resources</span>" de nuestra aplicación, ya sea que usemos <span class="codigo">Gradle</span> o <span class="codigo">Maven</span>:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjTbFgPWrIw1XQdc2G7Zr1QeZkbwreBBaVJX_9zaK9FXqSO16eE8QsFL4p6EMJ7OjVqLUeCDIPKRhSfdjCJiJWvuDtx3bDMJnK1DUvqGSYAoTdiovEeQ5hx0p1spfHl9n7so7irq_LTH_rP/s1600/SB2_3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="237" data-original-width="348" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjTbFgPWrIw1XQdc2G7Zr1QeZkbwreBBaVJX_9zaK9FXqSO16eE8QsFL4p6EMJ7OjVqLUeCDIPKRhSfdjCJiJWvuDtx3bDMJnK1DUvqGSYAoTdiovEeQ5hx0p1spfHl9n7so7irq_LTH_rP/s1600/SB2_3.png" /></a></div><br />
<br />
Aprovechemos para poner el siguiente contenido en el archivo, que indica que el valor de la propiedad "<span class="codigo">demo.valor</span>" es "<span class="codigo">Mundo</span>":<br />
<br />
<pre class="brush: plain; toolbar: false;" title="tutorial.properties">demo.valor = Mundo
</pre><br />
Hasta ahora nuestra clase se ve de la siguiente forma:<br />
<br />
<pre class="brush: java; toolbar: false; highlight: [7]" title="EjemploConfig.java">package com.javatutoriales.springboot.configuracion.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
@Configuration
@PropertySource("classpath:config/tutorial.properties")
public class EjemploConfig {
}
</pre><br />
Lo siguiente será colocar la propiedad que leeremos. Comenzaremos con algo sencillo, leeremos el valor de una cadena de una propiedad que llamaremos "<span class="codigo">demo.valor</span>", para indicar que esta es la propiedad que queremos leer la colocaremos en la anotación "<span class="codigo">@Value</span>" en el atributo en el que se colocará el valor, de la siguiente forma:<br />
<br />
<pre class="brush: java; toolbar: false;" title="EjemploConfig.java">@Value("${demo.valor}")
private String valor;
</pre><br />
Para terminar con esta parte aprovecharemos que estamos usando <span class="codigo">Lombok</span> para agregar un <span class="codigo">getter</span> del valor usando la anotación "<span class="codigo">@Getter</span>". Nuestra clase completa queda de la siguiente forma:<br />
<br />
<pre class="brush: java; toolbar: false;" title="EjemploConfig.java">package com.javatutoriales.springboot.configuracion.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import lombok.Getter;
@Configuration
@PropertySource("classpath:config/tutorial.properties")
@Getter
public class EjemploConfig {
@Value("${demo.valor}")
private String valor;
}
</pre><br />
Lo siguiente será crear el componente que hará uso de "<span class="codigo">EjemploConfig</span>". En nuestro caso ese componente será <a href="https://www.javatutoriales.com/2016/02/spring-mvc-parte-2-seleccion-de.html" target="_blank">un controlador de <span class="codigo">Spring MVC</span></a>, que nos permitirá regresar los valores que lea como respuesta de las peticiones del usuario, esto nos ayudará a ver rápidamente el resultado de los cambios de este valor.<br />
<br />
Creamos una clase llamada "<span class="codigo">DemoController</span>" dentro del paquete "<span class="codigo">controllers</span>" de nuestra aplicación:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgNmV4JrWULLW5brk5c2PuYDFbyixN5D1rE2Eto3qAJZ5XbSmIgfTw2JNl-FFD787KyjUrwuHf5kKaeorY1aheedo77EC8HTw4pdu-4pAJZ7OAReqGms4vTN69KQlQC2WJ0fo7kw_8vD51o/s1600/SB2_4.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="150" data-original-width="348" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgNmV4JrWULLW5brk5c2PuYDFbyixN5D1rE2Eto3qAJZ5XbSmIgfTw2JNl-FFD787KyjUrwuHf5kKaeorY1aheedo77EC8HTw4pdu-4pAJZ7OAReqGms4vTN69KQlQC2WJ0fo7kw_8vD51o/s1600/SB2_4.png" /></a></div><br />
<br />
Decoraremos esta clase con la anotación "<span class="codigo">@RestController</span>" para indicar que este componente es un controlador de <span class="codigo">Spring MVC</span> y luego colocaremos una propiedad de tipo "<span class="codigo">EjemploConfig</span>", la cual será inyectada por <span class="codigo">Spring</span> usando un <span class="codigo">setter</span> que colocaremos de forma automática con <span class="codigo">Lombok</span>. Para indicarle a <span class="codigo">Spring</span> que debe inyectar la instancia de "<span class="codigo">EjemploConfig</span>" usando inyección de <span class="codigo">setter</span> (esto es una buena práctica que ayuda a hacer más eficiente el tiempo de arranque de las aplicaciones y ayuda al momento de la creación de pruebas unitarias), decoraremos dicho <span class="codigo">setter</span> con la anotación "<span class="codigo">@Autowired</span>", como aprendimos a hacerlo en <a href="https://www.javatutoriales.com/2019/03/lombok-escribiendo-menos-codigo-y.html" target="_blank">el tutorial de Lombok</a>. Hasta ahora, nuestra clase se ve de la siguiente forma:<br />
<br />
<pre class="brush: java; toolbar: false;" title="DemoController.java">@RestController
public class DemoController {
@Setter (onMethod=@__(@Autowired))
private EjemploConfig config;
}
</pre><br />
El último paso es agregar nuestro método manejador de peticiones, el cual leerá el valor del parámetro y lo regresará en la respuesta a la petición hecha por el usuario. Este será un manejador muy simple:<br />
<br />
<pre class="brush: java; toolbar: false;" title="DemoController.java">@GetMapping
public String saluda() {
return "Hola " + config.getValor();
}
</pre><br />
Nuestra clase completa queda de la siguiente forma:<br />
<br />
<pre class="brush: java; toolbar: false;" title="DemoController.java">@RestController
public class DemoController {
@Setter (onMethod=@__(@Autowired))
private EjemploConfig config;
@GetMapping
public String saluda() {
return "Hola " + config.getValor();
}
}
</pre><br />
Ahora que ya tenemos el código, el siguiente paso será ejecutar nuestra aplicación como una aplicación Java (<span class="codigo">Alt + Shift + X, J</span>), con lo que se deberá iniciar la aplicación y debemos ver el banner de <span class="codigo">Spring Boot</span> en la consola de <span class="codigo">Eclipse</span>:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgyZqHKQCTG2xSsT3_5b8jIrOxoTn8DyAcmpd2RxnXsyTRfSiGrx6LCGQ4q7ABHJPnqiUE6aJie8SPk8vZB03U5yx2OdamHH40m-kA24TfOR7zINu6__jB8sh_lX6CAyOFGU22aftf_G9J3/s1600/SB2_5.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="144" data-original-width="378" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgyZqHKQCTG2xSsT3_5b8jIrOxoTn8DyAcmpd2RxnXsyTRfSiGrx6LCGQ4q7ABHJPnqiUE6aJie8SPk8vZB03U5yx2OdamHH40m-kA24TfOR7zINu6__jB8sh_lX6CAyOFGU22aftf_G9J3/s1600/SB2_5.PNG" /></a></div><br />
<br />
Si ahora entramos en la siguiente dirección:<br />
<br />
<a href="http://localhost:8080/" target="_blank">http://localhost:8080/</a><br />
<br />
Debemos ver esta salida:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgcpOVbqdkjpjfHthXd6cqo3uGNA_VqqsqQLBixEO_wgpYi4PK27A0SYsychvYtjDgV0PKeULOBRqgrSKqzFaY8t0FtDbsj4HYevQ_XUsgkFjnpEBtTIIhQkEklHRdwVBYXh7nr7iGMVFIp/s1600/SB2_6.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="231" data-original-width="550" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgcpOVbqdkjpjfHthXd6cqo3uGNA_VqqsqQLBixEO_wgpYi4PK27A0SYsychvYtjDgV0PKeULOBRqgrSKqzFaY8t0FtDbsj4HYevQ_XUsgkFjnpEBtTIIhQkEklHRdwVBYXh7nr7iGMVFIp/s1600/SB2_6.png" /></a></div><br />
<br />
Con esto podemos saber que le valor se leyó del archivo de manera correcta. El código que acabamos de escribir es el que usaremos prácticamente para el resto del tutorial.<br />
<br />
<br />
<h2 class="subtitulo"><a name="application_properties_dentro">Configurando con el Archivo application.properties Dentro de la Aplicación</a></h2>Ahora veremos el siguiente elemento dentro de la precedencia de la configuración, <a href="#listaPrecedencias">el tercero de la lista anterior</a>, los valores en el archivo "<span class="codigo">application.properties</span>". Este es el archivo de configuración por defecto de <span class="codigo">Spring Boot</span>, es aquí donde normalmente colocaremos los parámetros de configuración de nuestra aplicación. <span class="codigo">Spring</span> busca este archivo en las siguientes ubicaciones (en el orden mostrado):<br />
<ol><li>Un subdirectorio "<span class="codigo">/config</span>" dentro del directorio en el que se ejecuta la aplicación.</li>
<li>El directorio en el que se ejecuta la aplicación.</li>
<li>Un paquete "<span class="codigo">/config</span>" en el classpath de la aplicación.</li>
<li>La raíz del classpath de la aplicación.</li>
</ol>Cuando creamos nuestra aplicación usando <span class="codigo">Spring Initializr</span>, se creó este archivo ubicado en la raíz del classpath de la aplicación (el número 4 en la lista anterior):<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjKljISlfiKYLfJEkxee0SFR7LIkg6xPOrCgD9WGgwjWqN2zAAU9jvMZuoBGmNEcGiS4VvtsOJFPEv0y6LoRTzSC5fCyrGz4ii9T2kVOgy6Gvn1jvST20WkUJasww3aRPThDQkJnqGHvC8V/s1600/SB2_7.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="168" data-original-width="336" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjKljISlfiKYLfJEkxee0SFR7LIkg6xPOrCgD9WGgwjWqN2zAAU9jvMZuoBGmNEcGiS4VvtsOJFPEv0y6LoRTzSC5fCyrGz4ii9T2kVOgy6Gvn1jvST20WkUJasww3aRPThDQkJnqGHvC8V/s1600/SB2_7.png" /></a></div><br />
<br />
Abriremos este archivo, que en este momento debe estar vacío, y colocaremos el siguiente contenido:<br />
<br />
<br />
<pre class="brush: plain; toolbar: false;">demo.valor = desde application.properties dentro del jar de la aplicación
</pre><br />
Estamos colocando el mismo nombre de la propiedad, pero un valor diferente, con esto buscamos que este sea el valor que se muestre ahora como resultado de nuestra petición.<br />
<br />
Volvemos a ejecutar nuestra aplicación y entramos nuevamente a la siguiente dirección:<br />
<br />
<a href="http://localhost:8080/" target="_blank">http://localhost:8080/</a><br />
<br />
Con esto veremos la siguiente pantalla:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgXAiu_bXALUboLzVaI1E7rphNU74HtYFbn56OEtRTrmalFOtjGs88sptGrTy3DfUXnqNWRrpgYPEN9e_mKDHWujsnWrbymmtXarmi_ANo1QS5rUMRnkU9vbQUZri3BDCH0f3JVXjleNOp1/s1600/SB2_8.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="250" data-original-width="627" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgXAiu_bXALUboLzVaI1E7rphNU74HtYFbn56OEtRTrmalFOtjGs88sptGrTy3DfUXnqNWRrpgYPEN9e_mKDHWujsnWrbymmtXarmi_ANo1QS5rUMRnkU9vbQUZri3BDCH0f3JVXjleNOp1/s1600/SB2_8.png" /></a></div><br />
<br />
Como podemos ver, sin tener que modificar el código de la aplicación pudimos sobreescribir el parámetro "<span class="codigo">demo.valor</span>".<br />
<br />
<br />
<h2 class="subtitulo"><a name="application_properties_fuera">Configurando con el Archivo application.properties Fuera de la Aplicación</a></h2>El siguiente ejemplo de nuestros niveles de precedencia será el uso de un archivo "<span class="codigo">application.properties</span>" que se encuentre fuera del jar de la aplicación. Para esto será necesario crear el jar ejecutable de <span class="codigo">Spring Boot</span> como lo hicimos en <a href="https://www.javatutoriales.com/2019/02/spring-boot-parte-1-introduccion-y-hola.html" target="_blank">el tutorial anterior de la serie</a>. Nuevamente, crear este jar ejecutable es muy sencillo con <span class="codigo">Gradle</span>; lo único que debemos hacer es ir al panel de <span class="codigo">Gradle Tasks</span>, encontrar el grupo de tareas "<span class="codigo">build</span>", expandirlo, encontrar la tarea "<span class="codigo">bootJar</span>", hacer clic derecho sobre la tarea, y seleccionar la opción "<span class="codigo">Run Gradle Task</span>":<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj8QpmQfWjY7QnJkRKi-C9J7uZtcAfhL51XvBEAUbnI7LIgzMFBnXJbX2q4j9bqup-Kvmkog33uHiPy2OblgBdwrirhG3iVUD99bSxQMUQtI3rKI2NqJdPQIwHTnc7XB3piGh0ES8Yfd3Ke/s1600/SB2_9.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="265" data-original-width="677" height="156" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj8QpmQfWjY7QnJkRKi-C9J7uZtcAfhL51XvBEAUbnI7LIgzMFBnXJbX2q4j9bqup-Kvmkog33uHiPy2OblgBdwrirhG3iVUD99bSxQMUQtI3rKI2NqJdPQIwHTnc7XB3piGh0ES8Yfd3Ke/s400/SB2_9.png" width="400" /></a></div><br />
<br />
Vamos al archivo en el que se genero nuestro jar ("<span class="codigo">build\libs</span>") y creamos un archivo llamado "<span class="codigo">application.properties</span>":<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjMeF6dT4mWkLvS9DfByah-KMpyq2YwAGFZcxl8EZuY1lS4_LEQFASnSdgB-b5_eMT_ziKjV-yLHMDjQrwSoAAnoNJmJyPDHlJO4fLiuzrnROrHdUdVlpg2M7Fa1RVOcSUZ_mmCGXYGTVRc/s1600/SB2_10.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="128" data-original-width="296" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjMeF6dT4mWkLvS9DfByah-KMpyq2YwAGFZcxl8EZuY1lS4_LEQFASnSdgB-b5_eMT_ziKjV-yLHMDjQrwSoAAnoNJmJyPDHlJO4fLiuzrnROrHdUdVlpg2M7Fa1RVOcSUZ_mmCGXYGTVRc/s1600/SB2_10.png" /></a></div><br />
<br />
Colocamos el siguiente contenido dentro del archivo:<br />
<br />
<pre class="brush: plain; toolbar: false;">demo.valor = desde application.properties fuera del jar de la aplicación
</pre><br />
Y ejecutamos nuestra aplicación desde una terminar con el siguiente comando:<br />
<br />
<pre class="brush: plain; toolbar: false;">java -jar configuracion-0.0.1-SNAPSHOT.jar
</pre><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjlTifIsmdwyCPDmHzlZjwwIx6Bq0EsPioYA7nxYq8ewxo8a3TopF0_wGXGi_ch-k7T593mT4j87CDNqwOEKbuLZOKPrh0G0kZNR-GQ8nyFtu573kCAgaEhTkaxGQhMyFNsm7N7fh8yYSPh/s1600/SB2_11.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="245" data-original-width="542" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjlTifIsmdwyCPDmHzlZjwwIx6Bq0EsPioYA7nxYq8ewxo8a3TopF0_wGXGi_ch-k7T593mT4j87CDNqwOEKbuLZOKPrh0G0kZNR-GQ8nyFtu573kCAgaEhTkaxGQhMyFNsm7N7fh8yYSPh/s1600/SB2_11.png" /></a></div><br />
<br />
<br />
<h2 class="subtitulo"><a name="">Configurando con un Archivo de Perfil Específico fuera de la Aplicación</a></h2>Aprovechemos para mostrar el elemento 6 <a href="#listaPrecedencias">de la lista</a>, propiedades específicas al perfil de la aplicación que se encuentren en archivo de propiedades o <span class="codigo">YAML</span> fuera del jar de la aplicación ("<span class="codigo">application-{profile}.properties</span>"). <br />
<br />
En <span class="codigo">Spring Boot</span> podemos tener varias configuraciones dependiendo del perfil con el que se ejecute la aplicación; esto es especialmente útil cuando colocaremos nuestra aplicación en un servicio en la nube como <span class="codigo">AWS</span> o <span class="codigo">Azure</span>, ya que podemos tener instancias de la aplicación en distintas localidades (por lo regular cada servicio tiene un precio distinto dependiendo de la localidad en el que lo instalemos), por lo que podemos tener una configuración para la instancia de <span class="codigo">eu-central</span> y otra diferente para la instancia de <span class="codigo">eu-west</span>. También es útil cuando vamos a ejecutar nuestra aplicación en distintos ambientes como desarrollo, pruebas o producción.<br />
<br />
Para el ejemplo, crearemos un archivo llamado "<span class="codigo">application-prod.properties</span>" en el mismo directorio que el archivo "<span class="codigo">application.properties</span>" de hace un momento. Este archivo será leído cuando en nuestra aplicación esté activo el perfil "<span class="codigo">prod</span>".<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh3Fa47ot6eXNqgtqQVEr7Ch0oh_c7QxKb8EyvhKzZFBO2gLIVcte2Xaxud0rvN9gi9FGccBS4mYEpxyuJGuw-d9XFL2B88zuUgn2veaf0uXynoDYM7fR61I03fNW4yV58AI6uPK2JRjpvw/s1600/SB2_12.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="136" data-original-width="278" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh3Fa47ot6eXNqgtqQVEr7Ch0oh_c7QxKb8EyvhKzZFBO2gLIVcte2Xaxud0rvN9gi9FGccBS4mYEpxyuJGuw-d9XFL2B88zuUgn2veaf0uXynoDYM7fR61I03fNW4yV58AI6uPK2JRjpvw/s1600/SB2_12.PNG" /></a></div><br />
<br />
Colocamos el siguiente contenido dentro del archivo:<br />
<br />
<pre class="brush: plain; toolbar: false;">demo.valor = desde application.properties fuera del jar de la aplicación usando el perfil de producción
</pre><br />
Tenemos dos formas de indicar a <span class="codigo">Spring Boot</span> qué perfil o perfiles estará usando. La primera forma es como una propiedad de Java (un parámetro con <span class="codigo">-D</span>), la cual recordemos que debe aparecer <span class="negritas">antes del nombre del jar de nuestra aplicación </span>en la línea de comandos; de esta forma:<br />
<br />
<pre class="brush: plain; toolbar: false;">java -jar -Dspring.profiles.active=prod configuracion-0.0.1-SNAPSHOT.jar
</pre><br />
La segunda forma es como un argumento de nuestra aplicación, en este caso el argumento debe aparecer <span class="negritas">después del nombre del jar de nuestra aplicación</span> en la línea de comandos; de esta forma:<br />
<br />
<pre class="brush: plain; toolbar: false;">java -jar configuracion-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod
</pre><br />
Con cualquiera de las dos formas, al iniciar nuestra aplicación debemos ver en la línea de comandos la indicación de qué perfil está activo:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhbVla0uVhhby4uPCMVvN5zwnGNQXOXSkC2aCN0aIKZ09UmGNQm4PVGPnxiUZ6R3tAcGWrIpBBNhWvN2O0f9SYRJbY6D-eitgLoaz15RHl0oO3lY21LBXfW5uB_HlmEZhCe_xlEuvc_wTEt/s1600/SB2_13.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="209" data-original-width="1134" height="117" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhbVla0uVhhby4uPCMVvN5zwnGNQXOXSkC2aCN0aIKZ09UmGNQm4PVGPnxiUZ6R3tAcGWrIpBBNhWvN2O0f9SYRJbY6D-eitgLoaz15RHl0oO3lY21LBXfW5uB_HlmEZhCe_xlEuvc_wTEt/s640/SB2_13.png" width="640" /></a></div><br />
<br />
Al entrar nuevamente en la siguiente dirección:<br />
<br />
<a href="http://localhost:8080/" target="_blank">http://localhost:8080/</a><br />
<br />
Debemos ver la siguiente pantalla:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi6DVvyFq4lQ47uT8sfsg9t7ISvZYSOPBlXNqbcG01KQqmc7mrC2E34wz1BFVqy8wdrTL27VF9hzcjMvPJpj8HAQaanAHVH7jSEF1iRF_CvdAlTV5gDAtibK-K3iQM4rKTPh3iDpgtzEDSj/s1600/SB2_14.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="200" data-original-width="718" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi6DVvyFq4lQ47uT8sfsg9t7ISvZYSOPBlXNqbcG01KQqmc7mrC2E34wz1BFVqy8wdrTL27VF9hzcjMvPJpj8HAQaanAHVH7jSEF1iRF_CvdAlTV5gDAtibK-K3iQM4rKTPh3iDpgtzEDSj/s1600/SB2_14.png" /></a></div><br />
<br />
Con esto sabemos que el ejemplo funcionó correctamente.<br />
<br />
<br />
<h2 class="subtitulo"><a name="variable_entorno">Configurando con Variables de Entorno</a></h2>El siguiente es muy interesante, ya que ni siquiera deberemos modificar nada de nuestra aplicación, sino establecer el valor en una variable de entorno. En una terminar establecemos el siguiente valor de la variable, si están en Windows debe ser de esta forma:<br />
<br />
<pre class="brush: plain; toolbar: false;">SET demo.valor=Valor desde variable de entorno
</pre><br />
Si están en Linux:<br />
<br />
<pre class="brush: plain; toolbar: false;">export demo.valor=Valor desde variable de entorno
</pre><br />
Y en esa misma terminal ejecutar nuestra aplicación:<br />
<br />
<pre class="brush: plain; toolbar: false;">java -jar configuracion-0.0.1-SNAPSHOT.jar
</pre><br />
Con esto si entramos nuevamente a la siguiente dirección:<br />
<br />
<a href="http://localhost:8080/" target="_blank">http://localhost:8080/</a><br />
<br />
Debemos ver la siguiente pantalla:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEikhyphenhyphenDGZDJqpv540t1xGI7K27rO1M4xaeAWdtsJcRkCBWPHIZcSwD3imCxT47fQLzG9chIk-yxU-tKSNmDBzwpJWklXM6CwF9o9zpTFo-BfUpaualFvu_2ieW8WtmnZkB0oudQHzcqLeXkc/s1600/SB2_15.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="283" data-original-width="607" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEikhyphenhyphenDGZDJqpv540t1xGI7K27rO1M4xaeAWdtsJcRkCBWPHIZcSwD3imCxT47fQLzG9chIk-yxU-tKSNmDBzwpJWklXM6CwF9o9zpTFo-BfUpaualFvu_2ieW8WtmnZkB0oudQHzcqLeXkc/s1600/SB2_15.png" /></a></div><br />
<br />
<br />
<h2 class="subtitulo"><a name="propiedades_sistema_java">Configurando con Propiedades Java de Sistema</a></h2>Para la precedencia de la posición 8 de <a href="#listaPrecedencias">la lista</a>, propiedad de sistema Java, debemos detener nuestra aplicación y ejecutarla de la siguiente manera, pasando "<span class="codigo">demo.valor</span>" como una propiedad Java. Recuerden que esta propiedad debe colocarse usando el prefijo <span class="negritas">-D</span> y que este debe ponerse antes del nombre de nuestro jar, de la siguiente forma:<br />
<br />
<pre class="brush: plain; toolbar: false;">java -Ddemo.valor="Propiedad de sistema Java" -jar configuracion-0.0.1-SNAPSHOT.jar
</pre><br />
Con esto, al ingresar nuevamente en la siguiente dirección:<br />
<br />
<a href="http://localhost:8080/" target="_blank">http://localhost:8080/</a><br />
<br />
Debemos ver la siguiente pantalla:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEin4JsraSn2DTkO2bQmgdlVgGdP6z1rwkYatZ_wKt3FVKxw6ZSleRXyn9DSszjiF6Rs3ywCLUZXvw2-69_SeehP4os1E28z5DHnY0IZt8eJ5AymO7-yKNd-J4WPkt2OsOuu56H1Xgot98Io/s1600/SB2_16.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="283" data-original-width="607" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEin4JsraSn2DTkO2bQmgdlVgGdP6z1rwkYatZ_wKt3FVKxw6ZSleRXyn9DSszjiF6Rs3ywCLUZXvw2-69_SeehP4os1E28z5DHnY0IZt8eJ5AymO7-yKNd-J4WPkt2OsOuu56H1Xgot98Io/s1600/SB2_16.png" /></a></div><br />
<br />
<br />
<h2 class="subtitulo"><a name="spring_application_json">Configurando con Propiedades SPRING_APPLICATION_JSON</a></h2>Para el siguiente nivel de precedencia pasaremos hasta el número 12 de <a href="#listaPrecedencias">la lista</a>: propiedades "<span class="codigo">SPRING_APPLICATION_JSON</span>", este es una cadena en formato <span class="codigo">JSON</span> que podemos pasar de las siguientes tres formas, en cualquiera de las tres recuerden que deben tener un <span class="codigo">JSON</span> bien formado:<br />
<br />
<ol><li>Como un <span class="codigo">JSON</span> en la <span class="negritas">variable de entorno</span> "<span class="codigo">SPRING_APPLICATION_JSON</span>", de esta forma: <br />
<br />
<pre class="brush: plain; toolbar: false;">SET SPRING_APPLICATION_JSON={"demo":{"valor": "Valor desde SPRING_APPLICATION_JSON" }}
</pre><br />
</li>
<li>Como una propiedad Java de Sistema, de la siguiente forma, recuerden que esta propiedad debe aparecer antes que el nombre de nuestra aplicación:<br />
<br />
<pre class="brush: plain; toolbar: false;">-Dspring.application.json="{\"demo\":{\"valor\": \"Valor desde SPRING_APPLICATION_JSON como propiedad\" }}"
</pre><br />
</li>
<li>Como un argumento de línea de comandos, que debe aparecer después del nombre de nuestra aplicación: <br />
<br />
<pre class="brush: plain; toolbar: false;">--spring.application.json="{\"demo\":{\"valor\": \"Valor desde SPRING_APPLICATION_JSON como parametro\" }}"
</pre><br />
</li>
</ol>La precedencia de las opciones en la lista anterior también va de la que tiene menos precedencia a la que tiene más precedencia. <br />
<br />
Si ejecutamos nuestra aplicación de la siguiente forma:<br />
<br />
<pre class="brush: plain; toolbar: false;">java -jar configuracion-0.0.1-SNAPSHOT.jar --spring.application.json="{\"demo\":{\"valor\": \"Valor desde SPRING_APPLICATION_JSON como parametro\" }}"
</pre><br />
Y entramos en la siguiente dirección:<br />
<br />
<a href="http://localhost:8080/" target="_blank">http://localhost:8080/</a><br />
<br />
Debemos ver la siguiente pantalla:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjw4YVWCkq3MRGQLEwJqCFQ6T-uaXHoA3UT3H4T0PcmAcMeuQupT9BnfPDRWZblDODdhIfDgF0qvnRD0uz3_IgF5ph9nsP6-HSoGEVMu8FUYEJT0iH22p_8AAYzCDgtmE6gSUvUkz1HR3e0/s1600/SB2_17.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="283" data-original-width="607" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjw4YVWCkq3MRGQLEwJqCFQ6T-uaXHoA3UT3H4T0PcmAcMeuQupT9BnfPDRWZblDODdhIfDgF0qvnRD0uz3_IgF5ph9nsP6-HSoGEVMu8FUYEJT0iH22p_8AAYzCDgtmE6gSUvUkz1HR3e0/s1600/SB2_17.png" /></a></div><br />
<br />
<br />
<h2 class="subtitulo"><a name="linea_comandos">Configurando con Argumentos de Línea de Comandos</a></h2>Para terminar esta parte del tutorial veremos el nivel de precedencia número 13 de <a href="#listaPrecedencias">la lista anterior</a>, el cual es pasar el valor como un argumento en la línea de comandos. Esto ya lo hemos visto algunas veces en este tutorial; para hacerlo basta con pasar el nombre del parámetro precedido por dos guiones medios, seguido el signo de "<span class="codigo">=</span>" y a continuación el valor que le daremos a la variable. Este argumento debe aparecer después del nombre del jar de nuestra aplicación, de esta forma:<br />
<br />
<pre class="brush: plain; toolbar: false;">java -jar configuracion-0.0.1-SNAPSHOT.jar --demo.valor="valor como argumento de línea de comandos"
</pre><br />
Si ejecutamos el comando anterior y entramos en la siguiente dirección:<br />
<br />
<a href="http://localhost:8080/" target="_blank">http://localhost:8080/</a><br />
<br />
Debemos ver la siguiente pantalla:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg1nE8SMMo0nwLjEBuoo4Nlj-qTsI4fmk0napeq_75AEjBjD04HuLBGCnimw5c8Fji8Mvc9zjxOpfvVd52sMiohyphenhyphenC6pZETMwZcw-v7TMrt3nDZArUHU5PC5KQZQoGvhfFy966f7VGDVVrMa/s1600/SB2_18.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="283" data-original-width="607" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg1nE8SMMo0nwLjEBuoo4Nlj-qTsI4fmk0napeq_75AEjBjD04HuLBGCnimw5c8Fji8Mvc9zjxOpfvVd52sMiohyphenhyphenC6pZETMwZcw-v7TMrt3nDZArUHU5PC5KQZQoGvhfFy966f7VGDVVrMa/s1600/SB2_18.png" /></a></div><br />
<br />
Con esto hemos recorrido las precedencias de todos los lugares en los que podemos establecer parámetros en <span class="codigo">Spring Boot</span>; o bueno, casi. Dejamos de lado los casos de los parámetros de pruebas, pero esos los veremos en su tutorial correspondiente.<br />
<br />
<br />
<h2 class="titulo"><a name="environment">Leyendo Parámetros de Configuración con Environment</a></h2>Al inicio del tutorial mencioné que existen tres formas en las que podemos leer los valores de las propiedades, y que "<span class="codigo">@Value</span>" era el primero que veríamos. Ahora veremos la segunda forma que es usando el objeto "<span class="codigo"><a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/env/Environment.html" target="_blank">org.springframework.core.env.Environment</a></span>" de <span class="codigo">Spring</span>.<br />
<br />
Usar este objeto es prácticamente igual al uso de "<span class="codigo">@Value</span>", sólo que en este caso no debemos indicar de dónde se leerán las propiedades, ya que <span class="codigo">Spring Boot</span> sabe cuáles son los lugares posibles (que son los de <a href="#listaPrecedencias">la lista de precedencias</a>), por lo que en automático irá a buscar los valores en estas ubicaciones. Lo único que debemos hacer es inyectar un objeto de este tipo en nuestro controlador usando "<span class="codigo">@Autowired</span>"; nuevamente lo haremos a través de la inyección de <span class="codigo">setter</span>, y lo configuraremos usando <span class="codigo">Lombok</span> de esta forma:<br />
<br />
<pre class="brush: java; toolbar: false;">@Setter (onMethod=@__(@Autowired))
private Environment env;
</pre><br />
<span class="nota"><br />
*Nota: Como todos los atributos de nuestro controlador tienen <span class="codigo">setters</span>, y todos estos tienen exactamente la misma configuración (<span class="codigo">@Setter (onMethod=@__(@Autowired))</span>) podemos poner esta a nivel de clase; por claridad lo dejaremos como está en este tutorial, pero el lector puede tratar de hacer este cambio para comprobar que el resultado es el mismo.<br />
</span><br />
<br />
A continuación, crearemos un manejador nuevo de peticiones, para leer el valor de una manera diferente y no tener que modificar el manejador que ya tenemos. Nuestro manejador responderá a las peticiones hechas a la ruta "<span class="codigo">/env</span>" y leerá desde "<span class="codigo">Environment</span>" el valor de una propiedad llamada "<span class="codigo">demo.valorEnv</span>". El manejador queda por lo tanto de la siguiente forma<br />
<br />
<pre class="brush: java; toolbar: false;">@GetMapping("/env")
public String saludaEnv() {
return "Hola " + env.getProperty("demo.valorEnv");
}
</pre><br />
Lo siguiente es colocar el valor de esta propiedad en cualquiera de los lugares desde los que ya hemos visto que <span class="codigo">Spring Boot</span> puede leerlas; en mi caso (y lo más normal es hacerlo así) la colocaré en el archivo "<span class="codigo">application.properties</span>" debajo de la propiedad que ya teníamos, de la siguiente forma:<br />
<br />
<pre class="brush: plain; toolbar: false;">demo.valorEnv = leyendo con Evironment desde application.properties
</pre><br />
Ahora hay que ejecutar nuestra aplicación (<span class="codigo">Alt + Shift + X, J</span>) y entrar en la siguiente ruta:<br />
<br />
<a href="http://localhost:8080/env" target="_blank">http://localhost:8080/env</a><br />
<br />
Con esto debemos ver la siguiente pantalla:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgvxmWTCsTHokoM50c9VnUjByVy7wwLkteFHGLfIndIcs9iHKRYnhyphenhyphenLsTj50lW_nXdbZXmTvWKzlEr5dx6x4Xk9HT3Ee7IF-nmZskv5CQPsMQ0rzY65oXD4vGNGVTVrTMyQ5MMytveCF0N1/s1600/SB2_19.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="210" data-original-width="533" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgvxmWTCsTHokoM50c9VnUjByVy7wwLkteFHGLfIndIcs9iHKRYnhyphenhyphenLsTj50lW_nXdbZXmTvWKzlEr5dx6x4Xk9HT3Ee7IF-nmZskv5CQPsMQ0rzY65oXD4vGNGVTVrTMyQ5MMytveCF0N1/s1600/SB2_19.png" /></a></div><br />
<br />
Con lo cual podemos comprobar que el ejemplo ha funcionado correctamente.<br />
<br />
Las precedencias en los parámetros de configuración usando "<span class="codigo">Environment</span>" y usando "<span class="codigo">@PropertySource</span>" son exactamente las mismas, por lo que <a href="#listaPrecedencias">todo lo que hemos aprendido hasta ahora</a> continúa funcionando.<br />
<br />
<br />
<h2 class="titulo"><a name="configurationProperties">Leyendo Parámetros con @ConfigurationProperties</a></h2>La última forma de leer las propiedades es de las más interesantes, ya que usando "<span class="codigo"><a href="https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/context/properties/ConfigurationProperties.html" target="_blank">@ConfigurationProperties</a></span>" podemos asociar en automático <span class="negritas">todos los valores de las propiedades que tengan cierto prefijo</span>, con los atributos de una clase Java. Esto es muy común, por ejemplo, cuando trabajamos con propiedades de bases de datos o de servidores de correo para ayudarnos a no tener que estar colocando cada una de las propiedades en un elemento "<span class="codigo">@Value</span>" o leyéndolas con "<span class="codigo">env.getProperty("<span class="codigo">...</span>")</span>".<br />
<br />
Para nuestro ejemplo comencemos colocando las siguientes propiedades ficticias de configuración para el envío de un correo electrónico:<br />
<br />
<pre class="brush: plain; toolbar: false;">mail.hostname=javatutoriales.com
mail.adminMail=programadorjavablog@gmail.com
mail.port=25
mail.from=robot@javatutoriales.com
mail.defaultRecipients[0]=admin@javatutoriales.com
mail.defaultRecipients[1]=owner@javatutoriales.com
mail.additionalHeaders.redelivery=true
mail.additionalHeaders.secure=true
</pre><br />
Si usáramos alguno de los métodos pasados, deberíamos leer "a mano" cada uno de los 8 valores anteriores (6 si consideramos que "<span class="codigo">defaultRecipients</span>" es una lista de cadenas y que "<span class="codigo">additionalHeaders</span>" es un <span class="codigo">Map</span>).<br />
<br />
Usando "<span class="codigo">@ConfigurationProperties</span>" este trabajo se simplificará enormemente. <br />
<br />
Comencemos creando una nueva clase llamada "<span class="codigo">MailConfigurationProperties</span>" en el paquete "<span class="codigo">config</span>". En esta clase pondremos un atributo por cada uno de los 6 parámetros de configuración del mail. Como necesitaremos los <span class="codigo">setters</span> y <span class="codigo">getters</span> de estas propiedades aprovechemos para colocar estos elementos usando <span class="codigo">Lombok</span>, con las anotaciones "<span class="codigo">@Setter</span>", "<span class="codigo">@Getter</span>" y "<span class="codigo">@ToString</span>":<br />
<br />
<pre class="brush: java; toolbar: false;">@Setter
@Getter
@ToString
public class MailConfigurationProperties {
private String hostname;
private int port;
private String from;
private List<String> defaultRecipients;
private Map<String, String> additionalHeaders;
}
</pre><br />
Lo que resta es indicarle a <span class="codigo">Spring</span> que esta clase será usada como fuente de configuración, con la anotación "<span class="codigo">@Configuration</span>" y que en los atributos de esta clase se deben colocar los valores de la configuración leída (de las fuentes y <a href="#listaPrecedencias">con la precedencia que ya conocemos</a>); para esto último es que usamos la anotación "<span class="codigo">@ConfigurationProperties</span>". Si se fijan en la lista de propiedades anteriores todas estas propiedades inician con el mismo prefijo: "<span class="codigo">mail</span>", esto es importante para limitar el número de propiedades que serán usadas dentro de esta clase. Para indicar a <span class="codigo">Spring</span> qué prefijo es el que pertenece a este objeto de configuración, usamos el atributo "<span class="codigo">prefix</span>" en esa última anotación. Al final la clase queda de la siguiente forma:<br />
<br />
<pre class="brush: java; toolbar: false; highlight: [4,5]">@Setter
@Getter
@ToString
@Configuration
@ConfigurationProperties(prefix="mail")
public class MailConfigurationProperties {
private String hostname;
private int port;
private String from;
private List<String> defaultRecipients;
private Map<String, String> additionalHeaders;
}
</pre><br />
Lo siguiente es modificar nuestra clase "<span class="codigo">DemoController</span>" para inyectar un objeto de tipo "<span class="codigo">MailConfigurationProperties</span>" (nuevamente usando inyección de <span class="codigo">setter</span>) y usándolo en un nuevo manejador de peticiones. Para simplificar este manejador de peticiones y hacer fácil el comprobar que los valores han quedados establecidos de forma correcta, lo único que haremos es usar el método <span class="codigo">toString</span> (que generó <span class="codigo">Lombok</span>) y regresar este como el resultado de la invocación del manejador. La declaración del objeto "<span class="codigo">MailConfigurationProperties</span>" y el nuevo manejador de peticiones quedan de la siguiente forma:<br />
<br />
<pre class="brush: java; toolbar: false;">@Setter (onMethod=@__(@Autowired))
private MailConfigurationProperties mailProperties;
@GetMapping("/mail")
public String muestraMail() {
return mailProperties.toString();
}
</pre><br />
Ahora, debemos iniciar nuestra aplicación y entrar en la siguiente dirección:<br />
<br />
<a href="http://localhost:8080/mail" target="_blank">http://localhost:8080/mail</a><br />
<br />
Con lo que debemos ver la siguiente pantalla:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5DiP0jlfCE2hxNTC7IdbIqLav-AeW3UowNpRigtOzImTvei77RRSKRawKu843XCISC7b3AEobSP0DZBFHunG0jP2NUgvj3P3axpnQdGDFN9a24NySvhbv4_CVRvKsJQ6UVpR0a0vqy4GN/s1600/SB2_20.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="276" data-original-width="685" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5DiP0jlfCE2hxNTC7IdbIqLav-AeW3UowNpRigtOzImTvei77RRSKRawKu843XCISC7b3AEobSP0DZBFHunG0jP2NUgvj3P3axpnQdGDFN9a24NySvhbv4_CVRvKsJQ6UVpR0a0vqy4GN/s1600/SB2_20.png" /></a></div><br />
<br />
Con esto podemos comprobar lo sencillo que es leer de esta forma un conjunto "grande" de propiedades de configuración. Esto nos sirve incluso si dentro de la clase decorada con "<span class="codigo">@ConfigurationProperties</span>" tenemos referencias a otros objetos. Para entender esto vamos a crear una clase llamada "<span class="codigo">Credenciales</span>" en el paquete "<span class="codigo">config</span>". Esta clase tendrá tres propiedades sencillas de tipo <span class="codigo">String</span>, una para el usuario, otra para la contraseña, y otra para el host al que se conectará. Como necesitaremos una serie de <span class="codigo">getters</span>, <span class="codigo">setters</span> y mostrar los valores cargados desde el archivo de configuración, aprovecharemos <span class="codigo">Lombok</span> para que los genere usando las anotaciones "<span class="codigo">@Getter</span>", "<span class="codigo">@Setter</span>", y "<span class="codigo">@ToString</span>". La clase debe quedar de la siguiente forma:<br />
<br />
<pre class="brush: java; toolbar: false;">@Getter
@Setter
@ToString
public class Credenciales {
private String user;
private String password;
private String host;
}
</pre><br />
Ahora podemos poner un objeto de esta clase como atributo de "<span class="codigo">MailConfigurationProperties</span>"; la clase completa queda de esta forma: <br />
<br />
<pre class="brush: java; toolbar: false; highlight: [13]">@Setter
@Getter
@ToString
@Configuration
@ConfigurationProperties(prefix = "mail")
public class MailConfigurationProperties {
private String hostname;
private int port;
private String from;
private List<String> defaultRecipients;
private Map<String, String> additionalHeaders;
private Credenciales credenciales;
}
</pre><br />
Y colocamos estas propiedades adicionales en el archivo de configuración:<br />
<br />
<pre class="brush: plain; toolbar: false;">mail.credenciales.host=smtp.javatutoriales.com
mail.credenciales.user=admin
mail.credenciales.password=passwordSecreto
</pre><br />
Si ahora volvemos a ejecutar nuestra aplicación y a entrar en la siguiente dirección:<br />
<br />
<a href="http://localhost:8080/mail" target="_blank">http://localhost:8080/mail</a><br />
<br />
Debemos ver la siguiente pantalla:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMWG6ZJ9-a4wrA5kAPZk6N05MkmWHS_vIfojz-PJNNBIqEIC2x2OB_Km1_65MGQqDG-IeYMCRW2UXVtYa-UOMno_azyUX-Hfc2C4axkZ3zqn4n0g0TmlzHFDNISDSdo2CUlKUVcB-nMzR2/s1600/SB2_21.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="335" data-original-width="622" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMWG6ZJ9-a4wrA5kAPZk6N05MkmWHS_vIfojz-PJNNBIqEIC2x2OB_Km1_65MGQqDG-IeYMCRW2UXVtYa-UOMno_azyUX-Hfc2C4axkZ3zqn4n0g0TmlzHFDNISDSdo2CUlKUVcB-nMzR2/s1600/SB2_21.png" /></a></div><br />
<br />
Como vemos, hemos podido leer los valores del archivo de configuración con tan solo un par de cambios en unos minutos. "<span class="codigo">@ConfigurationProperties</span>" tiene las mismas precedencias con las que hemos estado trabajando de <a href="#listaPrecedencias">la lista anterior</a>, así que todo lo que hemos aprendido hasta ahora podemos aplicarlo. <br />
<br />
<br />
<h2 class="titulo"><a name="validaciones">Validando los Valores de las Propiedades</a></h2>Algo muy útil que podemos hace cuando trabajamos con "<span class="codigo">@ConfigurationProperties</span>" es colocar validaciones de los valores que esperamos leer del archivo de configuración. Con esto evitaremos que algún valor obligatorio no sea proporcionado, o que este no siga el formato esperado, o que algún valor se salga de un rango esperado.<br />
<br />
Para hacer esto utilizaremos las anotaciones de la especificación <span class="codigo">JSR-380 Bean Validation</span>, las cuales se encuentran en el paquete "<span class="codigo">javax.validation.constraints</span>". Este no será un tutorial de <span class="codigo">Bean Validation</span>, por lo que solo usaremos algunas de las anotaciones más básicas para mostrar su funcionalidad:<br />
<ol><li>"<span class="codigo">@NotEmpty</span>". Indica que el valor no puede ser vacío o nulo.</li>
<li>"<span class="codigo">@Positive</span>". Indica que el valor debe ser un número positivo (0 se considera un valor no válido). Los elementos nulos se consideran válidos.</li>
<li>"<span class="codigo">@Email</span>". El valor proporcionado debe tener el patrón de una dirección de correo electrónico.</li>
<li>"<span class="codigo">@Size</span>". La longitud debe estar dentro de los límites indicados.</li>
</ol>Todas las anotaciones anteriores están en el paquete "<span class="codigo">javax.validation.constraints</span>" y todas soportan una propiedad opcional "<span class="codigo">message</span>" en la que podemos indicar un mensaje personalizado que se mostrará en caso de que la validación falle.<br />
<br />
En el caso de a clase "<span class="codigo">Credenciales</span>" solamente validaremos que la longitud de la contraseña sea de entre 8 y 10 caracteres, de la siguiente forma:<br />
<br />
<pre class="brush: java; toolbar: false;">@Size(min=8, max=10)
private String password;
</pre><br />
<span class="nota">*Nota: Si quisiéramos implementar una validación más compleja, como que la contraseña incluya letras mayúsculas, minúsculas, números, caracteres especiales, etc. podemos usar la anotación "<span class="codigo">@Pattern</span>" y definir el patrón que queramos que sea validado.</span><br />
<br />
En el caso de la clase "<span class="codigo">MailConfigurationProperties</span>" validaremos que se proporcione un "<span class="codigo">hostname</span>", que el puerto sea un valor positivo y que el emisor y lo receptores sea correos electrónicos, de la siguiente forma (noten que la validación en el caso de "<span class="codigo">defaultRecipients</span>" se coloca dentro del tipo de la lista):<br />
<br />
<pre class="brush: java; toolbar: false; highlight: [1,4,7,9]">@NotEmpty(message="El correo es un parámetro obligatorio.")
private String hostname;
@Positive
private int port;
@Email
private String from;
private List<@Email String> defaultRecipients;
</pre><br />
Si ejecutamos nuestro ejemplo nuevamente debemos ver los siguientes errores en la consola:<br />
<br />
<pre class="brush: plain; toolbar: false;">***************************
APPLICATION FAILED TO START
***************************
Description:
Binding to target org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'mail' to com.javatutoriales.springboot.configuracion.config.MailConfigurationProperties$$EnhancerBySpringCGLIB$$a30b536c failed:
Property: mail.hostname
Value: null
Reason: El correo es un parámetro obligatorio.
Action:
Update your application's configuration
</pre><br />
Si observan detenidamente la salida anterior hay un problema: aunque hemos indicado que la contraseña debe tener entre 8 y 10 caracteres, y la contraseña en el archivo de configuración tiene 15, <span class="negritas">no hemos recibido un error de validación de este campo</span>, ¿Por qué?.... bien lo que pasa es que cuando tenemos validaciones en atributos de tipos complejos (o sea, que no son de tipos primitivos) <span class="negritas">debemos indicarle a <span class="codigo">Spring</span> que también debe revisar las validaciones de los atributos de este objeto</span>. Para eso debemos marcar el atributo con la anotación "<span class="codigo">@Valid</span>", de esta forma:<br />
<br />
<pre class="brush: java; toolbar: false;">@Valid
private Credenciales credenciales;
</pre><br />
Si volvemos a ejecutar nuestra aplicación, debemos ver la siguiente salida:<br />
<br />
<pre class="brush: plain; toolbar: false;">***************************
APPLICATION FAILED TO START
***************************
Description:
Binding to target org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'mail' to com.javatutoriales.springboot.configuracion.config.MailConfigurationProperties$$EnhancerBySpringCGLIB$$7174b4f2 failed:
Property: mail.hostname
Value: null
Reason: El correo es un parámetro obligatorio.
Property: mail.credenciales.password
Value: passwordSecreto
Reason: el tamaño tiene que estar entre 8 y 10
Action:
Update your application's configuration
</pre><br />
Ahora la salida que obtenemos es la esperada. Todo lo que debemos hacer para poder volver a ejecutar nuestra aplicación es corregir los errores indicados en las validaciones y listo, todo debe volver a funcionar de forma correcta.<br />
<br />
<br />
<h2 class="titulo"><a name="cifrado">Cifrando Parámetros de Configuración</a></h2>Algunas veces, como en el de las contraseñas, rutas, u otra información sensible, no querremos que cualquier persona que tenga acceso al archivo (sobre todo si el mismo es obtenido por un atacante o se coloca en un repositorio de código público) pueda leer el contenido en texto plano; en esto caso lo mejor es cifrar (o encriptar) estas propiedades en los archivos y descifrarlas una vez que se han leído.<br />
<br />
Para esto podemos hacer uso de una interface de <span class="codigo">Spring Boot</span> llamada "<span class="codigo"><a href="https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/env/EnvironmentPostProcessor.html" target="_blank">EnvironmentPostProcessor</a></span>", la cual nos permite crear una clase que obtendrá los valores de las propiedades de configuración <span class="negritas">después de haberla leído de las distintas fuentes, pero antes de ponerla a disposición del resto de las clases de la aplicación</span>.<br />
<br />
En este tutorial no haremos un cifrado complejo ya que queda fuera del alcance del tutorial. Usaremos el <a href="https://es.wikipedia.org/wiki/Cifrado_C%C3%A9sar" target="_blank">cifrado Cesar</a>, moviendo seis posiciones cada uno de los caracteres de la contraseña y del host.<br />
<br />
La interface "<span class="codigo">EnvironmentPostProcessor</span>" sólo tiene un método, con la siguiente firma:<br />
<br />
<pre class="brush: java; toolbar: false;">public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application)
</pre><br />
Es dentro de este método, usando la variable "<span class="codigo">environment</span>" que obtendremos los valores de las propiedades que queremos modificar, y posteriormente haremos el descifrado de la información. No entraré en mucho detalle de cómo funciona esta interface, ya que se puede volver un proceso muy complejo; para dejarlo lo más sencillo posible lo único que haremos será leer los valores, descifrarlos y colocarlos en otra fuente de propiedades a la que le daremos <span class="negritas">la prioridad más alta</span>. "<span class="codigo">EnvironmentPostProcessor</span>" es invocado <span class="negritas">antes incluso de que se ejecuten las validaciones</span>, así que no hay que preocuparse si las cadenas cifradas no cumplen con las validaciones, sólo debemos asegurarnos que las cadenas descifradas sí sean válidas.<br />
<br />
Cifraremos ambas propiedades usando un movimiento de 6 posiciones de las letras, por lo que las propiedades cifradas quedan de esta forma:<br />
<br />
<pre class="brush: plain; toolbar: false;">mail.hostname=pgbgzazuxogrky.ius
mail.credenciales.password=vgyycuxj
</pre><br />
Si en este momento ejecutamos nuevamente la aplicación, y entramos en la siguiente dirección:<br />
<br />
<a href="http://localhost:8080/mail" target="_blank">http://localhost:8080/mail</a><br />
<br />
Debemos ver las propiedades tal cual las escribimos en el archivo:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjS6DySMGEavu2HZm1pKwlu-QqAqSpzJRg8gLAjVSk_-aPC17MyPbPKV-jIRTyENpgSaHQ1Cgiy9k-qsbknFSKgUC1zyGp0_SRq-FWqYBYlHFXwD5Ui7TKPWOiLdD6t_pB-a1fHpftW8281/s1600/SB2_22.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="297" data-original-width="532" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjS6DySMGEavu2HZm1pKwlu-QqAqSpzJRg8gLAjVSk_-aPC17MyPbPKV-jIRTyENpgSaHQ1Cgiy9k-qsbknFSKgUC1zyGp0_SRq-FWqYBYlHFXwD5Ui7TKPWOiLdD6t_pB-a1fHpftW8281/s1600/SB2_22.png" /></a></div><br />
<br />
Ahora, crearemos una nueva clase llamada "<span class="codigo">EncriptionEnvironmentPostProcessor</span>" en el paquete "<span class="codigo">config</span>", esta clase debe implementar la interface "<span class="codigo">EnvironmentPostProcessor</span>". <br />
<br />
<pre class="brush: java; toolbar: false;">public class EncriptionEnvironmentPostProcessor implements EnvironmentPostProcessor {
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
}
}
</pre><br />
La implementación que haremos es muy sencilla: leeremos las propiedades que sabemos que estarán cifradas usando el argumento "<span class="codigo">environment</span>", las descifraremos, las colocaremos dentro de una nueva fuente de propiedades de configuración, y luego colocaremos esta nueva fuente dentro del objeto "<span class="codigo">environment</span>" dándole la prioridad más alta para asegurarnos que <span class="codigo">Spring</span> use estos valores nuevos. Esto lo haremos con la siguiente implementación del método "<span class="codigo">postProcessEnvironment</span>":<br />
<br />
<pre class="brush: java; toolbar: false; highlight: [10]">private static final String PROPERTY_SOURCE_NAME = "secretConfig";
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
Map<String, Object> map = new HashMap<String, Object>();
map.put("mail.credenciales.password", descifra(environment.getProperty("mail.credenciales.password"), 6));
map.put("mail.hostname", descifra(environment.getProperty("mail.hostname"), 6));
environment.getPropertySources().addFirst(new MapPropertySource(PROPERTY_SOURCE_NAME, map));
}
</pre><br />
El secreto está en la línea 10 y como podemos ver son tan solo 4 líneas de código las que nos permiten implementar esta funcionalidad. A propósito no he colocado la implementación del método "<span class="codigo">descrifra</span>" para uno usar mucho espacio en el post, pero pueden encontrarlo en el ejemplo de código en <span class="codigo">GitHub</span> al final del tutorial.<br />
<br />
Para que <span class="codigo">Spring Boot</span> use esta clase es necesario indicarle que esta será la implementación de "<span class="codigo">EnvironmentPostProcessor</span>". Para esto debemos colocar un archivo llamado "<span class="codigo">spring.factories</span>" en el directorio "<span class="codigo">META-INF</span>", que en <span class="codigo">Gradle</span> y <span class="codigo">Maven</span> se coloca dentro del directorio "<span class="codigo">src/main/resources</span>":<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhHbaWFQc83xjtkQJoOY3SMp_a5bRAD7BryH2tsXoGeynQViS3G3IPV62uR1Tml8urELNL5qny0KTjW_b0pYft8Mv7UrhQ8tD__Dc-tegRjlGGkASuWvxIRQFOsEyWoodJs0Mgo-jqMWZjc/s1600/SB2_24.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="168" data-original-width="220" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhHbaWFQc83xjtkQJoOY3SMp_a5bRAD7BryH2tsXoGeynQViS3G3IPV62uR1Tml8urELNL5qny0KTjW_b0pYft8Mv7UrhQ8tD__Dc-tegRjlGGkASuWvxIRQFOsEyWoodJs0Mgo-jqMWZjc/s1600/SB2_24.png" /></a></div><br />
<br />
Este archivo debe tener el siguiente contenido:<br />
<br />
<pre class="brush: plain; toolbar: false;">org.springframework.boot.env.EnvironmentPostProcessor=com.javatutoriales.springboot.configuracion.config.EncriptionEnvironmentPostProcessor
</pre><br />
Si ahora volvemos a ejecutar nuestra aplicación y entramos en la dirección:<br />
<br />
<a href="http://localhost:8080/mail" target="_blank">http://localhost:8080/mail</a><br />
<br />
Debemos ver la siguiente salida, en la que los valores ya podemos verlos sin el cifrado y en texto plano:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjBWAoI2P7IKCwzkZpnso2EDjgXRJiEvqAEjS29tnwBEGyn3x5Ue6QbIQ4vDNFR7fjR_o-fEPu4kZ3h-iFCN-tjI5ISy_eFbg3kRl1f-SODzsRqPYBDkAMc-Fwt9KZ5PRmI9NRxACRj-Tar/s1600/SB2_23.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="297" data-original-width="532" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjBWAoI2P7IKCwzkZpnso2EDjgXRJiEvqAEjS29tnwBEGyn3x5Ue6QbIQ4vDNFR7fjR_o-fEPu4kZ3h-iFCN-tjI5ISy_eFbg3kRl1f-SODzsRqPYBDkAMc-Fwt9KZ5PRmI9NRxACRj-Tar/s1600/SB2_23.png" /></a></div><br />
<br />
<br />
<h2 class="titulo"><a name="yaml">Configurando con Archivos YAML</a></h2>Para terminar este tutorial veremos una alternativa a los archivos de propiedades. Hasta ahora hemos estado usando archivos de propiedades para colocar los parámetros de configuración, pero esta no es la única opción, <span class="codigo">Spring</span> nos da como alternativa el uso de archivos <a href="https://es.wikipedia.org/wiki/YAML" target="_blank"><span class="codigo">YAML (YAML Ain't Markup Language – YAML no es un lenguaje de marcado)</span></a> el cual es un lenguaje de serialización de datos, que tiene la particularidad de ser más fácil de entender y organizar que los archivos de propiedades, ya que no hay que estar repitiendo la misma llave o prefijo muchas veces, sino que se ayuda del anidamiento de los valores, esto permite organizarlos de forma jerárquica. Por ejemplo, en lugar de escribir los valores del archivo de propiedades de esta forma:<br />
<br />
<pre class="brush: plain; toolbar: false;">demo.valor = desde application.properties
demo.valorEnv = leyendo con Evironment
mail.hostname=pgbgzazuxogrky.ius
mail.adminMail=programadorjavablog@gmail.com
mail.credenciales.host=smtp.javatutoriales.com
mail.credenciales.user=admin
</pre><br />
En la que repetimos un par de veces "<span class="codigo">demo</span>" y "<span class="codigo">mail</span>" y "<span class="codigo">mail.credenciales</span>", podemos escribirlo así en un archivo <span class="codigo">YAML</span>:<br />
<br />
<pre class="brush: plain; toolbar: false;">demo:
valor: desde application.yaml
valorEnv: leyendo con Evironment desde application.yaml
mail:
hostname: pgbgzazuxogrky.ius
adminMail: programadorjavablog@gmail.com
credenciales:
host: smtp.javatutoriales.com
user: admin
</pre><br />
Como podemos ver, eliminamos los elementos repetitivos de los nombres de las propiedades y sigue siendo claro qué elemento hijo pertenece a qué elemento padre.<br />
<br />
<span class="nota">*Nota: cuando anidamos un elemento debemos usar un espacios y no tabs, el saber esto les ayudará a ahorrar muchos problemas.</span><br />
<br />
<span class="codigo">Spring</span> da preferencia a los archivos de propiedades sobre los yaml, esto quiere decir que si tenemos un archivo "<span class="codigo">application.yaml</span>" y un archivo "<span class="codigo">application.properties</span>", <span class="codigo">Spring</span> leerá primero el archivo <span class="codigo">YAML</span> y luego el archivo <span class="codigo">properties</span>, sobreescibiendo los valores que se encuentren en el primero.<br />
<br />
Cuando usamos un archivo <span class="codigo">YAML</span> no es necesario hacer ninguna modificación en nuestro código, así que para nosotros es transparente. Para probar esto, vamos a crear un archivo "<span class="codigo">application.yaml</span>" en el directorio "<span class="codigo">src/main/resources</span>" (a la altura del archivo "<span class="codigo">application.properties</span>", el cual por cierto renombraremos agregando un "<span class="codigo">_</span>" al inicio del nombre del archivo, para que <span class="codigo">Spring Boot</span> no lea este archivo</span>"):<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiuQ_c2YjFdTR21C9AHDEitZp2GOtTbVMAEWF2GWqokYR2SyP9O7UxTwGkdPJiaLD_IYAnBVwy6pQj7Axj0CR-32nFwsfOrD5njJbW9bEUF4ScR0a2eRk-UBqTkalQpFGpHiYynfE-QTpt9/s1600/SB2_25.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="181" data-original-width="261" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiuQ_c2YjFdTR21C9AHDEitZp2GOtTbVMAEWF2GWqokYR2SyP9O7UxTwGkdPJiaLD_IYAnBVwy6pQj7Axj0CR-32nFwsfOrD5njJbW9bEUF4ScR0a2eRk-UBqTkalQpFGpHiYynfE-QTpt9/s1600/SB2_25.png" /></a></div><br />
<br />
Y colocaremos el siguiente contenido en el archivo, que básicamente es la traducción del archivo de propiedades al formato <span class="codigo">yaml</span> (noten que eliminé los acentos, ya que yaml no los soporta y el error que aparece si los dejamos no es claro):<br />
<br />
<pre class="brush: plain; toolbar: false;">demo:
valor: desde application.yaml dentro del jar de la aplicacion
valorEnv: leyendo con Evironment desde application.yaml
mail:
hostname: pgbgzazuxogrky.ius
adminMail: programadorjavablog@gmail.com
port: 35
from: robot@javatutoriales.com
defaultRecipients:
0: admin@javatutoriales.com
1: owner@javatutoriales.com
additionalHeaders:
redelivery: true
secure: true
credenciales:
host: smtp.javatutoriales.com
password: vgyycuxj
user: admin
</pre><br />
Si ahora ejecutamos nuevamente nuestra aplicación y entramos a cualquiera de las rutas de nuestra aplicación, debemos ver que el contenido se está leyendo de este nuevo archivo sin necesidad de modificar nada en nuestro código:<br />
<br />
<a href="http://localhost:8080/" target="_blank">http://localhost:8080/</a><br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgM6izkslRbHs3IfuouZxmn-C21bbyrTx-NZ5LOjCMCd-aYGlUGB4sx6A-_etF34hgho1bo5ut96gylZ28J-1IwynWEoaTgydDAe38n6Le4OLzgMVkqnlLc6o5xb1G9l4ShB0wlosJQ8xOh/s1600/SB2_26.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="280" data-original-width="690" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgM6izkslRbHs3IfuouZxmn-C21bbyrTx-NZ5LOjCMCd-aYGlUGB4sx6A-_etF34hgho1bo5ut96gylZ28J-1IwynWEoaTgydDAe38n6Le4OLzgMVkqnlLc6o5xb1G9l4ShB0wlosJQ8xOh/s1600/SB2_26.png" /></a></div><br />
<br />
<a href="http://localhost:8080/env" target="_blank">http://localhost:8080/env</a><br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhrEnZRORjMh-vAihYqDb1g552U5oiCm9SLbiItcuhoIqDZlU93-izk14rK-qqDXXhka0j_9aU9Arg-rNqRUtsKJEyJiMtCaQl1UJR3pjNzPT0e2r9qhruI3aVP0xPxRBsiCGtutKlIGV_C/s1600/SB2_27.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="280" data-original-width="690" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhrEnZRORjMh-vAihYqDb1g552U5oiCm9SLbiItcuhoIqDZlU93-izk14rK-qqDXXhka0j_9aU9Arg-rNqRUtsKJEyJiMtCaQl1UJR3pjNzPT0e2r9qhruI3aVP0xPxRBsiCGtutKlIGV_C/s1600/SB2_27.png" /></a></div><br />
<br />
<a href="http://localhost:8080/mail" target="_blank">http://localhost:8080/mail</a><br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhifGCCIenp67zFI7fuBgrqtp82G12BFbeCTlHRB-gk2rsPwUDtruFiXQF8CR7_w_TO5PguXSnCL7XDsz700aJ3nJoSHVXFG3y36usKjNUr6i94Uf-U9PV0K_N1jbNGJALnwARidaqdeuKs/s1600/SB2_28.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="280" data-original-width="690" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhifGCCIenp67zFI7fuBgrqtp82G12BFbeCTlHRB-gk2rsPwUDtruFiXQF8CR7_w_TO5PguXSnCL7XDsz700aJ3nJoSHVXFG3y36usKjNUr6i94Uf-U9PV0K_N1jbNGJALnwARidaqdeuKs/s1600/SB2_28.png" /></a></div><br />
<br />
<br />
Con lo cual comprobamos que el ejemplo funciona correctamente. Las precedencias de la <a href="#listaPrecedencias">lista inicial</a> siguen siendo válidas para los archivos <span class="codigo">yaml</span>.<br />
<br />
Una ventaja del uso de archivos <span class="codigo">YAML</span> contra los archivos de propiedades, es que podemos poner múltiples perfiles en un mismo archivo usando la llave "<span class="codigo">spring.profile</span>" para indicar el nombre del perfil que estamos configurando, y separando estos por tres guiones ("<span class="codigo">---</span>"). <br />
<br />
<pre class="brush: plain; toolbar: false;">server:
address: 192.168.1.100
port: 5000
---
spring:
profiles: development
server:
address: 127.0.0.1
---
spring:
profiles: produccion
server:
address: 192.168.1.120
</pre><br />
En el ejemplo anterior podemos ver una configuración inicial por default, en el que se coloca una URL y un puerto por el que trabajará nuestra aplicación. Después se indica la configuración del perfil "<span class="codigo">development</span>" en el cual se cambia la URL (y se mantiene el número de puerto) y otro perfil llamado "<span class="codigo">producción</span>" en el cual nuevamente se cambia la URL. <br />
<br />
Incluso podemos indicar que cierta configuración NO aplicará a un perfil, usando la negación ("<span class="codigo">!</span>") en el nombre del perfil:<br />
<br />
<pre class="brush: plain; toolbar: false;">spring:
profiles: !pruebas
security:
user:
password: facil123
</pre><br />
Para terminar, esta es una lista de las propiedades que normalmente querremos modificar en nuestra aplicación para ajustarla a nuestras necesidades. <span class="codigo">Spring Boot</span> tiene cientos de propiedades con valores por default, para ver la lista completa pueden visitar <a href="https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html" target="_blank">este sitio</a>.<br />
<br />
<pre class="brush: plain; toolbar: false;">debug=false # Enable debug logs.
trace=false # Enable trace logs.
server.address= # Network address to which the server should bind.
server.port=8080 # Server HTTP port.
logging.config= # Location of the logging configuration file. For instance, `classpath:logback.xml` for Logback.
logging.file.max-history=0 # Maximum of archive log files to keep. Only supported with the default logback setup.
logging.file.max-size=10MB # Maximum log file size. Only supported with the default logback setup.
logging.level.*= # Log levels severity mapping. For instance, `logging.level.org.springframework=DEBUG`.
spring.application.name= # Application name.
spring.profiles.active= # Comma-separated list of active profiles. Can be overridden by a command line switch.
</pre><br />
<br />
<br />
<h2 class="titulo"><a name="conclusion">Conclusión</a></h2>Nuestras aplicaciones siempre necesitarán externalizar su configuración para que no sea necesario el recompilar el código cuando hay un cambio en alguno de estos valores, ya sea de alguna ruta, password, usuario, etc. <span class="codigo">Spring Boot</span> nos da muchos lugares en los que podemos colocar estas configuraciones, y conociendo las precedencias de cada uno de estos lugares, nos será fácil sobrescribir los valores cuando tengamos que hacer un cambio, ya sea de forma permanente, para un ambiente específico, o para una prueba.<br />
<br />
En este tutorial aprendimos varias formas de leer estos valores configuración, en los cuales no es necesario modificar ni una sola línea de nuestro código, también aprendimos una manera en la que podremos realizar el descifrado de ciertas propiedades sin importar en dónde se encuentren estas propiedades. Finalmente, aprendimos que además de los archivos de propiedades tradicionales de Java, también podemos usar archivos en formato YAML, los cuales ayudarán a reducir el tamaño de nuestros archivos y ayudarán en el mantenimiento de los mismos.<br />
<br />
Espero que este tutorial les sea de utilidad. Si tienen alguna duda, sugerencia, comentario o aclaración, pueden dejarla en la sección de comentarios o enviar un correo a <a href="mailto:programadorjavablog@gmail.com">programadorjavablog@gmail.com</a> (pueden agregarme al chat de gmail). También pueden seguir <span class="codigo">JavaTutoriales</span> en las siguientes redes sociales:<br />
<ul><li><span class="codigo"><a href="https://www.facebook.com/JavaTutoriales/" target="_blank">Facebook</a></span></li>
<li><span class="codigo"><a href="https://twitter.com/JavaTutoriales" target="_blank">Twitter</a></span></li>
</ul><br />
Saludos y gracias.<br />
<br />
<span class="negritas">Descarga los archivos de este tutorial desde aquí:<span class="negritas"><br />
<ul><li><span class="codigo"><a href="https://github.com/JavaTutoriales/Spring-Boot-Tutorial2-Configuracion" target="_blank">Spring Boot 2 - Externalizando la Configuración</a></span></li>
</ul><br />
<span class="negritas">Entradas Relacionadas:</span><br />
<ul><li><span class="codigo"><a href="https://www.javatutoriales.com/2019/02/spring-boot-parte-1-introduccion-y-hola.html" target="_blank">Sprinng Boot 1 - Introducción y Hola Mundo</a></span></li>
</ul></div>Alexhttp://www.blogger.com/profile/06974037481671868076noreply@blogger.comtag:blogger.com,1999:blog-2012232069289957092.post-50588611165397051132019-03-05T04:04:00.000-08:002019-03-05T04:07:47.481-08:00Lombok, escribiendo menos código y logrando más cosas<div style="text-align: justify;">
En este tutorial no veremos un Framework, sino una herramienta Java que nos ayudará a <span class="negritas">simplificar mucho el código que escribimos</span> en cada una de nuestras clases de datos; o sea, las clases que no contienen lógica de negocio, sino que nos ayudan a "transportar" información, como son los <span class="codigo">POJOs</span> o los <span class="codigo">Value Objects</span>.<br />
<br />
<span class="codigo">Lombok</span> es una librería Java que automáticamente se conecta a nuestro editor o herramienta de construcción (como pueden ser <span class="codigo">Maven</span> o <span class="codigo">Eclipse</span>) y que nos ayuda a generar código para las tareas más repetitivas de nuestras clases como son la generación de métodos <span class="codigo">setter</span> y <span class="codigo">getter</span>, constructores, <span class="codigo">toString</span>, <span class="codigo">equals</span>, etc. ¿Recuerdan en <a href="https://www.javatutoriales.com/search/label/hibernate" target="_blank">los tutoriales de <span class="codigo">Hibernate</span></a> como la parte más tardada era generar las entidades por la cantidad de métodos <span class="codigo">getter</span> y <span class="codigo">setter</span> que estas tenían? Esas clases ocupan mucho espacio, en término de líneas de código; todo ese código podremos ahorrárnoslo con <span class="codigo">Lombok</span>, ya que por medio de anotaciones le indicaremos qué es lo que queremos que genere.<br />
<br />
En este tutorial aprenderemos cómo instalar <span class="codigo">Lombok</span> en nuestro IDE y veremos algunos ejemplos de uso.<br />
<br />
<a name='more'></a>Lo primero que veremos es cómo instalar <span class="codigo">Lombok</span> en nuestro IDE (en este caso <span class="codigo">Eclipse</span>).<br />
<br />
El primer paso es descargar el jar del instalador de <span class="codigo">Lombok</span> desde <a href="https://projectlombok.org/download" target="_blank">esta URL en su sitio oficial</a>. La versión más reciente de <span class="codigo">Lombok</span> al momento de escribir este tutorial es la <span class="codigo">1.18.6.</span><br />
<br />
Simplemente hacemos clic en el botón de descarga de la página anterior y listo, tendremos un jar de poco menos de <span class="codigo">2Mb</span> de tamaño.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhPyuUMUgqSnn7Emxut2sjlYDXxK7c-SlQeXgakDnhs8TlpIgLpkQwFZ73maB9Q9Vdcg-i90kchkf1v0kjTrS9PamUBrAuXX7o3k8OabJCaMV2w-Zjqv5yICIB8jl7EvxXgvi4AexLon_y2/s1600/LB1_1.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="68" data-original-width="471" height="57" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhPyuUMUgqSnn7Emxut2sjlYDXxK7c-SlQeXgakDnhs8TlpIgLpkQwFZ73maB9Q9Vdcg-i90kchkf1v0kjTrS9PamUBrAuXX7o3k8OabJCaMV2w-Zjqv5yICIB8jl7EvxXgvi4AexLon_y2/s400/LB1_1.PNG" width="400" /></a></div>
<br />
<br />
Este jar podemos usarlo directamente agregándolo al <span class="codigo">classpath</span>, o podemos integrarlo al IDE a través de un asistente que se abrirá al hacer doble clic sobre el jar. Es importante agregar <span class="codigo">Lombok</span> a nuestro IDE por las características de autocorrección que estos tienen, si el IDE no sabe que estamos usando <span class="codigo">Lombok</span> y tratamos de completar o escribimos una llamada a un <span class="codigo">getter</span> que aún no existe (porque <span class="codigo">Lombok</span> trabaja en tiempo de compilación para generar estos métodos) obtendremos un error, en algunos casos incluso no nos dejará compilar el proyecto.<br />
<br />
Para evitarnos problemas a futuro, haremos doble clic sobre el jar que acabamos de descargar.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhoccZK5xM5R4B-20wuEQlUy_6-7vR6InI6oOd0UlIBD2BvFbeWtoOUvzeopEuwb_2odk4DDbJ9ORnwXTZ2FD9Aac-X3o3N7Aii2iAwuhdDaQyIElM6O1TPn7o_pf0c2WY-KwsH4rc47gxQ/s1600/LB1_2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="488" data-original-width="842" height="231" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhoccZK5xM5R4B-20wuEQlUy_6-7vR6InI6oOd0UlIBD2BvFbeWtoOUvzeopEuwb_2odk4DDbJ9ORnwXTZ2FD9Aac-X3o3N7Aii2iAwuhdDaQyIElM6O1TPn7o_pf0c2WY-KwsH4rc47gxQ/s400/LB1_2.png" width="400" /></a></div>
<br />
<br />
Con esto comenzará a buscar las instalaciones que tenemos de <span class="codigo">Eclipse</span> para configurarse en forma automática. En mi caso tengo dos instalaciones, una que ya tiene <span class="codigo">Lombok</span> y otra que no, por lo que lo agregaré a este último. Si tu instalación de Eclipse no aparece puede ser que esté instalado en una ubicación diferente al default, en eso caso no te preocupes, puedes seleccionar la opción de "<span class="codigo">Sepecify Location...</span>" y seleccionar la instalación a la que quieres agregarlo.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjWm0YirRu-7lwXDOcgNuTuO9yXmQWaLD0aDSofbFYwvQ880UnMZEtw0D-9EBg_y1NQkY3mdUnPETYt3eqW3x6UDIHNviHBzGa6w0CI7RkGTHKdRTwQhZORycG4K6vBjAWXjO8oJL2gNJUP/s1600/LB1_3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="488" data-original-width="842" height="231" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjWm0YirRu-7lwXDOcgNuTuO9yXmQWaLD0aDSofbFYwvQ880UnMZEtw0D-9EBg_y1NQkY3mdUnPETYt3eqW3x6UDIHNviHBzGa6w0CI7RkGTHKdRTwQhZORycG4K6vBjAWXjO8oJL2gNJUP/s400/LB1_3.png" width="400" /></a></div>
<br />
<br />
Hacemos clic sobre el botón "<span class="codigo">Install/Update</span>" y con esto se realizará la instalación, que es prácticamente instantánea. Al terminar podemos presionar el botón "<span class="codigo">Quit Installer</span>" y listo, ya tendremos instalado <span class="codigo">Lombok</span> en nuestro IDE. Para comprobar que la instalación se realizó de forma correcta, debemos ir a ver la información de la versión de Eclipse ("<span class="codigo">Help -> About Eclipse IDE</span>") y después de la información del copy right debemos ver la versión de <span class="codigo">Lombok</span> instalada.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiY3bHs3Da5xJMxcUYw9JGBcakRXQClRRW6Z_zexA_OvvaatOQWhxAoUw3e7UkrOL6c6G4LQfC0DlQZr3g99MRHTNTF16Mu5KJTKJXQmF68QibtnlsbFzga8qRa2FpG2XNmx52fPJxjmSvQ/s1600/LB1_4.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="434" data-original-width="814" height="213" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiY3bHs3Da5xJMxcUYw9JGBcakRXQClRRW6Z_zexA_OvvaatOQWhxAoUw3e7UkrOL6c6G4LQfC0DlQZr3g99MRHTNTF16Mu5KJTKJXQmF68QibtnlsbFzga8qRa2FpG2XNmx52fPJxjmSvQ/s400/LB1_4.png" width="400" /></a></div>
<br />
<br />
¿Sólo podemos instalar <span class="codigo">Lombok</span> en Eclipse? No, se puede instalar en prácticamente cualquier IDE que soporte Java. El procedimiento para todos los IDEs basados en <span class="codigo">Eclipse</span>, es el mismo, estas son las instrucciones para instalarlo en <a href="https://projectlombok.org/setup/netbeans" target="_blank"><span class="codigo">NetBeans</span></a>, <a href="https://projectlombok.org/setup/vscode" target="_blank"><span class="codigo">Visual Studio Code</span></a> e <a href="https://projectlombok.org/setup/intellij" target="_blank"><span class="codigo">IntelliJ</span></a>.<br />
<br />
En este tutorial veremos sólo algunas de las funcionalidades más útiles de "<span class="codigo">Lombok</span>".<br />
<br />
Comencemos creando un nuevo proyecto <span class="codigo">Gradle</span> ("<span class="codigo">File -> New -> Gradle -> Gradle Project</span>"), daremos un nombre al proyecto (en mi caso será "<span class="codigo">LombokTutorial</span>"), damos una ubicación y presionamos el botón "<span class="codigo">Finish</span>", y después de unos minutos veremos nuestro nuevo proyecto en el panel de proyectos.<br />
<br />
El siguiente paso es agregar la dependencia de <span class="codigo">Lombok</span> al archivo "<span class="codigo">build.gradle</span>". Al abrir este archivo veremos que Gradle ya ha agregado algunas dependencias y un repositorio. En lo personal no me gustan los elementos por default que maneja Gradle, por lo que cambiaremos el contenido del archivo por el siguiente, en el que declaramos que usaremos el repositorio central de Maven y usaremos la librería de <span class="codigo">Lombok</span> para compilar y como pre-procesador de anotaciones, de la siguiente forma:<br />
<br />
<pre class="brush: groovy; ruler: true; first-line: 5; highlight: [6, 7, 11]">dependencies {
compileOnly 'org.projectlombok:lombok:1.18.6'
annotationProcessor 'org.projectlombok:lombok:1.18.6'
}
repositories {
mavenCentral()
}
</pre>
Si usan Maven tan solo deben declarar la siguiente dependencia:<br />
<br />
<pre class="brush: xml; ruler: true;"><dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.6</version>
<scope>provided</scope>
</dependency>
</pre>
<span class="nota">*Nota: Para que Eclipse vea los cambios en el archivo de Gradle, es necesario refrescar su configuración. Para esto hacemos clic derecho sobre el proyecto y en el menú que se abre seleccionamos la opción "<span class="codigo">Gradle -> Refresh Gradle Project</span>". Es importante saber que la opción de Gradle sólo aparecerá si hacemos clic sobre la raíz del proyecto o sobre alguno de los archivos de configuración de Gradle.</span><br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiCEzKwQ6FYa1nONblzDoDBsNryFFPWE0EpWDDweVkCmWgsyveqT645wrH2WUY93MI4EDuKgs9zVN-mjRzYxKNyXdKIn1UY-EXNy88wq5sd-TOl5A7Cgc6PWgVOsgb9n-sM8E_EgCTD84lS/s1600/LB1_5.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="687" data-original-width="592" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiCEzKwQ6FYa1nONblzDoDBsNryFFPWE0EpWDDweVkCmWgsyveqT645wrH2WUY93MI4EDuKgs9zVN-mjRzYxKNyXdKIn1UY-EXNy88wq5sd-TOl5A7Cgc6PWgVOsgb9n-sM8E_EgCTD84lS/s400/LB1_5.png" width="344" /></a></div>
<br />
<br />
Ahora crearemos un nuevo paquete, que en mi caso será "<span class="codigo">com.javatutoriales.lombok</span>" y adentro de esta clase crearemos otra llamada "<span class="codigo">Main</span>". Esta clase nos ayudará a probar que efectivamente <span class="codigo">Lombok</span> está funcionando como lo esperamos. Esta clase tendrá por el momento un método "<span class="codigo">main</span>" vacío, de la siguiente forma:<br />
<pre class="brush: java; ruler: true;">package com.javatutoriales.lombok;
public class Main {
public static void main(String[] args) {
}
}
</pre>
<br />
Comenzaremos con algo fácil. ¿Cuántas veces no hemos tenido problemas con nuestra aplicación en ejecución porque a alguien se le ocurrió mandar un parámetro nulo y nosotros no estábamos preparados para recibirlo o no sabemos cuál objeto es exactamente el que es <span class="codigo">null</span>? Pues bien, con <span class="codigo">Lombok</span> esto se terminó. <span class="codigo">Lombok</span> proporciona la anotación "<span class="codigo">@NonNull</span>" que verifica que el argumento que hayamos marcado con esta anotación no tenga un valor nulo, de lo contrario arroja una "<span class="codigo">NullPointerExeption</span>" indicando cuál parámetro es el que tiene el valor nulo.<br />
<br />
Para probar esta anotación crearemos una nueva clase llamada "<span class="codigo">EjemploNonNull</span>" en el paquete "<span class="codigo">com.javatutoriales.lombok</span>" y adentro de esta clase un método llamado "<span class="codigo">saluda</span>" que reciba un parámetro de tipo <span class="codigo">String</span> llamado "<span class="codigo">nombre</span>". Lo que haremos dentro de este método es regresar un saludo a la persona cuyo nombre se haya recibido como parámetro, y para elevar el nivel de complejidad convertiremos esta cadena en mayúsculas al momento de regresarlo:<br />
<br />
<pre class="brush: java; ruler: true;">public String saluda(String nombre) {
return "Hola " + nombre.toUpperCase();
}
</pre>
Eso traerá un problema potencial, ya que, si "<span class="codigo">nombre</span>" es nulo, nuestra aplicación se romperá. Vamos a comprobar esto. En el método "<span class="codigo">main</span>" de la clase que creamos hace un momento, colocaremos el siguiente código:<br />
<br />
<pre class="brush: java; ruler: true; highlight: [2]">EjemploNonNull nonNull = new EjemploNonNull();
System.out.format("%s\n\n", nonNull.saluda(null));
</pre>
El problema está justamente en la segunda línea, ya que como el nombre que estamos pasando es un valor nulo, nuestra aplicación arrojará la siguiente excepción:<br />
<br />
<pre class="brush: java; ruler: true;">Exception in thread "main" java.lang.NullPointerException
at com.javatutoriales.lombok.EjemploNonNull.saluda(EjemploNonNull.java:7)
</pre>
No es nada que no hayamos visto antes, pero tampoco nos dice mucho. Este es un ejemplo simple, pero ¿qué pasaría si tuviéramos varias llamadas encadenadas a varios métodos con varios parámetros, cómo sabríamos cuál es el valor nulo?<br />
<br />
Bien, con <span class="codigo">Lombok</span> esto es fácil, sólo agregamos la anotación "<span class="codigo">@NonNull</span>" al parámetro "<span class="codigo">nombre</span>", de esta forma:<br />
<br />
<pre class="brush: java; ruler: true;">public String saluda(@NonNull String nombre)
</pre>
Y eso es todo. Si volvemos a ejecutar nuestra aplicación veremos la siguiente salida:<br />
<br />
<pre class="brush: java; ruler: true; highlight:[1]">Exception in thread "main" java.lang.NullPointerException: nombre is marked @NonNull but is null
at com.javatutoriales.lombok.EjemploNonNull.saluda(EjemploNonNull.java:6)
</pre>
Con esto ahora podemos saber qué "<span class="codigo">nombre</span>" es quien es nulo y por lo tanto podremos corregir el error.<br />
<br />
Algo importante de "<span class="codigo">@NonNull</span>" es que debe agregarse en cada parámetro que queramos validar, ya sea de constructores o métodos, no existe una forma de decir que se validen todos los parámetros de un método. <br />
<br />
Ahora veremos la segunda característica útil de "<span class="codigo">Lombok</span>". Al inicio del tutorial hablábamos del problema de escribir los <span class="codigo">setter</span> y <span class="codigo">getters</span> a mano dentro de una clase Java; para ahorrarnos todo este trabajo <span class="codigo">Lombok</span> tiene un par de anotaciones que nos serán muy útiles. Crearemos una clase llamada "<span class="codigo">GettersSetters</span>" que nos ayudará a probar esa funcionalidad.<br />
<br />
A esta clase agregaremos tres propiedades de forma inicial, una cadena, un entero primitivo y una fecha:<br />
<br />
<pre class="brush: java; ruler: true;">package com.javatutoriales.lombok;
import java.util.Date;
public class GettersSetters {
private String cadena;
private int entero;
private Date fecha;
private boolean boleano;
}
</pre>
El outline de Eclipse nos ayudará a ver qué es lo que pasa con nuestra clase conforme agregamos estos métodos. En este momento debe verse así, lo que indica que nuestra clase solo tiene los campos marcados como privados:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhgleFeNE1a2LVJT6BhsHUVPtIhLLRg-PACN9WzCaoBlsEE6eLH2gSDDQhBO3HxSRp2WXatcA885noSqZNmdriqQasm0gSVzjylZtCy8-ezshCxplmtmXlUFzSwzFq4S9s_1EQaKMQtmJjX/s1600/LB1_6.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="162" data-original-width="217" height="149" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhgleFeNE1a2LVJT6BhsHUVPtIhLLRg-PACN9WzCaoBlsEE6eLH2gSDDQhBO3HxSRp2WXatcA885noSqZNmdriqQasm0gSVzjylZtCy8-ezshCxplmtmXlUFzSwzFq4S9s_1EQaKMQtmJjX/s200/LB1_6.PNG" width="200" /></a></div>
<br />
<br />
Ahora agregaremos nuestra primera anotación de "<span class="codigo">Lombok</span>": <span class="codigo">@Getter</span>. Esta anotación podemos agregarla a nivel de clase, con lo que todos los atributos no estáticos de la clase recibirán un <span class="codigo">getter</span>, de esta forma:<br />
<br />
<pre class="brush: java; ruler: true;highlight: [1]">@Getter
public class GettersSetters {
private String cadena;
private int entero;
private Date fecha;
private boolean boleano;
}
</pre>
Con esto, en el panel de outline debemos ver que se han agregado los <span class="codigo">getters</span> de estos atributos; también, podemos ver que los métodos tienen una visibilidad pública.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEigKzjMkXcjkLOyFRbu44HHbJSx2i1jhpNtRjcV9px_py4z6jXyaPo5gbHjvi5Jg6CUJaCxRRODs6jenCSC-XFnY3AsCbqnBWv0uqPqe9k8EEtGvbFp_bHLIz4Us65QqGmSLJ7-3BfLdb_i/s1600/LB1_7.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="166" data-original-width="204" height="259" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEigKzjMkXcjkLOyFRbu44HHbJSx2i1jhpNtRjcV9px_py4z6jXyaPo5gbHjvi5Jg6CUJaCxRRODs6jenCSC-XFnY3AsCbqnBWv0uqPqe9k8EEtGvbFp_bHLIz4Us65QqGmSLJ7-3BfLdb_i/s320/LB1_7.PNG" width="320" /></a></div>
<br />
<br />
Con esta simple anotación <span class="negritas">nos hemos ahorrado alrededor de 12 líneas de código</span>, esto hace nuestra clase más compacta además de ahorrarnos tiempo al escribirla. Lo mejor es que si agregamos un nuevo atributo se agrega también en automático su <span class="codigo">getter</span>.<br />
<br />
¿Qué ocurre si no queremos que los métodos <span class="codigo">getter</span> generados tengan una visibilidad pública? No hay ningún problema, <span class="codigo">Lombok</span> lo tiene considerado, dentro de la anotación <span class="codigo">@Getter</span> se tiene un atributo "<span class="codigo">value</span>", que recibe como valor un tipo "<span class="codigo">AccessLevel</span>" con los siguientes valores posibles:<br />
<ul>
<li><span class="codigo">MODULE</span></li>
<li><span class="codigo">NONE</span></li>
<li><span class="codigo">PACKAGE</span></li>
<li><span class="codigo">PRIVATE</span></li>
<li><span class="codigo">PROTECTED</span></li>
<li><span class="codigo">PUBLIC</span></li>
</ul>
Su valor por default es "<span class="codigo">PUBLIC</span>", esto quiere decir que los métodos generados son públicos, si queremos cambiar este comportamiento y hacer que, por ejemplo, los <span class="codigo">getters</span> generados tengan un nivel de visibilidad privado, lo único que tenemos que hacer es pasar esto como valor de nuestra anotación de esta forma:<br />
<br />
<pre class="brush: java; ruler: true;">@Getter(AccessLevel.PRIVATE)
</pre>
Y listo, con esto todos nuestros métodos generados tendrán un nivel de visibilidad privado<br />
<br />
¿Qué pasa si solo queremos cambiar el nivel de visibilidad de un método? Es muy sencillo, ya que la anotación <span class="codigo">@Getter</span> también se puede poner a nivel de un método, y ahí podemos realizar estas adecuaciones:<br />
<br />
<pre class="brush: java; ruler: true; highlight: [5]">@Getter
public class GettersSetters {
private String cadena;
private int entero;
@Getter(AccessLevel.PRIVATE)
private Date fecha;
private boolean boleano;
}
</pre>
Con esto, sólo el <span class="codigo">getter</span> de "<span class="codigo">fecha</span>" ha quedado con un tipo de acceso privado. ¿Qué pasa si, por el contrario, hay un atributo del que no queremos que se genere un <span class="codigo">getter</span>? Muy simple, sólo usamos el valor <span class="codigo">AccessLevel.NONE</span> en la anotación y listo, no se generará su <span class="codigo">getter</span>:<br />
<br />
<pre class="brush: java; ruler: true; highlight: [5]">@Getter
public class GettersSetters {
private String cadena;
private int entero;
@Getter(AccessLevel.NONE)
private Date fecha;
private boolean boleano;
}
</pre>
¿Qué más podemos hacer con esta anotación? Bueno, un par de cosas, la más importante a mi parecer es el poder indicar que el <span class="codigo">getter</span> generado debe tener una anotación, la cual podemos configurar. Esto es muy útil cuando usamos algún framework que requiere que coloquemos anotaciones a nivel de <span class="codigo">getter</span>, como puede ser <span class="codigo">Hibernate</span> o <span class="codigo">JPA</span>. En este caso debemos usar el atributo "<span class="codigo">onMethod</span>", el cual recibe las anotaciones que se colocarán en el <span class="codigo">getter</span>, de la siguiente forma:<br />
<br />
<pre class="brush: java; ruler: true; highlight: [1]">@Getter(onMethod=@__({@OneToOne}))
private Date fecha;
</pre>
Con esto, todos los <span class="codigo">getters</span> generados por <span class="codigo">Lombok</span> contendrán la anotación <span class="codigo">@OneToOne</span> que usamos para mapear relaciones en <span class="codigo">Hibernate</span>.<br />
<br />
Ya vimos cómo funciona <span class="codigo">@Getter</span>, ¿qué pasa con <span class="codigo">@Setter</span>? En realidad, funciona exactamente de la misma forma, se puede colocar a nivel de método o de atributo. Si lo ponemos en la clase creará todos los <span class="codigo">setters</span> de los atributos no estáticos de la clase; si lo ponemos en un atributo particular, sólo creará el <span class="codigo">setter</span> de ese atributo.<br />
<br />
Coloquémoslo para nuestro ejemplo a nivel de clase, de la siguiente forma:<br />
<br />
<pre class="brush: java; ruler: true; highlight: [2]">@Getter
@Setter
public class GettersSetters {
...
}
</pre>
Con lo cual, se generarán los <span class="codigo">setters</span> de todos los atributos (junto con los <span class="codigo">getters</span> que ya se tenían):<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjeT9oAU7kJEPce1cFqJjNs07E772k4EEaIBhMTGGRfkWI10orfTc2nGPsnIUtQxWMALE5OuTN_pnvsYMgJ5jC6IspMSN_5g4SLmVfA1KMqT_yhIpVYZa6_2AWUSpLLD7Nk9XcYVagR0U_P/s1600/LB1_8.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="244" data-original-width="243" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjeT9oAU7kJEPce1cFqJjNs07E772k4EEaIBhMTGGRfkWI10orfTc2nGPsnIUtQxWMALE5OuTN_pnvsYMgJ5jC6IspMSN_5g4SLmVfA1KMqT_yhIpVYZa6_2AWUSpLLD7Nk9XcYVagR0U_P/s320/LB1_8.png" width="319" /></a></div>
<br />
<br />
Las mismas reglas que aplican para <span class="codigo">@Getter</span> también aplican para <span class="codigo">@Setter</span>, incluso el atributo "<span class="codigo">onMethod</span>". Prácticamente la única diferencia entre estas dos anotaciones es que <span class="codigo">@Setter</span> permite agregar una anotación al parámetro que esta recibiendo (esto adicional a poder agregar una anotación al método completo), para esto proporciona un atributo "<span class="codigo">onParam</span>", de la siguiente forma:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi0yslKVpF_Bp5z6mx2WT2S1X-1ccnBGPpeViCFntBxKL4xEQ3pRRAHLtSp9txJCMPvUFeTYPit38AXvUc5bEQAQpEPeM55MdDYqupBfXapGB5KCH7gBG-Uq8rxgo85lyiFLTashRvTICco/s1600/LB1_9.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="32" data-original-width="274" height="37" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi0yslKVpF_Bp5z6mx2WT2S1X-1ccnBGPpeViCFntBxKL4xEQ3pRRAHLtSp9txJCMPvUFeTYPit38AXvUc5bEQAQpEPeM55MdDYqupBfXapGB5KCH7gBG-Uq8rxgo85lyiFLTashRvTICco/s320/LB1_9.PNG" width="320" /></a></div>
<br />
<br />
Con esto, el parámetro "<span class="codigo">fecha</span>" del método "<span class="codigo">setFecha(Data fecha)</span>" será decorado con la anotación "<span class="codigo">@NotNull</span>".<br />
<br />
Ambas anotaciones pueden convivir sin problema. Nuestra clase "<span class="codigo">GettersSetters</span>" se ve así:<br />
<br />
<pre class="brush: java; ruler: true;">@Getter
@Setter
public class GettersSetters {
private String cadena;
private int entero;
@Getter(AccessLevel.NONE)
@Setter(onParam=@__({@NonNull}))
private Date fecha;
private boolean boleano;
}
</pre>
Una cosa que hay que saber de "<span class="codigo">@Getter</span>" y "<span class="codigo">@Setter</span>" es que estos sólo funcionan con atributos de entidad; estos son los atributos no estáticos de una clase. En el caso de "<span class="codigo">@Setter</span>", este tampoco generará los métodos correspondientes para atributos final, ya que estos no pueden modificar su valor una vez establecidos.<br />
<br />
Comprobemos que todo funciona correctamente, paro eso regresaremos a nuestra clase "<span class="codigo">Main</span>" y dentro del método principal crearemos una instancia de "<span class="codigo">GettersSetters</span>" y llamaremos alguno de los métodos que se generaron de forma automática con las anotaciones de "<span class="codigo">Lombok</span>", en mi caso el método queda de la siguiente forma:<br />
<br />
<pre class="brush: java; ruler: true;">GettersSetters getSet = new GettersSetters();
getSet.setCadena("ABC123");
String valor = getSet.getCadena();
System.out.format("El valor del atributo cadena es: %s", valor);
</pre>
Con esto podemos ejecutar nuestra aplicación como una aplicación Java (<span class="codigo">Alt + Shift + X, J</span>) y si todo está bien, debemos ver la siguiente salida en la consola:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiPUZ2oaWfKomR1YHDEc5lfZKiefqM_CRwsxqByXKE0rY9d4bf9wCvt2xGQMpGk-14H0W8s5388Up7R_9rQnpY4ECw_ghdnw-1xhZxDuQdTWN2InB3zZGPrRFSFWXFnlRzfUCZ22tYoU98R/s1600/LB1_10.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="61" data-original-width="370" height="53" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiPUZ2oaWfKomR1YHDEc5lfZKiefqM_CRwsxqByXKE0rY9d4bf9wCvt2xGQMpGk-14H0W8s5388Up7R_9rQnpY4ECw_ghdnw-1xhZxDuQdTWN2InB3zZGPrRFSFWXFnlRzfUCZ22tYoU98R/s320/LB1_10.PNG" width="320" /></a></div>
<br />
<br />
Con esto podemos comprobar que <span class="codigo">Lombok</span> ha generado los métodos de forma correcta.<br />
<br />
Ahora, esto no es lo único que se puede hacer con "<span class="codigo">Lombok</span>Lombok</div>
ofrece tres alternativas:<br />
<ul>
<li>Generar un constructor sin argumentos.</li>
<li>Generar un constructor que recibe todos los parámetros obligatorios.</li>
<li>Generar un constructor que recibe todos los argumentos.</li>
</ul>
Para probar el funcionamiento de <span class="codigo">Lombok</span> con constructores, crearemos una nueva clase llamada "<span class="codigo">Constructores</span>" dentro del paquete "<span class="codigo">com.javatutoriales.lombok</span>". Colocaremos algunos atributos en esta clase, noten que algunos de estos atributos son estáticos y algunos otros están marcados como final.<br />
<br />
<pre class="brush: java; ruler: true;">public class Constructores {
private String cadena;
private int entero;
private static String cadenaEstatica;
private final String cadenaFinal;
private final int enteroFinal;
}
</pre>
Para la primera opción usamos la anotación "<span class="codigo">@NoArgsConstructor</span>", que generará el constructor sin parámetros. Este constructor se generará únicamente si no se tienen atributos de instancia marcados como "<span class="codigo">final</span>", ya que estos deben ser inicializados a más tardar en el constructor para que funcione de manera correcta; también podemos optar por que estos campos se inicialicen de forma automática con valores por default (<span class="codigo">0</span> / <span class="codigo">false</span> / <span class="codigo">null</span>) colocando el valor de "<span class="codigo">true</span>" en el atributo "<span class="codigo">force</span>" ("<span class="codigo">@NoArgsConstructor(force = true)</span>")<br />
<br />
Agregaremos esta primera anotación en la declaración de la clase:<br />
<br />
<pre class="brush: java; ruler: true; highlight: [1]">@NoArgsConstructor
public class Constructores {
...
}
</pre>
Con esto veremos que en el panel de Outline de Eclipse ha aparecido un nuevo constructor, el cual no recibe ningún argumento.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiK6LRzoijTjOs-C5z_pR8lej5UKLfR84t_8SnFm6TVXnGwh32FzWKa8i4jYK1fXydYzLofOwK31-Y0H9o74Itmjf3j2zvypg9PABvjS14ZMVibutCNCXV_kh6wuQ8JV_J3_wdtp49vcPmd/s1600/LB1_11.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="41" data-original-width="174" height="46" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiK6LRzoijTjOs-C5z_pR8lej5UKLfR84t_8SnFm6TVXnGwh32FzWKa8i4jYK1fXydYzLofOwK31-Y0H9o74Itmjf3j2zvypg9PABvjS14ZMVibutCNCXV_kh6wuQ8JV_J3_wdtp49vcPmd/s200/LB1_11.PNG" width="200" /></a></div>
<br />
<br />
Para probar el segundo caso, usaremos la anotación "<span class="codigo">@RequiredArgsConstructor</span>" la cual generará un constructor con cada uno de los atributos final no inicializados, así como todos los atributos marcados como "<span class="codigo">@NotNull</span>". Colocaremos la anotación de esta forma:<br />
<br />
<pre class="brush: java; ruler: true; highlight: [2]">@NoArgsConstructor
@RequiredArgsConstructor
public class Constructores {
...
}
</pre>
Con lo que veremos que se ha agregado un segundo constructor que recibe un <span class="codigo">String</span> y un <span class="codigo">int</span>, los cuales corresponden con los atributos <span class="codigo">final</span> "<span class="codigo">cadenaFinal</span>" y "<span class="codigo">enteroFinal</span>":<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEinK6kJxqUryQ_zo3ArdCTnjL9xbxkn5KTdvjVERLaidWugaShG4LQo4_bqONzwVfc0ehBUTd1WFKE-diWN5BQv3QoTNVyp3jg7aKMOQFQCe6WtPw5pnP0LXgTJdcYDslE1UfwMM5wbkcWm/s1600/LB1_12.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="54" data-original-width="201" height="53" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEinK6kJxqUryQ_zo3ArdCTnjL9xbxkn5KTdvjVERLaidWugaShG4LQo4_bqONzwVfc0ehBUTd1WFKE-diWN5BQv3QoTNVyp3jg7aKMOQFQCe6WtPw5pnP0LXgTJdcYDslE1UfwMM5wbkcWm/s200/LB1_12.png" width="200" /></a></div>
<br />
<br />
Finalmente, "<span class="codigo">@AllArgsConstructor</span>" genera un constructor con todos los parámetros no estáticos de la clase. Colocamos esta anotación así:<br />
<br />
<pre class="brush: java; ruler: true; highlight: [3]">@NoArgsConstructor
@RequiredArgsConstructor
@AllArgsConstructor
public class Constructores {
...
}
</pre>
Con esto, veremos que se agrega un nuevo constructor con cuatro parámetros, los cuales corresponden a todos los atributos no estáticos de la clase.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh7Ih0PaY3sEOQ8LT-cMJVbWB7FuFqdPITHd9enEFUtHFCxs3_Uop2JlH_3E_Z52JSWxwWKqnUoIYj6M9qIAMAHT4NZ0FU8ShhUeRHBmlU5NMT3qCgdgt6eKlVnbD6DfGGgEntCPfLviJke/s1600/LB1_13.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="86" data-original-width="275" height="100" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh7Ih0PaY3sEOQ8LT-cMJVbWB7FuFqdPITHd9enEFUtHFCxs3_Uop2JlH_3E_Z52JSWxwWKqnUoIYj6M9qIAMAHT4NZ0FU8ShhUeRHBmlU5NMT3qCgdgt6eKlVnbD6DfGGgEntCPfLviJke/s320/LB1_13.png" width="320" /></a></div>
<br />
<br />
Para comprobar el correcto funcionamiento de esta clase haremos dos cosas, primero agregaremos la anotación "<span class="codigo">@Getter</span>" al inicio de la clase, esto nos ayudará a leer los valores de los atributos que serán establecidos en el constructor, de esta forma:<br />
<br />
<pre class="brush: java; ruler: true; highlight: [2]">@AllArgsConstructor
@Getter
public class Constructores {
...
}
</pre>
Ahora regresaremos a nuestra clase "<span class="codigo">Main</span>" y colocaremos el siguiente código en donde probaremos que los valores pasados a los constructores generados con "<span class="codigo">@RequiredArgsConstructor</span>" y "<span class="codigo">@AllArgsConstructor</span>" funcionen correctamente (no podremos probar "<span class="codigo">@NoArgsConstructor</span>", ya que al tener campos <span class="codigo">final</span> este constructor nos dará errores. Nuestro código para la prueba se ve de la siguiente forma:<br />
<br />
<pre class="brush: java; ruler: true;">Constructores constructoresRequired = new Constructores("cadenaFinal", 10);
System.out.format("getCadenaFinal: %s\n", constructoresRequired.getCadenaFinal());
System.out.format("getEnteroFinal: %d\n\n", constructoresRequired.getEnteroFinal());
Constructores constructoresAllArgs = new Constructores("cadena", 1, "cadenaFinal", 2);
System.out.format("getCadena: %s\n", constructoresAllArgs.getCadena());
System.out.format("getEntero: %d\n", constructoresAllArgs.getEntero());
System.out.format("getCadenaFinal: %s\n", constructoresAllArgs.getCadenaFinal());
System.out.format("getEnteroFinal: %d\n", constructoresAllArgs.getEnteroFinal());
</pre>
Ahora, ejecutamos nuestra aplicación como una aplicación Java (<span class="codigo">Alt + Shift + X, J</span>), con lo que debemos ver la siguiente salida en la consola de Eclipse:<br />
<br />
<pre class="brush: java; ruler: true;">getCadenaFinal: cadenaFinal
getEnteroFinal: 10
getCadena: cadena
getEntero: 1
getCadenaFinal: cadenaFinal
getEnteroFinal: 2
</pre>
Con esto podemos comprobar que los constructores generados funcionan correctamente.<br />
<br />
Cada una de estas anotaciones soporta un par de atributos interesantes. El primero es "<span class="codigo">access</span>", el cual nos permite definir el nivel de acceso del constructor. Por default, los constructores son generados con visibilidad pública, pero si queremos cambiar esto podemos usar alguno de los valores de la enumeración "<span class="codigo">AccessLevel</span>" para modificarlo. Por ejemplo, si queremos que el constructor que recibe todos los parámetros (el generado por "<span class="codigo">@AllArgsConstructor</span>") tenga un nivel de acceso protegido, lo modificamos de la siguiente forma:<br />
<br />
<pre class="brush: java; ruler: true;">@AllArgsConstructor(access=AccessLevel.PROTECTED)
</pre>
Con esto veremos que ahora su nivel de visibilidad ha cambiado.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiCPtKVtQYO8v5X2qQy-tTepiC1l2l0tJ_pkBIGeaPvyIFCPbgAikpqj1dgL3lPfJAkdHknoHmEVGx7c3g6mma0kdKz2Twn_m7GKTpBHd0v_JpuNeI_dHd5Vnlpo_v3fId_AHvU9OEhzyLT/s1600/LB1_14.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="60" data-original-width="264" height="73" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiCPtKVtQYO8v5X2qQy-tTepiC1l2l0tJ_pkBIGeaPvyIFCPbgAikpqj1dgL3lPfJAkdHknoHmEVGx7c3g6mma0kdKz2Twn_m7GKTpBHd0v_JpuNeI_dHd5Vnlpo_v3fId_AHvU9OEhzyLT/s320/LB1_14.PNG" width="320" /></a></div>
<br />
<br />
Otro de los atributos soportados es "<span class="codigo">staticName</span>", este atributo hará que el constructor generado sea privado y se agregará un método estático de fábrica que nos permitirá obtener la instancia de la clase. Este método generado recibirá todos los parámetros que requiera el constructor para funcionar correctamente. "<span class="codigo">staticName</span>" recibe como valor el nombre del método estático que se generará, los nombres más comunes para este tipo de métodos son "<span class="codigo">getInstance</span>" y "<span class="codigo">of</span>". Modificaremos la anotación "<span class="codigo">@RequiredArgsConstructor</span>" para agregar este atributo de la siguiente forma:<br />
<br />
<pre class="brush: java; ruler: true;">@RequiredArgsConstructor(staticName="getInstance")
</pre>
Esto quiere decir que en vez de crear una instancia de nuestra clase "<span class="codigo">Constructores</span>" de esta forma:<br />
<br />
<pre class="brush: java; ruler: true;">new Constructores("cadenaFinal", 10);
</pre>
Ahora lo haremos así:<br />
<br />
<pre class="brush: java; ruler: true;">Constructores.getInstance("cadenaFinal", 10);
</pre>
Hagamos este cambio en el código de la clase "<span class="codigo">Main</span>" y volvamos a ejecutar el ejemplo. Con esto la salida no debe haber cambiado, con lo cual comprobamos que el ejemplo funciona correctamente.<br />
<br />
Ahora veremos otra de esas utilidades de <span class="codigo">Lombok</span> que pueden ayudarnos a ahorrar mucho tiempo, sobre todo en clases que pueden estar cambiando de forma constante. La clase <span class="codigo">Object</span>, de la que todas las clases en Java heredan directa o indirectamente, define un método "<span class="codigo">toString</span>" que regresa la representación de un objeto en forma de cadena. Este método es llamado en automático por muchas clases del core de java y muchos frameworks, por eso es importante realizar una correcta sobreescritura en nuestras clases. <span class="codigo">Lombok</span> proporciona la anotación "<span class="codigo">@ToString</span>" que permite realizar la implementación en automático del método "<span class="codigo">toString</span>". La implementación por default imprimirá el nombre de la clase, junto con cada campo en el orden en el que aparecen en la declaración de la clase y los separará por comas; es importante mencionar que sólo se mostrarán los atributos no estáticos de la clase.<br />
<br />
Para probarlo, creamos una nueva clase llamada "<span class="codigo">EjemploToString</span>" y le agregaremos algunos atributos para tener material para usar en el método "<span class="codigo">toString</span>", de esta forma:<br />
<br />
<pre class="brush: java; ruler: true;">public class EjemploToString {
private String cadena;
private char caracter;
private int entero;
private Date fecha;
private boolean boleano;
private String[] arreglo;
}
</pre>
Ahora, colocaremos la anotación "<span class="codigo">@ToString</span>" al inicio de la clase para generar este método; adicionalmente, pondremos también la anotación "<span class="codigo">@AllArgsConstructor</span>" para generar un constructor que nos permita inicializar los atributos del nuevo objeto. La declaración de la clase queda de la siguiente forma:<br />
<br />
<pre class="brush: java; ruler: true; highlight: [2]">@AllArgsConstructor
@ToString
public class EjemploToString {
private String cadena;
private char caracter;
private int entero;
private Date fecha;
private boolean boleano;
private String[] arreglo;
}
</pre>
Ahora, en el método "<span class="codigo">main</span>" inicializaremos una instancia de esta clase de la siguiente forma:<br />
<br />
<pre class="brush: java; ruler: true;">EjemploToString ejemploToString = new EjemploToString("cadena", 'c', 1, null, true, new String[]{"uno", "dos", "tres"});
</pre>
Con esto le hemos dado un valor a cada uno de los atributos (con excepción de la fecha), y esperamos que en el método "<span class="codigo">toString</span>" sean estos los valores que se muestren. Para probarlo, agregaremos la siguiente línea de código, que de manera implícita manda llamar al método "<span class="codigo">toString()</span>":<br />
<br />
<pre class="brush: java; ruler: true;">System.out.format("toString: %s", ejemploToString);
</pre>
Ahora, ejecutamos nuestra aplicación y debemos ver la siguiente salida en la consola de Eclipse:<br />
<br />
<pre class="brush: java; ruler: true;">toString: EjemploToString(cadena=cadena, caracter=c, entero=1, fecha=null, boleano=true, arreglo=[uno, dos, tres])
</pre>
Con lo que podemos comprobar que el ejemplo ha funcionado correctamente.<br />
<br />
¿Qué pasa si queremos que no se muestre alguno de los atributos? En ese caso, debemos marcar dicho atributo con la anotación "<span class="codigo">@ToString.Exclude</span>". Para probarlo, colocaremos esta anotación en el atributo "<span class="codigo">fecha</span>" de la clase "<span class="codigo">EjemploToString</span>":<br />
<br />
<pre class="brush: java; ruler: true; highlight: [1]">@ToString.Exclude
private Date fecha;
</pre>
Si ahora volvemos a ejecutar nuestra aplicación, debemos ver la siguiente salida:<br />
<br />
<pre class="brush: java; ruler: true;">toString: EjemploToString(cadena=cadena, caracter=c, entero=1, boleano=true, arreglo=[uno, dos, tres])
</pre>
<span class="codigo">Lombok</span> también da una forma de modificar el nombre que se muestra del atributo en el método "<span class="codigo">toString</span>". Podemos colocar este nombre en el atributo "<span class="codigo">name</span>" de la anotación "<span class="codigo">@ToString.Include</span>" que podemos colocar en el campo cuyo nombre queremos modificar; por ejemplo, si queremos que al llamar a "<span class="codigo">toString</span>" en vez de mostrarse "<span class="codigo">arreglo</span>" como el nombre de este campo se muestre "<span class="codigo">cadenas</span>", colocamos la anotación de esta forma:<br />
<br />
<pre class="brush: java; ruler: true; highlight: [1]">@ToString.Include(name="cadenas")
private String[] arreglo;
</pre>
Si volvemos a ejecutar nuestra aplicación, debemos ver la siguiente salida en la consola:<br />
<br />
<pre class="brush: java; ruler: true;">toString: EjemploToString(cadena=cadena, caracter=c, entero=1, boleano=true, cadenas=[uno, dos, tres])
</pre>
También, esta anotación nos da la posibilidad de definir una prioridad, que en el argot de <span class="codigo">Lombok</span> es llamado rango (<span class="codigo">rank</span>), para indicar el orden en el que serán mostrados los atributos. Por default, todos los atributos tienen el rango de <span class="codigo">0</span>; los campos con un rango mayor serán mostrados primeros. Agreguemos un rango de <span class="codigo">1</span> al arreglo de cadenas (para que se muestre primero su valor) y un rango de <span class="codigo">-1</span> al caracter (para que sea el último valor en mostrarse), de la siguiente forma:<br />
<br />
<pre class="brush: java; ruler: true; highlight: [4,14]">public class EjemploToString {
private String cadena;
@ToString.Include(rank=-1)
private char caracter;
private int entero;
@ToString.Exclude
private Date fecha;
private boolean boleano;
@ToString.Include(name="cadenas", rank=1)
private String[] arreglo;
}
</pre>
Si ahora ejecutamos nuevamente nuestra aplicación debemos ver la siguiente salida:<br />
<br />
<pre class="brush: java; ruler: true;">toString: EjemploToString(cadenas=[uno, dos, tres], cadena=cadena, entero=1, boleano=true, caracter=c)
</pre>
Con esto podemos comprobar que el ejemplo funciona de manera correcta.<br />
<br />
Una de las recomendaciones del lenguaje Java es siempre sobreescribir los métodos "<span class="codigo">equals</span>" y "<span class="codigo">hashCode</span>". La utilidad de estos métodos es que nos permiten comparar dos objetos para saber si son iguales (o equivalentes), usando para esto los valores de sus atributos a través del criterio que nosotros definamos. <br />
<br />
En el caso de "<span class="codigo">equals</span>", la implementación por default define que dos objetos son iguales si estos están en el mismo segmento de memoria; es decir, si los objetos que estamos comparando son la misma instancia (o, dicho de otra forma, sólo serán iguales si comparamos a una instancia con ella misma sin importar los valores de sus atributos). En el caso de la implementación de "<span class="codigo">hashCode</span>" pasa algo parecido y la implementación por default regresa la representación de la ubicación en memoria del objeto. <br />
<br />
Esto es poco útil en aplicaciones de mediana o gran escala en la que nos interesa saber si dos objetos son iguales (o equivalente) por los valores que les hemos dado a sus atributos como podrían ser un identificador o un nombre. Algo que es especialmente útil saber es que muchas colecciones en Java (<span class="codigo">ArrayList</span> o las implementaciones de <span class="codigo">Set</span>) obtienen los objetos con base al resultado obtenido de llamar a estos métodos, y si no los sobreescribimos de forma apropiada podríamos causar un enorme problema en nuestra aplicación sin siquiera saberlo.<br />
<br />
Comprobemos que lo anterior es verdad con un ejemplo. Crearemos una nueva clase llamada "<span class="codigo">EjemploEqualsHashcode</span>" que tendrá los siguientes atributos:<br />
<br />
<pre class="brush: java; ruler: true;">public class EjemploEqualsHashcode {
private long id;
private String matricula;
private String nombre;
}
</pre>
Decoraremos esta clase con la anotación "<span class="codigo">@AllArgsConstructor</span>" para tener una forma rápida de inicializar los valores de las instancias que crearemos:<br />
<br />
<pre class="brush: java; ruler: true;">@AllArgsConstructor
public class EjemploEqualsHashcode {
private long id;
private String matricula;
private String nombre;
}
</pre>
Regresemos a nuestro método "<span class="codigo">main</span>" y agregamos las siguientes líneas, las cuales crean dos instancias de "<span class="codigo">EjemploEqualsHashcode</span>" con exactamente los mismos valores:<br />
<br />
<pre class="brush: java; ruler: true;">EjemploEqualsHashcode instancia1 = new EjemploEqualsHashcode(2, "ABC123", "Alex");
EjemploEqualsHashcode instancia2 = new EjemploEqualsHashcode(2, "ABC123", "Alex");
</pre>
Ahora, para comprobar que con la implementación por default del JDK estos dos objetos no son equivalentes, agregaremos la siguiente línea de código:<br />
<br />
<pre class="brush: java; ruler: true;">System.out.printf("Las instancias son iguales?: %b\n", instancia1.equals(instancia2));
</pre>
Y para comprobar que estas instancias están en ubicaciones diferentes de memoria usaremos lo siguiente (esto no es completamente correcto, pero es una forma sencilla de entender lo que está mostrando el método "<span class="codigo">hashCode</span>"):<br />
<br />
<pre class="brush: java; ruler: true;">System.out.printf("Hash de la instancia 1: %h\n", instancia1.hashCode());
System.out.printf("Hash de la instancia 2: %h\n", instancia2.hashCode());
</pre>
Ahora ejecutemos nuestra aplicación, debemos ver una salida similar a la siguiente (en su caso la salida de los hashes será diferente):<br />
<br />
<pre class="brush: java; ruler: true;">Las instancias son iguales?: false
Hash de la instancia 1: 4aa298b7
Hash de la instancia 2: 7d4991ad
</pre>
Ahora, un ejemplo más alarmante (<span class="codigo">^_^!</span>). Agregaremos ambas instancias a un <span class="codigo">Set</span> y mostraremos que ambos objetos han quedado dentro de dicho <span class="codigo">Set</span>. Recordemos que, por definición, un <span class="codigo">Set</span> no admite objetos repetidos:<br />
<br />
<pre class="brush: java; ruler: true;">Set<EjemploEqualsHashcode> set = new HashSet<>();
set.add(instancia1);
set.add(instancia2);
System.out.printf("Los objetos del set son: %d, %s\n", set.size(), set);
</pre>
Si ahora ejecutamos nuestra aplicación, debemos ver la siguiente salida:<br />
<br />
<pre class="brush: java; ruler: true;">Los objetos del set son: 2, [com.javatutoriales.lombok.EjemploEqualsHashcode@7d4991ad, com.javatutoriales.lombok.EjemploEqualsHashcode@4aa298b7]
</pre>
Con esto comprobamos que ambas instancias son completamente diferentes para Java. ¿Qué podemos hacer si queremos que las considere iguales dentro de la lógica de nuestra aplicación (por ejemplo, para no tener dos objetos con los mismos valores dentro del hash)? Muy fácil, para esto hay que implementar los métodos "<span class="codigo">equals</span>" y "<span class="codigo">hashCode</span>" con nuestra propia lógica para decidir si efectivamente ambos objetos serán iguales o no.<br />
<br />
Al hacer esta implementación hay que tener en cuenta que existe una especie de contrato entre estos dos métodos:<br />
<br />
<span class="negritas">If two objects are equal according to the equals(Object) method, then calling the hashcode() method on each of the two objects must produce the same integer result.</span><br />
<br />
Con <span class="codigo">Lombok</span> lograr esto es muy fácil, sólo debemos agregar la anotación "<span class="codigo">@EqualsAndHashCode</span>" al inicio de nuestra clase, de la siguiente forma:<br />
<br />
<pre class="brush: java; ruler: true; highlight: [1]">@EqualsAndHashCode
public class EjemploEqualsHashcode {
...
}
</pre>
Con esto se realizará una implementación automática de ambos métodos, cumpliendo el contrato anterior. Para la implementación se usarán todos los atributos no estáticos de la clase. Si ahora volemos a ejecutar nuestro ejemplo, veremos la siguiente salida:<br />
<br />
<pre class="brush: java; ruler: true;">Las instancias son iguales?: true
Hash de la instancia 1: 6dd5ca43
Hash de la instancia 2: 6dd5ca43
Los objetos del set son: 1, [com.javatutoriales.lombok.EjemploEqualsHashcode@6dd5ca43]
</pre>
Ahora, para Java ambas instancias son iguales y por lo tanto ya no hay repetidos en el <span class="codigo">Set</span>.<br />
<br />
¿Qué pasa si no queremos incluir todos los atributos en la implementación de estos métodos? Por ejemplo, si quisiéramos que sólo se tomaran en cuenta el nombre y la matrícula.<br />
<br />
<pre class="brush: java; ruler: true;">EjemploEqualsHashcode instancia1 = new EjemploEqualsHashcode(2, "ABC123", "Alex");
EjemploEqualsHashcode instancia2 = new EjemploEqualsHashcode(2, "ABC123", "Juan");
</pre>
Si ejecutamos el ejemplo con la modificación anterior, volveremos a obtener los resultados del inicio:<br />
<br />
<pre class="brush: java; ruler: true;">Las instancias son iguales?: false
Hash de la instancia 1: 6dd5ca43
Hash de la instancia 2: 6dda02dd
Los objetos del set son: 2, [com.javatutoriales.lombok.EjemploEqualsHashcode@6dd5ca43, com.javatutoriales.lombok.EjemploEqualsHashcode@6dda02dd]
</pre>
Arreglar esto es muy simple, solo debemos marcar los atributos que no deseamos tomar en cuenta con la anotación "<span class="codigo">@EqualsAndHashCode.Exclude</span>", de esta forma:<br />
<br />
<pre class="brush: java; ruler: true; highlight: [1]">@EqualsAndHashCode.Exclude
private String nombre;
</pre>
Si volvemos a ejecutar nuestra aplicación obtendremos la siguiente salida:<br />
<br />
<pre class="brush: java; ruler: true;">Las instancias son iguales?: true
Hash de la instancia 1: 72ac50bf
Hash de la instancia 2: 72ac50bf
Los objetos del set son: 1, [com.javatutoriales.lombok.EjemploEqualsHashcode@72ac50bf]
</pre>
Con esto, volvemos a obtener el resultado que estamos esperando.<br />
<br />
Para terminar este tutorial, veremos la anotación "<span class="codigo">@Data</span>", la cual es un atajo para escribir las anotaciones "<span class="codigo">@ToString</span>", "<span class="codigo">@EqualsAndHashCode</span>", "<span class="codigo">@Getter</span>", "<span class="codigo">@Setter</span>" y "<span class="codigo">@RequiredArgsConstructor</span>" en nuestra clase, con todas las opciones de dichas anotaciones (incluyendo el atributo "<span class="codigo">staticConstructor</span>" para generar un método estático de fábrica). Esta anotación es excelente para escribir <span class="codigo">POJO</span>s. <br />
<br />
Para probar esta anotación, creamos una nueva clase llamada "<span class="codigo">EjemploData</span>" y le colocamos los siguientes atributos:<br />
<br />
<pre class="brush: java; ruler: true; highlight: [1]">@Data
public class EjemploData {
private final String cadena;
private final int entero;
private String[] cadenas;
private Date fecha;
}
</pre>
Al decorar esta clase con la anotación "<span class="codigo">@Data</span>" veremos que se han generado todos los métodos indicados anteriormente (<span class="codigo">getters</span>, <span class="codigo">setters</span>, <span class="codigo">toString</span>, <span class="codigo">equals</span>, etc.).<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhBE1Jl-91semDuw3QpLS_bnAc4TdzNjhx5hHioeUj2bLo9xE5504AnLaYy4dTW6gMnVPRlMS-xM9G4eCrw7mKvZsQpOeWvvG1G7tzgMKtowutDLe-GZNyhjqEcCVkUQFVrWSWRDnZ-ZBq7/s1600/LB1_15.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="290" data-original-width="223" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhBE1Jl-91semDuw3QpLS_bnAc4TdzNjhx5hHioeUj2bLo9xE5504AnLaYy4dTW6gMnVPRlMS-xM9G4eCrw7mKvZsQpOeWvvG1G7tzgMKtowutDLe-GZNyhjqEcCVkUQFVrWSWRDnZ-ZBq7/s320/LB1_15.PNG" width="246" /></a></div>
<br />
<br />
Haremos algunos ajustes en esta clase, como evitar que la fecha se muestre en el método "<span class="codigo">toString</span>" o evitar que el arreglo y la fecha sean tomados en cuenta en los métodos "<span class="codigo">equals</span>" y "<span class="codigo">hashCode</span>", además evitaremos que se genere el <span class="codigo">getter</span> del atributo "<span class="codigo">entero</span>". La clase completa queda de la siguiente forma:<br />
<br />
<pre class="brush: java; ruler: true; highlight: [5,8,11,12]">@Data
public class EjemploData {
private final String cadena;
@Getter(AccessLevel.NONE)
private final int entero;
@EqualsAndHashCode.Exclude
private String[] cadenas;
@ToString.Exclude
@EqualsAndHashCode.Exclude
private Date fecha;
}
</pre>
Ahora, regresaremos al método "<span class="codigo">main</span>", en el que colocaremos las siguientes líneas para realizar nuestra prueba:<br />
<br />
<pre class="brush: java; ruler: true;">EjemploData ejemploData1 = new EjemploData("cadena", 1);
EjemploData ejemploData2 = new EjemploData("cadena", 1);
System.out.format("getCadena: %s\n", ejemploData1.getCadena());
System.out.format("getCadena: %s\n", ejemploData2.getCadena());
System.out.printf("Las instancias son iguales?: %b\n", ejemploData1.equals(ejemploData2));
System.out.printf("Valores: %s\n\n", ejemploData1);
</pre>
Con esto comprobamos que el ejemplo funciona correctamente y terminamos el tutorial.<br />
<br />
Como pudimos ver a lo largo de este tutorial, <span class="codigo">Lombok</span> es una herramienta muy útil que nos permite escribir menos código y seguir teniendo control sobre el código que se genera, lo que la hace una herramienta perfecta para escribir nuestros <span class="codigo">POJO</span>s y <span class="codigo">VO</span>s.<br />
<br />
<span class="codigo">Lombok</span> ofrece más opciones, si quieren verlas todas pueden consultar <a href="https://projectlombok.org/features/all" target="_blank">su documentación oficial</a>.<br />
<br />
Estamos probando una nueva herramienta para dar formato al código, ¿qué les parece, es más claro así o nos quedamos con la herramienta anterior? Déjenme sus comentarios. ¿Qué nuevo tutorial les gustaría ver en el siguiente post?<br />
<br />
Espero que este tutorial les sea de utilidad. Si tienen alguna duda, sugerencia, comentario o aclaración, pueden dejarla en la sección de comentarios o enviar un correo a <a href="mailto:programadorjavablog@gmail.com">programadorjavablog@gmail.com</a> (pueden agregarme al chat de gmail). También pueden seguir <span class="codigo">JavaTutoriales</span> en las siguientes redes sociales:<br />
<ul>
<li><span class="codigo"><a href="https://www.facebook.com/JavaTutoriales/" target="_blank">Facebook</a></span></li>
<li><span class="codigo"><a href="https://twitter.com/JavaTutoriales" target="_blank">Twitter</a></span></li>
</ul>
<br />
Saludos y gracias.<br />
<br />
Descarga los archivos de este tutorial desde aquí:<br />
<br />
<li><span class="codigo"><a href="https://github.com/JavaTutoriales/Lombok" target="_blank">Lombok</a></span></li>
<br />
<br />
<!--
<span class="negritas">Entradas Relacionadas:</span><br />
<br />
--><br />Alexhttp://www.blogger.com/profile/06974037481671868076noreply@blogger.comtag:blogger.com,1999:blog-2012232069289957092.post-51357181176161280942019-02-24T10:22:00.003-08:002022-01-01T17:01:40.685-08:00Spring Boot Parte 1 - Introducción y Hola Mundo<div style="text-align: justify;">
<img src="https://img.shields.io/badge/JT-Spring-brightgreen" /><br />
<br />
Comenzamos hoy con esta serie tutoriales sobre Spring Boot para renovarnos un poco, especialmente después de haber podido aprender algo de <span class="codigo"><a href="https://www.javatutoriales.com/2010/09/spring-parte-1-introduccion.html" target="_blank">Spring Core</a></span> y de <span class="codigo"><a href="https://www.javatutoriales.com/2015/12/spring-mvc-parte-1-configuracion.html" target="_blank">Spring MVC</a></span><br />
<br />
Seguramente muchos habrán escuchado de <span class="codigo">Spring Boot</span>, sobre todo en el ámbito de los microservicios, pero ¿qué es <span class="codigo">Spring Boot</span>? <span class="codigo">Spring Boot</span> es un framework ligero que permite eliminar la mayor parte del trabajo de las configuraciones básicas de <span class="codigo">Spring</span>. Es una de esas maravillas dentro de la maravilla que ya es <span class="codigo">Spring</span>; literalmente <span class="negritas">en unos pocos segundos se puede tener una aplicación web recibiendo peticiones y lista para desplegarse</span>, ya que contiene <span class="negritas">un conjunto inteligente de valores por default</span>, por lo que requiere muy poca o ninguna configuración, y esa poca configuración se da en forma de anotaciones. No por nada ha sido una de las herramientas más utilizados por la comunidad de Spring desde su aparición en 2013.<br />
<br />
Además de esto, <span class="codigo">Spring Boot</span> proporciona un conjunto rico de herramientas integradas para sólo tener que preocuparnos de la lógica de la aplicación. A lo largo de esta serie de tutoriales iremos viendo y configurando cada una de estas herramientas.<br />
<br />
<a name='more'></a>Si recuerdan <a href="https://www.javatutoriales.com/2010/09/spring-parte-1-introduccion.html" target="_blank">los tutoriales anteriores</a>, uno de los problemas que tenemos con el uso de <span class="codigo">Spring Core</span> es que es difícil de configurar, se necesitan muchas dependencias y cablear muchas clases para poder tener algo funcional; además era muy simple equivocarse en algún valor (sobre todo si lo usábamos con configuración en <span class="codigo">XML</span>) nada funcionaba, y muchas veces era complicado lograr encontrar el error.<br />
<br />
Con esta breve explicación, comencemos pues con el código del tutorial.<br />
<br />
Lo primero es crear el proyecto, pero en vez de hacer esto directamente desde el IDE para posteriormente descargar las dependencias o buscarlas a través de <span class="codigo">Maven</span> o <span class="codigo">Gradle</span>, vamos a apoyarnos de <span class="codigo"><a href="https://start.spring.io/" target="_blank">Spring Initializr</a></span>, el cual es una especie de asistente que realizará la configuración inicial básica para comenzar con nuestro proyecto. Este asistente no podría ser más sencillo de utilizar; simplemente colocamos algunos detalles mínimos sobre nuestro proyecto, elegimos un sistema de construcción (<span class="codigo">Gradle</span> o <span class="codigo">Maven</span>), un lenguaje (<span class="codigo">Java</span>, <span class="codigo">Kotlin</span> o <span class="codigo">Groovy</span>) y una versión de <span class="codigo">Spring Boot</span> (que en mi caso será la <span class="codigo">2.1.3</span> que es la más nueva al momento de escribir este tutorial):<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgKj0jfxABnajNWrk1NCPuOOKRVI-YTtHB2M0dx_d4_Xi0d0gTiMGonLb3owG_gDkwUvaf_9Co8wsm_zXe5Sx5UdQjZZ8uXstXwbJ7PM-Dx-kW55ZhbmAvQ4HlP21iATT1iD9J4cUCYTlDu/s1600/SB1_1.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="474" data-original-width="967" height="196" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgKj0jfxABnajNWrk1NCPuOOKRVI-YTtHB2M0dx_d4_Xi0d0gTiMGonLb3owG_gDkwUvaf_9Co8wsm_zXe5Sx5UdQjZZ8uXstXwbJ7PM-Dx-kW55ZhbmAvQ4HlP21iATT1iD9J4cUCYTlDu/s400/SB1_1.PNG" width="400" /></a></div>
<br />
<br />
Como este es un tutorial introductorio, sólo elegiremos "<span class="codigo">Web</span>" como dependencia, pero conforme vayamos avanzando y nuestras aplicaciones se hagan más complejas, usaremos más tipos de dependencias; por ejemplo, en el siguiente tutorial usamos <span class="codigo">Lombok</span> para simplificar la escritura de nuestros POJOs.<br />
<br />
Una vez que hemos configurado nuestro proyecto, hacemos clic en el botón "<span class="codigo">Generate Project</span>" con lo que se descargará un archivo <span class="codigo">zip</span> con la configuración inicial de nuestra aplicación.<br />
<br />
<span class="nota">Nota: Esta no es la única forma de generar un proyecto en Spring Boot, pero creo que es la más fácil ya que coloca todas las dependencias necesarias en el archivo de configuración de gradle. Si no les gusta esta forma o por alguna razón no pueden acceder a este sitio, aún es posible crear el proyecto directamente en el IDE.</span><br />
<br />
Este <span class="codigo">zip</span> contendrá los archivos iniciales del proyecto.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgWzgPfYd-aAuk5invMyM3Gs7F27dfhDohUkk1alu52GfVk8S9CO32yfv4Cdq8vhF-jXmsoV90gLRqcg6XH1EgttukndAgXv5P4749Vdk9ULe2CxaxG3E0jHZDp2wGLvagiykdigwh-8y98/s1600/SB1_2.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="160" data-original-width="476" height="133" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgWzgPfYd-aAuk5invMyM3Gs7F27dfhDohUkk1alu52GfVk8S9CO32yfv4Cdq8vhF-jXmsoV90gLRqcg6XH1EgttukndAgXv5P4749Vdk9ULe2CxaxG3E0jHZDp2wGLvagiykdigwh-8y98/s400/SB1_2.PNG" width="400" /></a></div>
<br />
<br />
<ul>
<li><span class="codigo">build.gradle</span>, el script de construcción (build script) que indica cómo realizar la construcción del proyecto, es el equivalente al <span class="codigo">pom.xml</span> de <span class="codigo">Maven</span>.</li>
<li><span class="codigo">gradlew.bat</span>, un wrapper de Gradle para la línea de comandos. Este wrapper se asegura de que el proyecto siempre se construye usando la misma versión de Gradle, sin importar quién lo ejecute o qué versión de gradle esté instalada en el equipo; con este wrapper se evita incluso tener que instalar Gradle, es suficiente con tener Java.</li>
<li><span class="codigo">settings.gradle</span>, es otro script de construcción pero global con algunas otras configuraciones. Este archivo se lee antes que <span class="codigo">build.gradle</span> y es muy útil cuando trabajamos con multiproyectos.</li>
<li>Algunos directorios con una clase "<span class="codigo">main</span>" con algo de código para ejecutar nuestra aplicación.</li>
<br />
</ul>
Una vez que tenemos esta estructura, hay que importarla en Eclipse; vamos al menú "<span class="codigo">File -> Import...</span>", y en la ventana que se abre seleccionamos "<span class="codigo">Gradle -> Existing Gradle Project</span>".<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiuI_8Thx8jf9nFWsd3FWfg_rmzaVw2mueA59yMTDWUeMHNrtmHt8f3APNDPupqZzj3D7VmGIPt_rPFssA0_KG1HMtvtLMckzmWYrKsVD7x8d3RHUWR11bU4XDesTjAW7h2xuwr8F7MeJjT/s1600/SB1_3.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="542" data-original-width="510" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiuI_8Thx8jf9nFWsd3FWfg_rmzaVw2mueA59yMTDWUeMHNrtmHt8f3APNDPupqZzj3D7VmGIPt_rPFssA0_KG1HMtvtLMckzmWYrKsVD7x8d3RHUWR11bU4XDesTjAW7h2xuwr8F7MeJjT/s400/SB1_3.PNG" width="376" /></a></div>
<br />
<br />
Navegamos al directorio en el que descomprimimos el <span class="codigo">zip</span> de <span class="codigo">Spring Initializer</span> y presionamos el botón "<span class="codigo">Finish</span>" (las opciones que por default nos da Eclipse funcionarán muy bien para el proyecto). La importación de este tardará algunos segundos (o minutos) ya que realiza la descarga de algunos plugins. Al final veremos el proyecto en el explorador de proyectos:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgrWlbTtTx5_fDaKyMzPLJhV_M5uYKTt4GILv__Q0266O-h0ir6sE9l7J34LhLv1gwHcZj806covXuO08HjZE7cemoxwmlNk6iaoGWAzHiue245b6frPhSsH0pLS5B6hNCWwBsYLPFJSdog/s1600/SB1_4.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="298" data-original-width="350" height="340" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgrWlbTtTx5_fDaKyMzPLJhV_M5uYKTt4GILv__Q0266O-h0ir6sE9l7J34LhLv1gwHcZj806covXuO08HjZE7cemoxwmlNk6iaoGWAzHiue245b6frPhSsH0pLS5B6hNCWwBsYLPFJSdog/s400/SB1_4.png" width="400" /></a></div>
<br />
<br />
Una aplicación <span class="codigo">Spring Boot</span> se ejecuta igual que una aplicación Java "normal", no es necesario colocarla en algún contenedor o servidor para que se ejecute, y esto es porque <span class="negritas">las aplicaciones <span class="codigo">Spring Boot</span> ya tienen un servidor embebido</span>. El servidor que se use por default dependerá, entre otras cosas, de la versión de <span class="codigo">Spring Boot</span> que estemos usando; en este caso es un <span class="codigo">Tomcat</span> embebido, pero en versiones anteriores se usaba <span class="codigo">Netty</span>.<br />
<br />
Además del servidor, <span class="codigo">Spring Boot</span> integra todas las dependencias que necesita para funcionar dentro de un <span class="codigo negritas">Uber Jar</span> (del cual hablaremos más a detalle más adelante). <br />
<br />
¿Cómo sabe <span class="codigo">Gradle</span> qué dependencias usa <span class="codigo">Spring Boot</span>? Si recuerdan al inicio del tutorial usamos <span class="codigo">Spring Initializer</span> y seleccionamos "<span class="codigo">Web</span>" como dependencia; esta dependencia requiere a su vez de un conjunto de librerías para funcionar de manera correcta, esto aplica para todas las dependencias que vayamos a usar en nuestros proyectos conforme se vayan volviendo más complejos. Para poder manejar todo este conjunto de dependencias de una manera sencilla, <span class="codigo">Spring Boot</span> integra algo que se llaman "<span class="codigo"><a href="https://docs.spring.io/spring-boot/docs/current/reference/html/using-boot-build-systems.html#using-boot-starter" target="_blank">starters</a></span>", que es una especie de paquetes virtuales que no contienen código, sino una lista de las dependencias que necesita para funcionar. Si abrimos el archivo "<span class="codigo">build.gradle</span>" podremos ver que en la sección de dependencias tenemos actualmente dos:<br />
<br />
<ul>
<li><span class="codigo">org.springframework.boot:spring-boot-starter-web</span></li>
<li><span class="codigo">org.springframework.boot:spring-boot-starter-test</span></li>
</ul>
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjcG0OpYJth7kW9Gb4T-2L780D6stQn1lnhw-zaeXqj35FMn9AHRl9KwsOnrTcGxIeK-7HIK_S0wy_Ty_KIHwirJ75DtapSEOTXKzkgZoHFydgqnG63XtHAzll7mXXx4VSYJM4L1bTdZ3b0/s1600/SB1_5.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="94" data-original-width="543" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjcG0OpYJth7kW9Gb4T-2L780D6stQn1lnhw-zaeXqj35FMn9AHRl9KwsOnrTcGxIeK-7HIK_S0wy_Ty_KIHwirJ75DtapSEOTXKzkgZoHFydgqnG63XtHAzll7mXXx4VSYJM4L1bTdZ3b0/s1600/SB1_5.PNG" /></a></div>
<br />
<br />
Estas son los <span class="codigo">starters</span> para web y para pruebas, pero existen literalmente decenas de <span class="codigo">starters</span> dependiendo de las dependencias que hayamos seleccionado. Algunos de los <span class="codigo">starters</span> que podríamos usar en tutoriales más avanzados de la serie son, por ejemplo:<br />
<br />
<ul>
<li><span class="codigo">spring-boot-starter-thymeleaf</span></li>
<li><span class="codigo">spring-boot-starter-data-mongodb-reactive</span></li>
<li><span class="codigo">spring-boot-starter-webflux</span></li>
</ul>
<br />
<br />
Además de esto, <span class="codigo">Spring Initializer</span> creó una clase para iniciar nuestra aplicación. En mi caso la clase se llama "<span class="codigo">Tutorial1Application</span>" y está en el paquete "<span class="codigo">com.javatutoriales.stringboot.tutorial1</span>". La clase tiene el siguiente contenido:<br />
<pre><code>
package com.javatutoriales.stringboot.tutorial1;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
<span class="negritas">@SpringBootApplication</span>
public class Tutorial1Application {
public static void main(String[] args) {
SpringApplication.run(Tutorial1Application.class, args);
}
}
</code></pre>
Como vemos, esta clase está decorada con la anotacicón "<span class="codigo">@SpringBootApplication</span>" y sólo tiene un método <span class="codigo">main</span> que manda llamar un método estático "<span class="codigo">SpringApplication.run</span>". A continuación, una explicación de estos elementos:<br />
<br />
<ul>
<li>La anotación "<span class="codigo">@SpringBootApplication</span>" le dice a <span class="codigo">Spring Boot</span> que cuando se lance la clase, debe escanear recursivamente por componentes de <span class="codigo">Spring</span> dentro del paquete en el que se encuentra la clase (en mi caso "<span class="codigo">com.javatutoriales.stringboot.tutorial1</span>" y subpaquetes) y que los registre en el contexto de <span class="codigo">Spring</span>. También indica que se debe habilitar la autoconfiguración, que es un proceso donde se crean ciertos beans de forma automática basado en ciertas clases encontradas en el classpath, valores en archivos de propiedades, y otros factores. Finalmente, indica que esta clase ("<span class="codigo">Tutorial1Application</span>") también es una fuente de definiciones de beans de Spring.</li>
<li><span class="codigo">SpringApplication.run</span> le indica a <span class="codigo">Spring Boot</span> cuál clase es el punto inicial de la aplicación. En este caso es la misma clase <span class="codigo">Tutorial1Application</span>, pero podría ser alguna otra.</li>
</ul>
<br />
<br />
A partir de aquí ya podemos ejecutar nuestra aplicación. Tenemos dos formas de hacerlo, la tradicional "<span class="codigo">Run As -> Java Application</span>" (<span class="codigo">Alt + Shift + X, J</span>) o desde el panel de "<span class="codigo">Gradle Task</span>", en la categoría de "<span class="codigo">application</span>", con el task "<span class="codigo">bootRun</span>", debemos hacer clic derecho en esta tarea y seleccionar "<span class="codigo">Run Gradle Task</span>" en el menú que aparece.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgPMv-_jTR-rMqYXK_jRez8IjcP7q_jmhCVr6nHg2T1un0CPxmV1jTD7joNZT2xofil0HPFnBCDax9Bj0FlzIvQQrqtADILFzNrloH4Qnc9iSxyTF3B8ItsG90tsEiAYsH4txE3bCi_FRxo/s1600/SB1_6.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="207" data-original-width="991" height="133" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgPMv-_jTR-rMqYXK_jRez8IjcP7q_jmhCVr6nHg2T1un0CPxmV1jTD7joNZT2xofil0HPFnBCDax9Bj0FlzIvQQrqtADILFzNrloH4Qnc9iSxyTF3B8ItsG90tsEiAYsH4txE3bCi_FRxo/s640/SB1_6.png" width="640" /></a></div>
<br />
<br />
Con cualquiera de las dos opciones comenzará la ejecución de nuestra aplicación y deberemos ver una salida muy similar a la siguiente en la consola:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiifoiNkbjLPBLgKQohy0vidv_JU7TIxFCUDBKuVmhjiL9Ng6aejcirMzWSHikrIn-rqHbob4s4xZXji2C7A5VHICvWPz9WxUGQdhWCYBkBpWxn7JSJnUfLTHavX1DVKJZ4p1fGqIOxdMmh/s1600/SB1_7.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="382" data-original-width="1211" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiifoiNkbjLPBLgKQohy0vidv_JU7TIxFCUDBKuVmhjiL9Ng6aejcirMzWSHikrIn-rqHbob4s4xZXji2C7A5VHICvWPz9WxUGQdhWCYBkBpWxn7JSJnUfLTHavX1DVKJZ4p1fGqIOxdMmh/s640/SB1_7.png" width="640" /></a></div>
<br />
<br />
En la salida anterior quiero resaltas los siguientes elementos:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiAeNM5KAzQ-IrcfGdo95A3ggkJofQ6JXDhkttGLID2yxcSI5t6cbiOPvR-0W-5Zm69rAIpQ69E-3atJUHIlJxu1TQXjkee1dgbWKGtgcrmCifUQ4uWmCnMqFB52NHH9INHUndxvY4BsMix/s1600/SB1_8.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="382" data-original-width="1211" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiAeNM5KAzQ-IrcfGdo95A3ggkJofQ6JXDhkttGLID2yxcSI5t6cbiOPvR-0W-5Zm69rAIpQ69E-3atJUHIlJxu1TQXjkee1dgbWKGtgcrmCifUQ4uWmCnMqFB52NHH9INHUndxvY4BsMix/s640/SB1_8.png" width="640" /></a></div>
<br />
<br />
<ol>
<li>El banner superior nos indica que la aplicación se inició y qué versión de <span class="codigo">Spring Boot</span> se está utilizando. Este banner se puede cambiar poniendo un archivo llamado "<span class="codigo">banner.txt</span>" o "<span class="codigo">banner.png</span>" con el contenido que queramos que aparezca; este archivo debe ponerse en el directorio "<span class="codigo">src/main/resources</span>".</li>
<li>El <span class="codigo">Tomcat</span> embebido se inició en el puerto <span class="codigo">8080</span>.</li>
<li>Se indica el tiempo que tomó la aplicación en iniciar, en la imagen anterior son <span class="codigo">26.458 segundos</span>.</li>
</ol>
Ahora podemos ir a nuestro navegador y colocar la siguiente dirección:<br />
<br />
<a href="http://localhost:8080/" target="_blank">http://localhost:8080</a><br />
<br />
Con lo que debemos ver una pantalla como la siguiente:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsFNY6UWcAaWvwau4hK1rKl_OsfLtZhdAg25WsqvxAyWyBL1f0sBNWfUZLtj5u55JXyRyT59Dtn175mWtmJbbD2P2QCjWFKvW1TiALGn4B1ro4HLal-ETTdGBcQEWu_alf5E0I91UYrxLd/s1600/SB1_9.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="363" data-original-width="658" height="352" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsFNY6UWcAaWvwau4hK1rKl_OsfLtZhdAg25WsqvxAyWyBL1f0sBNWfUZLtj5u55JXyRyT59Dtn175mWtmJbbD2P2QCjWFKvW1TiALGn4B1ro4HLal-ETTdGBcQEWu_alf5E0I91UYrxLd/s640/SB1_9.png" width="640" /></a></div>
<br />
<br />
Aunque la pantalla anterior es de un error, no deben preocuparse ya que este error quiere decir que no hay ningún recurso o página que apunte a la <span class="codigo">URL</span> base del proyecto ("<span class="codigo">/</span>"); si nos fijamos bien la página nos dice que se trata de un <span class="codigo">error 404 (Not found)</span>, esto es porque, aunque ya tenemos nuestra aplicación esta continúa estando vacía.<br />
<br />
Lo que es importante resaltar aquí es que <span class="negritas">sin mayor esfuerzo tenemos una aplicación que se ejecuta en un tomcat embebido y no hemos tenido que escribir ni una sola línea de configuración o código</span>. <br />
<br />
Escribamos una clase para regresar un mensaje y que nuestra aplicación deje de estar vacía.<br />
<br />
Vamos a crear un nuevo paquete llamado "<span class="codigo">controllers</span>" y dentro de este una clase llamada "<span class="codigo">HelloWorldController</span>". Dentro de esta clase pondremos un nuevo método llamado "<span class="codigo">saludos</span>" que reciba un <span class="codigo">String</span> como parámetro y regrese también un <span class="codigo">String</span>, de la siguiente forma:<br />
<pre><code>
package com.javatutoriales.stringboot.tutorial1.controllers;
public class HelloWorldController {
public String saludos(String nombre) {
}
}
</code></pre>
Ahora vamos a colocar la lógica del método, este simplemente verá si la cadena "<span class="codigo">nombre</span>" está vacía, de ser así regresará un saludo genérico y en caso contrario regresará un saludo con el nombre del usuario, de la siguiente forma:<br />
<pre><code>
public String saludos(String nombre) {
return "".equals(nombre) ? "Hola Mundo!" : "Hola " + nombre;
}
</code></pre>
Lo que sigue es decorar la clase y método anteriores para que sean reconocidos como <a href="https://www.javatutoriales.com/2016/02/spring-mvc-parte-2-seleccion-de.html" target="_blank">controladores de peticiones de <span class="codigo">Spring Boot</span></a>. Las anotaciones que usaremos son:<br />
<br />
<ul>
<li>"<span class="codigo">@RestController</span>" que indica que la clase será usada como manejadora de peticiones para servicios web tipo <span class="codigo">REST</span>. Esto quiere decir que no regresará una página <span class="codigo">HTML</span> como resultado, sino que este resultado será escrito directamente en el cuerpo de la respuesta. Usaremos esta anotación para decorar nuestra clase.</li>
<li>"<span class="codigo">@GetMapping</span>" esta anotación es una abreviación para "<span class="codigo">@RequestMapping(method = RequestMethod.GET)</span>" que vimos en <a href="https://www.javatutoriales.com/2016/02/spring-mvc-parte-2-seleccion-de.html" target="_blank">el tutorial anterior</a>. Usaremos esta anotación para decorar el método "<span class="codigo">saludos</span>".</li>
<li>"<span class="codigo">@RequestParam</span>" que indicará que "<span class="codigo">nombre</span>" es un parámetro que estamos esperando recibir en la petición. Colocaremos algunos atributos adicionales en esta anotación para indicar que el parámetro no es requerido y además le daremos un valor por default. Usaremos esta anotación para decorar el parámetro "<span class="codigo">nombre</span>".</li>
</ul>
Nuestra clase completa queda de la siguiente forma:<br />
<pre><code>
package com.javatutoriales.stringboot.tutorial1.controllers;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloWorldController {
@GetMapping
public String saludos(@RequestParam(required = false, defaultValue = "") String nombre) {
return "".equals(nombre) ? "Hola Mundo!" : "Hola " + nombre;
}
}
</code></pre>
Ahora lo que sigue es reiniciar nuestra aplicación, cuando volvamos a entrar a la ruta:<br />
<br />
<a href="http://localhost:8080/" target="_blank">http://localhost:8080</a><br />
Debemos ver la siguiente salida:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi6HG0fd69cQDFbfY3KBLlvuNAc3PzpbQFo2_lqZkNiPFzoNe1VcwzjZLHgmxgaEx7JzxP4MG_xBCGc9but6xmfbh0zKc3dRx0BR7X0ovgAz8WuziJISJEEtKLWKF-NwfloGmbLG6GwcU3X/s1600/SB1_10.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="266" data-original-width="651" height="162" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi6HG0fd69cQDFbfY3KBLlvuNAc3PzpbQFo2_lqZkNiPFzoNe1VcwzjZLHgmxgaEx7JzxP4MG_xBCGc9but6xmfbh0zKc3dRx0BR7X0ovgAz8WuziJISJEEtKLWKF-NwfloGmbLG6GwcU3X/s400/SB1_10.png" width="400" /></a></div>
<br />
<br />
Y al entrar a:<br />
<br />
<a href="http://localhost:8080/?nombre=Alex" target="_blank">http://localhost:8080/?nombre=Alex</a><br />
<br />
Debemos ver la siguiente salida:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjPilkNQo3HH8UVUOPqdz_5EA2zpGTV_wwSLBgkznmVXUp01smsE9PaRFtgwv5TZzbSJZVo1D4PSpNBgnwfrU_p8XCiE6-d3zm8MFeJHZ7a_USRY1Dsk0baQxZti4etg7Dn7WRGG2WQ8QFK/s1600/SB1_11.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="266" data-original-width="651" height="162" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjPilkNQo3HH8UVUOPqdz_5EA2zpGTV_wwSLBgkznmVXUp01smsE9PaRFtgwv5TZzbSJZVo1D4PSpNBgnwfrU_p8XCiE6-d3zm8MFeJHZ7a_USRY1Dsk0baQxZti4etg7Dn7WRGG2WQ8QFK/s400/SB1_11.png" width="400" /></a></div>
<br />
<br />
Para terminar este tutorial, veremos cómo generar el <span class="codigo">über jar</span> de esta aplicación. Un <span class="codigo">über jar</span> es simplemente un jar que contiene todas las clases y dependencias que necesita nuestra aplicación para funcionar; esto es, las clases que nosotros mismos hemos escrito, las clases de <span class="codigo">Spring Boot</span> y las que necesita para trabajar, y cualquier otra librería que nuestra aplicación necesite y esté declarada de forma directa o transitiva en el archivo de configuración de <span class="codigo">Gradle</span>. Además de este empaquetado, el <span class="codigo">über jar</span> tiene la configuración necesaria para poder ejecutarse como una aplicación.<br />
<br />
Crear este <span class="codigo">über jar</span> es muy sencillo gracias a <span class="codigo">Gradle</span>; lo único que debemos hacer es ir al panel de <span class="codigo">Gradle Tasks</span>, encontrar el grupo de tareas "<span class="codigo">build</span>", expandirlo, encontrar la tarea "<span class="codigo">bootJar</span>", hacer clic derecho sobre la tarea, y seleccionar la opción "<span class="codigo">Run Gradle Task</span>":<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgBA6nNh5jMGbMqu2DGEZQW92p6cbCQGpZNK-MgT58O3tWQ9b_8rFSVS-iHcizaqMH7fIZaLGwH8hKwBXNbjSoNIMA2UeQHpHXU9xWTTxheWEcp6h9pkWw2_SUIREVYgsU4ExOmK6AjE2d1/s1600/SB1_12.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="275" data-original-width="895" height="196" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgBA6nNh5jMGbMqu2DGEZQW92p6cbCQGpZNK-MgT58O3tWQ9b_8rFSVS-iHcizaqMH7fIZaLGwH8hKwBXNbjSoNIMA2UeQHpHXU9xWTTxheWEcp6h9pkWw2_SUIREVYgsU4ExOmK6AjE2d1/s640/SB1_12.png" width="640" /></a></div>
<br />
<br />
Con esto Eclipse (o mejor dicho <span class="codigo">Gradle</span>) comenzará a compilar, probar y ensamblar nuestra aplicación en ese único <span class="codigo">Jar</span>. Una vez que termine podemos ir al directorio "<span class="codigo">build\libs</span>" de nuestro directorio de trabajo, y ahí deberemos ver un jar con el nombre y versión de nuestro proyecto, en mi caso "<span class="codigo">tutorial1-0.0.1-SNAPSHOT.jar</span>". Noten el tamaño del <span class="codigo">jar</span>, que en mi caso es de poco más de <span class="codigo">16Mb</span>.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiKrqCMbHgHIJSTT-ni-Y9IGLYNZDGIzeUwNi6Stm078A7QvFCE9vt9AQvZermLHSFAamwTKkM0m3bCPIfeKZeZGWQDaGfW8irntST7jr3KykheIN6uqx00cma6jIzGP8y9pOPouyouYSpK/s1600/SB1_13.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="64" data-original-width="519" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiKrqCMbHgHIJSTT-ni-Y9IGLYNZDGIzeUwNi6Stm078A7QvFCE9vt9AQvZermLHSFAamwTKkM0m3bCPIfeKZeZGWQDaGfW8irntST7jr3KykheIN6uqx00cma6jIzGP8y9pOPouyouYSpK/s1600/SB1_13.PNG" /></a></div>
<br />
<br />
Esto es debido a todas las clases que contiene. Si abren el <span class="codigo">jar</span> con cualquier herramienta para abrir archivos <span class="codigo">.zip</span> verán su contenido y que este es diferente a los <span class="codigo">jars</span> normales, esto es porque <span class="codigo">Spring Boot</span> usa una estructura particular para guardar las clases que se usan para ejecutar la aplicación.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiJlkNOzrcbmcdkZK8dwJL7TjGjHTCJj5cDjQuDwHoie-7giapBXJvsvDxIYle1mdcFTZiUQysLIJr1vnZbHpV5bCKOhgOTbBMp8fBjLGWST5c-z4Xy3o-8BQ1RpggzkYXgpGnb1Ny5ZtzY/s1600/SB1_14.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1124" data-original-width="1008" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiJlkNOzrcbmcdkZK8dwJL7TjGjHTCJj5cDjQuDwHoie-7giapBXJvsvDxIYle1mdcFTZiUQysLIJr1vnZbHpV5bCKOhgOTbBMp8fBjLGWST5c-z4Xy3o-8BQ1RpggzkYXgpGnb1Ny5ZtzY/s640/SB1_14.png" width="573" /></a></div>
<br />
<br />
Para ejecutar nuestra aplicación y comprobar que efectivamente este <span class="codigo">jar</span> contiene todo lo que necesitamos vamos a abrir una línea de comandos y movernos al directorio en el que se encuentra nuestro <span class="codigo">jar</span>; una vez ahí ejecutamos el siguiente comando:<br />
<pre><code>
java -jar tutorial1-0.0.1-SNAPSHOT.jar
</code></pre>
Con esto debemos ver la misma salida que en la consola del Eclipse:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgNGr9m25qNcpb1S6CwQ_ml0QgdbitzwDkqhF7XxTr50ypcm6j0WPcJ0n_0RtL-hi4lKZ072_bem_aCy-IXIkdNbKeuUPfF5IvtCAkmJlzofawRsORG6BuFto8_yI_4PBEeyWEEFo0t0LZq/s1600/SB1_15.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="460" data-original-width="1366" height="214" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgNGr9m25qNcpb1S6CwQ_ml0QgdbitzwDkqhF7XxTr50ypcm6j0WPcJ0n_0RtL-hi4lKZ072_bem_aCy-IXIkdNbKeuUPfF5IvtCAkmJlzofawRsORG6BuFto8_yI_4PBEeyWEEFo0t0LZq/s640/SB1_15.png" width="640" /></a></div>
<br />
<br />
Si entramos nuevamente a la ruta:<br />
<br />
<a href="http://localhost:8080/?nombre=Alex" target="_blank">http://localhost:8080/?nombre=Alex</a><br />
<br />
Debemos ver la siguiente pantalla:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgyBmPyiYkcv7RjU7hia8StcF9NHGEY4oNwFCKPfc_pZONHJt2siYktsymVb2ExbtyQFUuaafJt9GUJ9Y2WSuslO5gcrw_3x-XRifmWkVIYj7NoG6MU1jJgoelmUjNhDDRnjvb0IgOF0-n9/s1600/SB1_16.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="188" data-original-width="622" height="120" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgyBmPyiYkcv7RjU7hia8StcF9NHGEY4oNwFCKPfc_pZONHJt2siYktsymVb2ExbtyQFUuaafJt9GUJ9Y2WSuslO5gcrw_3x-XRifmWkVIYj7NoG6MU1jJgoelmUjNhDDRnjvb0IgOF0-n9/s400/SB1_16.png" width="400" /></a></div>
<br />
<br />
Con esto podemos comprobar que nuestra aplicación funciona correctamente.<br />
<br />
Como podemos ver, en unos cuantos minutos pudimos tener listo el esqueleto de una aplicación <span class="codigo">Spring Boot</span> muy simple, esto gracias al uso de <span class="codigo">Spring Initializr</span> y de los valores por default de <span class="codigo">Spring Boot</span>, los cuales usan un conjunto de configuraciones inteligentes para configurar de manera automática nuestra aplicación.<br />
<br />
A lo largo de esta serie de tutoriales veremos más elementos de <span class="codigo">Spring Boot</span> y cómo se integran con otros componentes de <span class="codigo">Spring</span> como <span class="codigo">Spring Data JPA</span>, <span class="codigo">Spring Validator</span>, entre otros.<br />
<br />
Espero que este tutorial les sea de utilidad. Si tienen alguna duda, sugerencia, comentario o aclaración, pueden dejarla en la sección de comentarios o enviar un correo a <a href="mailto:programadorjavablog@gmail.com">programadorjavablog@gmail.com</a> (pueden agregarme al chat de gmail). También pueden seguir <span class="codigo">JavaTutoriales</span> en las siguientes redes sociales:<br />
<ul>
<li><span class="codigo"><a href="https://www.facebook.com/JavaTutoriales/" target="_blank">Facebook</a></span></li>
<li><span class="codigo"><a href="https://twitter.com/JavaTutoriales" target="_blank">Twitter</a></span></li>
</ul>
Descarga los archivos de este tutorial desde aquí:<br />
<ul>
<li><span class="codigo"><a href="https://github.com/JavaTutoriales/Spring-Boot-Tutorial1-HolaMundo" target="_blank">Spring Boot 1 - Introducción</a></span></li>
</ul>
<!--
<span class="negritas">Entradas Relacionadas:</span><br /><br />
--><br /></div>
Alexhttp://www.blogger.com/profile/06974037481671868076noreply@blogger.comtag:blogger.com,1999:blog-2012232069289957092.post-78611082721157180802019-02-23T21:48:00.002-08:002019-02-23T21:48:24.822-08:00Java Tutoriales está de regreso y tendrá algunos cambiosHa sido un poco más de dos años desde la última vez que escribí. No ha sido por gusto sino por algunas causas de fuerza mayor; pero ya estoy finalmente de regreso. <br />
<br />
Con esto quiero decirles que el blog tendrá <b>algunos cambios</b>, no se preocupen que todos serán para bien ^_^. En primer lugar los tutoriales se mantienen con su mismo formato, no se preocupen por eso; aunque ahora buscaré que sean un poco más cortos ya que me tomaba mucho tiempo escribirlos y esa es una de las razones por las que tardaba tanto en escribir nuevos tutoriales, y algunas veces tenía que dejarlos a medias por un buen rato, con tutoriales más cortos podré publicar más seguido.<br />
<br />
En segundo lugar iré agregando no solamente tutoriales de Frameworks Java, sino también comenzaré a agregar tutoriales de <b>Patrones de Diseño</b>, <b>UML</b>, <b>Buenas Prácticas </b>y algo de <b>Arquitectura de Software</b>, para ir haciendo más completos los temas de los que trata; de vez en cuando incluso agregaré algunos temas que no tengan que ver con Java, pero sí con el mundo del desarrollo.<br />
<br />
Tercero, debido a los cambios que ha habido con Java en los últimos años, en los tutoriales ya no usaré NetBeans sino que <b>cambiaré a Eclipse</b>; esta es una decisión que no me ha gustado mucho pero la verdad desde que la fundación Apache tomó el proyecto, y debido a que Oracle aún no ha terminado de donar completamente todos los módulos y plugins el IDE me parece que ya no funciona tan bien como antes. También, ya no usaremos los jars de las librerías directamente, sino que haremos la gestión de dependencias a través de <b>Gradle</b>, esto un poco con la idea de ir aprendiendo poco a poco cómo funciona esta herramienta.... ¿que por qué no uso Maven? Personalmente en mi trabajo llevo usando Maven desde hace unos 10 años, es una herramienta muy completa, madura y estable; también hay mucho material sobre cómo funciona en infinidad de lugares en la red; Gradle es nuevo para mi así que aprovecharé para aprender a usarlo mejor ;).<br />
<br />
Finalmente, el código ya no será colocado en un sitio de Google para su descarga, sino que lo colocaré en <b>Github</b>, creo que así será más fácil para todos simplemente clonar el repositorio, y para mí será más fácil subirlo y actualizarlo.<br />
<br />
Bien, me parece que eso es todo; además de una ligera actualización de imagen del blog, que ya le hacía mucha falta. El 21 de enero pasado este blog cumplió <b>10 años</b>, así que ya era algo justo y necesario.<br />
<br />
Siempre es bueno estar de regreso en casa, así que sea bienvenidos y gracias por la paciencia; también perdón a todas las personas a las que no pude contestarles en este tiempo, pero ya estoy de regreso.<br />
<br />
Seguimos con las redes sociales del sitio, así que cualquier cosa no duden en ponerse en contacto. Por alguna razón Facebook eliminó el sitio anterior así que tuve que crear una nueva página de JavaTutoriales para poder estar en contacto; pueden encontrare en:<br />
<br />
<a href="https://twitter.com/JavaTutoriales">Twitter @JavaTutoriales</a><br />
<a href="mailto:programadorjavablog@gmail.com">GMail: programadorjavablog@gmail.com</a><br />
<a href="https://www.facebook.com/JavaTutoriales">Faceboook: @JavaTutoriales</a><br />
<br />
Esperen pronto la primera entrada de esta nueva etapa de JavaTutoriales!!Alexhttp://www.blogger.com/profile/06974037481671868076noreply@blogger.comtag:blogger.com,1999:blog-2012232069289957092.post-7885105905398362932016-02-19T04:58:00.002-08:002022-01-01T17:01:58.789-08:00Spring MVC - Parte 2: Selección de manejadores de peticiones<div style="text-align: justify;">
<img src="https://img.shields.io/badge/JT-Spring-brightgreen" /><br />
<br />
En el tutorial anterior aprendimos cómo configurar nuestra aplicación para hacer uso de <span class="codigo">Spring MVC</span>, además creamos dos <span class="codigo">Controllers</span> con un método cada uno para manejar las peticiones recibidas por el usuario. En esa ocasión configuramos los métodos de una manera simple y directa para responder a las peticiones, sin embargo, <span class="codigo">Spring MVC</span> permite varias formas de indicar a qué peticiones puede responder un manejador particular.<br />
<br />
En este tutorial aprenderemos las distintas maneras que tenemos para indicar a qué <span class="negritas">peticiones</span> debe responder un método dentro de un <span class="codigo">Controller</span>, y cómo podemos tomar ventaja de estas formas para recibir distintos tipos de información desde nuestras páginas web.<br />
<br />
<a name='more'></a>En este tutorial nos enfocaremos únicamente a cómo podemos configurar la anotación "<span class="codigo">@RequestMapping</span>" y dejaremos para otro tutorial para las distintas maneras que tenemos para recibir parámetros en los métodos de manejo de peticiones.<br />
<br />
El componente encargado de decidir a qué manejador debe ser dirigida una petición es el "<span class="codigo">RequestMappingHandlerMappings</span>". Podemos pensar en los métodos de los controladores como una colección de endpoints únicos con mapeos para cada método, que se derivan de la información que colocamos en las anotaciones "<span class="codigo">@RequestMapping</span>", tanto a nivel de clase como a nivel de método.<br />
<br />
Comencemos creando un nuevo proyecto en NetBeans. Para esto vamos al Menú "<span class="codigo">File->New Project...</span>" En la ventana que se abre seleccionamos la categoría "<span class="codigo">Java Web</span>" y en el tipo de proyecto "<span class="codigo">Web Application"</span>.<br />
<br />
Le damos una ubicación y un nombre al proyecto, en mi caso será "<span class="codigo">SpringMVCXMapeoPeticiones</span>". Hacemos clic en el botón "<span class="codigo">Next</span>" y se nos preguntará en qué servidor queremos desplegar nuestra aplicación, en mi caso usaré <span class="codigo">Tomcat 8</span> (pero debe funcionar igual en cualquier servidor de aplicaciones <span class="codigo">Java</span>). También cambiaré el context path para tener algo más claro, en mi caso pondré "<span class="codigo">/peticiones</span>". Presionamos el botón "<span class="codigo">Finish</span>" y veremos aparecer en el editor nuestra página "<span class="codigo">index.html</span>"<br />
<br />
Agregamos la biblioteca de "<span class="codigo">SpringMVC4.2</span>" que creamos en <a href="http://www.javatutoriales.com/2015/12/spring-mvc-parte-1-configuracion.html">el primer tutorial de la serie de Spring MVC</a>, para esto hacemos clic derecho sobre el nodo "<span class="codigo">Libraries</span>" del proyecto, en el menú que aparece elegimos la opción "<span class="codigo">Add Library...</span>" y en la ventana que se abre seleccionamos la biblioteca "<span class="codigo">SpringMVC4.2</span>".<br />
<br />
Creamos un nuevo paquete en el nodo "<span class="codigo">Source Packages</span>" del proyecto, que en este caso será "<span class="codigo">com.javatutoriales.springmvc.peticiones</span>".<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiKTZeIbG-v78fT7PmwGpw45Ssb1O70zXniyk-RKk2um10ykBsPb1LlgjrTVtdYs67Msu7jfvJNuAzeYVy6Ge-u4zA5E9Y5gwZXy3TiPfkkKUA5n9wR38-dawVZC0mgwrd5Y4CBUhGUupOY/s1600/S7_1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiKTZeIbG-v78fT7PmwGpw45Ssb1O70zXniyk-RKk2um10ykBsPb1LlgjrTVtdYs67Msu7jfvJNuAzeYVy6Ge-u4zA5E9Y5gwZXy3TiPfkkKUA5n9wR38-dawVZC0mgwrd5Y4CBUhGUupOY/s1600/S7_1.png" /></a></div><br />
<br />
Para este proyecto usaré configuración en <span class="codigo">Java</span>, si quieren ver con más detalle cómo hacer esto o quieren ver cómo hacer lo mismo con archivos de configuración en <span class="codigo">XML</span> pueden revisar <a href="http://www.javatutoriales.com/2015/12/spring-mvc-parte-1-configuracion.html">el tutorial anterior</a>, de todas formas al final del tutorial colocaré el código de ambas versiones para que puedan descargarlo e iré explicando las equivalencias entre en código <span class="codigo">Java</span> y el <span class="codigo">XML</span>.<br />
<br />
Dentro del paquete que acabamos de crear creamos otros dos paquetes, el primero será "<span class="codigo">config</span>", para poner el código de configuración de la aplicación, y el segundo será "<span class="codigo">controllers</span>" para poner... pues los <span class="codigo">Controller</span>s de la aplicación.<br />
<br />
Dentro del paquete "<span class="codigo">config</span>" creamos las clases "<span class="codigo">SpringWebConfig</span>" y "<span class="codigo">SpringWebApplicationInitializer</span>" que indicamos en el tutorial anterior. Dejaré el código de ambas clases aquí; si quieren una explicación detallada de cómo funciona cada una pueden revisarlo en el tutorial anterior.<br />
<pre><code>
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.javatutoriales.springmvc.peticiones.controllers")
public class SpringWebConfig {
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/pages/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
}
</code></pre><br />
<pre><code>
public class SpringWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(SpringWebConfig.class);
context.setServletContext(servletContext);
ServletRegistration.Dynamic dispatcher = servletContext.addServlet("spring-web", new DispatcherServlet(context));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/");
}
}
</code></pre><br />
Para el caso de los que realicen el tutorial con archivos de configuración en <span class="codigo">XML</span>, será necesario agregar dos archivos, el "<span class="codigo">web.xml</span>" y el archivo "<span class="codigo">springMVCconfig.xml</span>" dentro del directorio "<span class="codigo">WEB-INF</span>" y "<span class="codigo">/WEB-INF/config/</span>" respectivamente.<br />
<br />
El archivo "<span class="codigo">web.xml</span>" queda de la siguiente forma:<br />
<pre><code>
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<servlet>
<servlet-name>spring-web</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/config/springMVCconfig.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>spring-web</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>30</session-timeout>
</session-config>
</web-app>
</code></pre><br />
Y el archivo "<span class="codigo">springMVCconfig.xml</span>" queda de la siguiente forma:<br />
<pre><code>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd">
<context:component-scan base-package="com.javatutoriales.springmvc.peticiones.controllers" />
<mvc:annotation-driven />
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix">
<value>/WEB-INF/pages/</value>
</property>
<property name="suffix">
<value>.jsp</value>
</property>
</bean>
</beans>
</code></pre><br />
Ahora, dentro del paquete "<span class="codigo">controllers</span>" creamos una clase llamada "<span class="codigo">PeticionesController</span>". De momento a esta clase sólo le colocaremos la anotación "<span class="codigo">@Controller</span>":<br />
<pre><code>
@Controller
public class PeticionesController {
}
</code></pre><br />
La base de lo que veremos en este tutorial es la anotación "<span class="codigo">@RequestMapping</span>", esta anotación nos ayuda a indicar a qué peticiones responderá un método determinado.<br />
<br />
Ya en el tutorial anterior vimos un poco de este tema y decíamos que para esto se usa una combinación de los valores que tenemos en el context path (que definimos como "<span class="codigo">peticiones</span>"), el patrón de <span class="codigo">URL</span>s a las que responde el <span class="codigo">DispatcherServlet</span> ("<span class="codigo">/</span>"), el valor de la anotación "<span class="codigo">@RequestMapping</span>" de la clase y el valor de la anotación "<span class="codigo">@RequestMapping</span>" del método.<br />
<br />
Los dos primeros valores ya los tenemos, pero ahora veremos cómo manejar los dos últimos.<br />
<br />
Colocamos la anotación "<span class="codigo">@RequestMapping</span>" a nivel de la clase, y dentro de esta colocamos como valor "<span class="codigo">tutorial</span>". Eso quiere decir que la clase responderá a cualquier petición que llegue para nuestra aplicación e inmediatamente después del nombre del contexto tenga "<span class="codigo">/tutorial</span>", como por ejemplo:<br />
<pre><code>
http://localhost:8080/peticiones/tutorial/saludo
</code></pre><br />
o <br />
<pre><code>
http://localhost:8080/peticiones/tutorial/metodo
</code></pre><br />
(claro, siempre y cuando tengamos métodos que mapeen a estos valores). Hasta ahora nuestra clase se ve así:<br />
<pre><code>
@Controller
@RequestMapping("tutorial")
public class PeticionesController {
}
</code></pre><br />
Ahora agregaremos el primer método, el cual llamaremos "<span class="codigo">directo</span>", este método regresará un objeto "<span class="codigo">ModelAndView</span>" y no recibirá ningún parámetro:<br />
<pre><code>
public ModelAndView directo() {
}
</code></pre><br />
Pondremos una anotación "<span class="codigo">@RequestMapping</span>" en este método, con la que veremos la primera forma de indicar a qué peticiones responderá el método, que es indicar de forma directa la <span class="codigo">URL</span> de las peticiones, esto sin usar ningún patrón o expresión de ningún tipo. Colocamos como valor de nuestra anotación "<span class="codigo">directo</span>", de esta forma:<br />
<pre><code>
@RequestMapping("directo")
public ModelAndView directo() {
}
</code></pre><br />
Esto quiere decir que este método será quien maneje las peticiones que lleguen a la dirección http://localhost:8080/peticiones/tutorial/directo.<br />
<br />
Como vemos, en esta forma indicamos la <span class="codigo">URL</span> sin mayor "<span class="codigo">adorno</span>". Aquí podemos definir cualquier <span class="codigo">URL</span> que necesitemos, por ejemplo podría ser "<span class="codigo">/modulo/funcionalidad/metodo</span>", de la siguiente forma:<br />
<pre><code>
@RequestMapping("/modulo/funcionalidad/metodo")
</code></pre><br />
Ahora completemos el cuerpo del método. La vista que regresaremos se llamará "<span class="codigo">contenido</span>", y le pasaremos un atributo al modelo que se llamará "<span class="codigo">metodo</span>" y que nos ayudará a saber qué método fue el que se invocó. Colocaremos como valor de este atributo "<span class="codigo">directo</span>". Nuestro método directo queda de la siguiente forma:<br />
<pre><code>
@RequestMapping("directo")
public ModelAndView directo() {
return new ModelAndView("contenido", "metodo", "directo");
}
</code></pre><br />
Ahora creamos un directorio dentro de "<span class="codigo">WEB-INF</span>" que se llame "<span class="codigo">pages</span>", y dentro de este una <span class="codigo">jsp</span> llamada "<span class="codigo">contenido.jsp</span>":<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiAZmiTbvGUeRCty3VZRR3j3q9e0oXfWbhjLscMSVtkCOE-1dAkOc9fhnN04jasXHZqFHUfQIhTAWCpyCReu_D9S-Zh1hXTN3tzYifzykzS0cAnX9XmUTeAsJHlGBBvFnAkHn82Ej3OWoVZ/s1600/S7_2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiAZmiTbvGUeRCty3VZRR3j3q9e0oXfWbhjLscMSVtkCOE-1dAkOc9fhnN04jasXHZqFHUfQIhTAWCpyCReu_D9S-Zh1hXTN3tzYifzykzS0cAnX9XmUTeAsJHlGBBvFnAkHn82Ej3OWoVZ/s1600/S7_2.png" /></a></div><br />
<br />
El contenido de esta página mostrará el valor del atributo "<span class="codigo">metodo</span>" que estamos pasando al modelo, y queda de la siguiente forma:<br />
<pre><code>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JavaTutoriales.com | Spring MVC 2</title>
</head>
<body>
<h1>El método llamado fue:</h1>
<h2>${metodo}</h2>
</body>
</html>
</code></pre><br />
Si ejecutamos nuestra aplicación y entramos a la siguiente dirección:<br />
<pre><code>
<a href="http://localhost:8080/peticiones/tutorial/directo">http://localhost:8080/peticiones/tutorial/directo</a>
</code></pre><br />
Debemos ver la siguiente página.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhknhXaDBGWR2ZF-FYvz4lgIcODNk_9fjmawF4aq4t_IzHiqtucediytVN0QmhBW9Slk2HGgZKzZiWOc3kmQnqjL-XW4fjJn7CcyeHJsg5A8SX4ibfzl35ZPv0aIQttgAY0HuUXslTVi8qs/s1600/S7_3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="223" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhknhXaDBGWR2ZF-FYvz4lgIcODNk_9fjmawF4aq4t_IzHiqtucediytVN0QmhBW9Slk2HGgZKzZiWOc3kmQnqjL-XW4fjJn7CcyeHJsg5A8SX4ibfzl35ZPv0aIQttgAY0HuUXslTVi8qs/s400/S7_3.png" width="400" /></a></div><br />
<br />
Con lo que comprobamos que el método que se llamó efectivamente fue "<span class="codigo">directo</span>". <br />
<br />
<span class="codigo">Spring MVC</span> también nos permite recibir variables a través de una <span class="codigo">URL</span>, e indicar que un método debe manejar las peticiones que contienen estas variables. Estas variables son llamadas <span class="codigo"><span class="negritas">path variables</span></span>, y aunque estas caen dentro de varios temas de <span class="codigo">Spring MVC</span> las veremos de forma breve aquí y las abordaremos más a detalle en tutoriales posteriores.<br />
<br />
A estas variables podemos darles un nombre que luego usaremos para hacer referencia a ella y para recibir el valor de esa variable como un argumento de nuestro método.Las <span class="codigo">path variables</span> se indican colocándolas entre llaves o curly brackets (<span class="codigo">{</span> y <span class="codigo">}</span>).<br />
<br />
Agreguemos un nuevo método que se llame "<span class="codigo">pathVariables</span>" que regrese un objeto "<span class="codigo">ModelAndView</span>" y de momento no recibirá parámetros:<br />
<pre><code>
public ModelAndView pathVariables() {
}
</code></pre><br />
Anotaremos este método con "<span class="codigo">@RequestMapping</span>" para convertirlo en un manejador de peticiones. Como valor para esta anotación indicaremos una parte estática y una variable que será el "<span class="codigo">id</span>" de un usuario ficticio; recuerden que esta variable debemos colocarla entre llaves:<br />
<pre><code>
@RequestMapping("/usuario/{idUsuario}")
public ModelAndView pathVariables() {
}
</code></pre><br />
Dependiendo del tipo de dato que definamos que sea "<span class="codigo">idUsuario</span>" este método podrá manejar peticiones que se hagan a "<span class="codigo">http://localhost:8080/peticiones/tutorial/usuario/1</span>", "<span class="codigo">http://localhost:8080/peticiones/tutorial/usuario/alex</span>", etc. Si definimos "<span class="codigo">idUsuario</span>" como un tipo entero (<span class="codigo">int</span> o <span class="codigo">long</span>) sólo podrá manejar el primer tipo de petición; si lo definimos como <span class="codigo">String</span> podrá manejar tanto la primera como la segunda petición.<br />
<br />
Para indicar el parámetro que recibirá el valor de esta variable usamos la anotación "<span class="codigo">@PathVariable</span>" en el argumento del método "<span class="codigo">pathVariables</span>" que recibirá el valor de esta variable, de la siguiente forma:<br />
<pre><code>
@RequestMapping("/usuario/{idUsuario}")
public ModelAndView pathVariables(@PathVariable String idUsuario) {
}
</code></pre><br />
Esto quiere decir que el valor de la variable "<span class="codigo">idUsuario</span>" quedará en la cadena que tiene este mismo nombre.<br />
Para terminar el método regresemos un objeto de tipo "<span class="codigo">ModelAndView</span>" que nos envíe a la vista "<span class="codigo">contenido</span>" y que coloque "<span class="codigo">pathVariables</span>" como valor del atributo "<span class="codigo">metodo</span>", de esta forma:<br />
<pre><code>
@RequestMapping("/usuario/{idUsuario}")
public ModelAndView pathVariables(@PathVariable String idUsuario) {
return new ModelAndView("contenido", "metodo", "pathVariables");
}
</code></pre><br />
Hacemos una petición a la siguiente <span class="codigo">URL</span>:<br />
<pre><code>
<a href="http://localhost:8080/peticiones/tutorial/usuario/alex">http://localhost:8080/peticiones/tutorial/usuario/alex</a>
</code></pre><br />
Con lo que debemos ver la siguiente página:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgxExql1D6nY02Tblkwp_4oxrNBPeUxQ130BAfwgLI9xKLfpfHVGSs09ygVNb4B7DFVRcgwxG5-5zfX1pK0j-mtoBZf6PA5E18vGrcXtWCEW_TeDNfeKz9xctyYP4AVvWeXqefkaegJ70Xq/s1600/S7_5.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="227" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgxExql1D6nY02Tblkwp_4oxrNBPeUxQ130BAfwgLI9xKLfpfHVGSs09ygVNb4B7DFVRcgwxG5-5zfX1pK0j-mtoBZf6PA5E18vGrcXtWCEW_TeDNfeKz9xctyYP4AVvWeXqefkaegJ70Xq/s400/S7_5.png" width="400" /></a></div><br />
<br />
Podemos poner tantas <span class="codigo">path variables</span> como necesitemos; por ejemplo: si modificamos el valor de "<span class="codigo">@RequestMapping</span>" para que quede de la siguiente forma (y agregamos su variable correspondiente):<br />
<br />
<pre><code>
@RequestMapping("/usuario/{idUsuario}/tutorial/{idTutorial}")
public ModelAndView pathVariables(@PathVariable String idUsuario, @PathVariable String idTutorial) {
return new ModelAndView("contenido", "metodo", "pathVariables");
}
</code></pre><br />
Si ahora hacemos una petición a la siguiente <span class="codigo">URL</span>:<br />
<pre><code>
<a href="http://localhost:8080/peticiones/tutorial/usuario/alex/tutorial/springmvc">http://localhost:8080/peticiones/tutorial/usuario/alex/tutorial/springmvc</a>
</code></pre><br />
Veremos la misma pantalla que en el caso anterior:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgxExql1D6nY02Tblkwp_4oxrNBPeUxQ130BAfwgLI9xKLfpfHVGSs09ygVNb4B7DFVRcgwxG5-5zfX1pK0j-mtoBZf6PA5E18vGrcXtWCEW_TeDNfeKz9xctyYP4AVvWeXqefkaegJ70Xq/s1600/S7_5.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="227" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgxExql1D6nY02Tblkwp_4oxrNBPeUxQ130BAfwgLI9xKLfpfHVGSs09ygVNb4B7DFVRcgwxG5-5zfX1pK0j-mtoBZf6PA5E18vGrcXtWCEW_TeDNfeKz9xctyYP4AVvWeXqefkaegJ70Xq/s400/S7_5.png" width="400" /></a></div><br />
<br />
La recomendación es colocar siempre las <span class="codigo">path variables</span> en su propio segmento de <span class="codigo">URL</span> (entre diagonales o slashs "<span class="codigo">/</span>") para que estas sean más claras y tener un mejor manejo de las mismas, aunque en realidad nada nos impide hacer algo como esto:<br />
<pre><code>
@RequestMapping("/usuario/{idUsuario}/tutorial{idTutorial}")
</code></pre><br />
Fíjense que hemos eliminado la diagonal que separa "<span class="codigo">tutorial</span>" de la variable "<span class="codigo">idTutoria</span>" por lo que este método ahora puede manejar peticiones como la siguiente:<br />
<pre><code>
<a href="http://localhost:8080/peticiones/tutorial/usuario/alex/tutorialspringmvc">http://localhost:8080/peticiones/tutorial/usuario/alex/tutorialspringmvc</a>
</code></pre><br />
La tercera forma que tenemos para definir qué peticiones manejará un método es haciendo uso de algo llamado <span class="codigo">URIs templates</span>. Los templates de <span class="codigo">URI</span>s son usados para definir el patrón de peticiones que serán atendidas por un manejador particular.<br />
<br />
Los <span class="codigo">URI templates</span> so expresiones regulares que colocamos como valor de la anotación "<span class="codigo">@RequestMapping</span>". La sintaxis de estas expresiones es parecida a la que acabamos de ver para los URI templates: "<span class="codigo">{varName:regex}</span>" , y también podemos colocar tantas como queramos. <br />
<br />
Como podemos ver en la sintaxis, cuando los <span class="codigo">URI</span> usan expresiones regulares, están compuestos por dos partes, la primera es <span class="negritas">el nombre de la variable</span> que se usará para colocar el valor que se recibe en la <span class="codigo">URL</span> y que representa a esa expresión, y <span class="negritas">la segunda es la expresión regular</span>.<br />
<br />
Agreguemos un nuevo método llamado "<span class="codigo">template</span>" que no reciba parámetros y que regrese un objeto tipo "<span class="codigo">ModelAndView</span>":<br />
<pre><code>
public ModelAndView template() {
}
</code></pre><br />
Indicaremos que este método es un manejador de peticiones con la anotación "<span class="codigo">@RequestMapping</span>", y como valor de esta anotación colocaremos un template que nos permita recibir el nombre de un archivo que esté conformado por tres partes: el nombre, el número de versión y la extensión, esto podemos hacerlo de la siguiente forma:<br />
<pre><code>
@RequestMapping("{archivo:[a-zA-Z]+-[\\d\\.]+\\.[a-z]+}")
public ModelAndView template() {
}
</code></pre><br />
En donde "<span class="codigo">archivo</span>" es el nombre de la variable en la que quedará el valor que coloquemos en la <span class="codigo">URI</span>, y "<span class="codigo">[a-z-]+-[\\d\\.]+\\.[a-z]+</span>" es la expresión.<br />
<br />
La expresión anterior permite que este método maneje peticiones a la <span class="codigo">URI</span> "<span class="codigo">archivo-1.0.0.0.txt</span>" o "<span class="codigo">imagen-1.2.jpg</span>" o "<span class="codigo">elMejorTutorialDeSpringMVC-1.0.0.html</span>".<br />
<br />
Como esta es una especie de modificación de las path variables, podemos obtener el valor de la varable "<span class="codigo">archivo</span>" de la misma forma que lo hicimos antes, usando la anotación "<span class="codigo">@PathVariable</span>", de la siguiente forma:<br />
<pre><code>
@RequestMapping("{archivo:[a-zA-Z]+-[\\d\\.]+\\.[a-z]+}")
public ModelAndView template(@PathVariable String archivo) {
}
</code></pre><br />
Para terminar nuestro método regresemos un objeto "<span class="codigo">ModelAndView</span>" que nos envíe a la vista "<span class="codigo">contenido</span>" y coloque el atributo "<span class="codigo">metodo</span>" con el valor de "<span class="codigo">template</span>", por lo que nuestro método queda de la siguiente forma:<br />
<pre><code>
@RequestMapping("{archivo:[a-zA-Z]+-[\\d\\.]+\\.[a-z]+}")
public ModelAndView template() {
return new ModelAndView("contenido", "metodo", "template");
}
</code></pre><br />
Ahora, hagamos una petición a:<br />
<pre><code>
<a href="http://localhost:8080/peticiones/tutorial/pagina-1.2.3.html">http://localhost:8080/peticiones/tutorial/pagina-1.2.3.html</a>
</code></pre><br />
Con lo que debemos ver la siguiente página:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh7gWO6i2QEwN4P7zT5Z2Jlhog7jh-q0HA9BIbn4sqFKORe4Qfw7xOWZtBknUE0eNGYdBNqglZ5B5aVS1QmS6Y3TbYaF5uoqeH73i77UgLK5latO0vY1qToo_CBjSB7AyRU4oR44bCD1oMB/s1600/S7_4.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="223" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh7gWO6i2QEwN4P7zT5Z2Jlhog7jh-q0HA9BIbn4sqFKORe4Qfw7xOWZtBknUE0eNGYdBNqglZ5B5aVS1QmS6Y3TbYaF5uoqeH73i77UgLK5latO0vY1qToo_CBjSB7AyRU4oR44bCD1oMB/s400/S7_4.png" width="400" /></a></div><br />
<br />
Esto comprueba que el método "<span class="codigo">template</span>" fue llamado con nuestra petición.<br />
<br />
Al igual que en al caso de las path variables, podemos indicar tantos <span class="codigo">URI templates</span> como necesitemos, en este caso podríamos tomar ventaja de esto para obtener cada una de las partes que conforman el nombre del archivo anterior, de la siguiente forma:<br />
<pre><code>
@RequestMapping("{nombre:[a-zA-Z]+}-{version:[\\d\\.]+}{extension:\\.[a-z]+}")
public ModelAndView template(@PathVariable String nombre, @PathVariable String version, @PathVariable String extension) {
return new ModelAndView("contenido", "metodo", "template");
}
</code></pre><br />
Y así evitarnos el estar parseando o separando posteriormente cada parte de forma individual.<br />
<br />
Cuando tenemos varios métodos que pueden ser válidos para manejar la misma petición, <span class="codigo">Spring MVC</span> siempre elije el <span class="negritas">más específico</span>. En el ejemplo anterior definimos nuestro patrón de la siguiente forma:<br />
<pre><code>
{nombre:[a-zA-Z]+}-{version:[\\d\\.]+}{extension:\\.[a-z]+}
</code></pre><br />
Si definiéramos un patrón más específico para un tipo de peticiones en el que, por decir, indicáramos el nombre, la versión y la extensión de la página, de la siguiente forma:<br />
<pre><code>
pagina-1.2.3.html
</code></pre><br />
Cuando hagamos una petición para la siguiente URL:<br />
<pre><code>
http://localhost:8080/peticiones/tutorial/pagina-1.2.3.html
</code></pre><br />
Esta sería manejada por el método que defina este patrón, mientras que el resto de las peticiones seguirá siendo manejado por el método anterior. Veamos esto en código. Agreguemos un método aún más específico cuyo patrón sea "<span class="codigo">pagina-1.2.3.html</span>", de la siguiente forma:<br />
<pre><code>
@RequestMapping("pagina-1.2.3.html")
public ModelAndView templateMuchoMasEspecifico() {
return new ModelAndView("contenido", "metodo", "templateMuchoMasEspecifico");
}
</code></pre><br />
Si hacemos nuevamente una petición a la <span class="codigo">URL</span> anterior, veremos la siguiente página:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjPzNdpORPT9iWSFT8Mi4y6a4D4ZuCCP7pVDtPfRiw8vOY0f-yh2xHLInvdAZrW57A6RI_jSv4dFa8J0yC13A6uApTQXb64TKd5y4iNZGGOGabggh9fBfyAlxVSP42LfX-HZN9P-RyiuTBR/s1600/S7_6.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="302" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjPzNdpORPT9iWSFT8Mi4y6a4D4ZuCCP7pVDtPfRiw8vOY0f-yh2xHLInvdAZrW57A6RI_jSv4dFa8J0yC13A6uApTQXb64TKd5y4iNZGGOGabggh9fBfyAlxVSP42LfX-HZN9P-RyiuTBR/s400/S7_6.png" width="400" /></a></div><br />
<br />
Para determinar cuál de los métodos anteriores llamar, <span class="codigo">Spring MVC</span> usa algo llamado "<span class="codigo">Path Pattern Comparison</span>", el cual indica que cuando una <span class="codigo">URL</span> coincide con múltiples patrones, se usa un ordenamiento para encontrar el patrón más específico.<br />
<br />
Un patrón con un conteo bajo de variables de <span class="codigo">URI</span> y comodines es considerado más específico. Por ejemplo "<span class="codigo">/cursos/{curso}/*</span>" tiene <span class="negritas">1 variable URI y 1 comodín</span> y es considerado más específico que "<span class="codigo">/cursos/{curso}/**</span>" que tiene <span class="negritas">1 variable URI y 2 comodines</span>.<br />
<br />
Veamos un ejemplo, agregaremos un nuevo método con este último patrón, el nombre del método será "<span class="codigo">dosComodines</span>" y tiene el siguiente método:<br />
<pre><code>
@RequestMapping("/curso/{curso}/**")
public ModelAndView dosComodines(@PathVariable String curso) {
return new ModelAndView("contenido", "metodo", "dosComodines");
}
</code></pre><br />
Si ingresamos a la siguiente URL:<br />
<pre><code>
<a href="http://localhost:8080/peticiones/tutorial/curso/spring/abc">http://localhost:8080/peticiones/tutorial/curso/spring/abc</a>
</code></pre><br />
Veremos la siguiente pantalla:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhb4ImQIsXu5SwsV4bZtOuSmDG4oJlfbm0HjofnwBEXftbLWpGDL9mXU2st6iw8QHAGL1DQA3u-AI45k1EfpM7f9MR3I4UBdJ-1R_9t5rHiqyjb4apqAKTOZfEuPtm6x_cVsE3wBwtnXru8/s1600/S7_7.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="190" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhb4ImQIsXu5SwsV4bZtOuSmDG4oJlfbm0HjofnwBEXftbLWpGDL9mXU2st6iw8QHAGL1DQA3u-AI45k1EfpM7f9MR3I4UBdJ-1R_9t5rHiqyjb4apqAKTOZfEuPtm6x_cVsE3wBwtnXru8/s400/S7_7.png" width="400" /></a></div><br />
<br />
Si ahora agregamos un nuevo método con un patrón mucho más específico, en este caso "<span class="codigo">/curso/{curso}/*</span>", con el siguiente código:<br />
<pre><code>
@RequestMapping("/curso/{curso}/*")
public ModelAndView unComodin(@PathVariable String curso) {
return new ModelAndView("contenido", "metodo", "unComodin");
}
</code></pre><br />
Y entramos a la misma dirección de hace un momento:<br />
<pre><code>
<a href="http://localhost:8080/peticiones/tutorial/curso/spring/abc">http://localhost:8080/peticiones/tutorial/curso/spring/abc</a>
</code></pre><br />
Ahora veremos la siguiente pantalla:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi6xHOUWpq3jpdGYV4081pPTEtv72ufGKCOCNivB6b24dG9cgfBPWwJ3io7tJFtN3YDT7JueiRAx24-p29vOtq4ASH_N4g2Xvl02yXsN3g4lmiE61icCUwXj8wApn6CFGniYv1MpT1m5vHU/s1600/S7_8.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="188" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi6xHOUWpq3jpdGYV4081pPTEtv72ufGKCOCNivB6b24dG9cgfBPWwJ3io7tJFtN3YDT7JueiRAx24-p29vOtq4ASH_N4g2Xvl02yXsN3g4lmiE61icCUwXj8wApn6CFGniYv1MpT1m5vHU/s400/S7_8.png" width="400" /></a></div><br />
<br />
Como vemos, el método que se invoca es el que es más específico a esta petición, ya que incluye el nombre del curso, en este caso "<span class="codigo">spring</span>", y una ruta adicional. "<span class="codigo">dosComodines</span>" continuará invocándose con cualquier otra ruta como las siguientes:<br />
<pre><code>
<a href="http://localhost:8080/peticiones/tutorial/curso/spring/abc/123">http://localhost:8080/peticiones/tutorial/curso/spring/abc/123</a>
<a href="http://localhost:8080/peticiones/tutorial/curso/spring">http://localhost:8080/peticiones/tutorial/curso/spring</a>
<a href="http://localhost:8080/peticiones/tutorial/curso/hibernate">http://localhost:8080/peticiones/tutorial/curso/hibernate</a>
</code></pre><br />
Cuando dos patrones tienen el mismo conteo, <span class="negritas">el que es más largo se considera más específico</span>. Por ejemplo "<span class="codigo">/javatutoriales/spring*</span>" es más largo y por lo tanto más específico que "<span class="codigo">/javatutoriales/*</span>".<br />
<br />
Por ejemplo, si agregamos los siguientes métodos:<br />
<pre><code>
@RequestMapping("/javatutoriales/*")
public ModelAndView masCorto() {
return new ModelAndView("contenido", "metodo", "masCorto");
}
@RequestMapping("/javatutoriales/spring*")
public ModelAndView masLargo() {
return new ModelAndView("contenido", "metodo", "masLargo");
}
</code></pre><br />
Cuando entremos a la dirección:<br />
<pre><code>
<a href="http://localhost:8080/peticiones/tutorial/javatutoriales/hibernate">http://localhost:8080/peticiones/tutorial/javatutoriales/hibernate</a>
</code></pre><br />
Veremos la pantalla:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg1ZBKQz_unA0caN2wgUwsPjB9mIfIQKm3ddZRNNoMxxV2f1xeOxTcGJd1c3Kww9O6xbDL5e4EKhLF-EUQx3G1l8wcVnjwi080x7YKo1g-JvLH9NdZBYjFTyFDatmVJ_2xLflOBVEBTq8qp/s1600/S7_9.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="281" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg1ZBKQz_unA0caN2wgUwsPjB9mIfIQKm3ddZRNNoMxxV2f1xeOxTcGJd1c3Kww9O6xbDL5e4EKhLF-EUQx3G1l8wcVnjwi080x7YKo1g-JvLH9NdZBYjFTyFDatmVJ_2xLflOBVEBTq8qp/s400/S7_9.png" width="400" /></a></div><br />
<br />
Que indica que el método llamado fue "<span class="codigo">masCorto</span>", mientras que si hacemos una petición a esta <span class="codigo">URL</span>:<br />
<pre><code>
<a href="http://localhost:8080/peticiones/tutorial/javatutoriales/springmvc">http://localhost:8080/peticiones/tutorial/javatutoriales/springmvc</a>
</code></pre><br />
Veremos la pantalla:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEim4DXjenOi_KkuIaso649zMtxjGWLyha7lRio5GhqWXazOCqZxKaeDms5tkkgyWfclgN4wXxY7zEHjT30WLvsT_8hol1xkyX3hBJtvW1NVy_E11yL-AYy_TrzQKZ2OLByURgFFcv9SXC9x/s1600/S7_10.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="226" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEim4DXjenOi_KkuIaso649zMtxjGWLyha7lRio5GhqWXazOCqZxKaeDms5tkkgyWfclgN4wXxY7zEHjT30WLvsT_8hol1xkyX3hBJtvW1NVy_E11yL-AYy_TrzQKZ2OLByURgFFcv9SXC9x/s320/S7_10.png" width="320" /></a></div><br />
<br />
Que indica que el método llamado fue "<span class="codigo">masLargo</span>".<br />
<br />
Cuando dos patrones tienen el mismo conteo y longitud, <span class="negritas">el patrón con menos comodines es considerado más específico</span>. Por ejemplo "<span class="codigo">/tutoriales/{tutorial}</span>" es más específico que "<span class="codigo">/tutoriales/*</span>".<br />
<br />
Como ejercicio al lector, si agregamos un método con el patrón "<span class="codigo">/javatutoriales/{tutorial}</span>" y entramos a la siguiente dirección:<br />
<pre><code>
http://localhost:8080/peticiones/tutorial/javatutoriales/hibernate
</code></pre><br />
¿Qué método será llamado?<br />
<br />
Y si entramos a:<br />
<pre><code>
http://localhost:8080/peticiones/tutorial/javatutoriales/springmvc
</code></pre><br />
¿Qué método será llamado?<br />
<br />
Cuando tengan su respuesta, pueden dejarla en los comentarios <span class="codigo">^_^</span>.<br />
<br />
También existen unas reglas especiales adicionales:<br />
<ul><li>El patrón de mapeo por default, también conocido como "captura todo" ("<span class="codigo">/**</span>"), es menos específico que cualquier otro patrón.</li>
<li>Un patrón con un prefijo, como "<span class="codigo">/publico/**</span>" es menos específico que cualquier otro patrón que no contenga doble comodín. Por ejemplo "<span class="codigo">/publico/cursos/{a}/{b}/{c}</span>" es más específico.</li>
</ul><br />
Otra manera que tenemos para determinar qué método responderá una petición, es por medio del método <span class="codigo">HTTP</span> que indiquemos que manejará esa petición. <span class="codigo">@RequestMapping</span> mapea por default a todos los métodos <span class="codigo">HTTP</span>, sin embargo, podemos indicar exactamente a qué método o métodos debe responder usando su atributo "<span class="codigo">method</span>".<br />
<br />
"<span class="codigo">method</span>" recibe como valor un elemento de la enumeración "<span class="codigo">org.springframework.web.bind.annotation.RequestMethod</span>", la cual tiene una entrada por cada uno de los métodos <span class="codigo">HTTP</span> que existen, o sea:<br />
<ul><li><span class="codigo">GET</span></li>
<li><span class="codigo">HEAD</span></li>
<li><span class="codigo">POST</span></li>
<li><span class="codigo">PUT</span></li>
<li><span class="codigo">PATCH</span></li>
<li><span class="codigo">DELETE</span></li>
<li><span class="codigo">OPTIONS</span></li>
<li><span class="codigo">TRACE</span></li>
</ul><br />
Cuando en "<span class="codigo">@RequestMapping</span>" agregamos el atributo "<span class="codigo">method</span>", el valor del patrón de <span class="codigo">URL</span> a la que responderá el método se debe colocar en el atributo "<span class="codigo">value</span>".<br />
<br />
Para hacer las siguientes pruebas será necesario hacer un par de llamadas que solamente con <span class="codigo">HTML</span> es un poco complicado. Para facilitar un poco las cosas haremos un par de funciones con <span class="codigo"><a href="https://jquery.com/">jQuery</a></span>, para lo cual descargaremos la <a href="https://jquery.com/download/">última versión de <span class="codigo">jQuery 1</span></a>, que en este momento es <span class="codigo">1.12.0</span>. No usaremos <span class="codigo">jQuery 2</span> ya que no soporta las versiones <span class="codigo">6</span>, <span class="codigo">7</span> y <span class="codigo">8</span> de <span class="codigo">Internet Explorer</span> (aunque no creo que ninguno de los lectores siga usándolos).<br />
<br />
Vamos a <a href="https://jquery.com/download/">la página de descarga de <span class="codigo">jQuery</span></a> y bajamos la última versión de producción de <span class="codigo">jQuery</span> (la versión comprimida) y la colocamos dentro de un directorio "<span class="codigo">js</span>" dentro de las páginas web de la aplicación:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi1w2xW2qOWOgqaUaW3m2Fo5_BPCt-BJEmlQViL4erzvDoIwrsXAIVwScXcqFsDZNro0LYKugX0hmOR9sCQKIeZRcpdzJ1UuBVS1yuQ-BRLmMx7VUXDNOMYZCYYV95PD9GEw9xf1yzPIzi5/s1600/S7_11.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi1w2xW2qOWOgqaUaW3m2Fo5_BPCt-BJEmlQViL4erzvDoIwrsXAIVwScXcqFsDZNro0LYKugX0hmOR9sCQKIeZRcpdzJ1UuBVS1yuQ-BRLmMx7VUXDNOMYZCYYV95PD9GEw9xf1yzPIzi5/s1600/S7_11.png" /></a></div><br />
<br />
Regresaremos a la clase "<span class="codigo">PeticionesController</span>" y agregaremos cuatro métodos. Los cuatro tendrán como patrón de peticiones "<span class="codigo">/metodo</span>", solo que cada uno responderá a un método <span class="codigo">HTTP</span> distinto, para no usar todos los métodos nos limitaremos a los siguientes: <span class="codigo">GET</span>, <span class="codigo">POST</span>, <span class="codigo">PUT</span>, <span class="codigo">DELETE</span>. Las anotaciones de estos métodos quedan de la siguiente forma:<br />
<pre><code>
@RequestMapping(value = "/metodo", method = RequestMethod.GET)
@RequestMapping(value = "/metodo", method = RequestMethod.POST)
@RequestMapping(value = "/metodo", method = RequestMethod.PUT)
@RequestMapping(value = "/metodo", method = RequestMethod.DELETE)
</code></pre><br />
En el caso de los métodos "<span class="codigo">GET</span>" y "<span class="codigo">POST</span>" podemos manejarlos como lo hemos hecho hasta el momento, sin embargo en el caso de los métodos "<span class="codigo">PUT</span>" y "<span class="codigo">DELETE</span>", estos están bloqueados por default en <span class="codigo">Tomcat</span> (y en otros servidores también). Existen dos formas de hacer que estos métodos sean llamados de forma correcta. La primera es habilitarlos en el servidor, sin embargo no usaremos esta ya que depende de cada servidor y la versión del mismo (aunque si obtienen un mensaje como "<span class="codigo">HTTP 405 - JSPs only permit GET POST or HEAD</span>", lo siento no hay nada que hacer... excepto usar el segundo método).<br />
<br />
La segunda forma (más genérica) es simplemente indicar a <span class="codigo">Spring MVC</span> que convierta el valor de retorno del método a otro <span class="codigo">content-type</span> (o <span class="codigo">mime-type</span>), en este caso a texto plano, y que lo escriba directamente en el flujo de la respuesta; para esto usamos la anotación "<span class="codigo">@ResponseBody</span>" y colocamos el tipo de retorno del método como "<span class="codigo">String</span>". Como respuesta regresaremos el nombre del método que estamos invocando.<br />
<br />
Los métodos quedan de la siguiente forma:<br />
<pre><code>
@RequestMapping(value = "/metodo", method = RequestMethod.GET)
public ModelAndView peticionGet() {
return new ModelAndView("contenido", "metodo", "peticionGet");
}
@RequestMapping(value = "/metodo", method = RequestMethod.POST)
public ModelAndView peticionPost() {
return new ModelAndView("contenido", "metodo", "peticionPost");
}
@RequestMapping(value = "/metodo", method = RequestMethod.PUT)
@ResponseBody
public String peticionPut() {
return "peticionPut";
}
@RequestMapping(value = "/metodo", method = RequestMethod.DELETE)
@ResponseBody
public String peticionDelete() {
return "peticionDelete";
}
</code></pre><br />
Por ahora hemos terminado con el controlador. A continuación, modificaremos la página "<span class="codigo">index.html</span>" para incluir, en primer lugar, la etiqueta que indica que se hará uso de <span class="codigo">jQuery</span>.<br />
<pre><code>
<script src="js/jquery-1.12.0.min.js"></script>
</code></pre><br />
Agregaremos una etiqueta "<span class="codigo">div</span>", cuyo <span class="codigo">id</span> será "<span class="codigo">contenido</span>", y lo usaremos posteriormente para mostrar el resultado de la página que se regrese como respuesta a la petición. Esto nos dirá qué método estamos llamando.<br />
<pre><code>
<div id="contenido"></div>
</code></pre><br />
Además, pondremos un botón por cada uno de los métodos <span class="codigo">HTTP</span> que usaremos para la llamada:<br />
<pre><code>
<button id="botonGet">GET</button><br />
<button id="botonPost">POST</button><br />
<button id="botonPut">PUT</button><br />
<button id="botonDelete">DELETE</button><br />
</code></pre><br />
Ahora, agregamos un script con una función de inicio de <span class="codigo">jQuery</span>:<br />
<pre><code>
<script>
$(function () {
});
</script>
</code></pre><br />
Agregaremos una función auxiliar, con nombre "<span class="codigo">llamadaAjax</span>", que nos permita de forma sencilla hacer una llamada <span class="codigo">ajax</span> a nuestro controlador. En este caso usaremos la función "<span class="codigo">$.ajax</span>" de <span class="codigo">jQuery</span>. El único parámetro que variará en este caso es el método <span class="codigo">HTTP</span> que se usará en la petición. Por el momento el script se ve de la siguiente forma:<br />
<pre><code>
<script>
$(function () {
});
function llamadaAjax(metodo) {
$.ajax({
url: 'tutorial/metodo',
type: metodo,
dataType: "text"
});
}
</script>
</code></pre><br />
En caso de que la llamada sea exitosa se mostrará la página resultado en el <span class="codigo">div</span> "<span class="codigo">contenido</span>"; en caso de algún error se mostrará un mensaje que indique cuál es el código de error y la descripción del mismo, de la siguiente forma:<br />
<pre><code>
<script>
$(function () {
});
function llamadaAjax(metodo) {
$.ajax({
url: 'tutorial/metodo',
type: metodo,
dataType: "text",
success: function (data)
{
$('#contenido').html(data);
},
error: function (xhr, ajaxOptions, thrownError) {
$('#contenido').html('Error ' + xhr.status + ": " + thrownError + ".");
}
});
}
</script>
</code></pre><br />
Para terminar con el script, dentro de la función de inicialización de <span class="codigo">jQuery</span> agregamos un manejador del método "<span class="codigo">click</span>" para cada uno de los botones. En este manejador lo único que se hará será una llamada al método "<span class="codigo">llamadaAjax</span>", pasándole como parámetro el nombre del método <span class="codigo">HTTP</span> que se usará para la llamada:<br />
<pre><code>
$(function () {
$('#botonGet').click(function (e)
{
llamadaAjax('GET');
});
$('#botonPost').click(function (e)
{
llamadaAjax('POST');
});
$('#botonPut').click(function (e)
{
llamadaAjax('PUT');
});
$('#botonDelete').click(function (e)
{
llamadaAjax('DELETE');
});
});
</code></pre><br />
El script completo queda de la siguiente forma:<br />
<pre><code>
<script>
$(function () {
$('#botonGet').click(function (e)
{
llamadaAjax('GET');
});
$('#botonPost').click(function (e)
{
llamadaAjax('POST');
});
$('#botonPut').click(function (e)
{
llamadaAjax('PUT');
});
$('#botonDelete').click(function (e)
{
llamadaAjax('DELETE');
});
});
function llamadaAjax(metodo) {
$.ajax({
url: 'tutorial/metodo',
type: metodo,
dataType: "text",
success: function (data)
{
$('#contenido').html(data);
},
error: function (xhr, ajaxOptions, thrownError) {
$('#contenido').html('Error ' + xhr.status + ": " + thrownError + ".");
}
});
}
</script>
</code></pre><br />
Y la página completa así:<br />
<pre><code>
<!DOCTYPE html>
<html>
<head>
<title>JavaTutoriales.com | Spring MVC</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="js/jquery-1.12.0.min.js"></script>
</head>
<body>
<button id="botonGet">GET</button><br />
<button id="botonPost">POST</button><br />
<button id="botonPut">PUT</button><br />
<button id="botonDelete">DELETE</button><br />
<div id="contenido"></div>
<script>
$(function () {
$('#botonGet').click(function (e)
{
llamadaAjax('GET');
});
$('#botonPost').click(function (e)
{
llamadaAjax('POST');
});
$('#botonPut').click(function (e)
{
llamadaAjax('PUT');
});
$('#botonDelete').click(function (e)
{
llamadaAjax('DELETE');
});
});
function llamadaAjax(metodo) {
$.ajax({
url: 'tutorial/metodo',
type: metodo,
dataType: "text",
success: function (data)
{
$('#contenido').html(data);
},
error: function (xhr, ajaxOptions, thrownError) {
$('#contenido').html('Error ' + xhr.status + ": " + thrownError + ".");
}
});
}
</script>
</body>
</html>
</code></pre><br />
Si ejecutamos ahora nuestra aplicación veremos… una página de error mostrando un error "<span class="codigo">404</span>":<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhBc1ZT91NorhPZplO5wRbIO1ipyymg0N_blnv5ysc795cQCR4XEHU8HO_GrEwj49qv9R-N4EDv7Cq54YDLH3nL1RrI7oRP4qZKgbiHLPlBX1WQ7JSlaN4eSzPbvwganlyV5CIplj7hn0A4/s1600/S7_12.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="220" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhBc1ZT91NorhPZplO5wRbIO1ipyymg0N_blnv5ysc795cQCR4XEHU8HO_GrEwj49qv9R-N4EDv7Cq54YDLH3nL1RrI7oRP4qZKgbiHLPlBX1WQ7JSlaN4eSzPbvwganlyV5CIplj7hn0A4/s400/S7_12.png" width="400" /></a></div><br />
<br />
Esto ocurre ya que no tenemos mapeado un controlador para este tipo de peticiones. Los usuarios más avanzados tal vez se estén preguntando "<span class="negritas">¿Qué no se supone que por default deberíamos ver la página index.html? Esto es una funcionalidad básica de los Servlets</span>". La respuesta a esto es "<span class="negritas">Sí y no</span>". "<span class="codigo">Sí</span>", en cuanto a que esto debería de ocurrir: Si <span class="codigo">Spring MVC</span> no tiene mapeado un manejador para una petición esta debería delegar la misma petición al mecanismo default de <span class="codigo">Servlets</span>. Y "No", en cuanto a que definimos que todas las peticiones pasarán por el <span class="codigo">Dispatcher Servlet</span>, y no hemos definido que en otros casos <span class="codigo">Spring MVC</span> delegará la petición al motor de <span class="codigo">Servlets</span>.<br />
<br />
¿Cómo hacemos esto? Pues bien, en el caso de <span class="codigo">Tomcat</span> (y la mayoría de los motores de <span class="codigo">Servlets</span> y <span class="codigo">JSP</span>s de <span class="codigo">JEE</span>) tienen un <span class="codigo">Servlet</span> que por default maneja algunas de las peticiones que se hacen a la aplicación. En este caso particular la funcionalidad que nos interesa es la de servir contenido estático, en este caso la página "<span class="codigo">index.html</span>"; sin embargo, por la forma en la que estamos configurando la aplicación, este <span class="codigo">Servlet</span> por default <span class="negritas">JAMAS</span> es llamado.<br />
<br />
<span class="nota">Nota: Si quieren ver un poco más de información pueden revisar la referencia del <span class="codigo"><a href="https://tomcat.apache.org/tomcat-7.0-doc/default-servlet.html">Default Servlet de Tomcat</a></span>, también la documentación de la clase "<span class="codigo"><a href="http://docs.spring.io/spring/docs/4.2.x/javadoc-api/org/springframework/web/WebApplicationInitializer.html">WebApplicationInitializer</a></span>" nos será de utilidad.<br />
</span><br />
<br />
¿Cómo podemos cambiar esto? Pues bien, esto podemos hacerlo de forma programática en la clase "<span class="codigo">SpringWebConfig</span>", que es la que estamos usando para configurar el framework. Para poder delegar las peticiones que el framework no entienda al <span class="codigo">Servlet</span> por default del servidor, lo primero que debemos hacer es modificar nuestra clase "<span class="codigo">SpringWebConfig</span>" para que extienda de la clase "<span class="codigo">WebMvcConfigurerAdapter</span>". Esta clase tiene un método llamado "<span class="codigo">configureDefaultServletHandling</span>", que recibe un objeto de tipo "<span class="codigo">DefaultServletHandlerConfigurer</span>", que nos ayuda a configurar habilitar y configurar este <span class="codigo">Servlet</span> default, en este caso para habilitarlo sólo debemos invocar el método "<span class="codigo">enable()</span>" de esta instancia, de la siguiente forma:<br />
<pre><code>
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
</code></pre><br />
La nueva clase "<span class="codigo">WebMvcConfigurerAdapter</span>" queda de la siguiente forma:<br />
<pre><code>
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.javatutoriales.springmvc.peticiones.controllers")
public class SpringWebConfig extends WebMvcConfigurerAdapter {
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/pages/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
}
</code></pre><br />
Para cuando trabajamos con archivos en <span class="codigo">XML</span> existe una etiqueta equivalente, que es:<br />
<pre><code>
<mvc:default-servlet-handler/>
</code></pre><br />
Esta simplemente la colocamos en el archivo "<span class="codigo">springMVCconfig.xml</span>", el cual al final queda de la siguiente forma:<br />
<pre><code>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd">
<context:component-scan base-package="com.javatutoriales.springmvc.peticiones.controllers" />
<mvc:annotation-driven />
<mvc:default-servlet-handler/>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix">
<value>/WEB-INF/pages/</value>
</property>
<property name="suffix">
<value>.jsp</value>
</property>
</bean>
</beans>
</code></pre><br />
Ahora sí, si volvemos a ejecutar la aplicación veremos la siguiente pantalla, correspondiente a nuestra página "<span class="codigo">index.html</span>":<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQ2yWCWl9yvP3wPqNj8UeF7q3JeIP2v3QviMrKt-mYh2WmMY8NuzIYQBH5wT0qel70Y0fHqeEBnToadkXHdrpuHg0AT4rbc_G6ySMDDkAcyUxA4o2HcugPDgaCuJ1xkIiTSB6gJrwVNXb9/s1600/S7_13.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="220" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQ2yWCWl9yvP3wPqNj8UeF7q3JeIP2v3QviMrKt-mYh2WmMY8NuzIYQBH5wT0qel70Y0fHqeEBnToadkXHdrpuHg0AT4rbc_G6ySMDDkAcyUxA4o2HcugPDgaCuJ1xkIiTSB6gJrwVNXb9/s400/S7_13.png" width="400" /></a></div><br />
<br />
Si ahora presionamos cualquiera de los botones de la página veremos que en la parte de abajo aparece un mensaje indicando cuál método de nuestro controlador fue llamado.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj8vwt-MECxWrgFgyodrTBmBz2mIquZuhC_QGg-hyStld5CXU3y2jr9FPdb0rNn53P99XA4Zt9IobbmdBGX6Vvl1CTvdwFFmWe36WnLqltyS-aMmt1sneIrMb1oL_bAMnFzUMr4EB8-Aa0_/s1600/S7_14.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="220" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj8vwt-MECxWrgFgyodrTBmBz2mIquZuhC_QGg-hyStld5CXU3y2jr9FPdb0rNn53P99XA4Zt9IobbmdBGX6Vvl1CTvdwFFmWe36WnLqltyS-aMmt1sneIrMb1oL_bAMnFzUMr4EB8-Aa0_/s400/S7_14.png" width="400" /></a></div><br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjrpiuMsZS-aVIvNyRSjTHJU4OmiG9ZsrW2XcoJ3T6wpWuKZCTaBLiP5xH9Ktnjp8ZtmFiA3CNZ1mrOBC1bBWvS3nsjZXSTtb4CaDvzWPBxz4L-tddlpg9ujkGDrPzCZuws5_AqDXRv7deR/s1600/S7_15.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="220" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjrpiuMsZS-aVIvNyRSjTHJU4OmiG9ZsrW2XcoJ3T6wpWuKZCTaBLiP5xH9Ktnjp8ZtmFiA3CNZ1mrOBC1bBWvS3nsjZXSTtb4CaDvzWPBxz4L-tddlpg9ujkGDrPzCZuws5_AqDXRv7deR/s400/S7_15.png" width="400" /></a></div><br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh95PstL5j5QGJeLmZBnH7wmvW189nzK-odq-vyqb6OpIF_UfZFyVEhPKs4kKY6fUJtsoF0049qgnJoQSCujNMuVavKY2qw7dLG0xlWGlqkZB_aM8VApSB_9mxtybKk86NLqEgqf9HvFti0/s1600/S7_16.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="220" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh95PstL5j5QGJeLmZBnH7wmvW189nzK-odq-vyqb6OpIF_UfZFyVEhPKs4kKY6fUJtsoF0049qgnJoQSCujNMuVavKY2qw7dLG0xlWGlqkZB_aM8VApSB_9mxtybKk86NLqEgqf9HvFti0/s400/S7_16.png" width="400" /></a></div><br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiqV8cZ9IX3SY0ny4dVBqVU6OTyEn5bmm77HiDt6sHPfrt5sEa0un7_G-rL-naQVDR90xrLenhhpkSZT3LDbiT6X3PhceUPuFpN8qj2W-9MSDoKc7mblF0y4DgT2sx7xSi55qfEsMwyHm9P/s1600/S7_17.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="220" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiqV8cZ9IX3SY0ny4dVBqVU6OTyEn5bmm77HiDt6sHPfrt5sEa0un7_G-rL-naQVDR90xrLenhhpkSZT3LDbiT6X3PhceUPuFpN8qj2W-9MSDoKc7mblF0y4DgT2sx7xSi55qfEsMwyHm9P/s400/S7_17.png" width="400" /></a></div><br />
<br />
Esto indica que efectivamente el controlador está eligiendo el método que manejará cada petición en base al método <span class="codigo">HTTP</span> con el que se hace la petición <span class="codigo">^_^</span>.<br />
<br />
A continuación, veremos cómo determinar qué método manejará la petición en base al tipo de contenido que enviemos al servidor, o que estemos esperando recibir del mismo. Podemos trabajar con cualquier tipo de contenido, sin embargo nosotros nos limitaremos a los tipos de contenido <span class="codigo">JSON</span> y <span class="codigo">XML</span> para obtener la idea de cómo funciona.<br />
<br />
"<span class="codigo">@RequestMapping</span>" tiene, además de los atributos "<span class="codigo">value</span>" y "<span class="codigo">method</span>" los atributos "<span class="codigo">consumes</span>" y "<span class="codigo">produces</span>", para indicar qué tipo de datos puede recibir y generar, respectivamente. Estos atributos pueden recibir cualquier valor, sin embargo lo más común en recibir información en <span class="codigo">JSON</span>, <span class="codigo">XML</span>.<br />
Vamos a agregar un nuevo método llamado "<span class="codigo">peticionJson</span>". Este método regresará un "<span class="codigo">ModelAndView</span>" de forma similar a como lo hemos venido trabajando:<br />
<pre><code>
public ModelAndView peticionJson() {
return new ModelAndView("contenido", "metodo", "peticionJson");
}
</code></pre><br />
Anotaremos este método con "<span class="codigo">@RequestMapping</span>", el cual tendrá como <span class="codigo">value</span> "<span class="codigo">/metodo</span>", al igual que los manejadores pasados, y agregaremos el atributo "<span class="codigo">consumes</span>" para indicar que este método manejará peticiones que estén en formato <span class="codigo">JSON</span>, esto podemos indicarlo de varias formas. La primera forma es colocar directamente la cadena con el valor "<span class="codigo">application/json</span>", de esta manera:<br />
<pre><code>
@RequestMapping(value = "/metodo", consumes = "application/json")
</code></pre><br />
Sin embargo esto es propenso a errores de escritura o typos difíciles de encontrar, ya que podríamos escribir "<span class="codigo">aplication/json</span>" o "<span class="codigo">applicatlon/json</span>" y no darnos cuenta a simple vista. Para evita estos problemas lo recomendable es usar alguno de las constantes de la clase "<span class="codigo">MediaType</span>", de la siguiente forma:<br />
<pre><code>
@RequestMapping(value = "/metodo", consumes = MediaType.APPLICATION_JSON_VALUE)
</code></pre><br />
El método "<span class="codigo">peticionJson</span>" queda de la siguiente forma:<br />
<pre><code>
@RequestMapping(value = "/metodo", consumes = MediaType.APPLICATION_JSON_VALUE)
public ModelAndView peticionJson() {
return new ModelAndView("contenido", "metodo", "peticionJson");
}
</code></pre><br />
También crearemos un método equivalente a este, pero para recibir peticiones en <span class="codigo">XML</span>, el método se llamará "<span class="codigo">peticionXml</span>" y queda de la siguiente forma:<br />
<pre><code>
@RequestMapping(value = "/metodo", consumes = MediaType.APPLICATION_XML_VALUE)
public ModelAndView peticionXml() {
return new ModelAndView("contenido", "metodo", "peticionXml");
}
</code></pre><br />
Ninguno de los dos métodos recibe parámetros, ya que estos deben estar en el formato indicado. Veremos cómo hacer esto en el siguiente tutorial.<br />
<br />
Regresamos a "<span class="codigo">index.html</span>" y agregamos dos botones nuevos después de los 4 que ya tenemos, uno será para realizar la petición en <span class="codigo">JSON</span> y el otro para la petición en <span class="codigo">XML</span>:<br />
<pre><code>
<button id="botonPeticionJson">Petición Json</button><br />
<button id="botonPeticionXml">Petición Xml</button><br />
</code></pre><br />
Vamos a modificar un poco nuestra función "<span class="codigo">llamadaAjax</span>" en <span class="codigo">JavaScript</span>, agreguemos un nuevo parámetro, que será el tipo de información que estamos enviando al servidor:<br />
<pre><code>
function llamadaAjax(metodo, contentType)
</code></pre><br />
Para indicar el tipo de datos que estamos enviando, en el caso de la función "<span class="codigo">ajax</span>" de <span class="codigo">jQuery</span>, usamos el atributo "<span class="codigo">contentType</span>". Si recibimos el parámetro anterior queremos usar el valor que recibamos, y en caso contrario usamos el valor por default, que según <a href="http://api.jquery.com/jquery.ajax/">la documentación de la función "<span class="codigo">ajax</span>"</a> es "<span class="codigo">application/x-www-form-urlencoded; charset=UTF-8</span>":<br />
<pre><code>
contentType: contentType || 'application/x-www-form-urlencoded; charset=UTF-8'
</code></pre><br />
La función "<span class="codigo">llamadaAjax</span>" queda de la siguiente forma:<br />
<pre><code>
function llamadaAjax(metodo, contentType) {
$.ajax({
url: 'tutorial/metodo',
type: metodo,
dataType: "text",
contentType: contentType || 'application/x-www-form-urlencoded; charset=UTF-8',
success: function (data)
{
$('#contenido').html(data);
},
error: function (xhr, ajaxOptions, thrownError) {
$('#contenido').html('Error ' + xhr.status + ": " + thrownError + ".");
}
});
}
</code></pre><br />
No es necesario modificar las llamadas que ya tenemos a estas funciones, pero sí agregaremos un par de manejadores de clics más además de los que ya tenemos, uno para cada uno de los nuevos botones.<br />
<pre><code>
$('#botonPeticionJson').click(function (e) {
});
$('#botonPeticionXml').click(function (e) {
});
</code></pre><br />
Adentro de cada una de estas funciones agregaremos una llamada a "<span class="codigo">llamadaAjax</span>". En el primer argumento indicaremos que el método <span class="codigo">HTTP</span> que se usará para la petición será "<span class="codigo">POST</span>" (aunque podemos usar cualquier otro, ya que en el manejador no indicamos ningún método), en el segundo indicaremos el tipo de contenido "<span class="codigo">application/json</span>" y "<span class="codigo">application/xml</span>" según sea el caso. Como estaremos usando estos valores de manera contante, como buena práctica los declararemos como variables:<br />
<pre><code>
var JSON_CONTENT_TYPE = "application/json";
var XML_CONTENT_TYPE = "application/xml";
$('#botonPeticionJson').click(function (e)
{
llamadaAjax("POST", JSON_CONTENT_TYPE);
});
$('#botonPeticionXml').click(function (e)
{
llamadaAjax("POST", XML_CONTENT_TYPE);
});
</code></pre><br />
Ejecutamos nuevamente nuestra aplicación. Cuando presionemos el botón "<span class="codigo">Petición Json</span>" debemos ver la siguiente pantalla:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgQuyHSt458KUrOJ_gkQr-VGHDkVT24e0SuLBJY3K1aWXz8hyt6XXiFxiJFHD2oRfnim07X2OOLKO6nZu2E5ydvCFtmoyk5ys9F1hLc4Sdpc4C7f9oxTwEhFtIhYFFtUKAe1YB_2SF5ibvT/s1600/S7_18.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="217" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgQuyHSt458KUrOJ_gkQr-VGHDkVT24e0SuLBJY3K1aWXz8hyt6XXiFxiJFHD2oRfnim07X2OOLKO6nZu2E5ydvCFtmoyk5ys9F1hLc4Sdpc4C7f9oxTwEhFtIhYFFtUKAe1YB_2SF5ibvT/s400/S7_18.png" width="400" /></a></div><br />
<br />
Y cuando presionemos el botón "<span class="codigo">Petición XML</span>" debemos ver esta pantalla:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgAcyfG-OIq-8Kp8dPXHGDAiXGHi8pS5J6gDsQxNVCF4SkUBK7-tkuCHqSAF5x2bLaopOSe42KksQbKRogXZsUsr1rJ0YvUd1KUCfLYblDOYu_EFq6_Qw7u_zNd0h6_bLwW46hQOU-lG9Zb/s1600/S7_19.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="217" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgAcyfG-OIq-8Kp8dPXHGDAiXGHi8pS5J6gDsQxNVCF4SkUBK7-tkuCHqSAF5x2bLaopOSe42KksQbKRogXZsUsr1rJ0YvUd1KUCfLYblDOYu_EFq6_Qw7u_zNd0h6_bLwW46hQOU-lG9Zb/s400/S7_19.png" width="400" /></a></div><br />
<br />
La funcionalidad del resto de los botones debe permanecer sin cambios.<br />
<br />
Podemos hacer algo similar para el caso del tipo de dato que esperamos como respuesta de la petición, en esta ocasión haciendo uso del atributo "<span class="codigo">produces</span>" de "<span class="codigo">@RequestMapping</span>". Regresemos a "<span class="codigo">PeticionesController</span>" y agreguemos dos métodos nuevos similares a los anteriores, pero en este caso se llamarán "<span class="codigo">respuestaJson</span>" y "<span class="codigo">respuestaXml</span>".<br />
<pre><code>
public ModelAndView respuestaJson() {
}
public ModelAndView respuestaXml() {
}
</code></pre><br />
Ambos responderán también a las peticiones hechas a la <span class="codigo">URI</span> "<span class="codigo">/metodo</span>", e indicaremos qué el <span class="codigo">content type</span> de la respuesta será "<span class="codigo">application/json</span>" y "<span class="codigo">application/xml</span>" respectivamente (nuevamente, noten que esta vez en lugar de usar "<span class="codigo">consumes</span>" usaremos "<span class="codigo">produces</span>"):<br />
<pre><code>
@RequestMapping(value = "/metodo", produces = MediaType.APPLICATION_JSON_VALUE)
public ModelAndView respuestaJson() {
}
@RequestMapping(value = "/metodo", produces = MediaType.APPLICATION_XML_VALUE)
public ModelAndView respuestaXml() {
}
</code></pre><br />
Para terminar esta parte, regresamos los objetos "<span class="codigo">ModelAndView</span>" con los respectivos nombres de los métodos:<br />
<pre><code>
@RequestMapping(value = "/metodo", produces = MediaType.APPLICATION_JSON_VALUE)
public ModelAndView respuestaJson() {
return new ModelAndView("contenido", "metodo", "respuestaJson");
}
@RequestMapping(value = "/metodo", produces = MediaType.APPLICATION_XML_VALUE)
public ModelAndView respuestaXml() {
return new ModelAndView("contenido", "metodo", "respuestaXml");
}
</code></pre><br />
Regresamos a la página "<span class="codigo">index.html</span>" y agregamos dos botones nuevos debajo de los que ya tenemos, uno para la petición que regresará la respuesta en <span class="codigo">JSON</span> y otro para la petición que regresará la respuesta en <span class="codigo">XML</span>:<br />
<pre><code>
<button id="botonRespuestaJson">Respuesta Json</button><br />
<button id="botonRespuestaXml">Respuesta XML</button><br />
</code></pre><br />
Modificaremos nuevamente la función "<span class="codigo">llamadaAjax</span>" para agregar un tercer parámetro, el cual indicará el tipo de retorno que esperamos tener desde el controlador, el nombre de este parámetro será "<span class="codigo">accept</span>":<br />
<pre><code>
function llamadaAjax(metodo, contentType, accept)
</code></pre><br />
Para indicar el tipo esperado de retorno existe una cabera especial llamada "<span class="codigo">Accept</span>" que en <span class="codigo">jQuery</span> colocamos con el valor que pasamos al atributo "<span class="codigo">headers</span>". Si no se pasa el parámetro "<span class="codigo">accept</span>" querremos colocar un valor por default, que en este caso es "<span class="codigo">text/html</span>", el valor lo colocamos de la siguiente forma:<br />
<pre><code>
headers: {Accept: accept || "text/html"}
</code></pre><br />
El método "<span class="codigo">llamadaAjax</span>" completo queda de la siguiente forma:<br />
<pre><code>
function llamadaAjax(metodo, contentType, accept) {
$.ajax({
url: 'tutorial/metodo',
type: metodo,
dataType: "text",
contentType: contentType || 'application/x-www-form-urlencoded; charset=UTF-8',
headers: {Accept: accept || "text/html"},
success: function (data)
{
$('#contenido').html(data);
},
error: function (xhr, ajaxOptions, thrownError) {
$('#contenido').html('Error ' + xhr.status + ": " + thrownError + ".");
}
});
}
</code></pre><br />
Agregaremos dos nuevos manejadores de clic para los botones que acabamos de poner. Ambos pasarán <span class="codigo">null</span> como el segundo argumento, pero para el tercero colocaremos la variable correspondiente al tipo <span class="codigo">JSON</span> o <span class="codigo">XML</span>, de la siguiente forma:<br />
<pre><code>
$('#botonRespuestaJson').click(function (e)
{
llamadaAjax("POST", null, JSON_CONTENT_TYPE);
});
$('#botonRespuestaXml').click(function (e)
{
llamadaAjax("POST", null, XML_CONTENT_TYPE);
});
</code></pre><br />
Ejecutamos nuevamente nuestra aplicación, con lo que deberemos ver la siguiente pantalla, con nuestros dos nuevos botones:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhQWCZQxsuZ5KxinuYYhHKRKlblgb95t4aqPA0GBY-C18ktpkDsdOJjv_NxlAE-e-vDnN38vbr0oUgo7x6-2kN2La-mdLcPas84Kwun0c1yMyilh4hLoGWoKkbUsH2FXwPRr9S9UyLGut7u/s1600/S7_20.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="228" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhQWCZQxsuZ5KxinuYYhHKRKlblgb95t4aqPA0GBY-C18ktpkDsdOJjv_NxlAE-e-vDnN38vbr0oUgo7x6-2kN2La-mdLcPas84Kwun0c1yMyilh4hLoGWoKkbUsH2FXwPRr9S9UyLGut7u/s400/S7_20.png" width="400" /></a></div><br />
<br />
Al hacer clic en cada botón veremos el mensaje correspondiente, para el caso de la respuesta en <span class="codigo">JSON</span>:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhFSX7X3jlpZJwbl3r4WTmaJ666OMyzBSZd7oKJoCsdWAxa_rRlR2TWrggjKFzcosX3b2O9vzamvsguIzRA9yLB3-hZ1VRR2PlxZwj-iMPXJZ8z9Ily3a8UOtVu3y3kFOteP-BCJBd8iv06/s1600/S7_21.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="247" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhFSX7X3jlpZJwbl3r4WTmaJ666OMyzBSZd7oKJoCsdWAxa_rRlR2TWrggjKFzcosX3b2O9vzamvsguIzRA9yLB3-hZ1VRR2PlxZwj-iMPXJZ8z9Ily3a8UOtVu3y3kFOteP-BCJBd8iv06/s400/S7_21.png" width="400" /></a></div><br />
<br />
Y para la respuesta en <span class="codigo">XML</span>:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhKCa0gvpJnmCyoHHXrkn_kvy8StDzWb_khccI-qQUKZB3AnzCbuwelKrkyVdVD1aTArc3znPPcV0kwXZbdq2qvY9qR4d-Nvn9Izk91ZQCLr478BJ9itcnVVz1aT05qO2mLAUiIP8wb3Yi2/s1600/S7_22.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="247" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhKCa0gvpJnmCyoHHXrkn_kvy8StDzWb_khccI-qQUKZB3AnzCbuwelKrkyVdVD1aTArc3znPPcV0kwXZbdq2qvY9qR4d-Nvn9Izk91ZQCLr478BJ9itcnVVz1aT05qO2mLAUiIP8wb3Yi2/s400/S7_22.png" width="400" /></a></div><br />
<br />
El resto de los botones debe seguir funcionando de manera normal.<br />
<br />
<span class="codigo">Spring MVC</span> realiza por default algo conocido como "<span class="codigo">Suffix Pattern Matching</span>", eso quiere decir que un controlador por default está mapeado a la ruta que le indiquemos ("<span class="codigo">/metodo</span>" en el ejemplo anterior), pero también a "<span class="codigo">/metodo.*</span>". Esto hace que sea fácil solicitar distintas representaciones de un recurso a través de una <span class="codigo">URL</span>, como por ejemplo "<span class="codigo">metodo.json</span>" o "<span class="codigo">metodo.xml</span>". Cualquiera de las peticiones anteriores hará que la petición sea manejada por el método que regresa el tipo de dato correspondiente.<br />
<br />
Hagamos una pequeña modificación en los manejadores del evento "<span class="codigo">clic</span>" para nuestros dos últimos botones, de forma que podamos comprobar esto. En este caso en el botón "<span class="codigo">botonRespuestaJson</span>" haremos una petición a la URL "<span class="codigo">tutorial/metodo.json</span>", de la siguiente forma:<br />
<pre><code>
$.ajax({
url: 'tutorial/metodo.json',
type: 'POST',
dataType: "text",
success: function (data)
{
$('#contenido').html(data);
},
error: function (xhr, ajaxOptions, thrownError) {
$('#contenido').html('Error ' + xhr.status + ": " + thrownError + ".");
}
});
</code></pre><br />
Noten que en este caso no estamos indicando ni tipo de datos para la petición ni para la respuesta.<br />
<br />
Para el botón "<span class="codigo">botonRespuestaXml</span>" haremos algo parecido, pero en este caso la petición será a la <span class="codigo">URL</span> "<span class="codigo">tutorial/metodo.xml</span>", de la siguiente forma:<br />
<pre><code>
$.ajax({
url: 'tutorial/metodo.xml',
type: 'POST',
dataType: "text",
success: function (data)
{
$('#contenido').html(data);
},
error: function (xhr, ajaxOptions, thrownError) {
$('#contenido').html('Error ' + xhr.status + ": " + thrownError + ".");
}
});
</code></pre><br />
Si ejecutamos nuevamente la aplicación, veremos que ambos botones continúan llamando a sus manejadores correspondientes.<br />
<br />
Usando ambos métodos podemos incluso jugar con combinaciones de tipos de datos en la petición y en la respuesta, pudiendo enviar un tipo de dato y solicitar otro tipo para la respuesta. De esta forma podríamos tener un manejador que reciba una petición en <span class="codigo">JSON</span> y regrese su respuesta en <span class="codigo">JSON</span>, y otro manejador de reciba una petición en <span class="codigo">JSON</span> y regrese su respuesta en <span class="codigo">XML</span>; de forma similar para el <span class="codigo">XML</span> o cualquier otro tipo de dato.<br />
<br />
Hagamos nuevamente una modificación para comprobar esto. Primero en la clase "<span class="codigo">PeticionesController</span>" agregaremos cuatro nuevos manejadores de peticiones, todos ellos para la <span class="codigo">URI</span> "<span class="codigo">/metodo</span>", que es la misma que hemos estado manejando hasta el momento. Dos de estos manejadores recibirán peticiones en <span class="codigo">JSON</span>, uno regresará <span class="codigo">JSON</span> y el otro <span class="codigo">XML</span>. Los otros dos manejadores recibirán peticiones en <span class="codigo">XML</span> y regresarán <span class="codigo">JSON</span> y <span class="codigo">XML</span> respectivamente. A estos métodos les pondremos nombres acordes a lo que reciben y envían:<br />
<pre><code>
@RequestMapping(value = "/metodo", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ModelAndView peticionJsonRespuestaJson() {
return new ModelAndView("contenido", "metodo", "peticionJsonRespuestaJson");
}
@RequestMapping(value = "/metodo", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_XML_VALUE)
public ModelAndView peticionJsonRespuestaXml() {
return new ModelAndView("contenido", "metodo", "peticionJsonRespuestaXml");
}
@RequestMapping(value = "/metodo", method = RequestMethod.POST, consumes = MediaType.APPLICATION_XML_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ModelAndView peticionXmlRespuestaJson() {
return new ModelAndView("contenido", "metodo", "peticionXmlRespuestaJson");
}
@RequestMapping(value = "/metodo", method = RequestMethod.POST, consumes = MediaType.APPLICATION_XML_VALUE, produces = MediaType.APPLICATION_XML_VALUE)
public ModelAndView peticionXmlRespuestaXml() {
return new ModelAndView("contenido", "metodo", "peticionXmlRespuestaXml");
}
</code></pre><br />
Regresamos a "<span class="codigo">index.html</span>" y agregamos cuatro nuevos botones, uno por cada combinación de petición-respuesta que usaremos:<br />
<pre><code>
<button id="botonPeticionJsonRespuestaJson">Petición Json Respuesta Json</button><br />
<button id="botonPeticionJsonRespuestaXml">Petición Json Respuesta Xml</button><br />
<button id="botonPeticionXmlRespuestaJson">Petición Xml Respuesta Json</button><br />
<button id="botonPeticionXmlRespuestaXml">Petición Xml Respuesta Xml</button><br />
</code></pre><br />
Finalmente, agregaremos un manejador de clic para cada uno de los botones, usando el método "<span class="codigo">llamadaAjax</span>" que ya tenemos, de la siguiente forma:<br />
<pre><code>
$('#botonPeticionJsonRespuestaJson').click(function (e)
{
llamadaAjax("POST", JSON_CONTENT_TYPE, JSON_CONTENT_TYPE);
});
$('#botonPeticionJsonRespuestaXml').click(function (e)
{
llamadaAjax("POST", JSON_CONTENT_TYPE, XML_CONTENT_TYPE);
});
$('#botonPeticionXmlRespuestaJson').click(function (e)
{
llamadaAjax("POST", XML_CONTENT_TYPE, JSON_CONTENT_TYPE);
});
$('#botonPeticionXmlRespuestaXml').click(function (e)
{
llamadaAjax("POST", XML_CONTENT_TYPE, XML_CONTENT_TYPE);
});
</code></pre><br />
Al ejecutar la aplicación debemos ver la siguiente pantalla:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMrfZYJ2OxZOLXo3apjmMM5Cw_iWd62EQEirQTm7YFThrtzF_LnP_hk5AEt-_JshhyphenhyphenQoMp28LhGxgNtJhgP8UkqVXceU8Fc9Oirgf6rbKv_t4dA_0EwEDGpQO8stCTJXxiptCWsJIGZusC/s1600/S7_23.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="285" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMrfZYJ2OxZOLXo3apjmMM5Cw_iWd62EQEirQTm7YFThrtzF_LnP_hk5AEt-_JshhyphenhyphenQoMp28LhGxgNtJhgP8UkqVXceU8Fc9Oirgf6rbKv_t4dA_0EwEDGpQO8stCTJXxiptCWsJIGZusC/s400/S7_23.png" width="400" /></a></div><br />
<br />
Y al hacer clic en los botones correspondientes veremos que se invoca el manejador de peticiones adecuado:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhlLsx1lZaz9nf0u9-JftFs1i65CRK3BsjW_wUSftUVs9p4lqwqxoT7LDOerq6h8TAgss9pFYonwZyUg1g-3B1RpzpE1grRxjfj51wZp0MlqwfccAyhizmfkGTtCIGqhZzleBaLLclNbX9h/s1600/S7_24.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="285" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhlLsx1lZaz9nf0u9-JftFs1i65CRK3BsjW_wUSftUVs9p4lqwqxoT7LDOerq6h8TAgss9pFYonwZyUg1g-3B1RpzpE1grRxjfj51wZp0MlqwfccAyhizmfkGTtCIGqhZzleBaLLclNbX9h/s400/S7_24.png" width="400" /></a></div><br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZfYUwmEfJ1ByOotLKD1Hac7GFBhP_QwhnONfEg9N8P52gkVEypyX3QCN6410PnC3ekqjytzbWRZ_ZFLaBOYlXbSLdkmO5oml2F4VFd7s0dCcBk5AuKjfogt7ME-fDaOoV92hf3EC5ep0j/s1600/S7_25.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="285" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZfYUwmEfJ1ByOotLKD1Hac7GFBhP_QwhnONfEg9N8P52gkVEypyX3QCN6410PnC3ekqjytzbWRZ_ZFLaBOYlXbSLdkmO5oml2F4VFd7s0dCcBk5AuKjfogt7ME-fDaOoV92hf3EC5ep0j/s400/S7_25.png" width="400" /></a></div><br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgxjOH31d1xVf2-Tp-fmynrSJtMrtwLRwZSag9zNAX1Losqm4e8BqtplTpf1N2eRhLI7hhMmd9CiXeRt-qfcq9eq5cmT7TT7-MudH9odvkEzPZ70Ai_Umnv6-O3uzw1B_voneYJPxm6ZHiG/s1600/S7_26.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="285" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgxjOH31d1xVf2-Tp-fmynrSJtMrtwLRwZSag9zNAX1Losqm4e8BqtplTpf1N2eRhLI7hhMmd9CiXeRt-qfcq9eq5cmT7TT7-MudH9odvkEzPZ70Ai_Umnv6-O3uzw1B_voneYJPxm6ZHiG/s400/S7_26.png" width="400" /></a></div><br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgiRtT0AaS4Ax4_oKlmNDu0tK2Feehu689cj7EvbXGO1A3MSTG0lIy2TVvvfWn73g1rNXBffN8Gpo7nX61UUT_cyudIPxnWQxlGzImy8HVcHZvhTWG6kW-gVjr1ssSHAJ-21Skza2C7me1l/s1600/S7_27.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="285" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgiRtT0AaS4Ax4_oKlmNDu0tK2Feehu689cj7EvbXGO1A3MSTG0lIy2TVvvfWn73g1rNXBffN8Gpo7nX61UUT_cyudIPxnWQxlGzImy8HVcHZvhTWG6kW-gVjr1ssSHAJ-21Skza2C7me1l/s400/S7_27.png" width="400" /></a></div><br />
<br />
El resto de los botones continúa teniendo su funcionalidad normal.<br />
<br />
<span class="codigo">Spring MVC</span> va aún más allá para poder indicar los manejadores de peticiones, llegando incluso a detalles como los parámetros o cabeceras que se están recibiendo. Para el primero de los casos usamos el atributo "<span class="codigo">params</span>" de la anotación "<span class="codigo">@RequestMapping</span>", en este atributo indicamos qué parámetros debe tener la petición para ser manejada por este método.<br />
<br />
Agregaremos tres métodos nuevos, el primero manejará las peticiones que tengan un parámetro llamado "<span class="codigo">uno</span>", el segundo las que tengan un parámetro llamado "<span class="codigo">dos</span>", y el tercero las que tengan ambos parámetros. Los tres métodos quedan de la siguiente forma:<br />
<pre><code>
@RequestMapping(params = "uno", value = "/metodo")
public ModelAndView parametroUno() {
return new ModelAndView("contenido", "metodo", "parametroUno");
}
@RequestMapping(params = "dos", value = "/metodo")
public ModelAndView parametroDos() {
return new ModelAndView("contenido", "metodo", "parametroDos");
}
@RequestMapping(params = { "uno", "dos" }, value = "/metodo")
public ModelAndView parametroUnoDos() {
return new ModelAndView("contenido", "metodo", "parametroUnoDos");
}
</code></pre><br />
Para poder pasar estos parámetros desde la página "<span class="codigo">index.html</span>", haremos uso de tres formularios en los que pasaremos los parámetros como campos de tipo "<span class="codigo">hidden</span>", aunque en realidad esto no es necesario, podemos pasarlos como cualquier campo que necesitemos (texto, checkbox, select, etc.).<br />
<pre><code>
<form action="tutorial/metodo" method="POST">
<input type="hidden" name="uno" value="uno" />
<input value="Enviar Uno" type="submit" />
</form>
<form action="tutorial/metodo" method="POST">
<input type="hidden" name="dos" value="dos" />
<input value="Enviar Dos" type="submit" />
</form>
<form action="tutorial/metodo" method="POST">
<input type="hidden" name="uno" value="uno" />
<input type="hidden" name="dos" value="dos" />
<input value="Enviar Uno Dos" type="submit" />
</form>
</code></pre><br />
Ejecutamos nuevamente la aplicación, con lo que debemos ver la siguiente página:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjwWaSCsEMkE-EO09MPlprKhP2ThSqvksw_aqVlQA2mQqutbkb58ufkCTdSI6bkI6KLQXRafYnm0o_uOdXI_VVnlfNLatviwQyplL2GRjLjQa9fx3oA3KEmS-xKnBy8e-Y4dbFxO32_4YS2/s1600/S7_28.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="288" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjwWaSCsEMkE-EO09MPlprKhP2ThSqvksw_aqVlQA2mQqutbkb58ufkCTdSI6bkI6KLQXRafYnm0o_uOdXI_VVnlfNLatviwQyplL2GRjLjQa9fx3oA3KEmS-xKnBy8e-Y4dbFxO32_4YS2/s400/S7_28.png" width="400" /></a></div><br />
<br />
Al hacer clic en cada uno de los botones, veremos los mensajes correspondientes a los manejadores:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhWOkLRf7FCgu1hdNRlS-9Kx3Rp15rDolQw0OWOcmdJujeNKDtEWxtUoY0XEhdVm0nVl466UaPXrCTp9cPn3klM1G2Rf2cjadUV3ZxeHVRn2y9ysWYXNwN5-kFM_0xBrqDleFZDQHL2nLCp/s1600/S7_29.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="288" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhWOkLRf7FCgu1hdNRlS-9Kx3Rp15rDolQw0OWOcmdJujeNKDtEWxtUoY0XEhdVm0nVl466UaPXrCTp9cPn3klM1G2Rf2cjadUV3ZxeHVRn2y9ysWYXNwN5-kFM_0xBrqDleFZDQHL2nLCp/s400/S7_29.png" width="400" /></a></div><br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgq_OJ68mXIMpribO2FjRjxq06hGq9FPdmAguElLeGbp3e9NlQjvitiBetmq8u7UgD3oQGjKxGLmBdoH7VFR-PG9ajloHVxWuo-_K-DA4Hxhsr1vaJ-HqYv_2X1sVcRTzzDgkeVCR1Fwqr8/s1600/S7_30.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="288" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgq_OJ68mXIMpribO2FjRjxq06hGq9FPdmAguElLeGbp3e9NlQjvitiBetmq8u7UgD3oQGjKxGLmBdoH7VFR-PG9ajloHVxWuo-_K-DA4Hxhsr1vaJ-HqYv_2X1sVcRTzzDgkeVCR1Fwqr8/s400/S7_30.png" width="400" /></a></div><br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgGMF6g8X0PYw4GnTZsEjmCFM6Ih8IjyllYy2lPqMZ4CszluP1Xn_cTwVsGT6UOPZWtLuuZu_ugK80d3Ociiu5I2it5Dozqt2nruFx8uZYNnMVOyD4-PlAV6grDguu0SDcNkKl-jV56kLu5/s1600/S7_31.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="288" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgGMF6g8X0PYw4GnTZsEjmCFM6Ih8IjyllYy2lPqMZ4CszluP1Xn_cTwVsGT6UOPZWtLuuZu_ugK80d3Ociiu5I2it5Dozqt2nruFx8uZYNnMVOyD4-PlAV6grDguu0SDcNkKl-jV56kLu5/s400/S7_31.png" width="400" /></a></div><br />
<br />
<br />
El resto de los botones continúa funcionando de manera normal.<br />
<br />
Para terminar este tutorial, veremos cómo crear un manejador que responda dependiendo de las cabeceras que tenga la petición. Primero agregaremos el manejador, el cual será muy parecido a los anteriores, sólo que en esta ocasión usaremos el atributo "<span class="codigo">headers</span>" para indicar cuál encabezado es que el que esperamos que tenga el método, en este caso el nombre de la cabecera será "<span class="codigo">JavaTutoriales</span>". El manejador queda de la siguiente forma:<br />
<pre><code>
@RequestMapping(headers = { "JavaTutoriales" }, value = "/metodo")
public ModelAndView cabeceraJavaTutoriales() {
return new ModelAndView("contenido", "metodo", "cabeceraJavaTutoriales");
}
</code></pre>Ahora, de regreso a "<span class="codigo">index.html</span>" agregamos un botón para hacer la petición:<br />
<pre><code>
<button id="botonCabeceraJavaTutoriales">Petición Cabecera Java Tutoriales</button><br />
</code></pre><br />
Y un manejador del evento "<span class="codigo">click</span>" que se encargue de enviar la petición al método correspondiente, agregando la cabecera "<span class="codigo">JavaTutoriales</span>", para nuestro caso el valor de esta no es importante, así que nosotros le pondremos el valor "<span class="codigo">Spring MVC</span>", de la siguiente forma:<br />
<pre><code>
$('#botonCabeceraJavaTutoriales').click(function (e)
{
$.ajax({
url: 'tutorial/metodo',
type: 'POST',
dataType: "text",
headers: {JavaTutoriales:"SpringMVC"},
success: function (data)
{
$('#contenido').html(data);
},
error: function (xhr, ajaxOptions, thrownError) {
$('#contenido').html('Error ' + xhr.status + ": " + thrownError + ".");
}
});
});
</code></pre><br />
Ahora que todo está colocado en su lugar, ejecutamos nuevamente la aplicación con lo que debemos ver la siguiente pantalla:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjbBUNyQrCcojrgZKZjbh5AXcCES-Hd95Hly7IyuL3G1N2Yd2oOc7EaIh7SYbV5AncorIAN6zbdewwlHrjyggv-kKjlm9Au6tn6acFQOwCbqM2yWt4DDXnzuMMFIWjqXI6Oxmz81WuOKQEm/s1600/S7_32.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="288" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjbBUNyQrCcojrgZKZjbh5AXcCES-Hd95Hly7IyuL3G1N2Yd2oOc7EaIh7SYbV5AncorIAN6zbdewwlHrjyggv-kKjlm9Au6tn6acFQOwCbqM2yWt4DDXnzuMMFIWjqXI6Oxmz81WuOKQEm/s400/S7_32.png" width="400" /></a></div><br />
<br />
Al hacer clic en el botón "<span class="codigo">Petición Xml Respuesta Xml</span>" debemos ver la siguiente salida:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEheZMJNFP63Qm58tzlmwW1bCPuV-pWJYyJcTvwLcDJvqYxrbl1Dy4JTmjRXcLx-of3CEjAd1Jn9jV7O0mhfmsCOaxnmdfCJ4WfpmpIhVU19R0bgmnMGDZj2BzSN9xzwwSOB5oxytweaAU5S/s1600/S7_33.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="345" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEheZMJNFP63Qm58tzlmwW1bCPuV-pWJYyJcTvwLcDJvqYxrbl1Dy4JTmjRXcLx-of3CEjAd1Jn9jV7O0mhfmsCOaxnmdfCJ4WfpmpIhVU19R0bgmnMGDZj2BzSN9xzwwSOB5oxytweaAU5S/s400/S7_33.png" width="400" /></a></div><br />
<br />
Con lo que podemos comprobar que el manejador fue llamado correctamente.<br />
<br />
Como pudimos ver en este tutorial, hay muchas maneras de indicar qué método manejará las peticiones que haga nuestra aplicación, y tenemos en realidad una gran gama de posibilidades; en este tutorial tenemos <span class="negritas">16 métodos</span> que responden a distintas peticiones hechas a la misma <span class="codigo">URL</span>, y cada una se manda llamar de manera correcta, ya sea por el tipo de parámetros que se reciben o devuelven, por los parámetros o cabeceras que se reciben, etc. Como podemos ver, usando esta combinación de datos podemos indicar exactamente cuáles serán las condiciones con las que se puede llamar a un método particular.<br />
<br />
Espero que este tutorial les sea de utilidad. Si tienen alguna duda, sugerencia, comentario o aclaración, pueden dejarla en la sección de comentarios o enviar un correo a <a href="mailto:programadorjavablog@gmail.com">programadorjavablog@gmail.com</a> (pueden agregarme al chat de gmail). También pueden seguir <span class="codigo">JavaTutoriales</span> en las siguientes redes sociales:<br />
<ul><li><span class="codigo"><a href="https://www.facebook.com/JavaTutoriales/">Facebook</a></span></li>
<li><span class="codigo"><a href="https://plus.google.com/u/0/114078943018704761597">Google+</a></span></li>
<li><span class="codigo"><a href="https://twitter.com/JavaTutoriales">Twitter</a></span></li>
</ul>Descarga los archivos de este tutorial desde aquí:<br />
<ul><li><span class="codigo"><a href="https://sites.google.com/site/javatutoriales/springmvc/SpringMVCParametrosXMLConfig.zip">Spring MVC Parámetros Cofiguración con XML</a></span></li>
<li><span class="codigo"><a href="https://sites.google.com/site/javatutoriales/springmvc/SpringMVCParametrosJavaConfig.zip">Spring MVC Parámetros configuración Java</a></span></li>
</ul><br />
<span class="negritas">Entradas Relacionadas:</span><br />
<br />
<ul><li><a href="http://www.javatutoriales.com/2010/09/spring-parte-1-introduccion.html">Parte 1: Introducción</a></li>
<li><a href="http://javatutoriales.blogspot.com/2010/12/contenedores-de-ioc-e-inyeccion-de.html">Parte 2: Contenedores de IoC e Inyección de Dependencias</a></li>
<li><a href="http://javatutoriales.blogspot.com/2011/01/inyeccion-de-colecciones.html">Parte 3: Inyección de Colecciones</a></li>
<li><a href="http://www.javatutoriales.com/2011/02/spring-3-parte-4-ciclo-de-vida-de-los.html">Parte 4: Ciclo de Vida de los Beans</a></li>
<li><a href="http://www.javatutoriales.com/2015/12/spring-mvc-parte-1-configuracion.html">Spring MVC - Parte 1: Configuración</a></li>
</ul></div>Alexhttp://www.blogger.com/profile/06974037481671868076noreply@blogger.comtag:blogger.com,1999:blog-2012232069289957092.post-80458636914028542262015-12-23T18:15:00.002-08:002022-01-01T17:02:15.827-08:00Spring MVC - Parte 1: Configuración<div style="text-align: justify;">
<img src="https://img.shields.io/badge/JT-Spring-brightgreen" /><br />
<br />
En <a href="http://www.javatutoriales.com/2015/01/spring-parte-5-integracion-y-manejo-de.html">tutoriales anteriores</a> hemos visto cómo trabajar con los módulos core de <span class="codigo">Spring</span>, los cuales proporcionan el motor de inyección de dependencias y el contendor de beans. También tuvimos una serie completa de cómo trabajar con un framework MVC para aplicaciones web, <span class="codigo"><a href="http://www.javatutoriales.com/2011/06/struts-2-parte-1-configuracion.html">Struts 2</a></span>.<br />
<br />
<span class="codigo">Spring</span> tiene sus propios módulos que conforman un framework para el desarrollo de aplicaciones Java basadas en web, Este framework sigue el patrón de diseño MVC. A este conjunto de módulos se le conoce con el nombre de <span class="codigo">Spring MVC</span>.<br />
<br />
En esta serie de tutoriales aprenderemos a usar <span class="codigo">Spring MVC</span>. En este primer tutorial aprenderemos cómo configurar el framework de dos formas, una a través de archivos de configuración en XML y otra a través de código Java y haremos el respectivo Hola Mundo.<br />
<br />
<a name='more'></a>Al igual que <span class="codigo">Struts 2</span>, <span class="codigo">Spring MVC</span> es un framework de capa de presentación, basado o dirigido por peticiones. Esto quiere decir que los programadores estructuramos el diseño de la aplicación web en términos de <span class="negritas">métodos que manejan peticiones <span class="codigo">HTTP</span> individuales</span>. <br />
<br />
El framework define una serie de interfaces que siguen el patrón de diseño <a href="https://en.wikipedia.org/wiki/Strategy_pattern">Strategy</a> para todas las responsabilidades que deben ser manejadas por el framework. El objetivo de cada interface es ser simple y clara, para que sea fácil para los usuarios de <span class="codigo">Spring MVC</span> (o sea, nosotros) crear nuestras propias implementaciones.<br />
<br />
La pieza central de <span class="codigo">Spring MVC</span> es un componente llamado el "<span class="codigo">DispatcherServlet</span>", el cual sigue el <a href="https://en.wikipedia.org/wiki/Front_Controller_pattern">patrón de diseño front controller</a>, este envía las peticiones a los componentes designados para manejarlas.<br />
<br />
Las interfaces más importantes definidas por <span class="codigo">Spring MVC</span> son:<br />
<ul><li><span class="codigo">Controller</span>: Se encuentra <span class="negritas">entre el Modelo y la Vista</span>, maneja las peticiones que entran y redirige a las respuestas apropiadas. Estos serán los componentes que estaremos programando para manejar las peticiones.</li>
<li><span class="codigo">HandlerAdapter</span>: Es quien realiza en realidad la invocación del manejador de la petición (el <span class="codigo">DispatcherServlet</span> delega esa tarea a este componente), esto incluye inyectar los parámetros adecuados usando reflexión.</li>
<li><span class="codigo">HandlerInterceptor</span>: Intercepta las peticiones de entrada. Es comparable, pero no igual, a los filtros en el API de <span class="codigo">Servlet</span>s.</li>
<li><span class="codigo">HandlerMapping</span>: Selecciona objetos que manejan las peticiones de entrada basado en cualquier atributo o condición interna o externa a esos atributos.</li>
<li><span class="codigo">LocaleResolver</span>: Resuelve y opcionalmente guarda el <a href="https://docs.oracle.com/javase/tutorial/i18n/locale/create.html"><span class="codigo">Locale</span></a> de un usuario individual.</li>
<li><span class="codigo">MultipartResolver</span>: Facilita trabajar con la carga de archivos, envolviendo las peticiones de entrada.</li>
<li><span class="codigo">View</span>: Responsable de regresar una respuesta al cliente. Algunas peticiones pueden ir directo a la vista sin pasar por el modelo.</li>
<li><span class="codigo">ViewResolver</span>: Selecciona una vista basado en un nombre lógico para la vista.</li>
</ul><br />
Cada una de las interfaces anteriores tiene una responsabilidad importante dentro del framework, y la abstracción ofrecida por estas interfaces permite tener variaciones en sus implementaciones. Obviamente <span class="codigo">Spring MVC</span> contiene implementaciones de casi todas estas interfaces (digo casi todas porque nosotros debemos construir nuestros propios <span class="codigo">Controllers</span>), las cuales están construidas sobre el API de <span class="codigo">Servlet</span>s. Sin embargo, los desarrolladores somos libres de escribir nuestras propias implementaciones.<br />
<br />
Un principio clave en <span class="codigo">Spring MVC</span> es el principio de diseño "<span class="codigo">Abierto / Cerrado</span>" o, por su nombre en inglés, "<span class="codigo"><a href="http://www.objectmentor.com/resources/articles/ocp.pdf">Open for extension, closed for modification</a></span>". Algunos métodos en las clases core de <span class="codigo">Spring MVC</span> están marcados como <span class="codigo">final</span> para que los desarrolladores no podamos sobre-escribirlos y proporcionar nuestro propio comportamiento, de esta forma el framework garantiza que su comportamiento básico no puede ser modificado.<br />
<br />
Antes de comenzar con el ejemplo hablaremos un poco más del "<span class="codigo">DispatcherServlet</span>" que, como dijimos hace un instante, es la pieza central de <span class="codigo">Spring MVC</span>. <span class="codigo">Spring MVC</span> está, como muchos otros frameworks orientado a peticiones, diseñado alrededor de un <span class="codigo">Servlet</span> central que despacha las peticiones a los controladores y ofrece otra funcionalidad que facilita el desarrollo de aplicaciones web. Sin embargo, el "<span class="codigo">DispatcherServlet</span>" hace más que sólo eso, se encuentra completamente integrado con el contenedor de <a href="http://www.javatutoriales.com/2010/12/contenedores-de-ioc-e-inyeccion-de.html"><span class="codigo">IoC</span> de <span class="codigo">Spring</span></a>, lo cual nos permite usar el resto de características de <span class="codigo">Spring</span>.<br />
<br />
El flujo del procesamiento de peticiones del "<span class="codigo">DispatcherServlet</span>" se ilustra en el siguiente diagrama.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiobKHm-dO6xgU6Oddze6cTZECo-BruS9-mdDr9akgafOidfCu32Nx_OnCjkyL6k5y1ynj4SCcLzNEfcaL5ucELVLPQDDYamuSpIs3Rlt8D9Vi9ii0_nRXQ-KX2WAJ2mgLm-5-8Ld4LOnd2/s1600/S6_17.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="321" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiobKHm-dO6xgU6Oddze6cTZECo-BruS9-mdDr9akgafOidfCu32Nx_OnCjkyL6k5y1ynj4SCcLzNEfcaL5ucELVLPQDDYamuSpIs3Rlt8D9Vi9ii0_nRXQ-KX2WAJ2mgLm-5-8Ld4LOnd2/s400/S6_17.png" width="400" /></a></div><ol><li>El cliente hace una petición a la aplicación web, esta petición llega al <span class="codigo">DispatcherServlet</span>.</li>
<li>El <span class="codigo">DispatcherServlet</span> determina qué componente debe atender la petición y la envía a este.</li>
<li>El <span class="codigo">Controller</span> implementa la lógica específica para responder la petición, para lo que puede hacer uso de cualquier recurso que esté al alcance de cualquier aplicación Java (incluyendo conexiones con servicios web o con base de datos).</li>
<li>Una vez que el <span class="codigo">Controller</span> termina su proceso, regresa la petición al <span class="codigo">DispatcherServlet</span>, estableciendo los datos adecuados del modelo e indicando el nombre lógico de la vista que debe regresarse al cliente y un modelo lleno con los nombres y valores de los atributos que se usarán para generar la vista final.</li>
<li>En base al nombre lógico regresado por el <span class="codigo">Controller</span>, el <span class="codigo">DispatcherServlet</span> usa un <span class="codigo">ViewResolver</span> para determinar qué recurso debe utilizar para generar la vista final que se mostrará al usuario, este recurso puede ser una <span class="codigo">JSP</span>, una página <span class="codigo">HTML</span>, un template de <span class="codigo">Velocity</span>, un archivo de <span class="codigo">Excel</span>, un <span class="codigo">PDF</span>, etc.</li>
<li>El <span class="codigo">DispatcherServlet</span> obtiene la vista que será regresada al cliente.</li>
<li>El <span class="codigo">DispatcherServlet</span> finalmente regresa la vista adecuada al cliente.</li>
</ol>Como vemos, a partir del paso 5 decimos que debemos indicar qué componente y de qué forma se resolverá la vista (en este caso una página) debe regresarse en respuesta a la petición que haya hecho el usuario. Para esto <span class="codigo">Spring</span> tiene un componente llamado "<span class="codigo">ViewResolver</span>", el cual (como su nombre lo indica) se encarga de, en base a un nombre lógico, identificar el recurso (o página) al cual se está haciendo referencia. <br />
<br />
La resolución de vistas en <span class="codigo">Spring</span> es extremadamente flexible. Un <span class="codigo">Controller</span> es típicamente responsable de preparar el modelo con datos y seleccionar el nombre de la vista que será regresada al usuario, sin embargo también podría escribir la respuesta directamente en el stream de respuesta (esto lo veremos en un tutorial posterior). La resolución del nombre de la vista es altamente configurable y se puede integrar con representación basada en tecnologías de templates como <span class="codigo">JSP</span>, <span class="codigo">Velocity</span> y <span class="codigo">Freemarker</span>, o generar directamente <span class="codigo">XML</span>, <span class="codigo">JSON</span>, <span class="codigo">Atom</span>, y muchos otros tipos de contenido.<br />
<br />
<span class="codigo">Spring MVC</span> tiene varias implementaciones de <span class="codigo">ViewResolver</span>, como las que se muestran a continuación:<br />
<br />
<table><thead>
<tr> <th>ViewResolver</th><th>Descripción</th> </tr>
</thead> <tbody>
<tr> <td><span class="codigo">AbstractCachingViewResolver</span></td><td>Es un <span class="codigo">ViewResolver</span> abstracto que coloca vistas en caché. Algunas veces las vistas necesitan cierta preparación antes de que puedan ser usadas; extendiendo esta clase proporciona ese caché.</td> </tr>
<tr> <td><span class="codigo">XmlViewResolver</span></td><td>Implementción de <span class="codigo">ViewResolver</span> que acepta un archivo de configuración en <span class="codigo">XML</span> con los mismos <span class="codigo">DTD</span>s que los bean factories de <span class="codigo">Spring</span>. El archivo de configuración por default es "<span class="codigo">/WEB-INF/views.xml</span>"</td> </tr>
<tr> <td><span class="codigo">ResourceBundleViewResolver</span></td><td>Implementación de <span class="codigo">ViewResolver</span> que usa definiciones de beans en un <span class="codigo">ResourceBundle</span>, especificado por el nombre base del bundle. Tipicamente definimos el bundle en un archivo de propiedades, localizado en el <span class="codigo">classpath</span>. El nombre por default es "<span class="codigo">views.properties</span>"</td> </tr>
<tr> <td><span class="codigo">UrlBasedViewResolver</span></td><td>Implementación simple de <span class="codigo">ViewResolver</span> que realiza la resolución directa de nombres de vistas lógicas a <span class="codigo">URL</span>s, sin una definición explícita de mapeo. Es apropiado si nuestros nombres lógicos coinciden con los nombres de los recursos de vista de forma directa, sin la necesidad de mapeos adicionales.</td> </tr>
<tr> <td><span class="codigo">InternalResourceViewResolver</span></td><td>Subclase de <span class="codigo">UrlBasedViewResolver</span> que soporta vistas basadas en <span class="codigo">JSP</span>s. Tiene otras subclases útiles como <span class="codigo">JstlView</span> y <span class="codigo">TilesView</span>.</td> </tr>
<tr> <td><span class="codigo">VelocityViewResolver / FreeMarkerViewResolver</span></td><td>Subclase de <span class="codigo">UrlBasedViewResolver</span> soporta vistas creadas usando templates creados usando <span class="codigo">Velocity</span> o <span class="codigo">Freemarker</span>, respectivamente, y sublclases propias de estos.</td> </tr>
<tr> <td><span class="codigo">ContentNegotiatingViewResolver</span></td><td>Implementación de <span class="codigo">ViewResolver</span> que resuelve una vista basada en la petición para determinar el nombre del archivo de la vista, o en la cabecera "<span class="codigo">Accept</span>".</td> </tr>
</tbody> </table><br />
Los anteriores son sólo algunos ejemplos, ya que <span class="codigo">Spring MVC</span> tiene muchas implementaciones de <span class="codigo">ViewResolver</span>, como por ejemplo vistas que permiten generar archivos de <span class="codigo">Excel</span>, archivos <span class="codigo">PDF</span>, etc.<br />
<br />
Como ya habrán notado, el "<span class="codigo">DispatcherServlet</span>" es una implementación del patrón de diseño "<span class="codigo"><a href="https://en.wikipedia.org/wiki/Front_Controller_pattern">Front Controller</a></span>" (esto es algo que <span class="codigo">Spring MVC</span> comparte con muchos otros frameworks <span class="codigo">MVC</span>). Este <span class="codigo">Servlet</span> recibe las peticiones del usuario y dependiendo de la <span class="codigo">URL</span> a la que vaya esta petición, el "<span class="codigo">DispatcherServlet</span>" enviará la petición al <span class="codigo">Controller</span> adecuado; el determinar cuál es el <span class="codigo">Controller</span> adecuado depende de ciertas reglas, algunas de las cuales veremos a lo largo de este tutorial.<br />
<br />
El <span class="codigo">Controller</span> creará los objetos de modelo necesarios para la respuesta y los regresará al "<span class="codigo">DispatcherServlet</span>", el cual nuevamente delegará la generación de la representación de la respuesta al motor de generación de vista, o "<span class="codigo">View</span>", en base a una plantilla de la misma. <br />
<br />
Finalmente será el mismo "<span class="codigo">DispatcherServlet</span>" quien regresará la respuesta al usuario.<br />
<br />
Suficiente teoría, comencemos con el ejemplo.<br />
<br />
Lo primero que debemos hacer es descargar los <span class="codigo">jar</span> de <span class="codigo">Spring MVC</span>. Nosotros usaremos la última versión a la fecha que es la <span class="codigo">4.2.3</span>. Encontrar estos <span class="codigo">jar</span>s puede ser un poco complicado, pero podeos hacerlo desde <a href="http://maven.springframework.org/release/org/springframework/spring/4.2.3.RELEASE/">el repositorio de Maven de Spring</a>. Les recomiendo que descarguen el archivo (<span class="codigo">spring-framework-4.2.3.RELEASE-dist.zip</span>). <br />
<br />
También necesitaremos el archivo "<span class="codigo">commons-logging-api-1.2.jar</span>" que podemos encontrar en <a href="https://commons.apache.org/proper/commons-logging/download_logging.cgi">la página de descarga de commons-logging</a>, este lo podremos encontrar en el archivo "<span class="codigo">commons-logging-1.2-bin.zip</span>".<br />
<br />
En algunos servidores es posible que al ejecutar la aplicación obtengan el siguiente error:<br />
<pre><code>
java.lang.NoClassDefFoundError: javax/servlet/jsp/jstl/core/Config
...
Caused by: java.lang.ClassNotFoundException: javax.servlet.jsp.jstl.core.Config
</code></pre><br />
Esto ocurre porque algunos servidores no tienen las clases de <span class="codigo">JSTL</span> (que es lo que <span class="codigo">Spring MVC</span> usa para generar la vista). ¿Qué podemos hacer si obtenemos este error? Afortunadamente es muy sencillo arreglarlo. Hay que agregar un <span class="codigo">jar</span> más a nuestra librería, en este caso la librería del API de <span class="codigo">JSTL</span>, la cual podemos descargar desde <a href="http://search.maven.org/#browse|707331597">el repositorio de Maven</a>. Es necesario descargar el archivo "<span class="codigo">javax.servlet.jsp.jstl-api-1.2.1.jar</span>" y agregarlo a la biblioteca de "<span class="codigo">SpringMVC4.2</span>" que crearemos en un momento.<br />
<br />
Una vez que hayamos bajado el archivo con los <span class="codigo">jar</span>s de <span class="codigo">Spring</span>, lo descomprimimos y veremos que tiene un subdirectorio "<span class="codigo">libs</span>" el cual contiene todos los <span class="codigo">jar</span>s. Vamos al <span class="codigo">NetBeans</span> y nos dirigimos al menú "<span class="codigo">Tools -> Libraries</span>".<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg0olwZqcGhOjJVGqaJDUEVcHH2sabTTpBnAPsU9subo0ur_J9RMBdB2XXQm3ngI-Yvt4FN0mUkertUM2Rr4_ffXyTlCxRUSx3kISQn1OYJCkUTy0F8j6dJ4-P-Dte8uzEiLppDtfDmXL76/s1600/S6_1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg0olwZqcGhOjJVGqaJDUEVcHH2sabTTpBnAPsU9subo0ur_J9RMBdB2XXQm3ngI-Yvt4FN0mUkertUM2Rr4_ffXyTlCxRUSx3kISQn1OYJCkUTy0F8j6dJ4-P-Dte8uzEiLppDtfDmXL76/s320/S6_1.png" width="245" /></a></div><br />
<br />
En la ventana que se abre presionamos el botón "<span class="codigo">New Library...</span>":<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg5znX6GYblcVWYO3IFK-u25zt40P6tQUuyylc-X6Ex13dAjb61kWlCQGivCguUZM42CKJNb_DW_WGqP8jxt718YYfia-tbdaEYvAuXPqjclsks-4iGW7DVX_LBfqYOhBq3EknBgW4kFd8I/s1600/S6_2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="306" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg5znX6GYblcVWYO3IFK-u25zt40P6tQUuyylc-X6Ex13dAjb61kWlCQGivCguUZM42CKJNb_DW_WGqP8jxt718YYfia-tbdaEYvAuXPqjclsks-4iGW7DVX_LBfqYOhBq3EknBgW4kFd8I/s400/S6_2.png" width="400" /></a></div><br />
<br />
En esta nueva ventana colocamos como nombre de la biblioteca "<span class="codigo">SpringMVC4.2</span>" y como tipo dejamos "<span class="codigo">Class Libraries</span>":<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgX3p7GssTX9mVt8XjQZKgxY83hMuLMjHe4-Ir161YeIt26J-Iz6GMzjkc4fuy9LludWDdx65CsghyphenhyphenBdHXXUpmfv6_3v4udvYf1u23pcaio6KbaBb_VhyphenhyphenB_xcgZYwiYPoU_ZEjvYx1J9Mww/s1600/S6_3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="173" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgX3p7GssTX9mVt8XjQZKgxY83hMuLMjHe4-Ir161YeIt26J-Iz6GMzjkc4fuy9LludWDdx65CsghyphenhyphenBdHXXUpmfv6_3v4udvYf1u23pcaio6KbaBb_VhyphenhyphenB_xcgZYwiYPoU_ZEjvYx1J9Mww/s320/S6_3.png" width="320" /></a></div><br />
<br />
Ahora que tenemos nuestra biblioteca, presionamos el botón "<span class="codigo">Add Jar/Folder</span>" para agregar los nuevos archivos que conformarán la biblioteca. Agregamos los siguientes archivos:<br />
<ul><li><span class="codigo">spring-aop-4.2.3.RELEASE.jar</span></li>
<li><span class="codigo">spring-beans-4.2.3.RELEASE.jar</span></li>
<li><span class="codigo">spring-context-4.2.3.RELEASE.jar</span></li>
<li><span class="codigo">spring-core-4.2.3.RELEASE.jar</span></li>
<li><span class="codigo">spring-expression-4.2.3.RELEASE.jar</span></li>
<li><span class="codigo">spring-web-4.2.3.RELEASE.jar</span></li>
<li><span class="codigo">spring-webmvc-4.2.3.RELEASE.jar</span></li>
<li><span class="codigo">commons-logging-api-1.2.jar</span> (que bajamos aparte)</li>
</ul><br />
Con estos es suficiente para hacer nuestros hola mundo iniciales <span class="codigo">^_^</span>. Una vez seleccionados estos archivos, nuestra biblioteca debe verse así:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhz90fKFhfBbzVQIE2vaiFwhvfe45Too-AIDSsRNkzuhLt1LEPqhyphenhyphenfybmb2hhpLGkYEfErM1SUFe40rLcuImRp6JYzf33CwVBsfHdn8j2VrIE1jMZd1ytY78QHbJ8uqE34-YmvDRbGrclEx/s1600/S6_4.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="306" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhz90fKFhfBbzVQIE2vaiFwhvfe45Too-AIDSsRNkzuhLt1LEPqhyphenhyphenfybmb2hhpLGkYEfErM1SUFe40rLcuImRp6JYzf33CwVBsfHdn8j2VrIE1jMZd1ytY78QHbJ8uqE34-YmvDRbGrclEx/s400/S6_4.png" width="400" /></a></div><br />
<br />
Presionamos el botón "<span class="codigo">OK</span>" y nuestra biblioteca estará lista para ser usada.<br />
<br />
Como dijimos, en este primer tutorial de la serie veremos dos maneras de configurar <span class="codigo">Spring MVC</span>, la primera será la tradicional forma usando archivos de configuración en <span class="codigo">XML</span>, y la segunda será con una configuración hecha completamente en <span class="codigo">Java</span>, con la cual no necesitaremos ni un solo archivo <span class="codigo">XML</span>.<br />
<br />
<span class="nota">*Nota: Yo usaré dos proyectos, uno para usar archivos de configuración en <span class="codigo">XML</span> y otro para configuración <span class="codigo">Java</span>, y que el código de ambos no se mezcle.</span><br />
<br />
Lo primero que haremos es crear un nuevo proyecto en <span class="codigo">NetBeans</span>. Para esto vamos al Menú "<span class="codigo">File->New Project...</span>". En la ventana que se abre seleccionamos la categoría "<span class="codigo">Java Web</span>" y en el tipo de proyecto "<span class="codigo">Web Application</span>". <br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi3VHmM3kDVgdajsg4znhq7RLn6blKVGxcrexh8COM_GubpXeT1OO7S4W7dsplVJc95l9_NHPgVur58XGGDPO4m4uVLC9ONKs5k1yryCyw31Rrm5mLrqc6Vc04tG80qUb5XozINE2REaeCy/s1600/S6_5.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="275" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi3VHmM3kDVgdajsg4znhq7RLn6blKVGxcrexh8COM_GubpXeT1OO7S4W7dsplVJc95l9_NHPgVur58XGGDPO4m4uVLC9ONKs5k1yryCyw31Rrm5mLrqc6Vc04tG80qUb5XozINE2REaeCy/s400/S6_5.png" width="400" /></a></div><br />
<br />
Le damos una ubicación y un nombre al proyecto, en mi caso será "<span class="codigo">SpringMVCXMLConfig</span>" para el proyecto que usa archivos de configuración <span class="codigo">XML</span> y "<span class="codigo">SpringMVCJavaConf</span>" para el que usa la configuración <span class="codigo">Java</span>. Hacemos clic en el botón "<span class="codigo">Next</span>" y se nos preguntará en qué servidor queremos desplegar nuestra aplicación, en mi caso usaré <span class="codigo">Tomcat 8</span> (pero debe funcionar igual en cualquier servidor de aplicaciones <span class="codigo">Java</span>). También cambiaré el <span class="codigo">context path</span> para tener algo más claro, en mi caso pondré "<span class="codigo">holaspringmvc</span>":<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjP-16PWaAQL2m4I4AZo1jHPIGZq-pWS3qzQvkSVZ0J7SLuBfc2M-NuTXmOsgkCgyED9tuSKuWkyoLbhXu6RSwvDsZnFAwmy7Fa0S2im1ausGtkdArWVK18Y-6-TjRCmeq8Zd42CgjzizHx/s1600/S6_6.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="275" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjP-16PWaAQL2m4I4AZo1jHPIGZq-pWS3qzQvkSVZ0J7SLuBfc2M-NuTXmOsgkCgyED9tuSKuWkyoLbhXu6RSwvDsZnFAwmy7Fa0S2im1ausGtkdArWVK18Y-6-TjRCmeq8Zd42CgjzizHx/s400/S6_6.png" width="400" /></a></div><br />
<br />
Presionamos el botón "<span class="codigo">Finish</span>" y veremos aparecer en el editor nuestra página "<span class="codigo">index.html</span>"<br />
<br />
Agregamos la biblioteca de "<span class="codigo">SpringMVC4.2</span>" que acabamos de crear, para esto hacemos clic derecho sobre el nodo "<span class="codigo">Libraries</span>" del proyecto, en el menú que aparece elegimos la opción "<span class="codigo">Add Library...</span>"<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjz5bnmQTvHpLAJI8kIERggSpnwUMIMzgJdfMXOT-KrcDZdFSv74xiXICWBQKzQeN8y9RX4RUGDfPpOKvYY8M1T8cOGtUW7jXtpnWBDKG_wPVuk4u5E5RABfyXMATz57aK0j5Kr7r3Qsdw5/s1600/S6_7.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjz5bnmQTvHpLAJI8kIERggSpnwUMIMzgJdfMXOT-KrcDZdFSv74xiXICWBQKzQeN8y9RX4RUGDfPpOKvYY8M1T8cOGtUW7jXtpnWBDKG_wPVuk4u5E5RABfyXMATz57aK0j5Kr7r3Qsdw5/s1600/S6_7.png" /></a></div><br />
<br />
En la ventana que se abre seleccionamos la biblioteca "<span class="codigo">SpringMVC4.2</span>" <br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjoyNUovv8ppPbDUQr2BjJHVaZZl4RXW3PyIlk-ZBp_REuc8qdkkSYt1Qg572pdhLEYxr9GWHVdNIp663me6D5AErtpY5oHC8Lk0GK-veHdCHF0-oMwYrzU14FiBHassce8tiekY1o7b4Hs/s1600/S6_8.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjoyNUovv8ppPbDUQr2BjJHVaZZl4RXW3PyIlk-ZBp_REuc8qdkkSYt1Qg572pdhLEYxr9GWHVdNIp663me6D5AErtpY5oHC8Lk0GK-veHdCHF0-oMwYrzU14FiBHassce8tiekY1o7b4Hs/s1600/S6_8.png" /></a></div><br />
<br />
Con lo que nuestro proyecto tendrá los <span class="codigo">jar</span>s que definimos para esta librería:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEipatLrpJVBV9t0e_DtmTp2YfQbrCuhch4vEUT7b3oFPwg_FaPjOSFYlz53tMS1zM9_CAZfYhd04mlVZt-HT9snmruXp40gUyYPmshiWIJCE4lZI1alG1YfPF8KwnaJi2zTjJ6cnDX_MRVI/s1600/S6_16.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEipatLrpJVBV9t0e_DtmTp2YfQbrCuhch4vEUT7b3oFPwg_FaPjOSFYlz53tMS1zM9_CAZfYhd04mlVZt-HT9snmruXp40gUyYPmshiWIJCE4lZI1alG1YfPF8KwnaJi2zTjJ6cnDX_MRVI/s1600/S6_16.png" /></a></div><br />
<br />
Creamos un nuevo paquete en el nodo "<span class="codigo">Source Packages</span>" del proyecto:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjrhGK8Vh9N8-fwcTHevxMSrFj-UgKdti0irvSbQ7ih6pmc5MN16FR9ZA1nxLGvEvXuPEHU1lxxnarNfGLGvZLvZSwccE6CBSkqkQooSo9EC9B1rZ-rxi5RBE0xfjcygbmF8M4bGENeK8Tv/s1600/S6_13.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="361" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjrhGK8Vh9N8-fwcTHevxMSrFj-UgKdti0irvSbQ7ih6pmc5MN16FR9ZA1nxLGvEvXuPEHU1lxxnarNfGLGvZLvZSwccE6CBSkqkQooSo9EC9B1rZ-rxi5RBE0xfjcygbmF8M4bGENeK8Tv/s400/S6_13.png" width="400" /></a></div><br />
<br />
Y colocamos "<span class="codigo">com.javatutoriales.springmvc.configuracion.controllers</span>" como nombre del paquete. Dentro de este nuevo paquete colocamos una clase llamada "<span class="codigo">SaludoController</span>", que será nuestro primer controlador.<br />
<br />
Un <span class="codigo">Controller</span>, como ya hemos dicho, es <span class="codigo">POJO</span> que implementa un conjunto de métodos que son invocados para manejar las peticiones <span class="codigo">HTTP</span>.<br />
<br />
Lo primero que debemos hacer en esta clase para indicar que es un controlador, es colocar la anotación <span class="codigo">"@Controller"</span> a nivel de la clase, de esta forma:<br />
<pre><code>
@Controller
public class SaludoController {
}
</code></pre><br />
Ahora creamos un método llamado "<span class="codigo">saluda</span>", este método debe regresar un objeto de tipo "<span class="codigo">ModelAndView</span>", que es lo que regresan los métodos que esperan dar al usuario una respuesta en base a una página web (al contrario de un <span class="codigo">XML</span>, <span class="codigo">JSon</span> o flujo de bytes). Este objeto encapsula tanto el nombre de la página que será usada como vist como los datos que conforman el modelo. En cuanto a los parámetros que recibe el método, en realidad <span class="codigo">Spring MVC</span> ofrece un mundo de posibilidades en este tema, que abarcaremos en otro tutorial, por ahora dejaremos vacía esta lista de parámetros. Hasta ahora nuestro método se ve así:<br />
<pre><code>
public ModelAndView saluda() {
}
</code></pre><br />
Ahora debemos indicar qué vista será la que se regrese en caso de le ejecución exitosa de este método, para este ejemplo el nombre lógico de la vista que regresaremos será una página llamada "<span class="codigo">saludo</span>" (que crearemos en un momento), para esto simplemente regresaremos un nuevo objeto <span class="codigo">ModelAndView</span>, en cuyo constructor pasaremos el nombre lógico de la vista, o sea "<span class="codigo">saludo</span>".<br />
<pre><code>
public ModelAndView saluda() {
return new ModelAndView("saludo");
}
</code></pre><br />
Para terminar con este método, debemos colocarle la anotación <span class="codigo">@RequestMapping</span>. Esta anotación ayuda a indicar que este método será un <span class="negritas">manejador de peticiones</span>, y también ayuda a indicar el patrón de peticiones a las que responderá este manejador específico. <span class="codigo">Spring MVC</span> usa el valor de esta anotación <span class="negritas">a nivel método</span>, en combinación con la anotación <span class="negritas">a nivel clase</span> (que en este caso no hemos colocado por lo que el patrón que se usará será vacío), entre otras condiciones para decidir cuales peticiones serán las que atienda este método, esto lo explicaremos más detalladamente en el siguiente tutorial, ya que depende de varios factores dependiendo de cómo vayamos a recibir la información de la petición. En este caso no colocaremos ningún valor, lo que significa que responderá a la petición que se haga a la <span class="codigo">URL</span> vacía (o "<span class="codigo">/</span>"). Como tampoco hemos indicado un patrón de peticiones a nivel de clase lo que esperamos es que este método responda a las peticiones que se hagan a la <span class="codigo">URL</span> <span class="codigo">http://localhost:8080/holaspringmvc/</span>. Al final nuestro método "<span class="codigo">saluda</span>" queda de la siguiente forma:<br />
<pre><code>
@RequestMapping
public ModelAndView saluda() {
return new ModelAndView("saludo");
}
</code></pre><br />
Y la clase <span class="codigo">SaludoController</span> completa así:<br />
<pre><code>
@Controller
public class SaludoController {
@RequestMapping
public ModelAndView saluda() {
return new ModelAndView("saludo");
}
}
</code></pre><br />
Ahora crearemos la página que mostrará la respuesta a esta petición. Esta página estará en el directorio "<span class="codigo">/WEB-INF/pages/</span>" y será una <span class="codigo">JSP</span> llamada "<span class="codigo">saludo</span>". <br />
<br />
Creamos un nuevo directorio, dentro de "<span class="codigo">WEB-INF</span>", llamado "<span class="codigo">pages</span>" y dentro de este una nueva <span class="codigo">JSP</span> llamada "<span class="codigo">saludo.jsp</span>":<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi-NqxMC-9-m7RVYt5WalKJ-7u-WvpbDxSJ9L7mAyEupubPO8LyyqkXgpiLX4DF3JLMoesLhFLaHncI95s4E2GZL-F6J_SJhGzgu6ezzIq3ecAA_sGX5HCNARKffxDdqFVodEU5WmuvSHec/s1600/S6_14.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi-NqxMC-9-m7RVYt5WalKJ-7u-WvpbDxSJ9L7mAyEupubPO8LyyqkXgpiLX4DF3JLMoesLhFLaHncI95s4E2GZL-F6J_SJhGzgu6ezzIq3ecAA_sGX5HCNARKffxDdqFVodEU5WmuvSHec/s1600/S6_14.png" /></a></div><br />
<br />
El contenido de la página será muy sencillo y sólo tendrá la estructura básica de una página <span class="codigo">HTML</span> y un mensaje que diga "<span class="codigo">Hola <span class="codigo">Spring MVC</span></span>", de la siguiente forma:<br />
<pre><code>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title><span class="codigo">Spring MVC</span> - Java Tutoriales</title>
</head>
<body>
<h1>Hola <span class="codigo">Spring MVC</span></h1>
</body>
</html>
</code></pre><br />
Los siguientes pasos dependen del modo de configuración que hayamos decidido usar, ya sea archivos de configuración en <span class="codigo">XML</span> o configuración basada en <span class="codigo">Java</span>. Iniciaremos, como es costumbre, con la configuración en base a archivos <span class="codigo">XML</span>.<br />
<br />
<br />
<h2 class="titulo">Configuración con archivos XML</h2>En este caso para configurar el "<span class="codigo">DispatcherFilter</span>" de <span class="codigo">Spring MVC</span> necesitamos un <span class="negritas">deployment descriptor</span> o archivo "<span class="codigo">web.xml</span>" de nuestro proyecto web. Para agregar este archivo hacemos clic derecho en el proyecto y seleccionamos "<span class="codigo">New -> Other...</span>". <br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgXThbuQW3JqdLYVJ-7_bIEy6gCOyKlFUaPHkucJ1poYwbjaVOU2Vf2xhoXb6AaB6-Z0-6BxfZv9OiaX3B2_reUXAh9ftB4diuX7xOWBLtshrCClhix9d-LGFu6qzh2B2QELGnApr9KUrzt/s1600/S6_9.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="306" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgXThbuQW3JqdLYVJ-7_bIEy6gCOyKlFUaPHkucJ1poYwbjaVOU2Vf2xhoXb6AaB6-Z0-6BxfZv9OiaX3B2_reUXAh9ftB4diuX7xOWBLtshrCClhix9d-LGFu6qzh2B2QELGnApr9KUrzt/s400/S6_9.png" width="400" /></a></div><br />
<br />
En la categoría elegimos "<span class="codigo">Web</span>" y en "<span class="codigo">File Types</span>" seleccionamos "<span class="codigo">Standard Deployment Descriptor (web.xml)</span>"<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiIxA8P2gy7abbg4Wyu1s1aTYzDRP3e1K7Ysz68xVvPvuVZqLcpqNMJLhIw0aUJdPGbSU2UqGbScNsxZkBbgkihiOM3nKfsM4Y-XOmu7D7rN4bfXoVMI6Hbcez4ksXkZLPBR_mGUVrv9RWG/s1600/S6_10.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="275" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiIxA8P2gy7abbg4Wyu1s1aTYzDRP3e1K7Ysz68xVvPvuVZqLcpqNMJLhIw0aUJdPGbSU2UqGbScNsxZkBbgkihiOM3nKfsM4Y-XOmu7D7rN4bfXoVMI6Hbcez4ksXkZLPBR_mGUVrv9RWG/s400/S6_10.png" width="400" /></a></div><br />
<br />
Presionamos el botón "<span class="codigo">Finish</span>", con lo veremos aparecer el contenido de nuestro archivo.<br />
<br />
En este archivo configuraremos el <span class="codigo">DispatcherServlet</span> que, como ya dijimos, es la pieza central de <span class="codigo">Spring MVC</span>. Este <span class="codigo">Servlet</span> procesará todas las peticiones que vayan hacia el framework, y decidirá qué componente debe atender cada una de las solicitudes. <br />
<br />
A este <span class="codigo">Servlet</span> podemos darle cualquier nombre que queramos, y la clase que lo implementa es "<span class="codigo">org.springframework.web.servlet.DispatcherServlet</span>". Este <span class="codigo">Servlet</span> debe ser el primero en iniciar cuando se cargue la aplicación, por lo que deberá ser el número 1 en el orden de inicio. La configuración del "<span class="codigo">DispatcherServlet</span>" queda de la siguiente forma:<br />
<pre><code>
<servlet>
<servlet-name>spring-web</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
</code></pre><br />
Lo siguiente es indicar qué patrón de peticiones serán las que pasen a través del <span class="codigo">DispatcherServlet</span>, en nuestro caso queremos que <span class="negritas">todas las peticiones</span> pasen a través de él, por lo que la configuración queda de esta forma:<br />
<pre><code>
<servlet-mapping>
<servlet-name>spring-web</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</code></pre><br />
Como podemos ver, para configurar el patrón de URLs que se enviarán a este <span class="codigo">Servlet</span> debemos indicar, primero, el nombre que habíamos indicado anteriormente, y como patrón de <span class="codigo">URL</span>s "<span class="codigo">/</span>". <br />
<br />
Nuestro archivo "<span class="codigo">web.xml</span>" completo se ve de la siguiente forma:<br />
<pre><code>
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<servlet>
<servlet-name>spring-web</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>spring-web</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>30</session-timeout>
</session-config>
</web-app>
</code></pre><br />
Lo siguiente es configurar el "<span class="codigo">DispatcherServlet</span>" para indicar algunos datos particulares de cómo queremos que se comporte. El "<span class="codigo">DispatcherServlet</span>" trae por default un conjunto de configuraciones que sirven para la mayoría de los casos. En nuestro caso la única configuración que haremos es cómo mapear los nombres lógicos de las respuestas a las páginas que conforman las vistas. <br />
<br />
Esta configuración debe estar en un archivo <span class="codigo">XML</span> que debe colocarse a la misma altura que el archivo "<span class="codigo">web.xml</span>" – o sea en el directorio "<span class="codigo">WEB-INF</span>" de la aplicación. El nombre de este archivo debe seguir una convención, y debe ser el mismo nombre que le hayamos dado al "<span class="codigo">DispatcherServlet</span>" en la configuración del Deployment Descriptor (en nuestro caso "<span class="codigo">spring-web</span>"), agregando "<span class="codigo">-servlet.xml</span>", por lo que en mi caso el nombre del archivo debe ser "<span class="codigo">spring-web-servlet.xml</span>". <br />
<br />
¿Qué ocurre si queremos colocar el archivo de configuración de <span class="codigo">Spring MVC</span> en otra ubicación o queremos darle otro nombre? Para esto existe una manera de sobre-escribir la ruta del archivo, a través de un parámetro que pasamos a la configuración del "<span class="codigo">DispatcherServlet</span>". En mi caso me gusta tener estos archivos de configuración en un directorio "<span class="codigo">config</span>" dentro del "<span class="codigo">WEB-INF</span>". Para lograr esto agregamos un parámetro llamado "<span class="codigo">contextConfigLocation</span>", cuyo valor será la ubicación del archivo de configuración de <span class="codigo">Spring MVC</span>. En mi caso el nombre del archivo será "<span class="codigo">springMVCconfig.xml</span>" y estará en el directorio "<span class="codigo">WEB-INF/config</span>". <br />
<br />
Modificamos la configuración del "<span class="codigo">DispatcherServlet</span>" para incluir este parámetro, de la siguiente forma:<br />
<pre><code>
<servlet>
<servlet-name>spring-web</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/config/springMVCconfig.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
</code></pre><br />
Ahora, creamos un nuevo directorio llamado "<span class="codigo">config</span>" dentro de "<span class="codigo">WEB-INF</span>":<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiV-lmz66ph-09FavTOwT0hhU-PBDKuew9WiwlwQrKMb_HaPT1iMNmldxXn0miytFnGCg05ydqbG7ukP7xNQwZAP_DLZqCle05eaHwLuTouUzqOwzySGow3n-X1OCzX7kBxJjqod-oxnPCp/s1600/S6_11.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiV-lmz66ph-09FavTOwT0hhU-PBDKuew9WiwlwQrKMb_HaPT1iMNmldxXn0miytFnGCg05ydqbG7ukP7xNQwZAP_DLZqCle05eaHwLuTouUzqOwzySGow3n-X1OCzX7kBxJjqod-oxnPCp/s1600/S6_11.png" /></a></div><br />
<br />
Y dentro de este creamos un archivo <span class="codigo">XML</span> llamado "<span class="codigo">springMVCconfig.xml</span>":<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi8tOZS1TQr6_Ga5ejvndefY89OoXHDHZ5OC_4aXeTaepEF5rxGwyyDk8R-KeSmJlwwnlXbaoS5uAPzUA1S4dq4rbZ48gtWNXeRXGYZK-u02OZbuTpdfEMyPWsJoZDKkaCx2E_4LNDC1sHj/s1600/S6_12.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi8tOZS1TQr6_Ga5ejvndefY89OoXHDHZ5OC_4aXeTaepEF5rxGwyyDk8R-KeSmJlwwnlXbaoS5uAPzUA1S4dq4rbZ48gtWNXeRXGYZK-u02OZbuTpdfEMyPWsJoZDKkaCx2E_4LNDC1sHj/s1600/S6_12.png" /></a></div><br />
<br />
Inicialmente el contenido del archivo será el siguiente:<br />
<pre><code>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd">
</beans>
</code></pre><br />
Usamos el elemento "<span class="codigo">component-scan</span>" del namespace "<span class="codigo">context</span>" para indicar dónde se encuentran los componentes de <span class="codigo">Spring</span>; recuerden que este elemento habilita los elementos que tengan las anotaciones <span class="codigo">@Component</span>, <span class="codigo">@Repository</span>, <span class="codigo">@Service</span>, y <span class="codigo">@Controller</span> (en nuestro caso sólo usaremos esta última). Usamos el atributo "<span class="codigo">base-package</span>" para indicar en qué paquete están nuestros controladores, en nuestro caso "<span class="codigo">com.javatutoriales.springmvc.configuracion.controllers</span>". Este elemento queda de la siguiente forma:<br />
<pre><code>
<context:component-scan base-package="com.javatutoriales.springmvc.configuracion.controllers" />
</code></pre><br />
Lo siguiente es habilitar los componentes de <span class="codigo">Spring MVC</span> con sus configuraciones por default, esto lo hacemos con el elemento "<span class="codigo">annotation-driven</span>" del namespace "<span class="codigo">mvc</span>", de la siguiente forma:<br />
<pre><code>
<mvc:annotation-driven />
</code></pre><br />
Esta etiqueta adicionalmente registrará los "<span class="codigo">HandlerMapping</span>" y "<span class="codigo">HandlerAdapter</span>" requeridos para despachar los <span class="codigo">Controller</span>s. Adicionalmente, aplica algunas configuraciones por default basado en lo que se encuentra en nuestro <span class="codigo">classpath</span>. Estas configuraciones por default son:<br />
<ul><li>Agrega soporte para formatear campos numéricos anotados con <span class="codigo">@NumberFormat</span></li>
<li>Agrea soporte para formateo de campos tipo <span class="codigo">Date</span>, <span class="codigo">Calendar</span>, y <span class="codigo">Joda Time</span> anotados con <span class="codigo">@DateTimeFormat</span>, si <span class="codigo">Joda Time</span> está en el <span class="codigo">classpath</span></li>
<li>Agrega soporte para validar campos de entrada en <span class="codigo">@Controller</span> con <span class="codigo">@Valid</span>, si un proveedor <span class="codigo">JSR-303</span> se encuentra en el <span class="codigo">classpath</span></li>
<li>Agrega soporte para leer y escribir <span class="codigo">XML</span>, si <span class="codigo">JAXB</span> se encuentra en el <span class="codigo">classpath</span></li>
<li>Agrega soporte para leer y escribir <span class="codigo">JSON</span>, si <span class="codigo">Jackson</span> se encuentra en el <span class="codigo">classpath</span></li>
</ul><br />
Finalmente debemos indicar qué implementación de "<span class="codigo">ViewResolver</span>" usaremos. En este caso será un "<span class="codigo">InternalResourceViewResolver</span>", el cual como vimos permite indicar el nombre lógico de la página que representa la vista, siempre y cuando el nombre del archivo sea igual al nombre lógico de nuestra vista; esto quiere decir que si decidimos que nuestra vista serán <span class="codigo">JSP</span> y que estarán en el directorio "<span class="codigo">WEB-INF/pages</span>", y un <span class="codigo">Controller</span> indica que se debe regresar una vista llamada "<span class="codigo">accesoUsuario</span>", esto quiere decir que debe existir un archivo "<span class="codigo">/WEB-INF/pages/accesoUsuario.jsp</span>".<br />
<br />
Este "<span class="codigo">ViewResolver</span>" se configura como un bean "normal" de <span class="codigo">Spring</span>, de la siguiente forma:<br />
<pre><code>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
</bean>
</code></pre><br />
<br />
Para configurar el "<span class="codigo">InternalResourceViewResolver</span>" debemos indicar cuál será el prefijo de las páginas. Este por lo general lo usamos para indicar en qué directorio se encuentran las páginas (a partir de la raíz donde se colocan las páginas web), que en nuestro caso será en el directorio "<span class="codigo">/WEB-INF/pages</span>", por lo que hasta ahora la configuración se ve de la siguiente forma:<br />
<pre><code>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix">
<value>/WEB-INF/pages/</value>
</property>
</bean>
</code></pre><br />
<br />
La segunda propiedad que debemos indicar es el sufijo que tendrán las páginas. En esta propiedad por lo general indicamos la extensión de las mismas. En nuestro caso las páginas serán <span class="codigo">JSP</span>s, por lo que el sufijo será "<span class="codigo">.jsp</span>". La configuración queda de esta forma:<br />
<pre><code>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix">
<value>/WEB-INF/pages/</value>
</property>
<property name="suffix">
<value>.jsp</value>
</property>
</bean>
</code></pre><br />
<br />
Y el archivo "<span class="codigo">springMVCconfig.xml</span>" completo queda de la siguiente forma:<br />
<pre><code>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd">
<context:component-scan base-package="com.javatutoriales.springmvc.configuracion.controllers" />
<mvc:annotation-driven />
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix">
<value>/WEB-INF/pages/</value>
</property>
<property name="suffix">
<value>.jsp</value>
</property>
</bean>
</beans>
</code></pre><br />
<br />
Ya tenemos todos los elementos en su lugar, por lo que el siguiente paso es ejecutar la aplicación y entrar a la siguiente dirección:<br />
<pre><code>
<a href="http://localhost:8080/holaspringmvc">http://localhost:8080/holaspringmvc</a>
</code></pre><br />
<br />
Con lo que debemos de ver la siguiente pantalla:<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjT8vd_-twXPczYQ83-gZT3Mcrz2F_RIq0-PZL07CsLPc8bHT0JC513CMjocIvBcrNw_pnn6_1ovXAwr6RMIexr2UAAk-N21M3FfJwdIHTTN7XcB6Wri5S4o-Szk__IMnpeZsR73GFnzdTo/s1600/S6_15.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="190" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjT8vd_-twXPczYQ83-gZT3Mcrz2F_RIq0-PZL07CsLPc8bHT0JC513CMjocIvBcrNw_pnn6_1ovXAwr6RMIexr2UAAk-N21M3FfJwdIHTTN7XcB6Wri5S4o-Szk__IMnpeZsR73GFnzdTo/s400/S6_15.png" width="400" /></a></div><br />
<br />
Esto indica que todo está bien configurado y funciona correctamente.<br />
<br />
Ahora veremos cómo realizar esta misma configuración <span class="negritas">sin un solo archivo <span class="codigo">XML</span></span>.<br />
<br />
<br />
<h2 class="titulo">Configuración Java (sin XML)</h2>Lo primero que hay que saber es que esta configuración (al menos la que reemplaza el archivo <span class="codigo">web.xml</span>) sólo servirá en contenedores que implementen la especificación de <span class="codigo">Servlets 3.0+</span>, esto ya que a partir de esta versión es que se permite configurar el <span class="codigo">ServletContext</span> de manera programática.<br />
<br />
¿Qué ocurre si nuestro contenedor no soporta al menos la versión <span class="codigo">3.0</span> de <span class="codigo">Servlets</span>? Pues siempre podremos seguir usando el archivo <span class="codigo">web.xml</span> para configurar el <span class="codigo">DispatcherServlet</span>, el resto de la configuración programática puede realizarse sin problemas.<br />
<br />
A continuación, creamos un nuevo paquete, que en este caso será "<span class="codigo">com.javatutoriales.springmvc.configuracion.config</span>". En este nuevo paquete colocaremos las clases que nos servirán para realizar la configuración.<br />
<br />
La especificación <span class="codigo">3.0</span> de <span class="codigo">Servlet</span> introduce la interface <span class="codigo"><a href="https://docs.oracle.com/javaee/6/api/javax/servlet/ServletContainerInitializer.html">ServletContextInitializer</a></span>, las clases que implementan esta interface son notificadas por el contenedor sobre los eventos de inicio del contenedor de <span class="codigo">Servlet</span>s. <span class="codigo">Spring</span> hace uso de esto en su interface "<span class="codigo">WebApplicationInitializer</span>", que asegura que la clase que la implemente es detectada y usada automáticamente para inicializar cualquier contenedor de <span class="codigo">Servlets 3</span>. Así que es mediante esta interface que podremos configurar el "<span class="codigo">DispatcherServlet</span>" de <span class="codigo">Spring MVC</span>.<br />
<br />
Dentro del nuevo paquete creamos una clase llamada "<span class="codigo">SpringWebApplicationInitializer</span>", esta clase debe implementar la interface "<span class="codigo">WebApplicationInitializer</span>". Esta interface sólo tiene un método: "<span class="codigo">onStartup</span>", por lo que hasta ahora nuestra clase se ve de esta forma:<br />
<pre><code>
public class SpringWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
}
}
</code></pre><br />
Dentro del método "<span class="codigo">onStartup</span>" será necesario crear un <span class="codigo">ApplicationContext</span> de <span class="codigo">Spring</span>, también de forma programática, y agregarlo al <span class="codigo">ServletContext</span> de la aplicación. Para la primera parte podemos crear cualquiera de los <span class="codigo">ApplicationContext</span> que vimos en <a href="http://www.javatutoriales.com/2010/12/contenedores-de-ioc-e-inyeccion-de.html">el segundo tutorial de la serie</a>, en donde aprendimos que tenemos una clase llamada "<span class="codigo">AnnotationConfigApplicationContext</span>", la cual nos ayuda a inicializar los beans de las clases anotadas con cualquiera de los estereotipos de <span class="codigo">Spring</span>. Para el caso de las aplicaciones web existe una clase equivalente que es "<span class="codigo">AnnotationConfigWebApplicationContext</span>".<br />
<pre><code>
public void onStartup(ServletContext servletContext) throws ServletException {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
}
</code></pre><br />
A esta clase debemos indicarle cuáles son las clases que cuentan con las anotaciones que debe procesar. En nuestro caso será la clase que contenga la configuración de los elementos de <span class="codigo">Spring MVC</span>, que es una clase que aún no creamos, pero que se llamará "<span class="codigo">SpringWebConfig</span>". Para indicarle a "<span class="codigo">AnnotationConfigWebApplicationContext</span>" cuál es esta clase, usamos el método "<span class="codigo">register</span>". También debemos indicarle a esta clase cual será el <span class="codigo">ServletContext</span> que usará para la aplicación, en este caso será el mismo que el método "<span class="codigo">onStartup</span>" recibe como parámetro. Hasta ahora la configuración se ve así:<br />
<pre><code>
public void onStartup(ServletContext servletContext) throws ServletException {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(SpringWebConfig.class);
context.setServletContext(servletContext);
}
</code></pre><br />
El siguiente paso es registrar el <span class="codigo">DispatcherServlet</span> dentro de nuestro <span class="codigo">ServletContext</span>, para esto usamos el método "<span class="codigo">addServlet</span>" del <span class="codigo">ServleContext</span> que recibimos como parámetro. "<span class="codigo">addServlet</span>" recibe dos parámetros, el primero es el nombre del <span class="codigo">Servlet</span>, similar a como lo colocamos en el elemento "<span class="codigo">servlet-name</span>" cuando trabajamos con el archivo "<span class="codigo">web.xml</span>". El segundo parámetro es la instancia del <span class="codigo">DispatcherServlet</span> que recibirá las peticiones de los usuarios. Para crear la instancia de este <span class="codigo">DispatcherServlet</span> es necesario pasar el <span class="codigo">ApplicationContext</span> que contiene las definiciones de los controladores. <br />
<br />
El método "<span class="codigo">addServlet</span>" regresa una instancia de "<span class="codigo">ServletRegistration.Dynamic</span>" que nos ayudará a terminar de configurar el <span class="codigo">DispatcherServlet</span>. En esta instancia indicamos que este debe ser el primer <span class="codigo">Servlet</span> en ejecutarse cuando se inicie la aplicación y que todas las peticiones que lleguen a la aplicación deben pasar a través de él:<br />
<pre><code>
ServletRegistration.Dynamic dispatcher = servletContext.addServlet("spring-web", new DispatcherServlet(context));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/");
</code></pre><br />
y la clase <span class="codigo">SpringWebApplicationInitializer</span> completa queda de la siguiente forma:<br />
<pre><code>
public class SpringWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(SpringWebConfig.class);
context.setServletContext(servletContext);
ServletRegistration.Dynamic dispatcher = servletContext.addServlet("spring-web", new DispatcherServlet(context));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/");
}
}
</code></pre><br />
El último paso es indicar la configuración propia de los elementos de <span class="codigo">Spring MVC</span>, tal y como lo hicimos en el archivo "<span class="codigo">springMVCconfig.xml</span>", sólo que en este caso lo haremos completamente mediante código <span class="codigo">Java</span>. <br />
<br />
Lo primero es crear una nueva clase, dentro del paquete <span class="codigo">config</span>, que se llame "<span class="codigo">SpringWebConfig</span>":<br />
<pre><code>
public class SpringWebConfig {
}
</code></pre><br />
Esta clase contendrá la configuración de <span class="codigo">Spring MVC</span>, por lo que debemos colocar la anotación <span class="codigo"><a href="http://docs.spring.io/autorepo/docs/spring/4.1.1.RELEASE/javadoc-api/org/springframework/context/annotation/Configuration.html">@Configurable</a></span> a nivel de clase. Esta anotación indica que esta clase declara uno o más métodos anotados con <span class="codigo">@Bean</span>, los cuales deben ser procesados por el contenedor de <span class="codigo">Spring</span> para generar definiciones de los beans y peticiones de esos beans en tiempo de ejecución.<br />
<pre><code>
@Configuration
public class SpringWebConfig {
}
</code></pre><br />
El siguiente paso es colocar la anotación <span class="codigo"><a href="https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/servlet/config/annotation/EnableWebMvc.html">@EnableWebMvc</a></span>, que es el equivalente al elemento "<span class="codigo"><mvc:annotation-driven></span>", la cual ya explicamos anteriormente.<br />
<pre><code>
@Configuration
@EnableWebMvc
public class SpringWebConfig {
}
</code></pre><br />
A continuación debemos indicar en qué paquetes de nuestra aplicación se encuentran los <span class="codigo">Controller</span>s, tal como lo hicimos antes con el elemento "<span class="codigo"><context:component-scan></span>", aquí también tenemos una anotación equivalente que es <span class="codigo"><a href="http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/context/annotation/ComponentScan.html">@ComponentScan</a></span>, el cual funciona exactamente como el elemento del archivo de configuración en <span class="codigo">XML</span>, inclusive tiene el mismo atributo "<span class="codigo">basePackages</span>", que en este caso también colocaremos con el valor de "<span class="codigo">com.javatutoriales.springmvc.configuracion.controllers</span>".<br />
<pre><code>
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.javatutoriales.springmvc.configuracion.controllers")
public class SpringWebConfig {
}
</code></pre><br />
Finalmente, y para terminar con la configuración debemos crear los beans que <span class="codigo">Spring</span> usará en nuestra aplicación, en este caso es únicamente el <span class="codigo">ViewResolver</span> que indicará cómo se mapearán los nombres lógicos de las vistas con los recursos que implementarán la vista. <br />
<br />
Como dijimos hace un momento, esto lo hacemos con un método al que le colocamos la anotación <span class="codigo">@Bean</span> y que creará el objeto en cuestión. El nombre del método en este caso no importa, así que yo le pondré el nombre "<span class="codigo">viewResolver</span>", el método hasta ahora se ve de la siguiente manera:<br />
<pre><code>
@Bean
public ViewResolver viewResolver() {
}
</code></pre><br />
Nuevamente usaremos un "<span class="codigo">InternalResourceViewResolver</span>" como implementación, por lo que lo creamos de forma directa:<br />
<pre><code>
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
}
</code></pre><br />
Al igual que hicimos en el archivo de configuración, es necesario indicar cuál será el prefijo (o directorio en el que se encuentran las páginas que se usarán como vistas) y el sufijo (o extensión de nuestras páginas). La clase "<span class="codigo">InternalResourceViewResolver</span>" proporciona métodos para hacer esto de forma directa:<br />
<pre><code>
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/pages/");
viewResolver.setSuffix(".jsp");
}
</code></pre><br />
Finalmente, el último paso es regresar este objeto:<br />
<pre><code>
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/pages/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
</code></pre><br />
La clase "<span class="codigo">SpringWebConfig</span>" completa se ve de la siguiente forma:<br />
<pre><code>
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.javatutoriales.springmvc.configuracion.controllers")
public class SpringWebConfig {
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/pages/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
}
</code></pre><br />
Hemos terminado, nuestra configuración. Por fin podemos ejecutar la aplicación, entrar a la siguiente <span class="codigo">URL</span>:<br />
<pre><code>
<a href="http://localhost:8080/holaspringmvc">http://localhost:8080/holaspringmvc</a>
</code></pre><br />
y espera que aparezca el siguiente mensaje en pantalla:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEim-6926AQ2sEZbPggKqmFEcGaG53pcOaygDcjC4-XZ_JFAXu4MduCUl17rtaj24wCY0ihbev0vOaYkobbJzTBm34ZF-3a0SVkfIXHrWDzt67U0O-OUzwJnHGlGHZDoMNELE1b8kG3oH8lU/s1600/S6_18.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEim-6926AQ2sEZbPggKqmFEcGaG53pcOaygDcjC4-XZ_JFAXu4MduCUl17rtaj24wCY0ihbev0vOaYkobbJzTBm34ZF-3a0SVkfIXHrWDzt67U0O-OUzwJnHGlGHZDoMNELE1b8kG3oH8lU/s400/S6_18.png" width="400" /></a></div><br />
<br />
Con lo que podemos comprobar que todo está funcionando de manera correcta <span class="codigo">^_^</span>.<br />
<br />
Ahora que tenemos nuestro primer ejemplo básico agreguemos un segundo <span class="codigo">Controller</span>.<br />
<br />
Creamos una nueva clase que se llame "<span class="codigo">SaludosGeneralesController</span>" dentro del paquete "<span class="codigo">controllers</span>". A esta clase le colocamos la anotación "<span class="codigo">@Controller</span>".<br />
<pre><code>
@Controller
public class SaludosGeneralesController{
}
</code></pre><br />
En esta ocasión sí colocaremos la anotación "<span class="codigo">@RequestMapping</span>" a nivel de clase, esto con el objetivo de indicar un patrón del <span class="codigo">URL</span>s que serán enviados a los distintos métodos de esta clase. En este caso el valor que colocaremos en esta anotación será "<span class="codigo">/saludos</span>":<br />
<pre><code>
@Controller
@RequestMapping("/saludos")
public class SaludosGeneralesController{
}
</code></pre><br />
Ahora crearemos un nuevo método llamado "<span class="codigo">saluda</span>" que regresará un objeto de tipo <span class="codigo">String</span>, que será el nombre lógico de nuestra vista, y en esta ocasión sí recibirá un parámetro que será un objeto de tipo <span class="codigo"><a href="https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/ui/Model.html">Model</a></span>. <span class="codigo">Model</span> es una abstracción de <span class="codigo">Spring MVC</span> que representa… pues el modelo, o en este caso la información que se regresa desde la parte del negocio a la vista. En esta ocasión la usaremos para regresar un par de valores a una <span class="codigo">JSP</span>:<br />
<pre><code>
String saluda(Model model)
{
}
</code></pre><br />
Este método también lo marcaremos con "<span class="codigo">@RequestMapping</span>" para indicar que será un manejador de peticiones, y en este caso el valor que colocaremos a esta anotación será "<span class="codigo">saludoGeneral</span>":<br />
<pre><code>
@RequestMapping("/saludoGeneral")
String saluda(Model model) {
}
</code></pre><br />
Esto quiere decir que esté método será invocado cuando el usuario haga una petición a nuestro servidor usando en conjunto el <span class="codigo">context path</span> (que definimos como "<span class="codigo">holaspringmvc</span>"), el patrón de <span class="codigo">URL</span>s a las que responde el <span class="codigo">DispatcherServlet</span> ("<span class="codigo">/</span>"), el valor de la anotación "<span class="codigo">@RequestMapping</span>" de la clase ("<span class="codigo">/saludos</span>") y el valor de la anotación "<span class="codigo">@RequestMapping</span>" del método ("<span class="codigo">/saludoGeneral</span>"). Por lo tanto la <span class="codigo">URL</span> a la que esperamos que responda esta método es:<br />
<pre><code>
http://localhost:8080/holaspringmvc/saludos/saludoGeneral
</code></pre><br />
Dentro del método lo único que haremos será pasar dos pares de nombre-valor a nuestro modelo, que posteriormente mostraremos en una <span class="codigo">JSP</span>: "<span class="codigo">sitio</span>" cuyo valor será "<span class="codigo">JavaTutoriales.com</span>" y "<span class="codigo">tutorial</span>" cuyo valor será "<span class="codigo"><span class="codigo">Spring MVC</span></span>". <br />
<br />
Para poder agregar estos valores "<span class="codigo">Model</span>" proporciona un método llamado "<span class="codigo">addAttribute</span>", que recibe dos parámetros de tipo <span class="codigo">String</span>, el primero es el nombre del atributo y el segundo su valor, de la siguiente forma:<br />
<pre><code>
@RequestMapping("/saludoGeneral")
String saluda(Model model) {
model.addAttribute("sitio", "JavaTutoriales.com");
model.addAttribute("tutorial", "<span class="codigo">Spring MVC</span>");
}
</code></pre><br />
Finalmente regresamos el nombre lógico de la vista, que en este caso será "<span class="codigo">saludoGeneral</span>":<br />
<pre><code>
@RequestMapping("/saludoGeneral")
String saluda(Model model) {
model.addAttribute("sitio", "JavaTutoriales.com");
model.addAttribute("tutorial", "<span class="codigo">Spring MVC</span>");
return "saludoGeneral";
}
</code></pre><br />
La clase "<span class="codigo">SaludosGeneralesController</span>" completa queda de la siguiente forma:<br />
<pre><code>
@Controller
@RequestMapping("/saludos")
public class SaludosGeneralesController {
@RequestMapping("/saludoGeneral")
String saluda(Model model) {
model.addAttribute("sitio", "JavaTutoriales.com");
model.addAttribute("tutorial", "<span class="codigo">Spring MVC</span>");
return "saludoGeneral";
}
}
</code></pre><br />
Para terminar este tutorial crearemos una nueva <span class="codigo">JSP</span> en el directorio "<span class="codigo">/WEB-INF/pages</span>", que es donde estamos colocando las vistas que serán regresadas por la aplicación. Esta página se llamará "<span class="codigo">saludoGeneral.jsp</span>" y tendrá el siguiente contenido:<br />
<pre><code>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title><span class="codigo">Spring MVC</span> - JavaTutoriales.com</title>
</head>
<body>
<h1>${sitio}</h1>
<p>${tutorial}</p>
</body>
</html>
</code></pre><br />
Ejecutamos nuevamente nuestra aplicación y entramos a la siguiente dirección:<br />
<pre><code>
<a href="http://localhost:8080/holaspringmvc/saludos/saludoGeneral">http://localhost:8080/holaspringmvc/saludos/saludoGeneral</a>
</code></pre><br />
Con lo que debemos ver la siguiente página:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjfl88bQIxQ4K2WRBjKBalOKFeZTzJAEjO1TqwpRyew-E-NuftB7oF55bOWr1W6o_wS_3ZqH751EJvh37m8bBxZ11Y5eNKUEW-98kO73sHiYVe_qrykmdvhAjzVA34C4SecvrWCo_YOc1E5/s1600/S6_19.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="187" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjfl88bQIxQ4K2WRBjKBalOKFeZTzJAEjO1TqwpRyew-E-NuftB7oF55bOWr1W6o_wS_3ZqH751EJvh37m8bBxZ11Y5eNKUEW-98kO73sHiYVe_qrykmdvhAjzVA34C4SecvrWCo_YOc1E5/s400/S6_19.png" width="400" /></a></div><br />
<br />
Lo cual nuevamente nos indica que todo ha funcionado correctamente <span class="codigo">^_^</span>.<br />
<br />
En este pequeño tutorial hemos dado sólo una breve introducción a <span class="codigo">Spring MVC</span>, este framework (o módulo de <span class="codigo">Spring</span>) es en realidad muy grande y tiene muchos elementos que iremos aprendiendo a lo largo de esta serie de tutoriales. <br />
<br />
Espero que este tutorial les sea de utilidad. Si tienen alguna duda, sugerencia, comentario o aclaración, pueden dejarla en la sección de comentarios o enviar un correo a programadorjavablog@gmail.com (pueden agregarme al chat de gmail). También pueden seguir JavaTutoriales en las siguientes redes sociales:<br />
<ul><li><a href="https://www.facebook.com/JavaTutoriales/">Facebook</a></li>
<li><a href="https://plus.google.com/u/0/114078943018704761597">Google+</a></li>
<li><a href="https://twitter.com/JavaTutoriales">Twitter</a></li>
</ul><br />
Saludos y gracias.<br />
<br />
Descarga los archivos de este tutorial desde aquí:<br />
<br />
<ul><li><a href="https://sites.google.com/site/javatutoriales/springmvc/SpringMVCXMLConfig.zip?attredirects=0&d=1">Spring MVC Cofiguración con XML</a></li>
<li><a href="https://sites.google.com/site/javatutoriales/springmvc/SpringMVCJavaConf.zip?attredirects=0&d=1">Spring MVC configuración Java</a></li>
</ul><br />
<br />
Entradas Relacionadas:<br />
<ul><li><a href="http://www.javatutoriales.com/2010/09/spring-parte-1-introduccion.html">Parte 1: Introducción</a></li>
<li><a href="http://javatutoriales.blogspot.com/2010/12/contenedores-de-ioc-e-inyeccion-de.html">Parte 2: Contenedores de IoC e Inyección de Dependencias</a></li>
<li><a href="http://javatutoriales.blogspot.com/2011/01/inyeccion-de-colecciones.html">Parte 3: Inyección de Colecciones</a></li>
<li><a href="http://www.javatutoriales.com/2011/02/spring-3-parte-4-ciclo-de-vida-de-los.html">Parte 4: Ciclo de Vida de los Beans</a></li>
<li><a href="http://www.javatutoriales.com/2015/01/spring-parte-5-integracion-y-manejo-de.html">Parte 5: Integración y manejo de transacciones con Hibernate</a></li>
</ul></div>Alexhttp://www.blogger.com/profile/06974037481671868076noreply@blogger.comtag:blogger.com,1999:blog-2012232069289957092.post-12520126081420483352015-01-14T11:30:00.000-08:002015-01-14T11:30:17.852-08:00Spring - Parte 5: Integración y manejo de transacciones con Hibernate<div style="text-align: justify;">A lo largo de <a href="http://www.javatutoriales.com/2010/09/spring-parte-1-introduccion.html">una serie de tutoriales</a> hemos visto las bondades que nos ofrece el framework <span class="codigo"><span class="codigo">Spring</span></span> para el manejo del ciclo de vida de beans, inyección de dependencia y configuración a través de anotaciones y de archivos de configuración en <span class="codigo">XML</span>.<br /><br />
Además ya hemos visto cómo trabajar con <a href="http://www.javatutoriales.com/2009/05/hibernate-parte-1-persistiendo-objetos.html">el framework <span class="codigo">Hibernate</span></a> para el manejo de persistencia de nuestra aplicación. Este framework nos permite olvidarnos de escribir queries a mano para centrarnos en la lógica y el flujo de la aplicación, además de proporcionar mecanismos de caché de datos.<br /><br />
En este tutorial veremos cómo integrar estos dos frameworks para que <span class="codigo"><span class="codigo">Spring</span></span> se encargue del manejo de las sesiones y las transacciones que <span class="codigo">Hibernate</span> usa.<br /><br />
<a name='more'></a><span class="codigo"><span class="codigo">Spring</span></span> soporta integración con varios frameworks de persistencia como <span class="codigo">Hibernate</span>, Java Persistence API (<span class="codigo">JPA</span>), Java Data Objects (<span class="codigo">JDO</span>) e <span class="codigo">iBatis</span> (ahora myBatis) para realizar la implementación de los <span class="codigo">DAO</span>s (Data Access Object) y la estrategia de transacciones. Además podemos configurar todas las características que nuestro framework de <span class="codigo">ORM</span> soporte, a través del mecanismo de inyección de dependencias de <span class="codigo"><span class="codigo">Spring</span></span>. <br /><br />
El framework de <span class="codigo"><span class="codigo">Spring</span></span> agrega mejoras significativas a la capa de <span class="codigo">ORM</span> de nuestra elección cuando creamos aplicaciones que hacen uso de acceso a datos. Podemos dejarle a <span class="codigo">Spring</span> tanto del trabajo de integración como queramos, teniendo la tranquilidad de <span class="negritas">usar un API probada y optimizada</span>. También nos permite configurarlo de distintas maneras y <span class="negritas">a distintos niveles</span> haciéndolo muy flexible al momento de tener que modificar algún aspecto del manejo o implementación de persistencia y/o transacciones de nuestra aplicación.<br /><br />
Suficiente teoría. Comencemos con la parte práctica del tutorial.<br /><br />
Lo primero que haremos es crear un nuevo proyecto en <span class="codigo">NetBeans</span>. Para esto vamos al Menú "<span class="codigo">File->New Project...</span>". En la ventana que se abre seleccionamos la categoría "<span class="codigo">Java</span>" y en el tipo de proyecto "<span class="codigo">Java Application</span>". Le damos una ubicación y un nombre al proyecto, en mi caso será "<span class="codigo">SpringHibernateIntegracion</span>". Nos aseguramos que la opción "<span class="codigo">Create Main Class</span>" esté habilitada y la damos un nombre (incluyendo los paquetes) a la nueva clase, en mi caso será "<span class="codigo">com.javatutoriales.spring.integration.hibernate.Main</span>" (lo sé, es un poco larga pero esto sirve para que se más claro lo que estamos haciendo). Presionamos el botón "<span class="codigo">Finish</span>" y veremos aparecer en el editor nuestra clase "<span class="codigo">Main</span>".<br /><br />
<span class="nota">*Nota: Yo usaré dos proyectos, uno para usar archivos de configuración en XML y otro para usar anotaciones y que el código de ambos no se mezcle.</span><br /><br />
Agregamos la biblioteca de "<span class="codigo">Spring4</span>" que creamos en <a href="http://www.javatutoriales.com/2010/09/spring-parte-1-introduccion.html">el primer tutorial de la serie de <span class="codigo">Spring</span></a> y la biblioteca "<span class="codigo">Hibernate4</span>" que creamos en <a href="http://www.javatutoriales.com/2009/05/hibernate-parte-1-persistiendo-objetos.html">el primer tutorial de la serie de <span class="codigo">Hibernate</span></a>. También agregaremos el driver de <span class="codigo">MySQL</span>, a través de la biblioteca "<span class="codigo">MySQL JDBC Driver</span>" (incluida en el <span class="codigo">NetBeans</span>).<br /><br />
<span class="nota">*Nota: Estamos actualizando los tutoriales de <span class="codigo">Hibernate</span> para hacer uso de la versión 4, por lo que por ahora sólo deben saber que necesitan los siguientes jars, de la versión <span class="codigo">4.2.7</span> de <span class="codigo">Hibernate</span>: <span class="codigo">antlr-2.7.7.jar</span>, <span class="codigo">dom4j-1.6.1.jar</span>, <span class="codigo">hibernate-commons-annotations-4.0.2.Final.jar</span>, <span class="codigo">hibernate-core-4.2.7.SP1.jar</span>, <span class="codigo">hibernate-jpa-2.0-api-1.0.1.Final.jar</span>, <span class="codigo">javassist-3.18.1-GA.jar</span>, <span class="codigo">jboss-logging-3.1.0.GA.jar</span>, <span class="codigo">jboss-transaction-api_1.1_spec-1.0.1.Final.jar</span></span><br /><br />
Hacemos clic derecho sobre el nodo "<span class="codigo">Libraries</span>" del proyecto, en el menú que aparece elegimos la opción "<span class="codigo">Add Library...</span>"<br /><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiPPWoJKRToTgvXc9XVq4kRlIwPPtVC9gQak8IOcUXf07dIjoqN_-dhrtaeT148Q07qoLhG15gOjneZVU9i8MtaP6N2aaesmhvzDQBmOp0LCfXSj7y6ZsF8AOzAXgxVxV3KuFgXXT7ZnWmG/s1600/S5_1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiPPWoJKRToTgvXc9XVq4kRlIwPPtVC9gQak8IOcUXf07dIjoqN_-dhrtaeT148Q07qoLhG15gOjneZVU9i8MtaP6N2aaesmhvzDQBmOp0LCfXSj7y6ZsF8AOzAXgxVxV3KuFgXXT7ZnWmG/s1600/S5_1.png" height="320" width="275" /></a></div><br /><br />
En la ventana que se abre seleccionamos las bibliotecas "<span class="codigo">Hibernate4</span>", "<span class="codigo">Spring4</span>" y "<span class="codigo">MySQL JDBC Driver</span>":<br /><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiPtENVRcfiIsHeuE9tlRP3KQcNxpuBlHuRedaMozJRJWWNPQ4kbQ2nFq6yE2gHf85kzaH66YJIttnvPgKTi7bS02Prq_Jms2xiEq916P9hfE9_E2m7BuCAzAApHLHVuTlEImJne4R1BN1N/s1600/S5_2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiPtENVRcfiIsHeuE9tlRP3KQcNxpuBlHuRedaMozJRJWWNPQ4kbQ2nFq6yE2gHf85kzaH66YJIttnvPgKTi7bS02Prq_Jms2xiEq916P9hfE9_E2m7BuCAzAApHLHVuTlEImJne4R1BN1N/s1600/S5_2.png" height="320" width="292" /></a></div><br /><br />
Hasta el momento nuestras bibliotecas deben verse de la siguiente forma:<br /><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhcMvr2pzNP9ZApj3_eUbRX6JuVE0xbHg0aWHK84E-1YPBEOvM0-byaKoeb707rxs2GcCNq3ZZ2Q-7ZazNXU-e3yp0-X92ElHAdoj2Hcpqxyk46JhxcEi1xAu4lj-FVfLQc7vLIBdZ6VF2f/s1600/S5_3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhcMvr2pzNP9ZApj3_eUbRX6JuVE0xbHg0aWHK84E-1YPBEOvM0-byaKoeb707rxs2GcCNq3ZZ2Q-7ZazNXU-e3yp0-X92ElHAdoj2Hcpqxyk46JhxcEi1xAu4lj-FVfLQc7vLIBdZ6VF2f/s1600/S5_3.png" height="284" width="320" /></a></div><br /><br />
Adicionalmente debemos agregar los jars con las clases necesarias de <span class="codigo">Spring</span> para el manejo de persistencia:<br />
<ul><li><span class="codigo">spring-orm-4.1.3.RELEASE.jar</span></li>
<li><span class="codigo">spring-jdbc-4.1.3.RELEASE.jar</span></li>
<li><span class="codigo">spring-tx-4.1.3.RELEASE.jar</span></li>
</ul><br />
Y si queremos que <span class="codigo">Spring</span> maneje nuestras transacciones debemos agregar además los siguientes jars:<br />
<ul><li><span class="codigo">spring-aop-4.1.3.RELEASE.jar</span></li>
<li><span class="codigo">com.springsource.org.aopalliance-1.0.0.jar</span> (que pueden descargar desde <a href="http://www.java2s.com/Code/JarDownload/com.springsource.org.aopalliance/com.springsource.org.aopalliance-1.0.0.jar.zip">java2s</a>).</li>
</ul><br />
Nuestras bibliotecas finalmente deben verse así:<br /><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgrzArOOr5U1ctdbx-yqFVsq3z_Wt7B80z5PibOFAimilzQjx54JTidUNgw6Jo6xBIhUWD31OtdRmATXAYShkDtJV4fWJnhfrkIF6nRIWq8R-xIczN9A7GzcQxvDJaIv9iK4wYr9-75QOc4/s1600/S5_8.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgrzArOOr5U1ctdbx-yqFVsq3z_Wt7B80z5PibOFAimilzQjx54JTidUNgw6Jo6xBIhUWD31OtdRmATXAYShkDtJV4fWJnhfrkIF6nRIWq8R-xIczN9A7GzcQxvDJaIv9iK4wYr9-75QOc4/s1600/S5_8.png" height="292" width="320" /></a></div><br /><br />
Ahora que ya tenemos las librerías, el siguiente paso es crear la base de datos. Usaremos una base de datos en <span class="codigo">MySQL</span> que se llamara "<span class="codigo">tutorial_hibernate</span>". En esta base de datos pondremos sólo una tabla que será "<span class="codigo">CONTACTO</span>". Para crear esta tabla usaremos el siguiente script:<br />
<pre><code>
CREATE TABLE contacto (
ID bigint(20) NOT NULL AUTO_INCREMENT,
NOMBRE varchar(255) DEFAULT NULL,
email varchar(255) DEFAULT NULL,
telefono varchar(255) DEFAULT NULL,
PRIMARY KEY (ID)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8
</code></pre><br />
También crearemos una clase, cuyas instancias serán guardadas en la tabla anterior. Para esto, primero crearemos un nuevo paquete llamado "<span class="codigo">entidades</span>", y en este paquete creamos una nueva clase llamada "<span class="codigo">Contacto</span>", que debe implementar la interface "<span class="codigo">java.io.Serializable</span>":<br />
<pre><code>
public class Contacto implements Serializable{
}
</code></pre><br />
<span class="codigo">Contacto</span> será una clase muy sencilla que nos servirá para transportar los datos (un <span class="codigo">TO</span> [Transfer Object] o <span class="codigo">DTO</span> [Data Transfer Object]) desde y hacia la base de datos, por lo que sólo tendrá unos cuantos atributos, que serán equivalentes a las columnas de la tabla "<span class="codigo">contacto</span>" que tenemos en la base de datos (también colocaremos <span class="codigo">getters</span> y <span class="codigo">setters</span> de estos atributos):<br />
<pre><code>
public class Contacto implements Serializable {
private long id;
private String nombre;
private String email;
private String telefono;
public long getId() {
return id;
}
protected void setId(long id) {
this.id = id;
}
public String getNombre() {
return nombre;
}
public void setNombre(String nombre) {
this.nombre = nombre;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getTelefono() {
return telefono;
}
public void setTelefono(String telefono) {
this.telefono = telefono;
}
}
</code></pre><br />
En el código anterior, seguramente notaron que el método "<span class="codigo">setId</span>" está declarado como "<span class="codigo">protected</span>" y no como "<span class="codigo">public</span>". Esto ocurre porque en este caso no queremos que el usuario pueda establecer el valor del identificador del objeto, queremos que sea la base de datos quien genere este valor y <span class="codigo">Hibernate</span> quien lo coloque en el objeto. Haciendo el método protegido nos aseguramos de que sólo un objeto que extienda de "<span class="codigo">Contacto</span>" pueda establecer el valor (la cual, por cierto, es la forma de trabajar de <span class="codigo">Hibernate</span>).<br /><br />
En la clase "<span class="codigo">Contacto</span>" pondremos además un par de constructores, uno vacío y otro que reciba todos los atributos (excepto el <span class="codigo">id</span>). El primer constructor es un requisito de <span class="codigo">Hibernate</span> y también puede ser "<span class="codigo">protected</span>" por las mismas razones que expusimos hace un momento, y el segundo es para facilitarnos a nosotros la creación de los objetos.<br />
<pre><code>
protected Contacto() {
}
public Contacto(String nombre, String email, String telefono) {
this.nombre = nombre;
this.email = email;
this.telefono = telefono;
}
</code></pre><br />
La clase "<span class="codigo">Contacto</span>" completa queda de la siguiente forma:<br />
<pre><code>
public class Contacto implements Serializable {
private long id;
private String nombre;
private String email;
private String telefono;
protected Contacto() {
}
public Contacto(String nombre, String email, String telefono) {
this.nombre = nombre;
this.email = email;
this.telefono = telefono;
}
public long getId() {
return id;
}
protected void setId(long id) {
this.id = id;
}
public String getNombre() {
return nombre;
}
public void setNombre(String nombre) {
this.nombre = nombre;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getTelefono() {
return telefono;
}
public void setTelefono(String telefono) {
this.telefono = telefono;
}
}
</code></pre><br />
Ahora necesitamos una interface que defina las operaciones que podremos realizar en la capa de persistencia, sobre los registros de la tabla "<span class="codigo">contacto</span>". El crear esta interface nos ayudará a que nuestra aplicación mantenga un <span class="negritas">bajo acoplamiento</span>, separando el código de lógica de negocio del de acceso a datos, de esta forma podremos cambiar la implementación del <span class="codigo">DAO</span> siempre que use la misma interface, y nuestra capa de lógica ni siquiera notará el cambio. Crearemos una nueva interface llamada "<span class="codigo">ContactosDAO</span>", dentro de un paquete "<span class="codigo">persistencia</span>" de nuestra aplicación. Esta interface tendrá 5 métodos que nos permitirán guardar nuevos <span class="codigo">Contacto</span>s, actualizarlos, eliminarlos, y buscarlos. La interface "<span class="codigo">ContactosDAO</span>" queda de la siguiente forma:<br />
<pre><code>
public interface ContactosDAO {
void actualizaContacto(Contacto contacto) throws HibernateException;
void eliminaContacto(Contacto contacto) throws HibernateException;
long guardaContacto(Contacto contacto) throws HibernateException;
Contacto obtenContacto(long idContacto) throws HibernateException;
List<Contacto> obtenListaContactos() throws HibernateException;
}
</code></pre><br />
El siguiente paso es crear una implementación de la interface anterior, que contenga la lógica para conectarse a la base de datos. Como en nuestro caso la implementación será realizada con <span class="codigo">Hibernate</span>, nos basaremos en lo que aprendimos en <a href="http://www.javatutoriales.com/2009/05/hibernate-parte-1-persistiendo-objetos.html">la serie de tutoriales de <span class="codigo">Hibernate</span></a>. Recordemos que para poder hacer operaciones sobre la base de datos lo primero que necesitamos es obtener una instancia de la clase "<span class="codigo">org.hibernate.SessionFactory</span>". Obtener esta instancia es un trabajo "pesado", esto quiere decir que se necesita algo de tiempo para hacerlo y que consume una cantidad considerable de memoria, por lo tanto la recomendación es que esta instancia sea creada una sola vez, y que sea al inicio de la aplicación. <br /><br />
En <a href="http://www.javatutoriales.com/2009/05/hibernate-parte-1-persistiendo-objetos.html">los tutoriales de <span class="codigo">Hibernate</span></a> lográbamos esto con una clase de utilidad llamada "<span class="codigo">HibernateUtil</span>", o extendiendo de una clase base "<span class="codigo">AbstracHibernateDAO</span>", y creábamos la instancia de "<span class="codigo">SessionFactory</span>" en un bloque de inicialización estática dentro de e esta clase.<br /><br />
En esta ocasión <span class="codigo">Spring</span> se encargará de crear esta instancia, lo único que tendremos que hacer es configurar algunos datos para la creación de este "<span class="codigo">SessionFactory</span>", en el archivo de configuración de <span class="codigo">Spring</span> y este se encargará del resto. Además haremos uso de la inyección de dependencias para poder acceder a la instancia de "<span class="codigo">SessionFactory</span>" creada por <span class="codigo">Spring</span>. Veremos cómo hacer esto un poco más adelante en el tutorial.<br /><br />
Creamos una nueva clase, en el paquete "<span class="codigo">persistencia</span>", llamada "<span class="codigo">ContactosDAOHibernateImpl</span>". Esta clase implementará la interface "<span class="codigo">ContactosDAO</span>":<br />
<pre><code>
public class ContactosDAOHibernateImpl implements ContactosDAO{
}
</code></pre><br />
Antiguamente <span class="codigo">Spring</span> proporcionaba "<span class="codigo">templates</span>" para realizar los queries y mapearlos a objetos de negocio de nuestra aplicación. Sin embargo, ahora la estrategia recomendada es escribir las implementaciones de los <span class="codigo">DAO</span> usando <span class="codigo">Hibernate</span> "plano", o sea sin hacer uso de ninguna dependencia de <span class="codigo">Spring</span>.<br /><br />
Basándonos en <a href="http://www.javatutoriales.com/2009/05/hibernate-parte-1-persistiendo-objetos.html">la serie de tutoriales de Hibernate</a>, declararemos tres variables de instancia en esta clase. La primera será una instancia de "<span class="codigo">SessionFactory</span>", que es el objeto que usamos para crear los objetos "<span class="codigo">org.hibernate.Session</span>". La segunda será una instancia de "<span class="codigo">Session</span>", que nos proporciona las operaciones para guardar, eliminar y obtener objetos desde y hacia la base de datos; y la tercera será una instancia de "<span class="codigo">org.hibernate.Transaction</span>", que es un objeto que define una unidad de trabajo en la base de datos.<br />
<pre><code>
public class ContactosDAOHibernateImpl implements ContactosDAO {
private Session sesion;
private Transaction transaction;
private SessionFactory sessionFactory;
}
</code></pre><br />
También colocaremos un <span class="codigo">setter</span> para el "<span class="codigo">SessionFactory</span>". ¿Por qué? Porque recordemos que en este caso será <span class="codigo">Spring</span> quien se encargará de crear este objeto, y lo pasará a nuestra clase a través de ese <span class="codigo">setter</span>:<br />
<pre><code>
public class ContactosDAOHibernateImpl implements ContactosDAO {
private Session sesion;
private Transaction transaction;
private SessionFactory sessionFactory;
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
}
</code></pre><br />
Ahora colocaremos dos métodos privados en esta clase, que nos ayudarán a iniciar una operación, iniciando una sesión y una transacción; y a manejar los errores en caso de una excepción, haciendo un <span class="codigo"><span class="codigo">rollback</span></span> de la transacción. Estos métodos los tomaremos <a href="http://www.javatutoriales.com/2009/05/hibernate-parte-1-persistiendo-objetos.html">del tutorial de <span class="codigo">Hibernate</span></a> con una ligera modificación.<br /><br />
El primer método se llamará "<span class="codigo">iniciaOperacion</span>", y se encargará de iniciar una sesión, usando el objeto "<span class="codigo">SessionFactory</span>"; y de iniciar una transacción:<br />
<pre><code>
private void iniciaOperacion() throws HibernateException {
sesion = sessionFactory.getCurrentSession();
transaction = sesion.beginTransaction();
}
</code></pre><br />
El segundo se llamará "<span class="codigo">manejaExcepcion</span>", y nos permitirá hacer un <span class="codigo">rollback</span> de la transacción en caso de que exista un error, o sea, que una "<span class="codigo">HibernateException</span>" sea lanzada mientras trabajamos con la base de datos. Este código (aunque es sólo una línea) lo ponemos en este método por si después quisiéramos agregar más operaciones dentro de este método, como por ejemplo mandar el error a una bitácora o imprimir el stacktrace de la excepción.<br />
<pre><code>
private void manejaExcepcion(HibernateException he) throws HibernateException {
transaction.rollback();
}
</code></pre><br />
Ahora colocaremos a implementación de los métodos de la interface "<span class="codigo">ContactosDAO</span>". Estas implementaciones también las hemos tomado de <a href="http://www.javatutoriales.com/2009/05/hibernate-parte-1-persistiendo-objetos.html">los tutoriales de <span class="codigo">Hibernate</span></a>, por lo que no daremos una explicación de los mismos (además, parece que son bastante intuitivos). La clase "<span class="codigo">ContactosDAOHibernateImpl</span>" completa, omitiendo los imports, queda de la siguiente forma:<br />
<pre><code>
public class ContactosDAOHibernateImpl implements ContactosDAO {
private Session sesion;
private Transaction transaction;
private SessionFactory sessionFactory;
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
@Override
public long guardaContacto(Contacto contacto) throws HibernateException {
long id = 0;
try {
iniciaOperacion();
id = (Long) sesion.save(contacto);
transaction.commit();
} catch (HibernateException he) {
manejaExcepcion(he);
throw he;
} finally {
sesion.close();
}
return id;
}
@Override
public void actualizaContacto(Contacto contacto) throws HibernateException {
try {
iniciaOperacion();
sesion.update(contacto);
transaction.commit();
} catch (HibernateException he) {
manejaExcepcion(he);
throw he;
} finally {
sesion.close();
}
}
@Override
public void eliminaContacto(Contacto contacto) throws HibernateException {
try {
iniciaOperacion();
sesion.delete(contacto);
transaction.commit();
} catch (HibernateException he) {
manejaExcepcion(he);
throw he;
} finally {
sesion.close();
}
}
@Override
public Contacto obtenContacto(long idContacto) throws HibernateException {
Contacto contacto = null;
try {
iniciaOperacion();
contacto = (Contacto) sesion.get(Contacto.class, idContacto);
} finally {
sesion.close();
}
return contacto;
}
@Override
public List<Contacto> obtenListaContactos() throws HibernateException {
List<Contacto> listaContactos = null;
try {
iniciaOperacion();
listaContactos = sesion.createQuery("from Contacto").list();
} finally {
sesion.close();
}
return listaContactos;
}
private void iniciaOperacion() throws HibernateException {
sesion = sessionFactory.getCurrentSession();
transaction = sesion.beginTransaction();
}
private void manejaExcepcion(HibernateException he) throws HibernateException {
transaction.rollback();
}
}
</code></pre><br />
Ahora crearemos el archivo de configuración de <span class="codigo">Spring</span>. En este archivo declararemos los beans que <span class="codigo">Spring</span> usará para crear la conexión hacia la base de datos, el objeto <span class="codigo">sessionFactory</span>, y para indicar las clases que <span class="codigo">Hibernate</span> usará como entidades. En este momento colocaremos sólo los elementos esenciales, y los demás los dejaremos pendientes por un momento, ya que algunas cosas dependen de si estamos trabajando con anotaciones o con archivos de mapeo.<br /><br />
Para crear este archivo hacemos clic derecho sobre el nodo "<span class="codigo">Source Packages</span>" de nuestro proyecto, y luego en "<span class="codigo">New</span>" -> "<span class="codigo">SpringXMLConfig</span>":<br /><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgoet7453Y0fP973wtJg8sTDzgwd_irTiCOUtZPegOoORAaDW3eNY0fzjXrI737a8xjklLLkDX4qaEdWK3rKVgqAtulrpLPVdK0PZl9aa2de2pp2ZlxqKurPgZQGfGSIeWYYoXRefPWBIkR/s1600/S5_4.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgoet7453Y0fP973wtJg8sTDzgwd_irTiCOUtZPegOoORAaDW3eNY0fzjXrI737a8xjklLLkDX4qaEdWK3rKVgqAtulrpLPVdK0PZl9aa2de2pp2ZlxqKurPgZQGfGSIeWYYoXRefPWBIkR/s1600/S5_4.png" height="229" width="320" /></a></div><br /><br />
Si no les aparece esta opción (como es mi caso), seleccionen la opción "<span class="codigo">Other...</span>", y en la ventana que aparece seleccionen "<span class="codigo">Other</span>" como categoría, y "<span class="codigo">SpringXMLCongfig</span>" como tipo de archivo.<br /><br />
A este archivo le daremos "<span class="codigo">applicationContext</span>" como nombre (<span class="codigo">NetBeans</span> se encargará de poner la extensión <span class="codigo">.xml</span>). Antes de crear el archivo <span class="codigo">NetBeans</span> preguntará los namespaces de <span class="codigo">Spring</span> que queremos incluir en el archivo. Recodemos que un namespace nos permite separar elementos de configuración de un módulo de <span class="codigo">Spring</span> de los elementos de otro módulo, de forma que sea más claro lo que estamos configurando. Nosotros seleccionaremos los namapaces "<span class="codigo">aop</span>", "<span class="codigo">context</span>" y "<span class="codigo">tx</span>" (opcionalmente puede seleccionar también el namespace "<span class="codigo">jee</span>" si van a trabajar con <span class="codigo">JNDI</span>), los cuales permiten configurar los elementos de programación orientada a aspectos (hablaremos de esto más adelante), escaneo y configuración de componentes, y manejo de transacciones, respectivamente:<br /><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg5Kis57yLJoieBcS4FO29jTWBdhqL2tBHOZ2HyO1KKA0BBQpSc6ldDme6dUfjVsQs5S1uUD7wrX4VgE-7sebxse6FaijxNDrfaRPANWOCejnB2aJCXLFjyy5_OdPI1_IUc4fRIsnFk8hBF/s1600/S5_5.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg5Kis57yLJoieBcS4FO29jTWBdhqL2tBHOZ2HyO1KKA0BBQpSc6ldDme6dUfjVsQs5S1uUD7wrX4VgE-7sebxse6FaijxNDrfaRPANWOCejnB2aJCXLFjyy5_OdPI1_IUc4fRIsnFk8hBF/s1600/S5_5.png" height="270" width="400" /></a></div><br /><br />
Hacemos clic en el botón "<span class="codigo">Finish</span>", con lo que deberemos de ver nuestro archivo de configuración, el cual tiene más o menos el siguiente contenido:<br />
<pre><code>
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd">
</beans>
</code></pre><br />
Lo primero que haremos es crear el <span class="codigo">dataSource</span>, o la conexión hacia la base de datos.<br /><br />
<span class="codigo">Spring</span> ofrece varias opciones para configurar esta fuente de datos, entre las que se incluyen:<br />
<ul><li>Data sources a través de <span class="codigo">JNDI</span></li>
<li>Data sources a través de drivers <span class="codigo">JDBC</span></li>
<li>Data sources que usan un pool de conexiones</li>
</ul><br />
Obviamente hay algunas diferencias en las características de cada uno de estos tipos de data sources.<br /><br />
Si queremos simular un ambiente real de producción, o los parámetros de nuestra conexión cambiarán dependiendo del ambiente en el que estemos (por ejemplo: desarrollo, pruebas, producción) o estamos desarrollando un proyecto que será instalado en múltiples clientes, o simplemente no tenemos los datos de la conexión, pero nos será proporcionada a través de <span class="codigo">JNDI</span> (por ejemplo si instalamos nuestra aplicación en un servidor de aplicaciones), lo más recomendable es usar un data source a través de <span class="codigo">JNDI</span>.<br /><br />
El elemento "<span class="codigo"><jee:jndi-lookup></span>" permite recuperar cualquier objeto, incluyendo data sources, de <span class="codigo">JNDI</span> y ponerlo disponible como un bean de <span class="codigo">Spring</span>. El atributo "<span class="codigo">jndi-name</span>", de este elemento, se usa para especificar el nombre del recurso <span class="codigo">JNDI</span>. Finalmente, si la aplicación se ejecuta en un servidor de aplicaciones, podemos establecer la propiedad "<span class="codigo">resource-ref</span>" a "<span class="codigo">true</span>", con lo que el nombre del recurso será precedido con "<span class="codigo">comp/env/</span>". <br /><br />
Por ejemplo, si tenemos un recurso de tipo data source, cuyo nombre es "<span class="codigo">conexionTutorial</span>", entonces haremos referencia a este recurso de la siguiente forma:<br />
<pre><code>
<jee:jndi-lookup id="dataSource" jndi-name="/jdbc/conexionTutorial" resource-ref="true" />
</code></pre><br />
En caso de que no estemos trabajando con <span class="codigo">JNDI</span> la siguiente mejor opción es usar un data source que mantenga un pool de conexiones, y declarar los datos para esta conexión directamente en <span class="codigo">Spring</span>. La forma más común de hacer esto es usando el proyecto <span class="codigo"><a href="http://commons.apache.org/proper/commons-dbcp/">Data Base Connection Pool</a></span> de <span class="codigo">Apache</span>. Aunque tiene varias clases para proporcionar el pooling, la que se usa con más frecuencia es "<span class="codigo">BasicDataSource</span>".<br /><br />
Para crear este data source, declaramos un bean de <span class="codigo">Spring</span>, y establecemos los atributos necesarios para la conexión, como el nombre del driver a usar; la URL, username y password de la conexión; y el tamaño inicial del pool:<br />
<pre><code>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost/tutorial_hibernate" />
<property name="username" value="user" />
<property name="password" value="password" />
<property name="initialSize" value="5" />
<property name="maxActive" value="10" />
</bean>
</code></pre><br />
Para ver qué otros atributos soporta esta bean, pueden revisar la documentación de la clase "<span class="codigo"><a href="http://commons.apache.org/proper/commons-dbcp/apidocs/org/apache/commons/dbcp/BasicDataSource.html">BasicDataSource</a></span>".<br /><br />
Si no necesitamos cosas tan "<span class="codigo">sofisticadas</span>" como un pool de conexiones o si, como en nuestro caso, estamos haciendo sólo un ejemplo sencillo, podemos usar un driver <span class="codigo">JDBC</span> como data source. <span class="codigo">Spring</span> viene con dos clases que pueden servirnos de data source:<br />
<ul><li><span class="codigo">DriverManagerDataSource</span>. Regresa una nueva conexión cada vez que una conexión es requerida.</li>
<li><span class="codigo">SingleConnectionDataSource</span>. Regresa la misma conexión cada vez que una conexión es requerida.</li>
</ul>La configuración de ambas clases es igual. En nuestro caso elegiremos la clase "<span class="codigo">DriverManagerDataSource</span>". Aquí nuevamente crearemos un bean de <span class="codigo">Spring</span> y estableceremos la información requerida para la conexión a la base de datos (como el nombre del driver a usar; la URL, username y password de la conexión):<br />
<pre><code>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost/tutorial_hibernate" />
<property name="username" value="user" />
<property name="password" value="password" />
</bean>
</code></pre><br />
Nosotros elegiremos trabajar con este último data source, por lo que hasta ahora nuestro archivo de configuración de <span class="codigo">Spring</span> (omitiendo los namespaces) se ve de la siguiente forma:<br />
<pre><code>
<beans>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost/tutorial_hibernate" />
<property name="username" value="user" />
<property name="password" value="password" />
</bean>
</beans>
</code></pre><br />
El siguiente paso es crear el <span class="codigo">SessionFactory</span> que, como recordaremos, nos permitirá crear objetos "<span class="codigo">Session</span>" con los que interactuaremos con la base de datos. Para crear este objeto nuevamente declararemos un bean de <span class="codigo">Spring</span>. La clase que usemos dependerá del framework ORM que estemos usando. Como en nuestro caso usamos Hibernate 4, la clase que tenemos que usar es "<span class="codigo">org.springframework.orm.hibernate4.LocalSessionFactoryBean</span>":<br />
<pre><code>
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
</bean>
</code></pre><br />
Dentro de este bean podemos configuraremos algunos parámetros del framework <span class="codigo">ORM</span> que estemos usando, en este caso <span class="codigo">Hibernate</span>. Aquí pondremos parámetros que normalmente colocamos en el archivo de configuración de <span class="codigo">Hibernate</span> ("<span class="codigo">hibernate.cfg.xml</span>"), junto con algunos otros datos.<br /><br />
Alguna de esta información dependerá si estamos trabajando con archivos de configuración en <span class="codigo">XML</span>, o si estamos trabajando con anotaciones. Veremos la configuración común para ambas opciones y un poco más adelante veremos los elementos particulares para cada forma de trabajo.<br /><br />
Lo primero que debemos hacer es indicar el data source que se usará para conectarse a la base de datos, para ello usamos la propiedad "<span class="codigo">dataSource</span>" y hacemos referencia al bean "<span class="codigo">dataSource</span>" que creamos hace un momento:<br />
<pre><code>
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource">
<ref bean="dataSource"/>
</property>
</bean>
</code></pre><br />
Lo siguiente es configurar las propiedades de <span class="codigo">Hibernate</span> que no tienen que ver con la conexión a la base de datos, como por ejemplo pool de conexiones, caché, el dialecto que se usará, los archivos de mapeo o clases anotadas que representan las entidades, etc. De hecho, si ya contamos con un archivo "<span class="codigo">hibernate.cfg.xml</span>" o simplemente queremos apartar esta información del archivo de configuración de <span class="codigo">Spring</span> (por ejemplo si queremos que esta configuración pueda ser modificada por otra persona o no queremos llenar este archivo con mucha información), este bean nos permite hacer referencia a un archivo de configuración de <span class="codigo">Hibernate</span> del cual leerá estos parámetros. Para esto usamos la propiedad "<span class="codigo">configLocation</span>", al cual le pasamos una lista de ubicaciones de los archivos que utilizará, en este caso sólo tendremos uno, por lo que la propiedad queda de esta forma:<br />
<pre><code>
<property name="configLocation">
<value>classpath:hibernate.cfg.xml</value>
</property>
</code></pre><br />
Creamos este archivo, en la raíz del nodo "<span class="codigo">Source Packages</span>" del proyecto, como un documento <span class="codigo">XML</span>:<br /><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhsE9dWPaNcoFYnatFZ5wu2K1G4dKgKBQNuHmI-h7rRHiwn2M_v-uwTvEku9K3g4Wn_s8AwY1dPRcMNB2iBRxRuuS17GGk5R6CNu8Xmt6YK5IxOh0W7hT_A0PD5g68AIIZbUHQxrNbeyglU/s1600/S5_6.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhsE9dWPaNcoFYnatFZ5wu2K1G4dKgKBQNuHmI-h7rRHiwn2M_v-uwTvEku9K3g4Wn_s8AwY1dPRcMNB2iBRxRuuS17GGk5R6CNu8Xmt6YK5IxOh0W7hT_A0PD5g68AIIZbUHQxrNbeyglU/s1600/S5_6.png" /></a></div><br /><br />
En este archivo pondremos la información del dialecto de la base de datos, indicaremos que no queremos que se muestre el <span class="codigo">SQL</span> generado por <span class="codigo">Hibernate</span>, y que la base de datos debe eliminarse ni actualizarse cada vez que hagamos una conexión. El archivo hasta ahora tiene el siguiente contenido:<br />
<pre><code>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- Dialecto de la base de datos -->
<property name="dialect">org.hibernate.dialect.MySQL5Dialect</property>
<!-- Otras propiedades importantes -->
<property name="show_sql">false</property>
<property name="hbm2ddl.auto">none</property>
</session-factory>
</hibernate-configuration>
</code></pre><br />
Lo siguiente dependerá de si estamos usando archivos de mapeo en <span class="codigo">XML</span> o anotaciones. En el primer caso debemos indicar dónde se encuentra el archivo de mapeo de la clase "<span class="codigo">Contacto</span>" (archivo que no hemos creado todavía). Supongamos que lo hemos puesto en un directorio especial para los mapeos, debajo del paquete de las entidades, entonces tendremos que usar el atributo "<span class="codigo">resource</span>" del elemento "<span class="codigo">mapping</span>" de la siguiente forma:<br />
<pre><code>
<mapping resource="com/javatutoriales/spring/integration/hibernate/entidades/mapeos/Contacto.hbm.xml"/>
</code></pre><br />
Si por el contrario, estuviéramos usando anotaciones, deberemos usar el atributo "<span class="codigo">class</span>" del elemento "<span class="codigo">mapping</span>", haciendo referencia a la clase "<span class="codigo">Contacto</span>", de esta forma:<br />
<pre><code>
<mapping class="com.javatutoriales.spring.integration.hibernate.entidades.Contacto"/>
</code></pre><br />
El archivo de configuración final de <span class="codigo">Hibernate</span> depende entonces de si usamos anotaciones o archivos de mapeo. El archivo de configuración de <span class="codigo">Spring</span> hasta ahora se ve de la siguiente forma:<br />
<pre><code>
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost/tutorial_hibernate" />
<property name="username" value="user" />
<property name="password" value="password" />
</bean>
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource">
<ref bean="dataSource"/>
</property>
<property name="configLocation">
<value>classpath:hibernate.cfg.xml</value>
</property>
<!-- mapping class="com.javatutoriales.spring.integration.hibernate.entidades.Contacto"/-->
<mapping resource="com/javatutoriales/spring/integration/hibernate/entidades/mapeos/Contacto.hbm.xml"/>
</bean>
</beans>
</code></pre><br />
Ahora, ¿qué pasa si no queremos poner esta información en el archivo de <span class="codigo">Hibernate</span>, sino directamente en el archivo de <span class="codigo">Spring</span>? Para eso podemos usar la propiedad "<span class="codigo">hibernateProperties</span>" del bean "<span class="codigo">sessionFactory</span>". En esta propiedad colocaremos un conjunto de, valga la redundancia, propiedades que indican los valores de las (si, es la tercera vez) propiedades de <span class="codigo">Hibernate</span>. Para reproducir lo que teníamos en el archivo de configuración de <span class="codigo">Hibernate</span>, debemos colocar las propiedades de esta forma:<br />
<pre><code>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop>
<prop key="hibernate.show_sql">true</prop>
</props>
</property>
</code></pre><br />
Los más observadores se habrán percatado de que aún no hemos colocado la información de en dónde se encuentran las entidades (o los archivos de mapeos de las mismas) que serán manejadas por <span class="codigo">Hibernate</span>. Para esto tenemos cuatro maneras, las primeras dos para cuando trabajamos con archivos de mapeo en <span class="codigo">XML</span>.<br /><br />
La primera de las formas consiste en indicar, a través de la propiedad "<span class="codigo">mappingResources</span>" del bean "<span class="codigo">sessionFactory</span>", donde se encuentra cada uno de los archivos de mapeo de nuestras entidades, a través de una lista de valores; de la siguiente forma:<br />
<pre><code>
<property name="mappingResources">
<list>
<value>com/javatutoriales/spring/integracion/mapeos/Contacto.hbm.xml</value>
</list>
</property>
</code></pre><br />
Esto está bien si tenemos pocas entidades pero en aplicaciones de tamaño mediano a grande este no será el caso, y no sólo tener colocar todos los archivos de mapeo puede ser tardado, sino que el tener que estar quitando los de las entidades que quitamos del diseño de nuestro sistema, o tener que recordar agregar los nuevos, puede ser una labor propensa a errores. Debido a esto les recomiendo el uso de otra propiedad del bean "<span class="codigo">sessionFactory</span>", llamada "<span class="codigo">mappingDirectoryLocations</span>" (la cual es la segunda forma de indicar dónde están nuestras entidades). En esta propiedad lo único que debemos hacer es indicar los lugares en los que se encuentran los archivos de mapeo, y digo lugares por si tenemos nuestros archivos de mapeo esparcidos por varios lugares de nuestra aplicación (lo cual no debería de pasar), la propiedad de configura de la siguiente forma:<br />
<pre><code>
<property name="mappingDirectoryLocations">
<list>
<value>classpath:com/javatutoriales/spring/integracion/mapeos/</value>
</list>
</property>
</code></pre><br />
Y la configuración completa de nuestro bean "<span class="codigo">sessionFactory</span>" se ve de la siguiente forma:<br />
<pre><code>
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource">
<ref bean="dataSource"/>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop>
<prop key="hibernate.show_sql">true</prop>
</props>
</property>
<property name="mappingDirectoryLocations">
<list>
<value>classpath:com/javatutoriales/spring/integracion/mapeos/</value>
</list>
</property>
</bean>
</code></pre><br />
La tercera forma de configurar la ubicación de nuestras entidades manejadas por <span class="codigo">Hibernate</span> es para cuando anotamos estas entidades. Podemos usar la propiedad "<span class="codigo">annotatedClasses</span>" para indicar cada una de las clases que están anotadas como entidades, de la siguiente manera:<br />
<pre><code>
<property name="annotatedClasses">
<list>
<value>com.javatutoriales.spring.integracion.entidades.Contacto</value>
</list>
</property>
</code></pre><br />
Aquí tendremos el mismo problema que cuando indicamos los archivos de mapeo uno por uno, por lo que les recomiendo que usen la cuarta forma, que es a través de la propiedad "<span class="codigo">packagesToScan</span>" del bean "<span class="codigo">sessionFactory</span>", en la cual indicamos la lista de los paquetes en los que se encuentran nuestras entidades, de la siguiente manera:<br />
<pre><code>
<property name="packagesToScan">
<list>
<value>com.javatutoriales.spring.integracion.entidades</value>
</list>
</property>
</code></pre><br />
Por lo que la configuración del bean "<span class="codigo">sessionFactory</span>" queda de la siguiente forma si trabajamos con anotaciones:<br />
<pre><code>
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource">
<ref bean="dataSource"/>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.hbm2ddl.auto">none</prop>
</props>
</property>
<property name="packagesToScan">
<list>
<value>com.javatutoriales.spring.integration.hibernate.entidades</value>
</list>
</property>
</bean>
</code></pre><br />
Y así si trabajamos con archivos de mapeo:<br />
<pre><code>
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource">
<ref bean="dataSource"/>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.hbm2ddl.auto">none</prop>
</props>
</property>
<property name="mappingDirectoryLocations">
<list>
<value>classpath:com/javatutoriales/spring/integration/hibernate/entidades/mapeos</value>
</list>
</property>
</bean>
</code></pre><br />
Ahora que tenemos la estructura básica para los ejemplos dividiremos este tutorial en dos partes. Recordemos que cada uno de estos frameworks tiene dos formas de ser configurados: con archivos de configuración en XML y con Anotaciones. En la primer parte del tutorial veremos sólo la parte de trabajo con archivos de configuración en XML, y en la segunda parte veremos cómo trabajar con anotaciones. Pero recuerden que pueden usar ambas formas de trabajo mezcladas a su conveniencia.<br /><br />
<br />
<h2 class="subtitulo">1.1 Integración usando archivos de configuración en XML</h2>Lo primero que haremos es crear el archivo de mapeo de <span class="codigo">Hibernate</span> para la entidad "<span class="codigo">Contacto</span>".<br /><br />
Crearemos un nuevo paquete llamado "<span class="codigo">mapeos</span>" debajo del paquete de entidades quedando el paquete "<span class="codigo">com.javatutoriales.spring.integration.hibernate.entidades.mapeos</span>". Dentro de este nuevo paquete creamos un nuevo archivo <span class="codigo">XML</span> llamado "<span class="codigo">Contacto.hbm</span>" (NetBeans agregará automáticamente la extensión <span class="codigo">.xml</span>):<br /><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjKDWWhUDHLQ7zErfkr0MZp9PfH_rKrpKCoxk_kMmWHfl_5sDcbYjiB1UZH0D5KoWg3SlZeJdverWgg-aQkV5VbIkdYQ16cwjMJkBGHuShfs26hLbi3fqNWxiLRdyhbN-EBF5kk7T52AWUk/s1600/S5_7.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjKDWWhUDHLQ7zErfkr0MZp9PfH_rKrpKCoxk_kMmWHfl_5sDcbYjiB1UZH0D5KoWg3SlZeJdverWgg-aQkV5VbIkdYQ16cwjMJkBGHuShfs26hLbi3fqNWxiLRdyhbN-EBF5kk7T52AWUk/s1600/S5_7.png" /></a></div><br /><br />
Este mapeo será igual al que tenemos en el tutorial de <span class="codigo">Hibernate</span>:<br />
<pre><code>
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.javatutoriales.spring.integration.hibernate.entidades.Contacto" table="CONTACTOS">
<id name="id" column="ID">
<generator class="identity" />
</id>
<property name="nombre" type="string" column="NOMBRE" />
<property name="email" />
<property name="telefono" />
</class>
</hibernate-mapping>
</code></pre><br />
Ahora debemos colocar en el archivo de configuración de <span class="codigo">Spring</span> un bean para poder obtener una instancia de nuestra clase "<span class="codigo">ContactosDAOHibernateImpl</span>":<br />
<pre><code>
<bean id="contactoDAO" class="com.javatutoriales.spring.integration.hibernate.persistencia.ContactosDAOHibernateImpl" />
</code></pre><br />
Como este bean hace uso de una instancia de "<span class="codigo">SessionFactory</span>" para el manejo de persistencia (y lo recibe a través del <span class="codigo">getter</span> que le colocamos) debemos pasarle la instancia del bean "<span class="codigo">sessionFactory</span>" usando el elemento "<span class="codigo">property</span>":<br />
<pre><code>
<bean id="contactoDAO" class="com.javatutoriales.spring.integration.hibernate.persistencia.ContactosDAOHibernateImpl">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
</code></pre><br />
El último paso es colocar en nuestra clase "<span class="codigo">Main</span>" el código para obtener el bean "<span class="codigo">ContactoDAO</span>" y hacer uso de este para guardar y recuperar un <span class="codigo">Contacto</span> de la base de datos. Para obtener los beans de <span class="codigo">Spring</span> haremos uso de la interface "<span class="codigo">ApplicationContext</span>" y de la clase "<span class="codigo">ClassPathXmlApplicationContext</span>" como vimos en <a href="http://www.javatutoriales.com/2010/12/contenedores-de-ioc-e-inyeccion-de.html">el segundo tutorial de <span class="codigo">Spring</span></a>. El resto del código es bastante simple, sólo usamos los métodos "<span class="codigo">guardaContacto</span>" y "<span class="codigo">obtenContacto</span>" para guardar y recuperar una instancia de <span class="codigo">Contacto</span> y luego sólo imprimimos el <span class="codigo">nombre</span> y el <span class="codigo">email</span> del <span class="codigo">Contacto</span> en la consola:<br />
<pre><code>
public static void main(String[] args) throws Exception {
ApplicationContext appContext = new ClassPathXmlApplicationContext("applicationContext.xml");
ContactosDAO contactosDAO = appContext.getBean(ContactosDAO.class);
long idContactoGuardado = contactosDAO.guardaContacto(new Contacto("Nombre 1", "Email 1", "Telefono 1"));
Contacto contactoGuardado = contactosDAO.obtenContacto(idContactoGuardado);
System.out.println("Nombre: " + contactoGuardado.getNombre());
System.out.println("Email: " + contactoGuardado.getEmail());
}
</code></pre><br />
Al ejecutar el código debemos ver la siguiente salida en consola (después de quitar algunos de los mensajes de <span class="codigo">Hibernate</span>):<br />
<pre><code>
Hibernate: insert into CONTACTOS (NOMBRE, email, telefono) values (?, ?, ?)
Hibernate: select contacto0_.ID as ID1_0_0_, contacto0_.NOMBRE as NOMBRE2_0_0_, contacto0_.email as email3_0_0_, contacto0_.telefono as telefono4_0_0_ from CONTACTOS contacto0_ where contacto0_.ID=?
Nombre: Nombre 1
Email: Email 1
</code></pre><br />
Con esto comprobamos que la aplicación funciona correctamente. Ahora veamos cómo hacer lo mismo mediante anotaciones.<br /><br />
<br />
<h2 class="subtitulo">1.2 Integración usando Anotaciones</h2>En este caso hay que colocar las anotaciones de <span class="codigo">Hibernate</span> en la entidad <span class="codigo">Contacto</span>. Nuevamente, estas anotaciones serán las mismas que vimos en <a href="http://www.javatutoriales.com/2009/05/hibernate-parte-2-persistiendo-objetos.html">el segundo tutorial de <span class="codigo">Hibernate</span></a>:<br />
<pre><code>
@Entity
@Table(name="contacto")
public class Contacto implements Serializable
{
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private long id;
private String nombre;
@Column(name="e_mail")
private String email;
private String telefono;
public Contacto()
{
}
public Contacto(String nombre, String email, String telefono)
{
this.nombre = nombre;
this.email = email;
this.telefono = telefono;
}
public String getEmail()
{
return email;
}
public void setEmail(String email)
{
this.email = email;
}
public long getId()
{
return id;
}
private void setId(long id)
{
this.id = id;
}
public String getNombre()
{
return nombre;
}
public void setNombre(String nombre)
{
this.nombre = nombre;
}
public String getTelefono()
{
return telefono;
}
public void setTelefono(String telefono)
{
this.telefono = telefono;
}
}
</code></pre><br />
Ahora hay que agregar un par de anotaciones en la clase "<span class="codigo">ContactosDAOHibernateImpl</span>". La primera es para indicarle a <span class="codigo">Spring</span> que esta será una clase que debe manejar como un bean y que el propósito del bean es realizar acceso a datos, para esto usamos la anotación "<span class="codigo">@Repository</span>" del paquete "<span class="codigo">org.springframework.stereotype</span>" a nivel de clase:<br />
<pre><code>
@Repository
public class ContactosDAOHibernateImpl implements ContactosDAO {
...
}
</code></pre><br />
Adicionalmente hay que colocar la anotación "<span class="codigo">@Autowired</span>" en el atributo de tipo "<span class="codigo">SessionFactory</span>" para indicar que este atributo deberá ser inyectado automáticamente por <span class="codigo">Spring</span>:<br />
<pre><code>
@Autowired
private SessionFactory sessionFactory;
</code></pre><br />
El último paso es indicar en al archivo de configuración de <span class="codigo">Spring</span> que debe realizar la configuración mediante anotaciones y en qué paquetes se encuentran las clases en las que debe buscar anotaciones. Esto lo hacemos con los siguiente elementos en el archivo "<span class="codigo">applicationContext</span>":<br />
<pre><code>
<context:annotation-config/>
<context:component-scan base-package="com.javatutoriales.spring.integration.hibernate.persistencia" />
</code></pre><br />
Ahora solo falta agregar el mismo códigode antes en nuestra clase <span class="codigo">Main</span> si no lo hemos hecho aún: <br />
<pre><code>
public static void main(String[] args) throws Exception {
ApplicationContext appContext = new ClassPathXmlApplicationContext("applicationContext.xml");
ContactosDAO contactosDAO = appContext.getBean(ContactosDAO.class);
long idContactoGuardado = contactosDAO.guardaContacto(new Contacto("Nombre 1", "Email 1", "Telefono 1"));
Contacto contactoGuardado = contactosDAO.obtenContacto(idContactoGuardado);
System.out.println("Nombre: " + contactoGuardado.getNombre());
System.out.println("Email: " + contactoGuardado.getEmail());
}
</code></pre><br />
Y si todo está bien al ejecutar nuestra aplicación debemos la siguiente salida en la consola:<br />
<pre><code>
Hibernate: insert into contactos (e_mail, nombre, telefono) values (?, ?, ?)
Hibernate: select contacto0_.id as id1_0_0_, contacto0_.e_mail as e_mail2_0_0_, contacto0_.nombre as nombre3_0_0_, contacto0_.telefono as telefono4_0_0_ from contactos contacto0_ where contacto0_.id=?
Nombre: Nombre 1
Email: Email 1
</code></pre><br />
Con lo que comprobamos que todo ha funcionado correctamente. El siguiente paso ahora que ya logramos la conexión con la base de datos es manejar las transacciones de la aplicación desde <span class="codigo">Spring</span> y no desde <span class="codigo">Hibernate</span> como lo hemos estado haciendo.<br /><br />
<br />
<h2 class="titulo">2. Manejo de transacciones con <span class="codigo">Spring</span></h2>Tradicionalmente las transacciones son la parte más "complicada" y obscura de manejar cuando trabajamos con base de datos y sin embargo es la más importante para mantener la consistencia de información y a la que menos atención le ponemos. Recordemos que una transacción es una secuencia de acciones que son tratadas como una única unidad de trabajo. Estas acciones deben ser ejecutarse de una forma "<span class="negritas">todo o nada</span>", eso quiere decir que todas las acciones dentro de la transacción deben ser exitosas o ninguna debe de serlo. De esta forma se asegura la integridad y consistencia de los datos.<br /><br />
El concepto de transacciones puede ser descrito con las siguientes cuatro propiedades conocidas como <span class="codigo">ACID</span>, por sus siglas en inglés:<br />
<ul><li><span class="negritas">Atomicity</span>: Una transacción debe ser tratada como una sola operación u unidad de trabajo, lo que significa que toda la secuencia de operaciones debe ser exitosa o ninguna debe de serlo.</li>
<li><span class="negritas">Consistency</span>: Representa la consistencia o integridad referencial de la base de datos, llaves primarias únicas en trablas, etc.</li>
<li><span class="negritas">Isolation</span>: Puede haber muchas transacciones procesándose dentro del mismo conjunto de datos al mismo tiempo, cada transacción debe ser aislada de otras para prevenir corrupción de datos.</li>
<li><span class="negritas">Durability</span>: Una vez que una transacción se ha completado, el resultado de esta transacción debe ser hecha de forma permanente y no debe ser borrada de la base de datos aunque el sistema falle.</li>
</ul><br />
El manejo de las transacciones a la base de datos es una de las razones principales para usar <span class="codigo">Spring</span>. El framework de <span class="codigo">Spring</span> proporciona una capa de abstracción consistente para el manejo de transacciones y agrega los siguientes beneficios:<br />
<ul><li>Modelo consistente de programación a través de distintas APIs como Java Transaction API (<span class="codigo">JTA</span>), <span class="codigo">JDBC</span>, <span class="codigo">Hibernate</span>, Java Persistence API (<span class="codigo">JPA</span>), y Java Data Objects (<span class="codigo">JDO</span>).</li>
<li>Soporte para manejo declarativo de transacciones.</li>
<li>Soporte para manejo programático de transacciones.</li>
<li>Excelente integración con la abstracción de datos de <span class="codigo">Spring</span> (lo que hemos visto en este tutorial).</li>
</ul><br />
La idea de manejar transacciones con <span class="codigo">Spring</span> es que lo haremos de manera similar sin importar cuál mecanismo de transacciones usemos. Además la idea es que sea una alternativa a las transacciones <span class="codigo">EJB</span> y agregar capacidades transaccionales a los <span class="codigo">POJO</span>s.<br /><br />
Antes de comenzar con el código debemos entender un concepto que es la propagación de la transacción.<br /><br />
Cuando se trabaja con transacciones manejadas por <span class="codigo">Spring</span> el desarrollador debe especificar cómo se debe comportar una transacción en términos de propagación, o sea debe decidir cómo deben encapsularse los métodos de negocio en cuanto a las transacciones lógicas y físicas. Los métodos en distintos beans pueden ejecutarse en el scope de la misma transacción o ser divididos en múltiples transacciones. Esto hace que se deba tomar en cuenta detalles como la forma en que la transacción interna afecta la transacción externa.<br /><br />
Existen siete comportamientos de propagación de transacciones:<br />
<ul><li><span class="codigo">REQUIRED</span></li>
<li><span class="codigo">REQUIRES_NEW</span></li>
<li><span class="codigo">NESTED</span></li>
<li><span class="codigo">MANDATORY</span></li>
<li><span class="codigo">NEVER</span></li>
<li><span class="codigo">NOT_SUPPORTED</span></li>
<li><span class="codigo">SUPPORTS</span></li>
</ul><h3 class="subtitulo">Propagación REQUIRED</h3>Indica que la misma transacción será usada si ya existe una transacción abierta en el contexto de ejecución del método actual del bean. Si no existe una transacción <span class="codigo">Spring</span> creará una nueva. Si múltiples métodos configurados como "<span class="codigo">REQUIRED</span>" son llamados de manera anidada cada uno será asignado a transacciones lógicas distintas pero a la misma transacción física. Esto significa que si un método interno causa el <span class="codigo">rollback</span> de una transacción, el método externo fallará al realizar un commit y también se le hará un <span class="codigo">rollback</span>.<br /><br />
<h3 class="subtitulo">Propagación REQUIRES_NEW</h3>Indica que siempre se necesitará una nueva transacción física en el contenedor. En otras palabras, la transacción interna puede hacer <span class="codigo">commit</span> o <span class="codigo">rollback</span> independientemente de la transacción externa, o sea que la transacción externa no será afectada por el resultado de la transacción interna ya que se ejecutan en distintas transacciones físicas.<br /><br />
<h3 class="subtitulo">Propagación NESTED</h3>Hace que las distintas transacciones usen la misma transacción física pero establece <span class="codigo">savepoints</span> entre las distintas invocaciones a métodos de tal forma que las transacciones internas pueden hacer <span class="codigo">rollback</span> de manera independiente a la transacción externa. Para esto se usan savepoints de <span class="codigo">JDBC</span> por lo que este comportamiento sólo debería ser usado cuando se usa <span class="codigo">Spring</span> con transacciones manejadas por <span class="codigo">JDBC</span>.<br /><br />
<h3 class="subtitulo">Propagación MANDATORY</h3>Indica que ya debe existir una transacción iniciada previamente para que el método pueda ser ejecutado. Si no existe una transacción se lanzará una excepción.<br /><br />
<h3 class="subtitulo">Propagación NEVER</h3>Indica que no debe existir una transacción previamente iniciada. Si una transacción existe se lanzará una excepción.<br /><br />
<h3 class="subtitulo">Propagación NOT_SUPPORTED</h3>Se ejecutará fuera del scope de cualquier transacción. Si ya existe una transacción abierta esta será pausada. <br /><br />
<h3 class="subtitulo">Propagación SUPPORTS</h3>Se ejecutará en el scope de la transacción si ya existe una abierta. Si no hay una transacción el método se ejecutará de todas formas pero de una forma no transaccional.<br /><br />
Dependiendo del tipo de operación que estemos realizando nos convendrá usar un comportamiento de propagación u otro.<br /><br />
Como ya es costumbre <span class="codigo">Spring</span> proporciona dos formas de indicar el manejo de transacciones, una mediante configuraciones en un archivo <span class="codigo">XML</span> y otra mediante código. Comenzaremos viendo la forma de declaración de transacciones en un archivo de configuración en <span class="codigo">XML</span>.<br /><br />
<br />
<h2 class="subtitulo">2.1. Declaración de Transacciones en archivos de mapeo XML</h2>Lo primero que debemos hacer es declarar el uso de un manejador de transacciones. Esta debe ser una implementación de la interface "<span class="codigo">PlatformTransactionManager</span>". Existen varias implementaciones concretas que podemos utilizar dependiendo del mecanismo de persistencia que usemos:<br />
<ul><li>"<span class="codigo">HibernateTransactionManager</span>", para cuando trabajamos con <span class="codigo">Hibernate</span> 3 o 4.</li>
<li>"<span class="codigo">DataSourceTransactionManager</span>", para cuando trabajamos con un <span class="codigo">DataSource JDBC</span>.</li>
<li>"<span class="codigo">JpaTransactionManager</span>", para cuando trabajamos con una implementación de <span class="codigo">JPA</span></li>
<li>"<span class="codigo">JdoTransactionManager</span>", para cuando trabajamos con <span class="codigo">JDO</span>.</li>
<li>"<span class="codigo">JtaTransactionManager</span>", para cuando usamos <span class="codigo">JTA</span> como mecanismo de manejo de transacciones.</li>
<li>"<span class="codigo">JmsTransactionManager</span>", para cuando realizamos transacciones en un servidor <span class="codigo">JMS</span>.</li>
<li>"<span class="codigo">CciLocalTransactionManager</span>", para cuando debemos sincronizar transacciones con un recurso <span class="codigo">CCI</span> (Common Client Interface).</li>
</ul><br />
Para este ejemplo usaremos el manejador de transacciones para <span class="codigo">Hibernate</span> 4 que se encuentra en el paquete "<span class="codigo">org.springframework.orm.hibernate4</span>" (si trabajan con <span class="codigo">Hibernate</span> 3 deben usar el del paquete correspondiente).<br /><br />
Debemos crear un bean que debe recibir como propiedad el bean de tipo "<span class="codigo">LocalSessionFactoryBean</span>" que creamos anteriormente con el nombre de "<span class="codigo">sessionFactory</span>", de esta forma indicamos que este bean debe manejar las transacciones que se realicen con ese <span class="codigo">sessionFactory</span>:<br />
<pre><code>
<bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
</code></pre><br />
<span class="codigo">Spring</span> tiene dos formas de marcar las transacciones, una programática que no veremos en estos tutoriales porque son invasivos en el código (y es lo que estamos tratando de evitar) y otra declarativa que es la manera preferida de trabajar con <span class="codigo">Spring</span> ya que sólo debemos indicar cómo deben ser manejadas las transacciones de nuestra aplicación y <span class="codigo">Spring</span> se encargará de que estas indicaciones se cumplan. Si en algún momento queremos que se manejen de forma distinta sólo es necesario cambiar la configuración sin necesidad de tocar nuevamente el código.<br /><br />
El manejo declarativo de transacciones en <span class="codigo">Spring</span> se hace posible mediante <a href="http://es.wikipedia.org/wiki/Programación_orientada_a_aspectos">Programación Orientada a Aspectos</a>. Con esto es posible especificar el comportamiento transaccional a nivel de método.<br /><br />
Cuando trabajamos con programación orientada a aspectos en <span class="codigo">Spring</span> debemos indicarlo mediante el namespace "<span class="codigo">aop</span>", este contiene elementos que nos ayudarán a marcar los puntos de corte, que son las definiciones de las firmas de los métodos en los que se aplicarán los advices (que en este caso es la indicación del manejo de transacciones).<br /><br />
Para comprender lo siguiente deben entender un poco de Programación Orientada a Aspectos, no es el tema de este tutorial, así que les recomiendo leer el siguiente documento <a href="http://computacion.cs.cinvestav.mx/~acaceres/courses/udo/poo/files/slides/POA-01.pdf">http://computacion.cs.cinvestav.mx/~acaceres/courses/udo/poo/files/slides/POA-01.pdf</a> (la imagen de la presentación es un poco… bueno ya lo verán, pero la información es muy buena ñ_ñ).<br /><br />
En <span class="codigo">Spring</span> el <span class="codigo">pointcut designator</span> que usamos con más frecuencia es "<span class="codigo">execution</span>", el cual se refiere a la ejecución de un método. <br /><br />
Con la teoría anterior espero que sea suficiente para entender lo que haremos a continuación.<br /><br />
Primero usaremos el elemento "<span class="codigo">config</span>" del namespace "<span class="codigo">aop</span>" para indicar que configuraremos un <span class="codigo">pointcut</span>:<br />
<pre><code>
<aop:config>
</aop:config>
</code></pre><br />
Dentro de este indicaremos los <span class="codigo">pointcuts</span> a los que afectaremos, nosotros queremos que sean todos los métodos, sin importar los parámetros que reciban, de todas las clases que se encuentren dentro del paquete "<span class="codigo">com.javatutoriales.spring.integracion.persistencia</span>", que es donde tenemos las clases para manejo de persistencia. Hasta ahora nuestro <span class="codigo">pointcut</span> se ve así:<br />
<pre><code>
com.javatutoriales.spring.integracion.persistencia.*.*(..)
</code></pre><br />
También debemos indicar que no nos importa el tipo de retorno que tenga el método, para esto usamos el comodín:<br />
<pre><code>
* com.javatutoriales.spring.integracion.persistencia.*.*(..)
</code></pre><br />
Finalmente debemos indicar un <span class="codigo">pointcut designator</span>, para indicar cuándo queremos que se aplique este aspecto cada vez que se ejecute el método indicado, para eso usamos el designador <span class="codigo">execution</span>. Nuestro <span class="codigo">pointcut</span> queda de esta forma:<br />
<pre><code>
execution(* com.javatutoriales.spring.integracion.persistencia.*.*(..))
</code></pre><br />
Ahora debemos indicar esto dentro del elemento "<span class="codigo">aop:config</span>". Para ello usamos el elemento "<span class="codigo">aop:pointcut</span>" al cual debemos, como a todo bean de <span class="codigo">Spring</span>, darle un identificador y debemos colocar la expresión anterior en al atributo "<span class="codigo">expression</span>" del elemento:<br />
<pre><code>
<aop:config>
<aop:pointcut id="pointcutsPersistencia" expression="execution(* com.javatutoriales.spring.integracion.persistencia.*.*(..))"/>
</aop:config>
</code></pre><br />
Dejaremos este elemento por el momento para continuar creando el advice, que es el bean que entrará en acción cuando se ejecute el <span class="codigo">endpoint</span> correspondiente. Para esto usaremos el namespace "<span class="codigo">tx</span>", con su elemento "<span class="codigo">advice</span>", a este debemos darle un identificador e indicar el manejador de transacciones que usará. En nuestro caso el identificador que le daremos será "<span class="codigo">adviceTransacciones</span>" y el manejador de transacciones será "<span class="codigo">transactionManager</span>", que es el bean de tipo "<span class="codigo">HibernateTransactionManager</span>" que definimos anteriormente:<br />
<pre><code>
<tx:advice id="adviceTransacciones" transaction-manager="transactionManager">
</tx:advice>
</code></pre><br />
Como un tip, si llamamos a nuestro manejador de transacciones "<span class="codigo">transactionManager</span>" podemos omitir el atributo "<span class="codigo">transaction-manager</span>" (si tiene cualquier otro nombre deberemos agregarlo) quedando de la siguiente manera:<br />
<pre><code>
<tx:advice id="adviceTransacciones">
</tx:advice>
</code></pre><br />
Lo siguiente que debemos hacer es indicar la forma en la que deben comportarse cada uno de los métodos de las clases que hemos indicado. Para eso usamos el elemento "<span class="codigo">tx:method</span>". Este elemento cuenta con varios atributos que nos permitirá, entre otras cosas, indicar el patrón de nombres de los métodos a los que se aplicará la configuración, el comportamiento de propagación que tendrá la transacción del método, el nivel de aislamiento, el timeout de la transacción, etc.<br /><br />
La siguiente tabla muestra los elementos que podemos configurar y sus valores por default.<br /><br />
<table><thead>
<tr><th>Atributo</th><th>Requerido</th><th>Valor por Default</th><th>Descripción</th></tr>
</thead>
<tbody>
<tr><td><span class="codigo">name</span></td><td>Si</td><td>-</td><td>El nombre del (los) método(s) a los que se asociarán los atributos de la transacción. El caracter comodín (<span class="codigo">*</span>) puede ser usado para asociar los mismos atributos de la transacción con varios métodos como <span class="codigo">obten*</span>, <span class="codigo">maneja*</span>, <span class="codigo">guarda*</span>, etc.</td></tr>
<tr><td><span class="codigo">propagation</span></td><td>No</td><td><span class="codigo">REQUIRED</span></td><td>El comportamiento de propagación de la transacción.</td></tr>
<tr><td><span class="codigo">isolation</span></td><td>No</td><td><span class="codigo">DEFAULT</span></td><td>El nivel de aislamiento de la transacción.</td></tr>
<tr><td><span class="codigo">timeout</span></td><td>No</td><td>-</td><td>El timeout para que la transacción se termine, en segundos.</td></tr>
<tr><td><span class="codigo">read-only</span></td><td>No</td><td><span class="codigo">False</span></td><td>Indica si la transacción será de sólo lectura.</td></tr>
<tr><td><span class="codigo">rollback-for</span></td><td>No</td><td>-</td><td>Excepciones que causarán un <span class="codigo">rollback</span> de la transacción (lo explicaremos más adelante).</td></tr>
<tr><td><span class="codigo">no-rollback-for</span></td><td>No</td><td>-</td><td>Excepciones que <span class="negritas">NO</span> causarán un <span class="codigo">rollback</span> de la transacción (lo explicaremos más adelante).</td></tr>
</tbody>
</table><br /><br />
El elemento "<span class="codigo">tx:method</span>" debe estar envuelto en un elemento "<span class="codigo">tx:attributes</span>", de la siguiente forma:<br />
<pre><code>
<tx:advice id="adviceTransacciones">
<tx:attributes>
</tx:attributes>
</tx:advice>
</code></pre><br />
Para nuestro ejemplo configuraremos varios métodos. Primero haremos que todos los métodos que inician con "<span class="codigo">obten</span>", que es la convención que estamos usando para los métodos que obtienen información de la base de datos. Queremos que estos métodos sean de sólo lectura, y no importa si existe una transacción o no en la base de datos, por lo que usaremos el comportamiento de propagación <span class="codigo">SUPPORTS</span>. El resto de los atributos pueden quedarse con sus valores por default.<br />
<pre><code>
<tx:method name="obten*" propagation="SUPPORTED" read-only="true" />
</code></pre><br />
Ahora configuraremos el resto de los métodos que son el método para agregar, eliminar y actualizar un <span class="codigo">Contacto</span>. El comportamiento de estos métodos será prácticamente igual, deben usar la transacción existente o iniciar una nueva si no existe y debe modificar la base de datos. En este caso podemos configurarlos usando un elemento "<span class="codigo">tx:method</span>" para cada uno, o usando el caracter comodín (esto dependerá de cuántos métodos más tengan sus clases, para el caso de este ejemplo es inofensivo pero deben tener cuidado con esto). La configuración para el resto de los métodos queda de la siguiente forma:<br />
<pre><code>
<tx:method name="*" propagation="REQUIRED" />
</code></pre><br />
Y como el valor por default para el atributo "<span class="codigo">propagation</span>" es "<span class="codigo">REQUIRED</span>" puede quedar sólo así:<br />
<pre><code>
<tx:method name="*" />
</code></pre><br />
Y la configuración completa del advice queda así:<br />
<pre><code>
<tx:advice id="adviceTransacciones">
<tx:attributes>
<tx:method name="obten*" propagation="SUPPORTS" read-only="true" />
<tx:method name="*" />
</tx:attributes>
</tx:advice>
</code></pre><br />
El último paso es regresar a la configuración de nuestro aspecto e indicar qué advice se aplicará a cuál pointcut, para esto usamos el elemento "<span class="codigo">aop:advisor</span>" dentro del elemento "<span class="codigo">aop:config</span>". Aquí indicaremos el advice usando el atributo "<span class="codigo">advice-ref</span>" y el pointcut con el atributo "<span class="codigo">pointcut-ref</span>", de la siguiente forma:<br />
<pre><code>
<aop:advisor advice-ref="adviceTransacciones" pointcut-ref="pointcutsPersistencia"/>
</code></pre><br />
La configuración completa del pointcut queda de la siguiente forma:<br />
<pre><code>
<aop:config>
<aop:pointcut id="pointcutsPersistencia" expression="execution(* com.javatutoriales.spring.integracion.persistencia.*.*(..))"/>
<aop:advisor advice-ref="adviceTransacciones" pointcut-ref="pointcutsPersistencia"/>
</aop:config>
</code></pre><br />
También debemos hacer un cambio en la clase "<span class="codigo">ContactosDAOHibernateImpl</span>". Ya que no estaremos manejando las transacciones en esta clase podemos quitar todo el código referente a estas. Eso quiere decir eliminar el atributo de tipo "<span class="codigo">Transaction</span>" que tiene y los lugares a donde se hace referencia (que son básicamente los lugares donde se hacen "<span class="codigo">commits</span>" y "<span class="codigo">rollbacks</span>") y los lugares donde se cierra la sesión y se inicia la transacción. Por lo tanto único que dejaremos es el inicio y uso de la sesión. La clase "<span class="codigo">ContactosDAOHibernateImpl</span>" queda de la siguiente forma:<br />
<pre><code>
public class ContactosDAOHibernateImpl implements ContactosDAO {
private Session sesion;
private SessionFactory sessionFactory;
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
@Override
public long guardaContacto(Contacto contacto) throws HibernateException {
long id = 0;
try {
iniciaOperacion();
id = (Long) sesion.save(contacto);
} catch (HibernateException he) {
throw he;
}
return id;
}
@Override
public void actualizaContacto(Contacto contacto) throws HibernateException {
try {
iniciaOperacion();
sesion.update(contacto);
} catch (HibernateException he) {
throw he;
}
}
@Override
public void eliminaContacto(Contacto contacto) throws HibernateException {
try {
iniciaOperacion();
sesion.delete(contacto);
} catch (HibernateException he) {
throw he;
}
}
@Override
public Contacto obtenContacto(long idContacto) throws HibernateException {
Contacto contacto = null;
iniciaOperacion();
contacto = (Contacto) sesion.get(Contacto.class, idContacto);
return contacto;
}
@Override
public List<Contacto> obtenListaContactos() throws HibernateException {
List<Contacto> listaContactos = null;
iniciaOperacion();
listaContactos = sesion.createQuery("from Contacto").list();
return listaContactos;
}
private void iniciaOperacion() throws HibernateException {
sesion = sessionFactory.getCurrentSession();
}
}
</code></pre><br />
Como vemos la clase queda mucho más corta y podemos eliminar el método "<span class="codigo">manejaExcepcion</span>".<br /><br />
Si ahora volvemos a ejecutar nuestra clase "<span class="codigo">Main</span>" debemos ver la siguiente salida en la consola:<br />
<pre><code>
Hibernate: insert into CONTACTOS (NOMBRE, email, telefono) values (?, ?, ?)
Hibernate: select contacto0_.ID as ID1_0_0_, contacto0_.NOMBRE as NOMBRE2_0_0_, contacto0_.email as email3_0_0_, contacto0_.telefono as telefono4_0_0_ from CONTACTOS contacto0_ where contacto0_.ID=?
Nombre: Nombre 1
Email: Email 1
</code></pre><br />
Que como podemos ver es la misma salida de la consola que la vez pasada, con esto comprobamos que la forma declarativa de transacciones funciona correctamente <span class="codigo">^_^</span>.<br /><br />
Ahora veremos la última parte de este tutorial en el que veremos cómo hacer en manejo de transacciones por medio de anotaciones.<br /><br />
<br />
<h2 class="subtitulo">2.2. Declaración de Transacciones en anotaciones</h2>Para trabajar con anotaciones hay que indicarlo por medio de un elemento en el archivo de configuración <span class="codigo">XML</span>:<br />
<pre><code>
<tx:annotation-driven />
</code></pre><br />
Este elemento indicará que las transacciones serán marcadas por medio de anotaciones en las clases que estén contenidas en el paquete indicado por el elemento "<span class="codigo"><context:component-scan></span>". En este elemento debemos indicar cuál es el manejador de transacciones que será usado; si el nombre de nuestro manejador de transacciones es "<span class="codigo">transactionManager</span>" podemos dejar el elemento "<span class="codigo"><tx:annotation-drive /></span>" vacío, de lo contrario debemos usar el atributo "<span class="codigo">transaction-manager</span>" para indicar cuál es el nombre del manejador de transacciones. Por ejemplo si el nombre de nuestro bean fuera "<span class="codigo">txManager</span>" la anotación quedaría:<br />
<pre><code>
<tx:annotation-driven transaction-manager="txManager" />
</code></pre><br />
El siguiente cambio necesario es colocar la anotación "<span class="codigo">@Transactional</span>" en los métodos de la clase "<span class="codigo">ContactosDAOHibernateImpl</span>". Debemos colocar esta anotación en cada uno de los métodos donde se deba manejar transacciones. Esta anotación puede tener los siguientes atributos, de los cuales ninguno es obligatorio:<br /><br />
<table><thead>
<tr><th>Atributo</th><th>Tipo</th><th>Valor por Default</th><th>Descripción</th></tr>
</thead>
<tbody>
<tr><td><span class="codigo">readOnly</span></td><td><span class="codigo">boolean</span></td><td><span class="codigo">false</span></td><td>Indica si la transacción será de sólo lectura.</td></tr>
<tr><td><span class="codigo">timeout</span></td><td><span class="codigo">int</span></td><td>El timeout del sistema de transacciones usado</td><td>El timeout para que la transacción se termine, en segundos.</td></tr>
<tr><td><span class="codigo">value</span></td><td><span class="codigo">String</span></td><td>-</td><td>Un calificador opcional que especifica el manejador de transacciones que se usará (en caso de que usemos varios manejadores de transacciones).</td></tr>
<tr><td><span class="codigo">propagation</span></td><td>Enum: <span class="codigo">Propagation</span></td><td><span class="codigo">PROPAGATION_REQUIRED</span></td><td>El comportamiento de propagación de la transacción.</td></tr>
<tr><td><span class="codigo">isolation</span></td><td>Enum: <span class="codigo">Isolation</span></td><td><span class="codigo">ISOLATION_DEFAULT</span></td><td>El nivel de aislamiento de la transacción.</td></tr>
<tr><td><span class="codigo">rollbackFor</span></td><td>Arreglo de objetos <span class="codigo">Class</span> que deben extender de <span class="codigo">Throwable</span></td><td>-</td><td>Excepciones que causarán un <span class="codigo">rollback</span> de la transacción (lo explicaremos más adelante).</td></tr>
<tr><td><span class="codigo">rollbackForClassName</span></td><td>Arreglo de <span class="codigo">String</span> con el nombre de las clases que deben extender de <span class="codigo">Throwbale</span></td><td>-</td><td>Nombres de las clases de Excepciones que causarán un <span class="codigo">rollback</span>.</td></tr>
<tr><td><span class="codigo">noRollbackFor</span></td><td>Arreglo de objetos <span class="codigo">Class</span> que deben extender de <span class="codigo">Throwable</span></td><td>-</td><td>Excepciones que <span class="negritas">NO</span> causarán un <span class="codigo">rollback</span> de la transacción (lo explicaremos más adelante).</td></tr>
<tr><td><span class="codigo">noRollbackForClassName</span></td><td>Arreglo de <span class="codigo">String</span> con el nombre de las clases que deben extender de <span class="codigo">Throwable</span></td><td>-</td><td>Arreglo de nombres que <span class="negritas">NO</span> causarán el <span class="codigo">rollback</span> de la transacción.</td></tr>
</tbody>
</table>Lo que debemos hacer son dos pasos simples. Primero colocar la anotación "<span class="codigo">@Transactional</span>" en cada uno de los métodos transaccionales de "<span class="codigo">ContactosDAOHibernateImpl</span>", que son:<br />
<ul><li><span class="codigo">guardaContacto</span></li>
<li><span class="codigo">actualizaContacto</span></li>
<li><span class="codigo">eliminaContacto</span></li>
<li><span class="codigo">obtenContacto</span></li>
<li><span class="codigo">obtenListaContactos</span></li>
</ul><br />
Esta anotación deberíamos de colocarla sólo en los métodos públicos, si la colocamos en otro tipo de métodos no obtendremos ninguna excepción pero tampoco funcionará <span class="codigo">^_^!</span><br /><br />
Debemos indicar también cuál será el comportamiento de nuestras transacciones y si estas serán de sólo lectura. <br /><br />
En nuestro caso los métodos que sólo obtienen información (los que inician con "<span class="codigo">obten</span>") son métodos de sólo lectura y con un comportamiento de propagación de tipo "<span class="codigo">SUPPORTS</span>", por lo que la anotación queda de la siguiente forma (recuerden que debemos usar la anotación "<span class="codigo">@Transactional</span>" del paquete "<span class="codigo">org.springframework.transaction.annotation</span>"):<br />
<pre><code>
@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
public Contacto obtenContacto(long idContacto) throws HibernateException
@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
public List<Contacto> obtenListaContactos() throws HibernateException
</code></pre><br />
El resto de los métodos usan los valores por default de la anotación, por lo que pueden quedar de la siguiente forma:<br />
<pre><code>
@Transactional
public long guardaContacto(Contacto contacto) throws HibernateException
@Transactional
public void actualizaContacto(Contacto contacto) throws HibernateException
@Transactional
public void eliminaContacto(Contacto contacto) throws HibernateException
</code></pre><br />
El segundo paso es, al igual que lo hicimos al trabajar con archivos de mapeo, quitar el atributo de tipo "<span class="codigo">Transaction</span>", junto con sus usos dentro de la clase, y los lugares donde se hace commit, <span class="codigo">rollback</span> o se cierra la sesión, ya que <span class="codigo">Spring</span> se encargará ahora de eso. <br /><br />
La clase "<span class="codigo">ContactosDAOHibernateImpl</span>" queda de la siguiente forma:<br />
<pre><code>
@Repository
public class ContactosDAOHibernateImpl implements ContactosDAO {
private Session sesion;
@Autowired
private SessionFactory sessionFactory;
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
@Override
@Transactional
public long guardaContacto(Contacto contacto) throws HibernateException {
long id = 0;
try {
iniciaOperacion();
id = (Long) sesion.save(contacto);
} catch (HibernateException he) {
throw he;
}
return id;
}
@Override
@Transactional
public void actualizaContacto(Contacto contacto) throws HibernateException {
try {
iniciaOperacion();
sesion.update(contacto);
} catch (HibernateException he) {
throw he;
}
}
@Override
@Transactional
public void eliminaContacto(Contacto contacto) throws HibernateException {
try {
iniciaOperacion();
sesion.delete(contacto);
} catch (HibernateException he) {
throw he;
}
}
@Override
@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
public Contacto obtenContacto(long idContacto) throws HibernateException {
Contacto contacto = null;
iniciaOperacion();
contacto = (Contacto) sesion.get(Contacto.class, idContacto);
return contacto;
}
@Override
@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
public List<Contacto> obtenListaContactos() throws HibernateException {
List<Contacto> listaContactos = null;
iniciaOperacion();
listaContactos = sesion.createQuery("from Contacto").list();
return listaContactos;
}
private void iniciaOperacion() throws HibernateException {
sesion = sessionFactory.getCurrentSession();
}
}
</code></pre>Si volvemos a ejecutar nuestra clase "<span class="codigo">Main</span>" obtendremos la siguiente salida en consola:<br />
<pre><code>
Hibernate: insert into contactos (e_mail, nombre, telefono) values (?, ?, ?)
Hibernate: select contacto0_.id as id1_0_0_, contacto0_.e_mail as e_mail2_0_0_, contacto0_.nombre as nombre3_0_0_, contacto0_.telefono as telefono4_0_0_ from contactos contacto0_ where contacto0_.id=?
Nombre: Nombre 1
Email: Email 1
</code></pre><br />
Con lo que comprobamos que la aplicación funciona correctamente.<br /><br />
Para terminar este tutorial veremos cómo manejar las excepciones para hacer <span class="codigo">rollbacks</span> de forma automática desde <span class="codigo">Spring</span>.<br /><br />
<br />
<h2 class="titulo">3. Manejo de Excepciones para Rollback Automático</h2>Una parte importante del trabajo con base de datos es que cuando ocurra algún problema con nuestra aplicación las operaciones dentro de estas se deshagan de forma automática (o sea que ocurra un <span class="codigo">rollback</span>). <br /><br />
Por default <span class="codigo">Spring</span> hace un <span class="codigo">rollback</span> de toda excepción de tipo <span class="codigo">Runtime</span> y de las clases que extienden de <span class="codigo">Error</span>. Las excepciones verificadas que sean lanzadas desde un método transaccional no resultarán en un <span class="codigo">rollback</span> a menos que lo indiquemos de forma explícita en configuración.<br /><br />
Veamos un ejemplo de esto. Modificaremos un poco la clase "<span class="codigo">ContactosDAOHibernateImpl</span>" para hacer que falle después de guardar un <span class="codigo">Contacto</span>. Agregaremos la siguiente línea de código para que se lance una "<span class="codigo">IllegalStateException</span>" (que extiende de <span class="codigo">RuntimeException</span>) justo después de guardar el <span class="codigo">Contacto</span>:<br />
<pre><code>
if(true)
{
throw new IllegalStateException("oops");
}
</code></pre><br />
El método queda de la siguiente forma:<br />
<pre><code>
public long guardaContacto(Contacto contacto) throws HibernateException {
long id = 0;
try {
iniciaOperacion();
id = (Long) sesion.save(contacto);
if(true)
{
throw new IllegalStateException("oops este contacto no se guardará");
}
} catch (HibernateException he) {
throw he;
}
return id;
}
</code></pre><br />
Si limpiamos nuestra base de datos y ejecutamos nuevamente nuestra clase "<span class="codigo">Main</span>" veremos la siguiente salida:<br />
<pre><code>
Hibernate: insert into contactos (e_mail, nombre, telefono) values (?, ?, ?)
Exception in thread "main" java.lang.IllegalStateException: oops este contacto no se guardará
</code></pre><br />
Como vemos la excepción de lanza y si revisamos nuevamente nuestra base de datos veremos que esta continúa vacía. Hasta aquí todo normal.<br /><br />
Ahora veremos que este comportamiento cambia cuando se lanza una excepción verificada. Primero crearemos un nuevo paquete para excepciones: "<span class="codigo">com.javatutoriales.spring.integration.hibernate.excepciones</span>". Dentro de este paquete crearemos una nueva clase llamada "<span class="codigo">DatabaseException</span>" que extienda de "<span class="codigo">Exception</span>":<br />
<pre><code>
public class DatabaseException extends Exception {
}
</code></pre><br />
Esta clase tendrá un constructor que reciba una cadena que será el mensaje que se mostrará en la consola cuando se muestre el stacke trace de la excepción. La clase "<span class="codigo">DatabaseException</span>" completa queda de la siguiente forma:<br />
<pre><code>
public class DatabaseException extends Exception {
public DatabaseException(String string) {
super(string);
}
}
</code></pre><br />
Ahora modificaremos la interface "<span class="codigo">ContactosDAO</span>" para hacer que el método "<span class="codigo">guardaContacto</span>" lance una "<span class="codigo">DatabaseException</span>" en vez de la "<span class="codigo">HibernateException</span>" que lanza actualmente:<br />
<pre><code>
long guardaContacto(Contacto contacto) throws DatabaseException;
</code></pre><br />
Con este cambio también será necesario cambiar la firma del método en la clase "<span class="codigo">ContactosDAOHibernateImpl</span>" para que también lance la "<span class="codigo">DatabaseException</span>"; modificaremos el cuerpo del método para lanzar una "<span class="codigo">DatabaseException</span>" en vez de la "<span class="codigo">IllegalStateException</span>":<br />
<pre><code>
public long guardaContacto(Contacto contacto) throws DatabaseException {
long id = 0;
try {
iniciaOperacion();
id = (Long) sesion.save(contacto);
if(true)
{
throw new DatabaseException("Este contacto se guardará");
}
} catch (HibernateException he) {
throw he;
}
return id;
}
</code></pre><br />
Si volvemos a ejecutar nuestra aplicación veremos la siguiente salida en la consola:<br />
<pre><code>
Hibernate: insert into contactos (e_mail, nombre, telefono) values (?, ?, ?)
Exception in thread "main" com.javatutoriales.spring.integration.hibernate.excepciones.DatabaseException: Este contacto se guardará
</code></pre><br />
Como vemos se vuelve a lanzar la excepción pero si revisamos la base de datos veremos que en este caso el <span class="codigo">Contacto</span> si fue almacenado. Con esto podemos comprobar que, efectivamente, el <span class="codigo">rollback</span> ocurre cuando se lanza una excepción de tipo <span class="codigo">Runtime</span>. <br /><br />
<span class="codigo">Spring</span> nos permite definir cuáles excepciones causarán que ocurra un <span class="codigo">rollback</span> y cuáles no, con esto podríamos definir que cuando se lance una "<span class="codigo">DatabaseException</span>" ocurra un <span class="codigo">rollback</span> y cuando se lance una supuesta "<span class="codigo">DataNotFoundException</span>" no ocurra el <span class="codigo">rollback</span>. O de forma similar, podríamos definir que cuando ocurra una "<span class="codigo">IllegalStateException</span>" no curra un <span class="codigo">rollback</span> pero sí cuando ocurra una "<span class="codigo">IllegalArgumentException</span>".<br /><br />
Hacer esto es sumamente simple y depende de cómo estemos trabajando. Si hacemos uso de archivos de configuración en <span class="codigo">XML</span> debemos usar el atributo "<span class="codigo"><span class="codigo">rollback</span>-for</span>" del elemento "<span class="codigo"><tx:method></span>" en el archivo de configuración de <span class="codigo">Spring</span> para indicar las excepciones que causarán un <span class="codigo">rollback</span> y "<span class="codigo">no-<span class="codigo">rollback</span>-for</span>" para las excepciones que NO causarán un <span class="codigo">rollback</span>. El nombre de la clase puede ser el fully qualified class name o el nombre simple siempre y cuando no haya un conflicto con el nombre de otra clase. Si vamos a especificar más de una clase de cualquiera de los dos atributos debemos separarlos por coma. <br /><br />
Por ejemplo, supongamos que queremos indicar que se debe hacer <span class="codigo">rollback</span> para "<span class="codigo">DatabaseException</span>" y que no se debe hacer para "<span class="codigo">IllegalStateException</span>" y "<span class="codigo">IllegalArgumentException</span>", la configuración del advice quedaría de la siguiente forma:<br />
<pre><code>
<tx:advice id="adviceTransacciones">
<tx:attributes>
<tx:method name="obten*" propagation="SUPPORTS" read-only="true" <span class="codigo">rollback</span>-for="DatabaseException" no-<span class="codigo">rollback</span>-for="IllegalStateException, IllegalArgumentException" />
<tx:method name="*" <span class="codigo">rollback</span>-for="DatabaseException" no-<span class="codigo">rollback</span>-for="IllegalStateException, IllegalArgumentException" />
</tx:attributes>
</tx:advice>
</code></pre><br />
Para el caso de las anotaciones hay que usar el atributo "<span class="codigo">rollbackFor</span>" para las excepciones que deben causar un <span class="codigo">rollback</span> y "<span class="codigo">noRollbackFor</span>" para las excepciones que no deben causar un <span class="codigo">rollback</span>. En ambos casos se debe usar un arreglo de objetos class (existe otra versión que usa cadenas con el nombre de la clase, pero para este ejemplo es más fácil usar estos atributos). Si vamos a indicar sólo una clase podemos usar un objeto <span class="codigo">Class</span> simple; si vamos a indicar varias clases debemos indicar un arreglo de objetos <span class="codigo">Class</span>, los cuales marcamos entre corchetes. La configuración para el método "<span class="codigo">guardaContacto</span>" haciendo uso de anotaciones quedaría de la siguiente forma:<br />
<pre><code>
@Transactional(rollbackFor = DatabaseException.class, noRollbackFor = {IllegalStateException.class, IllegalArgumentException.class})
</code></pre><br />
Al ejecutar nuevamente la aplicación debemos ver que el <span class="codigo">Contacto</span> ya no se almacena en la base de datos gracias a que se hace un <span class="codigo">rollback</span> de la operación.<br /><br />
Podemos combinar ambos métodos cuando desarrollamos una aplicación para, por ejemplo, indicar mediante el archivo de configuración el comportamiento que deben tener todas las clases en la capa de persistencia y posteriormente anotar en la capa de lógica de la aplicación los métodos que deban ejecutarse de forma atómica, indicando que si ocurre una excepción en cualquiera de los distintos métodos de la capa de persistencia que sean llamados debe hacerse un <span class="codigo">rollback</span> de todas las operaciones dejando consistente toda nuestra información.<br /><br />
Como podemos ver, integrar <span class="codigo">Hibernate</span> a nuestras aplicaciones hechas con <span class="codigo">Spring</span> es simple, sólo hay que saber qué configurar y tener claro el comportamiento que deben tener nuestras transacciones, las cuales ya hemos visto que también podemos indicar de varias formas. Recuerden que también podemos trabajar mezclando anotaciones y la configuración en el archivo de <span class="codigo">Spring</span>. <br /><br />
Espero que este tutorial les sea de utilidad. Si tienen alguna duda, sugerencia, comentario o aclaración, pueden dejarla en la sección de comentarios o enviar un correo a <a href="mailto:programadorjavablog@gmail.com">programadorjavablog@gmail.com</a> (pueden agregarme al chat de gmail). También pueden seguir <span class="codigo">JavaTutoriales</span> en las siguientes redes sociales:<br /><br />
<ul><li><a href="http://www.facebook.com/JavaTutoriales"><span class="codigo">Facebook</span></a></li>
<li><a href="https://plus.google.com/u/0/114078943018704761597"><span class="codigo">Google+</span></a></li>
<li><a href="https://twitter.com/#!/JavaTutoriales"><span class="codigo">Twitter</span></a></li>
</ul><br />
Saludos y gracias.<br /><br />
<span class="negritas">Descarga los archivos de este tutorial desde aquí:</span><br /><br />
<ul><li><a href="https://sites.google.com/site/javatutoriales/spring/SpringHibernateIntegracion.zip?attredirects=0&d=1"><span class="ligaArchivo">Integración Spring-Hibernate con XML</span></a></li>
<li><a href="https://sites.google.com/site/javatutoriales/spring/SpringHibernateIntegracionAnotaciones.zip?attredirects=0&d=1"><span class="ligaArchivo">Integración Spring-Hibernate con anotaciones</span></a></li>
</ul><br />
<span class="negritas">Entradas Relacionadas:</span><br /><br />
<ul><li><a href="http://www.javatutoriales.com/2010/09/spring-parte-1-introduccion.html">Parte 1: Introducción</a></li>
<li><a href="http://javatutoriales.blogspot.com/2010/12/contenedores-de-ioc-e-inyeccion-de.html">Parte 2: Contenedores de IoC e Inyección de Dependencias</a></li>
<li><a href="http://javatutoriales.blogspot.com/2011/01/inyeccion-de-colecciones.html">Parte 3: Inyección de Colecciones</a></li>
<li><a href="http://www.javatutoriales.com/2011/02/spring-3-parte-4-ciclo-de-vida-de-los.html">Parte 4: Ciclo de Vida de los Beans</a></li>
</ul></div>Alexhttp://www.blogger.com/profile/06974037481671868076noreply@blogger.comtag:blogger.com,1999:blog-2012232069289957092.post-81479809609520254262013-11-29T14:07:00.001-08:002013-11-29T16:10:28.215-08:00Struts 2 - Parte 7: Tags<div style="text-align: justify;">Cuando desarrollamos aplicaciones es importante tener un buen <span class="codigo">back-end</span> que soporte la lógica de la aplicación, y del mismo modo es importante tener un buen <span class="codigo">front-end</span>, ya que será esta con la que nuestros usuarios interactuarán introduciendo y viendo información. Es por esto que para crear la vista (las páginas web) debemos usar un conjunto de elementos que nos permitan mostrar la información correcta, darle el formato adecuado, y realizar pequeños bloques de lógica en caso de ser necesario (ciclos, condiciones, constantes, etc.).<br />
<br />
En el mundo web de <span class="codigo">Java</span> estos componentes generalmente son implementados en forma de etiquetas (<span class="codigo">tags</span>). Prácticamente todos los frameworks web de <span class="codigo">Java</span> incluyen su propio conjunto de etiquetas que se integran perfectamente con los motores de negocio, para hacer uso de las ventajas particulares del framework. <span class="codigo">Struts 2</span> no es la excepción, y presenta un conjunto amplio de etiquetas para manejar la información que será presentada al cliente, y además ayudar a dar formato a los componentes de la interfaz de usuario, dándonos la libertad de elegir entre varios "modos".<br />
<br />
En este tutorial aprenderemos a usar todas las etiquetas que <span class="codigo">Struts 2</span> nos proporciona actualmente, junto con algunos detalles de cada uno de los temas o layouts que nos proporciona.<br />
<br />
<a name='more'></a><br />
<span class="codigo">Struts 2</span> divide sus etiquetas más importantes en 4 categorías:<br />
<ul><li><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#control">De <span class="negritas">control</span></a></li>
<li><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#datos">De <span class="negritas">datos</span></a></li>
<li><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#formulario">De <span class="negritas">formulario</span></a></li>
<li><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#noformulario"><span class="negritas">UI de no formulario</span></a></li>
</ul><br />
Lo primero que haremos es crear un nuevo proyecto en NetBeans. Vamos al menú "<span class="codigo">File -> New Project...</span>". En la ventana que aparece seleccionamos la categoría "<span class="codigo">Java Web</span>" y en el tipo de proyecto "<span class="codigo">Web Application</span>". Presionamos el botón "<span class="codigo">Next ></span>" y le damos un nombre y una ubicación a nuestro proyecto; presionamos nuevamente el botón "<span class="codigo">Next ></span>" y en este punto se nos preguntará el servidor que queremos usar. En nuestro caso usaremos el servidor "<span class="codigo">Tomcat 7.0</span>", con la versión 5 de <span class="codigo">JEE</span>. También podemos cambiar el context path de la aplicación web, yo la cambiaré a "<span class="codigo">/tags</span>" para que sea más fácil escribir la dirección en el navegador. Presionamos el botón "<span class="codigo">Finish</span>".<br />
<br />
Una vez que tengamos nuestro proyecto debemos recordar agregar la biblioteca "<span class="codigo">Struts2</span>" (o "<span class="codigo">Struts2Anotaciones</span>" si van a hacer uso de anotaciones, como es mi caso <span class="codigo">^_^</span>), que creamos en <a href="http://www.javatutoriales.com/2011/06/struts-2-parte-1-configuracion.html">el primer tutorial de la serie de <span class="codigo">Struts 2</span></a>. Hacemos clic derecho sobre el nodo "<span class="codigo">Libraries</span>" del proyecto. En el menú que aparece seleccionamos la opción "<span class="codigo">Add Library...</span>". En la ventana que aparece seleccionamos la biblioteca "<span class="codigo">Struts2</span>" o "<span class="codigo">Struts2Anotaciones</span>" y presionamos "<span class="codigo">Add Library</span>". Con esto ya tendremos los jars de <span class="codigo">Struts 2</span> en nuestro proyecto.<br />
<br />
Ahora configuramos el filtro "<span class="codigo">struts2</span>" en el deployment descriptor. Abrimos el archivo "<span class="codigo">web.xml</span>" y colocamos el siguiente contenido, como se explicó en el primer tutorial de la serie:<br />
<pre><code>
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</code></pre><br />
Creamos también un modelo sencillo de datos para tener la información que se mostrará. Haremos uso de dos clases muy sencillas, "<span class="codigo">Usuario</span>" y "<span class="codigo">Direccion</span>". Primero creamos dos nuevos paquetes, el primero llamado "<span class="codigo">com.javatutoriales.struts2.tags.actions</span>", que de momento quedará vacío, y uno segundo llamado "<span class="codigo">com.javatutoriales.struts2.tags.modelo</span>". Dentro de este paquete creamos primero la clase "<span class="codigo">Direccion</span>" la cual tendrá sólo 2 campos:<br />
<ul><li>"<span class="codigo">calle</span>" de tipo <span class="codigo">String</span></li>
<li>"<span class="codigo">codigoPostal</span>" de tipo <span class="codigo">String</span></li>
</ul>Junto con un par de constructores y los <span class="codigo">getters</span> y <span class="codigo">setters</span> de las propiedades anteriores.<br />
<br />
El código completo de la clase "<span class="codigo">Direccion</span>" queda de la siguiente forma:<br />
<pre><code>
public class Direccion implements Serializable {
private String calle;
private String codigoPostal;
public Direccion() {
}
public Direccion(String calle, String codigoPostal) {
this.calle = calle;
this.codigoPostal = codigoPostal;
}
public String getCalle() {
return calle;
}
public void setCalle(String calle) {
this.calle = calle;
}
public String getCodigoPostal() {
return codigoPostal;
}
public void setCodigoPostal(String codigoPostal) {
this.codigoPostal = codigoPostal;
}
}
</code></pre><br />
La clase "<span class="codigo">Usuario</span>" también será muy sencilla, tendrá las siguientes propiedades:<br />
<ul><li>"<span class="codigo">nombre</span>" de tipo <span class="codigo">String</span></li>
<li>"<span class="codigo">edad</span>" de tipo <span class="codigo">int</span></li>
<li>"<span class="codigo">direccion</span>" de tipo <span class="codigo">Direccion</span></li>
</ul><br />
Al igual que en el caso anterior, agregamos un par de constructores, <span class="codigo">getters</span> y <span class="codigo">setters</span>.<br />
<br />
El código completo de la clase "<span class="codigo">Usuario</span>" queda de la siguiente forma:<br />
<pre><code>
public class Usuario implements Serializable {
private String nombre;
private int edad;
private Direccion direccion;
public Usuario() {
}
public Usuario(String nombre, int edad) {
this.nombre = nombre;
this.edad = edad;
}
public String getNombre() {
return nombre;
}
public void setNombre(String nombre) {
this.nombre = nombre;
}
public int getEdad() {
return edad;
}
public void setEdad(int edad) {
this.edad = edad;
}
public Direccion getDireccion() {
return direccion;
}
public void setDireccion(Direccion direccion) {
this.direccion = direccion;
}
}
</code></pre><br />
Ya con nuestro modelo de datos, podemos comenzar a hablar de lleno sobre las etiquetas.<br />
<br />
Como dijimos antes: <span class="codigo">Struts 2</span> proporciona etiquetas para varios propósitos:<br />
<ul><li><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#control">De <span class="negritas">control</span></a>: Permiten indicar qué y cómo deben dibujarse los componentes de la interfaz de usuario. Básicamente control de flujo y un poco de manejo de datos.</li>
<li><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#datos">De <span class="negritas">datos</span></a>: Manejar algunos valores de información, así como creación y manipulación de datos.</li>
<li><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#formulario">De <span class="negritas">formulario</span></a>: Los componentes que pueden formar parte de un formulario.</li>
<li><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#noformulario"><span class="negritas">UI de no formulario</span></a>: Información extra que se muestra pero que no pertenece a un formulario (mensajes de error o de resultados de las acciones).</li>
</ul><br />
Existen un par de tipos más (Ajax y de temas y plantillas) pero no son importantes para el desarrollo del día a día. Para los que pregunten por las etiquetas de Ajax, estas son un legado que permanece de las primeras versiones de <span class="codigo">Struts 2</span>, donde se incluía como parte del core la biblioteca dojo. Sin embargo al momento de tratar de combinarlas con una versión más nueva de dojo o con otro framework javascript esta entraba en conflicto llegando a resultados inesperados, por lo que fue eliminada; aunque se puede descargar como un plugin adicional.<br />
<br />
<div class="nota">*Nota: Todas estas etiquetas se encuentran dentro de una misma biblioteca, llamada convenientemente "struts-tags". Si recuerdan de los tutoriales anteriores, hemos estado importando esta biblioteca en cada una de las páginas que hemos creado.</div><br />
<br />
Cuando trabajamos en un proyecto web, y queremos usar una biblioteca de etiquetas en una página, debemos importarla usando la directiva "<span class="codigo">taglib</span>", indicando un prefijo que no es otra cosa que un namespace para diferenciar las etiquetas de una biblioteca de la de otra (en caso de que haya una etiqueta con el mismo nombre en más de una biblioteca), y la <span class="codigo">uri</span> de la biblioteca de etiquetas. En el caso de la etiquetas de <span class="codigo">Struts 2</span>, se colocan en la <span class="codigo">uri</span> "<span class="codigo">/struts-tags</span>", y por convención usamos el prefijo "<span class="codigo">s</span>" para hacer referencia a ellas. Por lo que la directiva <span class="codigo">taglib</span> que usamos queda de la siguiente forma:<br />
<pre><code>
<%@taglib uri="/struts-tags" prefix="s" %>
</code></pre><br />
Ahora que sabemos cómo llamar a las etiquetas de <span class="codigo">Struts 2</span>, hablemos un poco de los temas antes de entrar de lleno en cada uno de los tipos de etiquetas.<br />
<br />
<span class="codigo">Struts 2</span> proporciona un par de temas integrados para el acomodo o layout de los componentes. Cada uno de los temas podemos indicarlo en dos niveles: <span class="negritas">aplicación</span> y <span class="negritas">componente</span>. Si colocamos un tema a nivel de componente, este se aplicará a <span class="negritas">ese componente y todos sus componentes hijos</span>. Si lo aplicamos a nivel de aplicación <span class="negritas">todos los componentes de nuestro sistema</span> tendrán dicho tema.<br />
<br />
Los tres temas que ofrece <span class="codigo">Struts 2</span> son los siguientes:<br />
<ul><li><span class="codigo">css_xhtml</span>: Coloca los componentes agregando algunos <span class="codigo">div</span>s.</li>
<li><span class="codigo">xhtml</span>: Este es el tema por default. Coloca los componentes en dos columnas (etiqueta y componente) usando tablas para acomodarlos.</li>
<li><span class="codigo">simple</span>: el tema más sencillo, sin nada extra más que el componente que se quiere mostrar.</li>
</ul><br />
Veamos la diferencia del <span class="codigo">HTML</span> generado con cada uno de los temas.<br />
<br />
Comencemos con el tema por default (<span class="codigo">xhtml</span>). Si colocamos el siguiente formulario en una página (por favor noten el valor del atributo "<span class="codigo">theme</span>" en la etiqueta "<span class="codigo">s:form</span>"):<br />
<pre><code>
<s:form theme="xhtml">
<s:textfield label="Nombre" name="nombre" />
<s:textfield label="Edad" name="edad" />
<s:submit value="Enviar" />
</s:form>
</code></pre><br />
Obtendremos la siguiente salida en <span class="codigo">HTML</span>:<br />
<pre><code>
<form id="" action="/tags/" method="post">
<table class="wwFormTable">
<tr>
<td class="tdLabel">
<label for="_nombre" class="label">Nombre:</label>
</td>
<td>
<input type="text" name="nombre" value="" id="_nombre"/>
</td>
</tr>
<tr>
<td class="tdLabel">
<label for="_edad" class="label">Edad:</label>
</td>
<td>
<input type="text" name="edad" value="" id="_edad"/>
</td>
</tr>
<tr>
<td colspan="2">
<div align="right">
<input type="submit" id="_0" value="Enviar"/>
</div>
</td>
</tr>
</table>
</form>
</code></pre><br />
Como podemos ver, hace uso de tablas (una técnica nada recomendada) para el acomodo de componentes. También agrega algunas clases de <span class="codigo">CSS</span>.<br />
<br />
Si ahora cambiamos el valor del atributo "<span class="codigo">theme</span>" por "<span class="codigo">css_xhtml</span>" la salida será:<br />
<pre><code>
<form id="" action="/tags/" method="post">
<div id="wwgrp__nombre" class="wwgrp">
<div id="wwlbl__nombre" class="wwlbl">
<label for="_nombre" class="label">Nombre:</label>
</div>
<br />
<div id="wwctrl__nombre" class="wwctrl">
<input type="text" name="nombre" value="" id="_nombre"/>
</div>
</div>
<div id="wwgrp__edad" class="wwgrp">
<div id="wwlbl__edad" class="wwlbl">
<label for="_edad" class="label">Edad:</label>
</div>
<br />
<div id="wwctrl__edad" class="wwctrl">
<input type="text" name="edad" value="" id="_edad"/>
</div>
</div>
<div align="right" id="wwctrl__0">
<input type="submit" id="_0" value="Enviar"/>
</div>
</form>
</code></pre><br />
Ahora se han eliminado las tablas y en su lugar se colocan varios <span class="codigo">div</span>s.<br />
<br />
Finalmente, si ponemos el "<span class="codigo">theme</span>" con el valor "<span class="codigo">simple</span>" obtenemos el siguiente <span class="codigo">HTML</span>:<br />
<pre><code>
<form id="" action="/tags/" method="post">
<input type="text" name="nombre" value="" id="_nombre"/>
<input type="text" name="edad" value="" id="_edad"/>
<input type="submit" id="_0" value="Enviar"/>
</form>
</code></pre><br />
Como vemos, lo de simple no es broma, en realidad es prácticamente lo mismo que colocamos en las etiquetas de <span class="codigo">Struts 2</span>.<br />
<br />
En mi caso me gusta agregar de forma manual los <span class="codigo">div</span>s o elementos para el acomodo y las clases <span class="codigo">CSS</span>, así que el tema que usaré para los tutoriales será el tema "<span class="codigo">simple</span>". Hay que escribir un poco más, pero se tiene más control sobre el resultado final.<br />
<br />
Ahora, en el ejemplo anterior indicamos el tema a nivel de componente. Si creamos un segundo formulario o agregamos más componentes fuera de este formulario, estos tendrán el tema por default (<span class="codigo">xhtml</span>). ¿Cómo hacer entonces que todos los componentes del sitio tengan el mismo tema sin tener que agregarlo en cada elemento? <span class="codigo">Struts 2</span> permite hacer esto agregando la propiedad "<span class="codigo">struts.ui.theme</span>" con el nombre del tema que queramos usar (en este caso "<span class="codigo">simple</span>") en el archivo "<span class="codigo">struts.properties</span>".<br />
<br />
Agregamos un nuevo archivo de propiedades en la raíz de los paquetes de la aplicación. Para esto hacemos clic derecho sobre el nodo "<span class="codigo">Source Packages</span>" y en el menú que se abre seleccionamos la opción "<span class="codigo">New</span>" -> "<span class="codigo">Properties File</span>" (si no encuentran esta opción tendrán que ir a "<span class="codigo">Other…</span>" y en la ventana que se abre seleccionar la categoría "<span class="codigo">Other</span>" y como tipo de archivo "<span class="codigo">Properties File</span>".<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJC5qZ8zDRfbfwi6mdyD57RYTSjpalx1Loa0u6UrfC9cQAhOcrsjHO4A4kqUQLq5zkNfK-eYpE071rjIF2dMli0_NOBivciDg3GAShI3o8QK15N44hzu2hNFad3_FiRHMkd05TRUZ-R4dV/s1600/S7_1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJC5qZ8zDRfbfwi6mdyD57RYTSjpalx1Loa0u6UrfC9cQAhOcrsjHO4A4kqUQLq5zkNfK-eYpE071rjIF2dMli0_NOBivciDg3GAShI3o8QK15N44hzu2hNFad3_FiRHMkd05TRUZ-R4dV/s400/S7_1.png" /></a></div><br />
<br />
Le damos como nombre a este archivo "<span class="codigo">struts</span>" (NetBeans se encargará de colocar la extensión) y hacemos clic en el botón "<span class="codigo">Finish</span>". Borramos el contenido del archivo que aparece y colocamos la siguiente línea:<br />
<pre><code>
struts.ui.theme = simple
</code></pre><br />
Con esto indicamos a <span class="codigo">Struts 2</span> que el tema por default para nuestra aplicación debe ser el tema "<span class="codigo">simple</span>".<br />
<br />
Ahora comencemos a ver cómo funcionan las etiquetas, para esto tenemos que generar un conjunto de datos de prueba, haciendo uso de las dos clases del modelo de datos que creamos anteriormente.<br />
<br />
La primera cosa importante que debemos saber es que los objetos que pueden mostrarse en las <span class="codigo">tag</span>s de <span class="codigo">Struts 2</span> deben estar en el "<span class="codigo">ActionContext</span>" (pueden revisar <a href="http://www.javatutoriales.com/2011/06/struts-2-parte-2-ognl.html">el segundo tutorial de la serie</a> donde se explica qué es), hay varias formas de colocar objetos en este contexto. La más sencilla es con los objetos que <span class="codigo">Struts 2</span> regresa después de la ejecución de un <span class="codigo">Action</span> (los atributos de los cuales hemos colocado <span class="codigo">getters</span>).<br />
<br />
Además hacemos uso de <span class="codigo">OGNL</span> para pasar valores a las etiquetas (también pueden revisar <a href="http://www.javatutoriales.com/2011/06/struts-2-parte-2-ognl.html">el segundo tutorial de la serie</a> como recordatorio de <span class="codigo">OGNL</span>).<br />
<br />
Primero veremos cómo funcionan las <span class="negritas">etiquetas de control</span>.<br />
<br />
<h2 class="titulo"><a href="http://www.blogger.com/null" id="control">Etiquetas de Control</a></h2>Nos sirven para realizar un control básico de flujo (condiciones y ciclos) y para el trabajo con colecciones.<br />
<br />
Las etiquetas de esta categoría son:<br />
<ul><li><span class="codigo"><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#if">if</a></span>: La etiqueta básica de control</li>
<li><span class="codigo"><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#elseif">elseif</a></span>: Creo que tampoco necesita explicación</li>
<li><span class="codigo"><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#else">else</a></span>: Mismo caso que la etiqueta anterior</li>
<li><span class="codigo"><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#iterator">iterator</a></span>: La etiqueta de ciclos (similar a un <span class="codigo">for</span>) que nos permite recorrer los valores de una colección.</li>
<li><span class="codigo"><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#generator">generator</a></span>: Se encarga de crear un iterador en base a un conjunto de valores, usando el separador indicado.</li>
<li><span class="codigo"><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#append">append</a></span>: Se encarga de juntar las salidas de varios iteradores, de tal forma que al usar todos los elementos de uno comienza a usar los elementos del siguiente.</li>
<li><span class="codigo"><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#merge">merge</a></span>: Trabaja de forma similar a "<span class="codigo">append</span>" sólo que en vez de usar primero todos los elementos de un iterador y continuar con el siguiente iterador, este expone el primer elemento de todos los iteradores, después el segundo elemento de todos los iteradores, y así sucesivamente.</li>
<li><span class="codigo"><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#sort">sort</a></span>: Se encarga de ordenar los elementos de una colección, usando el "<span class="codigo">Comparator</span>" indicado.</li>
<li><span class="codigo"><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#subset">subset</a></span>: Recibe un iterador y expone un subconjunto de sus elementos.</li>
</ul><br />
Comencemos entonces creando un <span class="codigo">Action</span> que llenará una lista con objetos de tipo "<span class="codigo">Usuario</span>" y "<span class="codigo">Direccion</span>". En el paquete "<span class="codigo">com.javatutoriales.struts2.tags.actions</span>" creamos una clase llamada "<span class="codigo">ControlAction</span>" que extenderá de la clase "<span class="codigo">ActionSupport</span>":<br />
<pre><code>
public class ControlAction extends ActionSupport{
}
</code></pre><br />
Como estamos usando anotaciones, indicaremos que este <span class="codigo">Action</span> se encontrará en el namespace "<span class="codigo">/</span>", tendrá el nombre "<span class="codigo">control</span>", y en el caso de una ejecución exitosa, nos regresará a la página "<span class="codigo">/control.jsp</span>":<br />
<pre><code>
@Namespace(value = "/")
@Action(value = "control", results = {
@Result(name = "success", location = "/control.jsp")})
public class ControlAction extends ActionSupport {
}
</code></pre><br />
Colocaremos como atributo, junto con su <span class="codigo">setter</span>, una lista de objetos "<span class="codigo">Usuario</span>" que será con la que trabajaremos usando las etiquetas de control:<br />
<pre><code>
private List<Usuario> usuarios = new ArrayList<Usuario>();
public List<Usuario> getUsuarios() {
return usuarios;
}
</code></pre><br />
Ahora sobre-escribimos el método "<span class="codigo">execute</span>" para poblar esta lista de la siguiente manera:<br />
<pre><code>
@Override
public String execute() throws Exception {
for (int i = 0; i < 10; i++) {
Usuario usuario = new Usuario("Usuario " + i, i + 10);
if (i % 2 == 0) {
usuario.setDireccion(new Direccion("calle " + i, "ABC" + i));
}
usuarios.add(usuario);
}
return SUCCESS;
}
</code></pre><br />
El método es muy sencillo, lo único que hace es crear 10 objetos "<span class="codigo">Usuario</span>", dándoles un nombre y una edad, y a los objetos pares les establece una dirección. Esto quiere decir que tendremos 6 usuarios con una dirección y 4 sin una.<br />
<br />
Ahora crearemos un "<span class="codigo">Comparator</span>" para ordenar los objetos "<span class="codigo">Usuario</span>". Haremos uso de una clase interna y regresaremos una instancia de esta clase usando un <span class="codigo">getter</span>. La implementación de la interfaz "<span class="codigo">Comparator</span>" en este caso no es muy importante, podríamos usar el nombre o la edad, en este caso elegiré el nombre pero haré la comparación de forma inversa, es decir, haremos un ordenamiento de manera ascendente (de mayor a menor). Para esto simplemente debemos comparar el nombre del segundo usuario con el del primero (en vez de hacer la comparación del primero con el segundo):<br />
<pre><code>
private Comparator<Usuario> comparadorUsuarios = new ComparadorUsuarios();
public Comparator<Usuario> getComparadorUsuarios() {
return comparadorUsuarios;
}
class ComparadorUsuarios implements Comparator<Usuario> {
@Override
public int compare(Usuario t, Usuario t1) {
return t1.getNombre().compareTo(t.getNombre());
}
}
</code></pre><br />
Con esto ya tenemos terminado nuestro <span class="codigo">Action</span>. El código completo de la clase "<span class="codigo">ControlAction</span>" queda de la siguiente forma:<br />
<pre><code>
@Namespace(value = "/")
@Action(value = "control", results = {
@Result(name = "success", location = "/control.jsp")})
public class ControlAction extends ActionSupport {
private List<Usuario> usuarios = new ArrayList<Usuario>();
private Comparator<Usuario> comparadorUsuarios = new ComparadorUsuarios();
@Override
public String execute() throws Exception {
for (int i = 0; i < 10; i++) {
Usuario usuario = new Usuario("Usuario " + i, i + 10);
if (i % 2 == 0) {
usuario.setDireccion(new Direccion("calle " + i, "ABC" + i));
}
usuarios.add(usuario);
}
return SUCCESS;
}
public List<Usuario> getUsuarios() {
return usuarios;
}
public Comparator<Usuario> getComparadorUsuarios() {
return comparadorUsuarios;
}
class ComparadorUsuarios implements Comparator<Usuario> {
@Override
public int compare(Usuario t, Usuario t1) {
return t1.getNombre().compareTo(t.getNombre());
}
}
}
</code></pre><br />
Ahora crearemos una nueva <span class="codigo">JSP</span> llamada "<span class="codigo">control.jsp</span>". Hacemos clic derecho en el nodo "<span class="codigo">Web Pages</span>" y seleccionamos la opción "<span class="codigo">New -> JSP...</span>". En la ventana que se abre le damos como nombre a nuestra página "<span class="codigo">control</span>" y hacemos clic en el botón "<span class="codigo">Finish</span>".<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgje1lVVonqFyMliF93xGNkmDCRnsYKzOGqmXOqKZafLZH1GXXt6eudbQvompYUII7mfEn__IWGtLojBFWINIT89Z8R-5TYCeayzkPGKM51Sm1EX1MTU4UgD-I0_tQUbqlmlmXhcTulKrzw/s1600/S7_2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgje1lVVonqFyMliF93xGNkmDCRnsYKzOGqmXOqKZafLZH1GXXt6eudbQvompYUII7mfEn__IWGtLojBFWINIT89Z8R-5TYCeayzkPGKM51Sm1EX1MTU4UgD-I0_tQUbqlmlmXhcTulKrzw/s400/S7_2.png" /></a></div><br />
<br />
Colocaremos todas las etiquetas de esta categoría en esta página. Aunque al final quedará un poco enredado podremos usarla como referencia para cuando tengamos dudas más adelante <span class="codigo">^_^</span>.<br />
<br />
Primero indicaremos que haremos uso de las etiquetas de <span class="codigo">Struts 2</span> colocando la directiva <span class="codigo">taglib</span> que vimos anteriormente:<br />
<pre><code>
<%@taglib uri="/struts-tags" prefix="s" %>
</code></pre><br />
<br />
<h3 class="subtitulo"><a href="http://www.blogger.com/null" id="if"><span class="codigo">if</span></a>, <a href="http://www.blogger.com/null" id="elseif"><span class="codigo">elseif</span></a> y <a href="http://www.blogger.com/null" id="else"><span class="codigo">else</span></a></h3>Serán las primeras etiquetas que veremos, y en general unas de las que más usaremos durante nuestros desarrollos. No necesitan mucha explicación, ya que son los equivalentes a las sentencias de control de flujo básicas de Java.<br />
<br />
<span class="codigo">if</span> y <span class="codigo">elseif</span> comparten un atributo, que se muestra en la siguiente tabla:<br />
<br />
<br />
<br />
<br />
<br />
<table><thead>
<tr><th>Nombre</th><th>Obligatorio</th><th>Valor Default</th><th>Tipo</th><th>Descripción</th></tr>
</thead> <tbody>
<tr><td><span class="codigo">test</span></td><td>Si</td><td> </td><td><span class="codigo">Boolean</span><br />
<br />
<br />
<td>Expresión que determina si el contenido del cuerpo de la etiqueta será mostrado.</td></td></tr>
</tbody></table><br />
<br />
La etiqueta <span class="codigo">else</span> no tiene atributos.<br />
<br />
Veremos un ejemplo muy simple en el que usaremos las tres etiquetas al mismo tiempo.<br />
<br />
Si colocamos las etiquetas de la siguiente forma (recuerden que <span class="codigo">test</span> recibe cualquier expresion que regrese un valor booleano):<br />
<pre><code>
<s:if test="%{false}">
Este mensaje no se vera
</s:if>
<s:elseif test="%{true}">
Este mensaje si se vera
</s:elseif>
<s:else>
Este mensaje tampoco se vera
</s:else>
</code></pre><br />
<br />
Obtendremos el siguiente código:<br />
<pre><code>
Este mensaje si se vera
</code></pre><br />
Que se ve así en el navegador<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwf5FUMHzU9V05t8n6s88FqCapZN4spk2ag_1xbJXsmKevu6jPJqAYtpfBPCp9liPS_G-K9eDgBau6A-YuD_gcaKHZ9YLXmOT88sHKuPLDNmr2OxhCAPr0vWGExGABKRvw-9NqP5FL5-11/s1600/S7_75.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwf5FUMHzU9V05t8n6s88FqCapZN4spk2ag_1xbJXsmKevu6jPJqAYtpfBPCp9liPS_G-K9eDgBau6A-YuD_gcaKHZ9YLXmOT88sHKuPLDNmr2OxhCAPr0vWGExGABKRvw-9NqP5FL5-11/s400/S7_75.png" /></a></div><br />
<br />
Así de simple funciona esta etiqueta.<br />
<br />
<h3 class="subtitulo"><a href="http://www.blogger.com/null" id="iterator"><span class="codigo">iterator</span></a></h3>Esta es otra etiqueta usaremos mucho durante nuestros desarrollos. Nos permite ciclar a través de una lista de valores, al igual que un ciclo <span class="codigo">for</span>. Los valores pueden ser indicados a través de números (valor de inicio, valor de fin, incremento) o indicando una colección (la cual será recorrida de inicio a fin).<br />
<br />
Los atributos de esta etiqueta se muestran en la siguiente tabla:<br />
<br />
<br />
<br />
<br />
<table><thead>
<tr><th>Nombre</th><th>Obligatorio</th><th>Valor Default</th><th>Tipo</th><th>Descripción</th></tr>
</thead><tbody>
<tr><td><span class="codigo">var</span></td><td>No</td><td> </td><td><span class="codigo">String</span><br />
<br />
<br />
<td>El nombre de la variable que será usada para referir al valor actual del ciclo. Esta variable es puesta en la cima del <span class="codigo">ValueStack</span>.</td></td></tr>
<tr><td><span class="codigo">value</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>La colección sobre la cual se realizará la iteración.</td></tr>
<tr><td><span class="codigo">begin</span></td><td>No</td><td>0</td><td><span class="codigo">Integer</span></td><td>El índice en el cual empezará la iteración (en caso de indicarse).</td></tr>
<tr><td><span class="codigo">end</span></td><td>No</td><td> </td><td><span class="codigo">Integer</span></td><td>El índice en el cual terminará la iteración (en caso de indicarse).</td></tr>
<tr><td><span class="codigo">step</span></td><td>No</td><td>1</td><td><span class="codigo">Integer</span></td><td>El valor por el cual será incrementado el índice en cada iteración (en caso de indicarse).</td></tr>
<tr><td><span class="codigo">status</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>El nombre de la variable que se usará para referir una instancia de <span class="codigo">IteratorStatus</span>, la cual nos indica el estatus de cada iteración.</td></tr>
</tbody></table><br />
Ahora el ejemplo, comenzaremos ciclando a través de una serie de valores estáticos. Vamos a nuestra página "<span class="codigo">control.jsp</span>" y agregamos la siguiente etiqueta:<br />
<pre><code>
<s:iterator begin="1" end="10" step="2" var="valorActual">
</s:iterator>
</code></pre><br />
Con esto indicamos que iteraremos a través de los valores del 1 al 10 haciendo incrementos de 2 en cada iteración. También indicamos que el valor actual se colocará en una variable llamada "<span class="codigo">valorActual</span>". Hay que recordar que este valor se colocará en la cima del <span class="codigo">ValueStack</span>... ¿y esto qué quiere decir? Básicamente que podemos obtener el valor sin siquiera indicar su nombre. O sea que podemos hacer esto (recordando que usamos la etiqueta "<span class="codigo">property</span>" nos sirve para mostrar valores en el HTML de salida):<br />
<pre><code>
<s:property />
</code></pre><br />
Si este valor no estuviera en la cima del <span class="codigo">ValueStack</span> tendríamos que hacer esto:<br />
<pre><code>
<s:property value="#valorActual" />
</code></pre><br />
Con esto, la etiqueta queda de esta forma:<br />
<pre><code>
<s:iterator begin="1" end="10" step="2" var="valorActual">
<s:property />
</s:iterator>
</code></pre><br />
Si ahora ejecutamos nuestra aplicación, debemos ver la siguiente salida:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiI9AT_ufd7Xxsgidb3QS58BQD1d20nl7h5OBCCFx8Vuc9pfiFTZgQStbC4vkF7p421wzvdEwOnYyZe_b7pfy7TtKPKyEyMVjIj1I_NvrMvBluHb3KROhBsTgqDFIWDHzLDbTTKO3pMNjic/s1600/S7_3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiI9AT_ufd7Xxsgidb3QS58BQD1d20nl7h5OBCCFx8Vuc9pfiFTZgQStbC4vkF7p421wzvdEwOnYyZe_b7pfy7TtKPKyEyMVjIj1I_NvrMvBluHb3KROhBsTgqDFIWDHzLDbTTKO3pMNjic/s400/S7_3.png" /></a></div><br />
Podemos ver en la salida del <span class="codigo">HTML</span> los valores esperados.<br />
<br />
Ahora juguemos un poco con el valor del estatus. ¿Qué podemos hacer con esta variable? Según <a href="http://struts.apache.org/release/2.3.x/struts2-core/apidocs/org/apache/struts2/views/jsp/IteratorStatus.html">la documentación de la clase IteratorStatus</a>, esta clase expone 6 variables:<br />
<ul><li><span class="codigo">index</span>: el índice actual del iterador, el cual comienza en 0.</li>
<li><span class="codigo">count</span>: el número de iteraciones que se han dado.</li>
<li><span class="codigo">first</span>: verdadero en caso de que le índice actual sea 0.</li>
<li><span class="codigo">even</span> (par): verdadero si el índice actual es par.</li>
<li><span class="codigo">odd</span> (impar): verdadero si el índice actual es impar.</li>
<li><span class="codigo">last</span>: verdadero si la iteración actual es la última.</li>
</ul><br />
Para el ejemplo, crearemos una tabla que muestre el valor actual de todas las variables anteriores. Lo primero es modificar la etiqueta "<span class="codigo">iterator</span>" que tenemos para agregar el atributo "<span class="codigo">status</span>":<br />
<pre><code>
<s:iterator begin="1" end="10" step="2" var="valorActual" status="estatus">
</s:iterator>
</code></pre><br />
<br />
Como en la cima del <span class="codigo">ValueStack</span> se encuentra la variable "<span class="codigo">valorActual</span>", para leer los valores de "<span class="codigo">esatus</span>" debemos indicar usando el signo de número (<span class="codigo">#</span>) el nombre de la variable utilizada (en este caso "<span class="codigo">estatus</span>"). Por ejemplo, para mostrar el valor del índice, lo mostramos de la siguiente manera:<br />
<pre><code>
<s:property value="#estatus.index" />
</code></pre><br />
Hacemos lo mismo para el resto de los valores, por lo que nuestro iterador queda de la siguiente forma:<br />
<pre><code>
<s:iterator begin="1" end="10" step="2" var="valorActual" status="estatus">
<s:property />
<s:property value="#estatus.index" />
<s:property value="#estatus.count" />
<s:property value="#estatus.first" />
<s:property value="#estatus.last" />
<s:property value="#estatus.even" />
<s:property value="#estatus.odd" />
</s:iterator>
</code></pre><br />
Lo que resta es agregar el formato de la tabla y unos encabezados:<br />
<pre><code>
<table>
<thead>
<tr>
<td>Valor</td>
<td>Indice</td>
<td>Conteo</td>
<td>¿Es primero?</td>
<td>¿Es último?</td>
<td>¿Es par?</td>
<td>¿Es impar?</td>
</tr>
</thead>
<tbody>
<s:iterator begin="1" end="10" step="2" var="valorActual" status="estatus">
<tr>
<td><s:property /></td>
<td><s:property value="#estatus.index" /></td>
<td><s:property value="#estatus.count" /></td>
<td><s:property value="#estatus.first" /></td>
<td><s:property value="#estatus.last" /></td>
<td><s:property value="#estatus.even" /></td>
<td><s:property value="#estatus.odd" /></td>
</tr>
</s:iterator>
</tbody>
</table>
</code></pre><br />
Si ejecutamos nuevamente la aplicación, debemos ver la siguiente salida<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMnB4xze3UBjTv_yu6kuACpDRlEX7BVKTcroBrR2td1h_2FbITnMVHPpY4xJ8AUPVftvZQeKFwlUYKwxzQIMsrV_JYgTWVyIDt__u7aFBUBoAaWSXNyOsOY0oVf3GkoQzQBOZtCApplFrw/s1600/S7_4.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMnB4xze3UBjTv_yu6kuACpDRlEX7BVKTcroBrR2td1h_2FbITnMVHPpY4xJ8AUPVftvZQeKFwlUYKwxzQIMsrV_JYgTWVyIDt__u7aFBUBoAaWSXNyOsOY0oVf3GkoQzQBOZtCApplFrw/s400/S7_4.png" /></a></div><br />
Ahora veremos cómo iterar a través de una <span class="negritas">colección de objetos</span> que obtengamos a través de un <span class="codigo">Action</span>, para esto mostraremos todos los valores de la lista de usuarios del <span class="codigo">Action</span> "<span class="codigo">ControlAction</span>". La forma de hacerlo es muy similar a la anterior, sólo que en vez de usar los atributos "<span class="codigo">begin</span>", "<span class="codigo">end</span>" y "<span class="codigo">step</span>" usaremos el atributo "<span class="codigo">value</span>". Este atributo recibe, como se indica en la lista antes presentada, una colección sobre la cual se realizará la iteración.<br />
<br />
Para mostrar todos los valores de la lista "<span class="codigo">usuarios</span>" debemos colocar la etiqueta como sigue:<br />
<pre><code>
<s:iterator value="usuarios">
</code></pre><br />
Con esto iteraremos a través de todos los elementos de la colección. Para mostrar el nombre del usuario actual debemos colocar en nombre del atributo, de la clase "<span class="codigo">Usuario</span>", que contiene el valor que queremos mostrar, y ponerlo en el atributo "<span class="codigo">value</span>" de la etiqueta "<span class="codigo">s:property</span>":<br />
<pre><code>
<s:property value="nombre" />
</code></pre><br />
Si dejamos la etiqueta de la siguiente forma:<br />
<pre><code>
<s:iterator value="usuarios">
<s:property value="nombre" /> <br />
</s:iterator>
</code></pre><br />
Obtendremos la siguiente salida:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEibB0CsAcOlWmYrY5RXEdMAbOpGbOefYF_Q-tchMzImRK9c0MJPyeYtUzUf13Mj8J15jX2ahseTkrgYPNsANK4w2rttG4Y9KiPoVnVi1vM2Jtz1cJE9nFgsJRsPS48vpp-hojMUgoD0otZ2/s1600/S7_5.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEibB0CsAcOlWmYrY5RXEdMAbOpGbOefYF_Q-tchMzImRK9c0MJPyeYtUzUf13Mj8J15jX2ahseTkrgYPNsANK4w2rttG4Y9KiPoVnVi1vM2Jtz1cJE9nFgsJRsPS48vpp-hojMUgoD0otZ2/s400/S7_5.png" /></a></div><br />
<br />
Cuando trabajamos con colecciones, también es posible indicar el índice de inicio, el índice de fin, y el valor del incremento de los elementos que queremos mostrar. Por ejemplo, si quisiéramos mostrar los elementos cuyo índice se encuentra entre el 3 y el 8, lo colocaríamos de la siguiente forma:<br />
<pre><code>
<s:iterator value="usuarios" begin="3" end="8">
</code></pre><br />
Con lo que obtendremos la siguiente salida:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEipmM3GGjElSokWBelBm14HmUMLFIGG3EQ2CK_-UpRalVMeIivt57_hUhvhLU5VOADd6JT40Dq4j5UmCEyVXpHqbAx_uRxiHXEfXkK4WcKVUqyAv3gYbpXMUBRUKY5kpM_sZc8YsEdQSXce/s1600/S7_6.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEipmM3GGjElSokWBelBm14HmUMLFIGG3EQ2CK_-UpRalVMeIivt57_hUhvhLU5VOADd6JT40Dq4j5UmCEyVXpHqbAx_uRxiHXEfXkK4WcKVUqyAv3gYbpXMUBRUKY5kpM_sZc8YsEdQSXce/s400/S7_6.png" /></a></div><br />
Si estos mismos índices quisiéramos iterarlos de dos en dos, sólo debemos agregar el atributo "<span class="codigo">step</span>":<br />
<pre><code>
<s:iterator value="usuarios" begin="3" end="8" step="2">
</code></pre><br />
Y la salida será la siguiente:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhDdK1XZzm573GZk07nqP8gw7f-R6JrshnIaED_M9PNbt7jyJIKLevP-NurBpLRebzeTUfm2oQZJVG9YVFS3VhaV3C0ByODbdAIDX-kIW9xggudwPX8Xr_9NfFomFlLbOqxOoxKjGzxmmtB/s1600/S7_7.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhDdK1XZzm573GZk07nqP8gw7f-R6JrshnIaED_M9PNbt7jyJIKLevP-NurBpLRebzeTUfm2oQZJVG9YVFS3VhaV3C0ByODbdAIDX-kIW9xggudwPX8Xr_9NfFomFlLbOqxOoxKjGzxmmtB/s400/S7_7.png" /></a></div><br />
Como ven, usar esta etiqueta es bastante directo.<br />
<br />
Ahora veremos la siguiente etiqueta.<br />
<br />
<h3 class="subtitulo"><a href="http://www.blogger.com/null" id="generator"><span class="codigo">generator</span></a></h3>Esta etiqueta crea un nuevo iterador, llenándolo con los valores de la cadena que recibe en su atributo "<span class="codigo">val</span>". Cuando usamos esta etiqueta, el iterador siempre queda en la cima del <span class="codigo">ValueStack</span>, y es eliminado cuando se encuentra la etiqueta de cierre del iterador.<br />
<br />
Los atributos de esta etiqueta se muestran en la siguiente tabla:<br />
<br />
<table><thead>
<tr> <th>Nombre</th><th>Obligatorio</th><th>Valor Default</th><th>Tipo</th><th>Descripción</th> </tr>
</thead> <tbody>
<tr> <td><span class="codigo">var</span></td><td>Si</td><td> </td><td><span class="codigo">String</span></td><td>El nombre de la variable que será usada para referir al valor actual del ciclo. Esta variable es puesta en la cima del <span class="codigo">ValueStack</span>.</td> </tr>
<tr> <td><span class="codigo">val</span></td><td>Si</td><td> </td><td><span class="codigo">String</span></td><td>La cadena que será parseada para crear el iterador.</td> </tr>
<tr> <td><span class="codigo">separator</span></td><td>Si</td><td> </td><td><span class="codigo">String</span></td><td>La cadena que se usa para separar cada uno de los valores de "<span class="codigo">val</span>" y convertirlos en los elementos del iterador.</td> </tr>
<tr> <td><span class="codigo">count</span></td><td>No</td><td> </td><td><span class="codigo">Integer</span></td><td>El número máximo de entradas que debe haber en el iterador.</td> </tr>
<tr> <td><span class="codigo">converter</span></td><td>No</td><td> </td><td><span class="codigo">org.apache.struts2.util.</span><br />
<span class="codigo">IteratorGenerator.Converter</span></td><td>El conversor que se usa para transformar de <span class="codigo">String</span> a un objeto cada uno de los valores del atributo "<span class="codigo">val</span>"</td> </tr>
</tbody> </table><br />
<br />
Una cosa importante que hay que saber de los iteradores que se generan en base a esta etiqueta, es que <span class="negritas">sólo viven durante el "<span class="codigo">scope</span>" de la etiqueta</span>, eso quiere decir que en cuanto se encuentre la etiqueta de cierre el iterador desaparecerá.<br />
<br />
Comencemos con un ejemplo simple. Generemos un iterador en base a una serie de números, del 1 al 5. Para esto debemos pasar una cadena con los valores al atributo "<span class="codigo">val</span>" e indicar cuál es el separador de estos valores usando el atributo "<span class="codigo">separator</span>":<br />
<pre><code>
<s:generator val="%{'1,2,3,4,5'}" separator=",">
</code></pre><br />
Como ven, pasamos todos los valores dentro de la misma cadena, separando cada uno con una coma.<br />
<br />
Ahora que el iterador está creado, necesitamos usar la etiqueta "<span class="codigo"><s:iterator></span>" de la cual ya vimos la forma de trabajo. En este caso como el iterador nuevo está en la cima del <span class="codigo">ValueStack</span> no hace falta hacer ninguna indicación (al menos que queramos modificar el comportamiento del iterador):<br />
<pre><code>
<s:generator val="%{'1,2,3,4,5'}" separator=",">
<s:iterator>
<s:property />
</s:iterator>
</s:generator>
</code></pre><br />
El código anterior genera la siguiente salida:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgRkOYSc1Kg92iMbOWRZnsqOaXg9W5FwoYsL7Ml58_cnXsaqcXapl71W9et1Kr3pYADT_TDviYJD99eKU-xJTI7BU6vUWCPZU2TRW9blqoC1FtrO6VnxPjkgPaSS3ybZjoj1f5_M7xn1eMa/s1600/S7_8.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgRkOYSc1Kg92iMbOWRZnsqOaXg9W5FwoYsL7Ml58_cnXsaqcXapl71W9et1Kr3pYADT_TDviYJD99eKU-xJTI7BU6vUWCPZU2TRW9blqoC1FtrO6VnxPjkgPaSS3ybZjoj1f5_M7xn1eMa/s400/S7_8.png" /></a></div><br />
<br />
El funcionamiento del iterador generado es el mismo que de cualquier otro iterador, y sus elementos pueden ser de cualquier tipo soportado por <span class="codigo">OGNL</span>.<br />
<br />
<h3 class="subtitulo"><a href="http://www.blogger.com/null" id="append"><span class="codigo">append</span></a></h3>Esta etiqueta permite juntar los elementos de varios iteradores en un nuevo iterador. En el nuevo iterador primero se encontrarán todos los elementos del primer iterador, luego los elementos del segundo, y así sucesivamente.<br />
<br />
Los atributos de esta etiqueta se muestran en la siguiente tabla:<br />
<br />
<table><thead>
<tr> <th>Nombre</th><th>Obligatorio</th><th>Valor Default</th><th>Tipo</th><th>Descripción</th> </tr>
</thead> <tbody>
<tr> <td><span class="codigo">var</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>El nombre de la variable que será usada para referir al iterador creado. Esta variable es puesta en la cima del <span class="codigo">ValueStack</span>.</td> </tr>
</tbody> </table><br />
<br />
Para este ejemplo crearemos dos iteradores, uno con la etiqueta "<span class="codigo">generator</span>" y otro con la lista de usuarios que se obtiene del "<span class="codigo">ControlAction</span>". Primero crearemos el iterador con la etiqueta "<span class="codigo">generator</span>":<br />
<pre><code>
<s:generator val="%{'a,b,c,d'}" separator="," var="generado" >
</s:generator>
</code></pre><br />
Podemos ver que en este caso usamos el atributo "<span class="codigo">var</span>" de la etiqueta "<span class="codigo">s:generator</span>" para poder hacer referencia posteriormente a este iterador. También debemos recordar que el iterador creado con esta etiqueta sólo estará disponible dentro del scope de la misma.<br />
Ahora usamos la etiqueta "<span class="codigo">s:append</span>" para crear el nuevo iterador. Usamos el atributo "<span class="codigo">var</span>" para indicar cuál será el identificador que tendrá este iterador:<br />
<pre><code>
<s:generator val="%{'a,b,c,d'}" separator="," var="generado" >
<s:append var="iteradorAgregado">
</s:append>
</s:generator>
</code></pre><br />
El paso final es indicar, usando la etiqueta "<span class="codigo">s:param</span>" y su atributo "<span class="codigo">value</span>", los elementos de qué iteradores serán agregados al nuevo iterador:<br />
<pre><code>
<s:generator val="%{'a,b,c,d'}" separator="," var="generado" >
<s:append var="iteradorAgregado">
<s:param value="generado" />
<s:param value="usuarios" />
</s:append>
</s:generator>
</code></pre><br />
Ahora que se tiene un nuevo iterador, llamado "<span class="codigo">iteradorAgregado</span>", podemos usarlo en cualquier parte de la página como un iterador normal:<br />
<pre><code>
<s:iterator value="%{#iteradorAgregado}">
<s:property /> <br />
</s:iterator>
</code></pre><br />
Si ejecutamos la aplicación veremos la siguiente salida:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgdWdmO8YFT80ew8xeg8mA_43KEIDMmZ26nooa9BeXfRvZa-8_58jFi9KabSxhlsdcw4pujWjthu37H_8GNmbN2aOXNOrfolRBIa89tHxCDJbk3BwuCTbrrH_qOxPNPTNZgr0viAkTBQZuV/s1600/S7_9.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgdWdmO8YFT80ew8xeg8mA_43KEIDMmZ26nooa9BeXfRvZa-8_58jFi9KabSxhlsdcw4pujWjthu37H_8GNmbN2aOXNOrfolRBIa89tHxCDJbk3BwuCTbrrH_qOxPNPTNZgr0viAkTBQZuV/s400/S7_9.png" /></a></div><br />
<br />
La salida se ve un poco extraña en la parte que muestra la lista de usuario, pero esto es porque no sobrecargamos el método "<span class="codigo">to<span class="codigo">String</span></span>". Aun así podemos ver que, efectivamente, los elementos de los dos iteradores que indicamos se han agregado al nuevo iterador; primero todos los elementos del primer iterador, y posteriormente todos los elementos del segundo iterador.<br />
Ahora veremos el funcionamiento de la siguiente etiqueta.<br />
<br />
<h3 class="subtitulo"><a href="http://www.blogger.com/null" id="merge"><span class="codigo">merge</span></a></h3>Esta etiqueta funciona de forma muy similar a "<span class="codigo">append</span>", juntando las salidas de varios iteradores en un nuevo iterador. La diferencia principal es que mientras que "<span class="codigo">append</span>" agrega todos los elementos de un iterador antes de comenzar a agregar los elementos del siguiente, "<span class="codigo">merge</span>" coloca primero todos los elementos del índice 0 de todos los iteradores, luego los elementos del índice 1, luego todos los elementos del índice 2, así sucesivamente.<br />
<br />
Los atributos de esta etiqueta se muestran en la siguiente tabla:<br />
<br />
<table><thead>
<tr> <th>Nombre</th><th>Obligatorio</th><th>Valor Default</th><th>Tipo</th><th>Descripción</th> </tr>
</thead> <tbody>
<tr> <td><span class="codigo">var</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>El nombre de la variable que será usada para referir al iterador creado. Esta variable es puesta en la cima del <span class="codigo">ValueStack</span>.</td> </tr>
</tbody> </table><br />
<br />
Nuevamente crearemos, para este ejemplo, dos iteradores, uno con la etiqueta "<span class="codigo">generator</span>" y otro con la lista de usuarios que se obtiene del "<span class="codigo">ControlAction</span>". Primero crearemos el iterador con la etiqueta "<span class="codigo">generator</span>":<br />
<pre><code>
<s:generator val="%{'a,b,c,d'}" separator="," var="generado" >
</s:generator>
</code></pre><br />
Ahora usamos la etiqueta "<span class="codigo">s:merge</span>" para crear el nuevo iterador. Usamos el atributo "<span class="codigo">var</span>" para indicar cuál será el identificador que tendrá este iterador:<br />
<pre><code>
<s:generator val="%{'a,b,c,d'}" separator="," var="generado" >
<s:merge var="iteradorFusionado">
</s:merge>
</s:generator>
</code></pre><br />
Finalmente, indicamos cuáles iteradores serán fusionados. Para esto usamos la etiqueta "<span class="codigo">s:param</span>" en cuyo atributo "<span class="codigo">value</span>" indicamos el nombre del iterador a fusionar:<br />
<pre><code>
<s:generator val="%{'a,b,c,d'}" separator="," var="generado" >
<s:merge var="iteradorFusionado">
<s:param value="generado" />
<s:param value="usuarios" />
</s:merge>
</s:generator>
</code></pre><br />
Ahora con el nuevo iterador, llamado "<span class="codigo">iteradorFusionado</span>", podemos imprimir los valores contenidos en este:<br />
<pre><code>
<s:iterator value="%{#iteradorFusionado}">
<s:property /><br />
</s:iterator>
</code></pre><br />
Al actualizar la página obtenemos la siguiente salida:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgBf7E7jbqkPxR4ZhjapjitApq2A49LxNmxJD_LKGoewIR6QakXvpsCVUq3H5qrV7FXqUxv-jbMTsdY0oRwnplBjrhFIEftgxPJtEvip_j8F1meL3bizQKN_FceZSF8BfKK8MyvfYR2oXWB/s1600/S7_10.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgBf7E7jbqkPxR4ZhjapjitApq2A49LxNmxJD_LKGoewIR6QakXvpsCVUq3H5qrV7FXqUxv-jbMTsdY0oRwnplBjrhFIEftgxPJtEvip_j8F1meL3bizQKN_FceZSF8BfKK8MyvfYR2oXWB/s400/S7_10.png" /></a></div><br />
<br />
Nuevamente la salida se ve un poco extraña en la parte que muestra la lista de usuario, pero esto es porque no sobrecargamos el método "<span class="codigo">to<span class="codigo">String</span></span>". Pero podemos comprobar que los dos iteradores se han funcionado como lo esperábamos, primero los valores del índice 0 de ambos iteradores, luego los del índice 1, etc.<br />
<br />
<h3 class="subtitulo"><a href="http://www.blogger.com/null" id="sort"><span class="codigo">sort</span></a></h3><span class="codigo">sort</span> es una de esas etiquetas que nos ahorran mucho esfuerzo, ya que permite ordenar los elementos de una colección haciendo uso de un "<span class="codigo">Comparator</span>".<br />
<br />
La lista ordenada vivirá solo dentro de las etiquetas de apertura y cierre de "<span class="codigo">s:sort</span>", y será colocada en la variable que indiquemos usando su atributo "<span class="codigo">var</span>".<br />
<br />
Los atributos de esta etiqueta se muestran en la siguiente tabla:<br />
<br />
<table><thead>
<tr> <th>Nombre</th><th>Obligatorio</th><th>Valor Default</th><th>Tipo</th><th>Descripción</th> </tr>
</thead> <tbody>
<tr> <td><span class="codigo">comparator</span></td><td>Si</td><td> </td><td><span class="codigo">java.util.Comparator</span></td><td>El comparador que será usado para ordenar los elementos de la colección.</td> </tr>
<tr> <td><span class="codigo">source</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>La colección que será ordenada.</td> </tr>
<tr> <td><span class="codigo">var</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>El nombre de la variable que será usada para referir al iterador creado que contendrá los elementos ordenados. Esta variable es puesta en la cima del <span class="codigo">ValueStack</span>.</td> </tr>
</tbody> </table><br />
<br />
Como podemos ver en la tabla anterior, para hacer uso de esta etiqueta necesitamos proporcionar un objeto de tipo "<span class="codigo">java.util.Comparator</span>", el cual contiene las reglas para el ordenamiento de los objetos.<br />
<br />
Si recuerdan, cuando creamos el <span class="codigo">Action</span> "<span class="codigo">ControlAction</span>" incluimos una clase anidada llamada "<span class="codigo">ComparadorUsuarios</span>" que implementa la interface "<span class="codigo">Comparator</span>" y que se encarga de ordenar a los objetos de tipo "<span class="codigo">Usuario</span>" por orden alfabético usando su nombre. También incluimos una instancia de esta clase dentro de las propiedades de la clase "<span class="codigo">ControlAction</span>". Pues bien, será esta instancia la que usaremos en el atributo "<span class="codigo">comparator</span>" de la etiqueta "<span class="codigo">s:sort</span>":<br />
<pre><code>
<s:sort comparator="comparadorUsuarios" source="usuarios">
</s:sort>
</code></pre><br />
En este momento ya se ha creado un nuevo iterador, con sus elementos ordenados (en orden descendente), y ha sido puesto en la cima del "<span class="codigo">ValueStack</span>", por lo que podemos acceder a él simplemente colocando la etiqueta "<span class="codigo">s:iterator</span>" sin más parámetros:<br />
<pre><code>
<s:sort comparator="comparadorUsuarios" source="usuarios">
<s:iterator>
</s:iterator>
</s:sort>
</code></pre><br />
El último paso es indicar qué atributo del iterador de "<span class="codigo">Usuarios</span>" queremos mostrar, en este caso el "<span class="codigo">nombre</span>":<br />
<pre><code>
<s:sort comparator="comparadorUsuarios" source="usuarios">
<s:iterator>
<s:property value="nombre" /><br />
</s:iterator>
</s:sort>
</code></pre><br />
Si ejecutamos la aplicación deberemos ver la siguiente salida:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhxgWaiPMWs1-lU1yO2gLJDMxyr54bV55mUtBGF6R4QxLi237OIAsGsPzf9BTBtmn-JHwn1L35SYOkVOTC-RrYWc4UiDxPkNVQu6WnmgKglPue9Wm-14cXGeH7qQ7MGid1PLy2als-SqosO/s1600/S7_11.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhxgWaiPMWs1-lU1yO2gLJDMxyr54bV55mUtBGF6R4QxLi237OIAsGsPzf9BTBtmn-JHwn1L35SYOkVOTC-RrYWc4UiDxPkNVQu6WnmgKglPue9Wm-14cXGeH7qQ7MGid1PLy2als-SqosO/s400/S7_11.png" /></a></div><br />
<br />
Podemos ordenar la salida de varias formas dependiendo del "<span class="codigo">Comparator</span>" que usemos, pero recuerden que la colección ordenada sólo estará disponible dentro de las etiquetas "<span class="codigo">s:sort</span>".<br />
Ahora veremos cómo usar la última etiqueta de control.<br />
<br />
<h3 class="subtitulo"><a href="http://www.blogger.com/null" id="subset"><span class="codigo">subset</span></a></h3>Esta etiqueta recibe un iterador y regresa un iterador con un subconjunto de elementos del original. Existen varias maneras de indicar cómo debe ser este subconjunto, desde un cierto número de elementos, hasta usando un elemento de decisiones.<br />
<br />
Los atributos de esta etiqueta se muestran en la siguiente tabla:<br />
<br />
<table><thead>
<tr> <th>Nombre</th><th>Obligatorio</th><th>Valor Default</th><th>Tipo</th><th>Descripción</th> </tr>
</thead> <tbody>
<tr> <td><span class="codigo">count</span></td><td>No</td><td> </td><td><span class="codigo">Integer</span></td><td>Indica el número de elementos que debe haber en el subconjunto</td> </tr>
<tr> <td><span class="codigo">start</span></td><td>No</td><td> </td><td><span class="codigo">Integer</span></td><td>El índice inicial de las entradas en la fuente, que estarán disponibles en el subconjunto.</td> </tr>
<tr> <td><span class="codigo">source</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>Indica la colección de la cual será tomado el subconjunto.</td> </tr>
<tr> <td><span class="codigo">decider</span></td><td>No</td><td> </td><td><span class="codigo">org.apache.struts2.util.</span><br />
<span class="codigo">SubsetIteratorFilter.Decider</span></td><td>Determina si un elemento particular debe ser incluido en el subconjunto resultante.</td> </tr>
<tr> <td><span class="codigo">var</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>El nombre de la variable que será usada para referir al iterador creado que contendrá los elementos del subconjunto. Esta variable es puesta en la cima del <span class="codigo">ValueStack</span>.</td> </tr>
</tbody> </table><br />
Comencemos con el ejemplo simple. Nuevamente usaremos la colección de "<span class="codigo">usuarios</span>" del "<span class="codigo">ControlAction</span>". Primero limitaremos el número de elementos que tendrá la colección a 3, usando el atributo "<span class="codigo">count</span>":<br />
<pre><code>
<s:subset source="usuarios" count="3">
</s:subset>
</code></pre><br />
Para mostrar los valores del subconjunto, basta con indicar el uso de un iterador y que la propiedad de usuario que queremos mostrar es el nombre:<br />
<pre><code>
<s:subset source="usuarios" count="3">
<s:iterator>
<s:property value="nombre" /><br />
</s:iterator>
</s:subset>
</code></pre><br />
Si ejecutamos nuevamente la aplicación debemos ver la siguiente salida:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvkGZpjG10FbvuI0Q_ZeiW3EAwNuD2Uaw12unoobQy4tpCa15ZdP7lK1ma9Iw6OdbD69Ax9J1DdatyRJJuFwKWY8DVphhaZvehFZz6M0aZNgRIFy8MaF2QiWzZcBZJbn-CcQkKJsD0CM6_/s1600/S7_12.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvkGZpjG10FbvuI0Q_ZeiW3EAwNuD2Uaw12unoobQy4tpCa15ZdP7lK1ma9Iw6OdbD69Ax9J1DdatyRJJuFwKWY8DVphhaZvehFZz6M0aZNgRIFy8MaF2QiWzZcBZJbn-CcQkKJsD0CM6_/s400/S7_12.png" /></a></div><br />
<br />
Ahora, supongamos que no queremos iniciar el subconjunto desde el elemento en el índice 0 de la colección original, sino desde el índice 2 (manteniendo el conteo de 3 elementos en el subconjunto), para esto usamos el atributo "<span class="codigo">start</span>":<br />
<pre><code>
<s:subset source="usuarios" count="3" start="2">
</s:subset>
</code></pre><br />
Nuevamente indicamos que la propiedad de usuario que queremos mostrar es el nombre:<br />
<pre><code>
<s:subset source="usuarios" count="3" start="2">
<s:iterator>
<s:property value="nombre" /><br />
</s:iterator>
</s:subset>
</code></pre><br />
Si ejecutamos la aplicación debemos ver la siguiente salida:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjof1MucnQPgIQypwqR17vvEIsm1xcRdyS4900t9PibZgPop8B7gADUpfIZqgbKFlCgtMJNWNtPXu7Y2ug94DGk_kVOCxVk9kxcfhqlPp5GgI0-nsAOG0GfoE5pk9l2Wcum4TfraZAoHbkE/s1600/S7_13.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjof1MucnQPgIQypwqR17vvEIsm1xcRdyS4900t9PibZgPop8B7gADUpfIZqgbKFlCgtMJNWNtPXu7Y2ug94DGk_kVOCxVk9kxcfhqlPp5GgI0-nsAOG0GfoE5pk9l2Wcum4TfraZAoHbkE/s400/S7_13.png" /></a></div><br />
<br />
Ahora veamos el ejemplo interesante, en el que usaremos un "<span class="codigo">Decider</span>" para determinar cuáles elementos estarán en la colección. Para esto debemos crear un objeto que implemente la interface "<span class="codigo">org.apache.struts2.util.SubsetIteratorFilter.Decider</span>". Esta interface sólo tiene un método:<br />
<pre><code>
public boolean decide(Object o) throws Exception;
</code></pre><br />
El cual regresa <span class="codigo">true</span> en caso de que el objeto deba estar en el subconjunto, y <span class="codigo">false</span> en caso de que no deba estarlo.<br />
<br />
Para implementar esta interface crearemos una nueva clase anidada dentro del "<span class="codigo">ControlAction</span>", yo llamaré a esta clase "<span class="codigo">FiltroUsuarios</span>":<br />
<pre><code>
class FiltroUsuarios implements SubsetIteratorFilter.Decider {
}
</code></pre><br />
Ahora implementamos el método "<span class="codigo">decide</span>". En este caso la implementación que haremos es sencilla, sólo queremos el subconjunto de todos los "<span class="codigo">Usuarios</span>" que tienen una "<span class="codigo">Direccion</span>" establecida (o sea, para los usuarios que su "<span class="codigo">Direccion</span>" no es <span class="codigo">null</span>):<br />
<pre><code>
public boolean decide(Object o) throws Exception {
Usuario usuario = (Usuario) o;
return usuario.getDireccion() != null;
}
</code></pre><br />
La clase "<span class="codigo">FiltroUsuarios</span>" completa queda de la siguiente forma:<br />
<pre><code>
class FiltroUsuarios implements SubsetIteratorFilter.Decider {
public boolean decide(Object o) throws Exception {
Usuario usuario = (Usuario) o;
return usuario.getDireccion() != null;
}
}
</code></pre><br />
Ahora creamos una nueva instancia de esta clase, dentro del "<span class="codigo">ControlAction</span>" y agregamos un <span class="codigo">getter</span> para este nuevo atributo:<br />
<pre><code>
private SubsetIteratorFilter.Decider decider = new FiltroUsuarios();
public SubsetIteratorFilter.Decider getDecider() {
return decider;
}
</code></pre><br />
Dentro de la etiqueta "<span class="codigo">s:subset</span>" indicamos que usaremos el decider que acabamos de crear:<br />
<pre><code>
<s:subset source="usuarios" decider="decider">
</s:subset>
</code></pre><br />
El resto es mostrar los valores del subconjunto:<br />
<pre><code>
<s:subset source="usuarios" decider="decider">
<s:iterator>
<s:property value="nombre" /><br />
</s:iterator>
</s:subset>
</code></pre><br />
Si ejecutamos la aplicación debemos ver la siguiente salida:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjYytHj219ZXFVvYxKZQzu2klxK5oYK9TAyKyxMrn8fCeyugJvCmgaamB2o_DNRRmJtj8CfChrOH1OXu8PBuOPy4fXY7VRRRdd7es-1qTtT1JL9zHxio8Y-PbTErBSzRamWS4WinqM9k5-S/s1600/S7_14.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjYytHj219ZXFVvYxKZQzu2klxK5oYK9TAyKyxMrn8fCeyugJvCmgaamB2o_DNRRmJtj8CfChrOH1OXu8PBuOPy4fXY7VRRRdd7es-1qTtT1JL9zHxio8Y-PbTErBSzRamWS4WinqM9k5-S/s400/S7_14.png" /></a></div><br />
<br />
También podemos usar el resto de los atributos para limitar el número de elementos que se muestran:<br />
<br />
<pre><code>
<s:subset source="usuarios" decider="decider" count="3" start="2">
<s:iterator>
<s:property value="nombre" /><br />
</s:iterator>
</s:subset>
</code></pre><br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgW5aVOgUNeDle7-1Fxj-MsEzxrL_9wZFYLn8w8k9jUo4jYafeX9p4Qr0NMkZSMum9rAhJZDVZW7PeeFEkZ0Mrk6Ib-GCPgsSeF71fRSHGsKdPHXATFttLqesRTgw5nglQo4_Ue5B6j2ulu/s1600/S7_15.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgW5aVOgUNeDle7-1Fxj-MsEzxrL_9wZFYLn8w8k9jUo4jYafeX9p4Qr0NMkZSMum9rAhJZDVZW7PeeFEkZ0Mrk6Ib-GCPgsSeF71fRSHGsKdPHXATFttLqesRTgw5nglQo4_Ue5B6j2ulu/s400/S7_15.png" /></a></div><br />
<br />
Con esto terminamos las etiquetas de control, ahora veremos cómo trabajar con las etiquetas de datos.<br />
<br />
<h2 class="titulo"><a href="http://www.blogger.com/null" id="datos">Etiquetas de Datos</a></h2>Estas etiquetas permiten mostrar información al usuario de varias maneras y también crear objetos para ejecutar algunas acciones.<br />
<br />
Las etiquetas de esta categoría son:<br />
<ul><li><span class="codigo"><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#a">a</a></span>: Crea una etiqueta <span class="codigo"><a></span> (un hipervínculo) en <span class="codigo">HTML</span></li>
<li><span class="codigo"><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#url">url</a></span>: Se usa para crear una <span class="codigo">URL</span>.</li>
<li><span class="codigo"><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#action">action</a></span>: Permite llamar a un <span class="codigo">Action</span> directamente de una <span class="codigo">JSP</span>.</li>
<li><span class="codigo"><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#bean">bean</a></span>: Crea una instancia de una clase dentro de una <span class="codigo">JSP</span>.</li>
<li><span class="codigo"><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#bean">date</a></span>: Da formato una fecha de distintas formas.</li>
<li><span class="codigo"><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#i18n">i18n</a></span>: Obtiene un <span class="codigo">ResourceBundle</span> y lo pone en el <span class="codigo">ValueStack</span>.</li>
<li><span class="codigo"><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#text">text</a></span>: Muestra un texto <span class="codigo">i18n</span>.</li>
<li><span class="codigo"><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#include">include</a></span>: Incluye la salida del resultado de la ejecución de un <span class="codigo">Servlet</span> o de una <span class="codigo">JSP</span>.</li>
<li><span class="codigo"><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#param">param</a></span>: Etiqueta genérica que se usa para pasar valores a otras etiquetas.</li>
<li><span class="codigo"><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#property">property</a></span>: Se usa para mostrar el valor de una propiedad.</li>
<li><span class="codigo"><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#push">push</a></span>: Coloca un valor en la cima del <span class="codigo">ValueStack</span>.</li>
<li><span class="codigo"><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#set">set</a></span>: Asigna un valor a una variable en un scope especificado.</li>
<li><span class="codigo"><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#debug">debug</a></span>: Imprime el contenido del <span class="codigo">ValueStack</span>.</li>
</ul><br />
Para esta categoría no será necesario crear un nuevo <span class="codigo">Action</span>. Pero para mentener un orden, crearemos una nueva <span class="codigo">JSP</span> llamada "<span class="codigo">datos.jsp</span>":<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhuKwzD6HwsXqz1ORXJGHpUn67aUGvEePTA68rDi8fkI6tJa_mtlaxtjRDyzhfr7lMzM2Pz9_Lbt5MIMBp4MdyNRw2o_fqVSfqInYDKNuYIn03OiJ7yKP5LYV_2O1qdmgy-rUE3DqRTGCvy/s1600/S7_16.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhuKwzD6HwsXqz1ORXJGHpUn67aUGvEePTA68rDi8fkI6tJa_mtlaxtjRDyzhfr7lMzM2Pz9_Lbt5MIMBp4MdyNRw2o_fqVSfqInYDKNuYIn03OiJ7yKP5LYV_2O1qdmgy-rUE3DqRTGCvy/s400/S7_16.png" /></a></div><br />
<br />
En esta nueva página indicamos que usaremos las etiquetas de <span class="codigo">Struts 2</span> usando la directiva "<span class="codigo">@taglib</span>":<br />
<pre><code>
<%@taglib uri="/struts-tags" prefix="s" %>
</code></pre><br />
Comencemos a ver el uso de estas etiquetas:<br />
<br />
<h3 class="subtitulo"><a href="http://www.blogger.com/null" id="a"><span class="codigo">a</span></a></h3>Permite generar un hipervínculo (etiqueta <span class="codigo"><a></span> en <span class="codigo">HTML</span>). Podemos indicar si queremos que el enlace generado sea hacia alguna <span class="codigo">URL</span>, interna o externa, o para la ejecución de algún <span class="codigo">Action</span> de nuestra aplicación.<br />
<br />
Los atributos de esta etiqueta se muestran a continuación. Como la mayoría de los atributos se derivan directamente de la etiqueta de <span class="codigo">HTML</span> o son heredados del componente que <span class="codigo">Struts 2</span> usa como base, sólo se explicarán los atributos que tengan sentido para esta etiqueta, y los demás se amontonarán en la última fila de la tabla.<br />
<br />
<table><thead>
<tr> <th>Nombre</th><th>Obligatorio</th><th>Valor Default</th><th>Tipo</th><th>Descripción</th> </tr>
</thead> <tbody>
<tr> <td><span class="codigo">action</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>El <span class="codigo">Action</span> para el cual se generará el enlace.</td> </tr>
<tr> <td><span class="codigo">encode</span></td><td>No</td><td><span class="codigo">true</span></td><td><span class="codigo">Boolean</span></td><td>Indica si es necesario codificar los parámetros que se envían en el enlace.</td> </tr>
<tr> <td><span class="codigo">escapeAmp</span></td><td>No</td><td><span class="codigo">true</span></td><td><span class="codigo">Boolean</span></td><td>Especifica si se debe escapar el símbolo ampersand (<span class="codigo">&</span>) como <span class="codigo">&amp;</span> o no.</td> </tr>
<tr> <td><span class="codigo">forceAddSchemeHostAndPort</span></td><td>No</td><td><span class="codigo">false</span></td><td><span class="codigo">Boolean</span></td><td>Indica si se debe forzar el agregar el esquema, el host y el puerto en el enlace que se genera.</td> </tr>
<tr> <td><span class="codigo">href</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>La <span class="codigo">URL</span> a la que apuntará el enlace.</td> </tr>
<tr> <td><span class="codigo">includeContext</span></td><td>No</td><td><span class="codigo">true</span></td><td><span class="codigo">Boolean</span></td><td>Si el contexto actual debe ser incluido en la <span class="codigo">URL</span>.</td> </tr>
<tr> <td><span class="codigo">includeParams</span></td><td>No</td><td><span class="codigo">none</span></td><td><span class="codigo">String</span></td><td>Puede tener los valores "<span class="codigo">none</span>", "<span class="codigo">get</span>", y "<span class="codigo">all</span>", indicando qué parámetros deben ser incluidos en el enlace.</td> </tr>
<tr> <td><span class="codigo">namespace</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>El namespace que se usará al generar el enlace (en caso de que sea para un <span class="codigo">Action</span>).</td> </tr>
<tr> <td><span class="codigo">method</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>El método del <span class="codigo">Action</span> que se invocará.</td> </tr>
<tr> <td colspan="5"><span class="codigo">accesskey, anchor, cssClass, cssErrorClass, cssErrorStyle, cssStyle, disabled, errorPosition, javascriptTooltip, key, label, labelSeparator, labelposition, name, onblur, onchange, onclick, ondblclick, onfocus, onkeydown, onkeypress, onkeyup, onmousedown, onmousemove, onmouseout, onmouseover, onmouseup, onselect, openTemplate, portletMode, portletUrlType, requiredLabel, requiredPosition, scheme, tabindex, template, templateDir, theme, title, tooltip, tooltipCssClass, tooltipDelay, tooltipIconPath, value, windowState</span></td> </tr>
</tbody> </table><br />
De los atributos anteriores la mayoría sólo tienen sentido cuando se trabaja para generar enlaces a <span class="codigo">Action</span>s, la cual es la verdadera utilidad de esta etiqueta.<br />
<br />
Veamos los ejemplos. Lo primero que haremos es generar dos enlaces, un enlace "normal" a una página, y un enlace a un <span class="codigo">Action</span>. Como sólo tenemos otra página (<span class="codigo">control.jsp</span>) y un solo <span class="codigo">Action</span> (<span class="codigo">ControlAction</span>) en nuestra aplicación, crearemos los respectivos enlaces a estos.<br />
<br />
Colocamos una etiqueta "<span class="codigo">s:a</span>", en cuyo atributo "<span class="codigo">href</span>" colocaremos el nombre de la página a la que queremos ir, en este caso "<span class="codigo">control.jsp</span>":<br />
<pre><code>
<s:a href="control.jsp">Página control</s:a>
</code></pre><br />
Hasta aquí tenemos el mismo resultado que si usáramos une etiqueta de hipervínculo normal de <span class="codigo">HTML</span> (<span class="codigo">a</span>).<br />
<br />
Ahora crearemos el segundo enlace, en este caso al <span class="codigo">ControlAction</span>. Para crear el enlace, debemos colocar el nombre del <span class="codigo">Action</span>, que en este caso es "<span class="codigo">control</span>", en el atributo "<span class="codigo">Action</span>" de la etiqueta:<br />
<pre><code>
<s:a <span class="codigo">Action</span>="control" ><span class="codigo">Action</span> control</s:a>
</code></pre><br />
El código de los enlaces, agregando un pequeño salto de línea, queda de la siguiente forma:<br />
<pre><code>
<s:a href="control.jsp">Página control</s:a>
<br />
<s:a action="control" >Action control</s:a>
</code></pre><br />
Si ejecutamos la aplicación en entramos a la siguiente dirección:<br />
<pre><code>
<a href="http://localhost:8080/tags/datos.jsp">http://localhost:8080/tags/datos.jsp</a>
</code></pre><br />
Debemos ver la siguiente salida:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEit-cQJ7xJOABOO_1sgk4tolV3dcpYUP3VOwpJ_4WkiaruGMuLDT_sq_pT9JZ9ohLs67UDx2Ooltt4tPl2RXNdEsuHRu_-pfZVob2DyBovxYKgJ06icEKwkNOW9WCME4x3IJ-P-hJHu9CxT/s1600/S7_17.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEit-cQJ7xJOABOO_1sgk4tolV3dcpYUP3VOwpJ_4WkiaruGMuLDT_sq_pT9JZ9ohLs67UDx2Ooltt4tPl2RXNdEsuHRu_-pfZVob2DyBovxYKgJ06icEKwkNOW9WCME4x3IJ-P-hJHu9CxT/s400/S7_17.png" /></a></div><br />
<br />
Si revisamos el código generado, veremos lo siguiente:<br />
<pre><code>
<a href="control.jsp">Página control</a>
<a href="/tags/control.action">Action control</a>
</code></pre><br />
Podemos observar que el primer enlace es idéntico, o casi, al primer enlace que colocamos. El segundo enlace es un poco más interesante, ya que el framework se encarga de buscar cuál <span class="codigo">Action</span>, dentro del namespace actual (en este caso "<span class="codigo">tags</span>") tiene el nombre de "<span class="codigo">control</span>", y coloca la ruta de este <span class="codigo">Action</span> en el atributo "<span class="codigo">href</span>" de la etiqueta "<span class="codigo">a</span>". <br />
<br />
Si hacemos clic en cualquiera de los enlaces anteriores, seremos redirigidos a la misma página. <br />
Ahora, ¿qué pasaría si quisiéramos pasar algunos parámetros al enlace generado? Para eso podemos hacer uso de la etiqueta "<span class="codigo">s:param</span>" y sus atributos "<span class="codigo">name</span>" y "<span class="codigo">value</span>". Supongamos que queremos pasar dos parámetros en cada enlace, "<span class="codigo">param1</span>" y "<span class="codigo">param2</span>", con los valores "<span class="codigo">abc</span>" y "<span class="codigo">123</span>" respectivamente, para eso debemos modificar nuestro código de la siguiente forma:<br />
<pre><code>
<s:a href="control.jsp">Página control
<s:param name="param1" value="%{'abc'}" />
<s:param name="param2" value="%{'123'}" />
</s:a>
<br />
<s:a action="control">Action control
<s:param name="param1" value="%{'abc'}" />
<s:param name="param2" value="%{'123'}" />
</s:a>
</code></pre><br />
Si ejecutamos la aplicación veremos que el código generado para los enlaces es el siguiente:<br />
<pre><code>
<a href="control.jsp">Página control</a>
<br />
<a href="/tags/control.action?param1=abc&param2=123">Action control</a>
</code></pre><br />
Aquí podemos ver una de las diferencias entre usar el atributo "<span class="codigo">href</span>" y el atributo "<span class="codigo">action</span>". El <span class="codigo">URL</span> primer caso no se han agregado los parámetros de la petición al enlace, pero en el segundo casi sí. ¿Por qué ocurre esto? La respuesta es muy fácil: no lo sé <span class="codigo">^_^!</span>. ¿Qué podemos hacer si queremos pasar parámetros a la etiqueta "<span class="codigo">s:a</span>" usando el atributo "<span class="codigo">href</span>"? Para esos casos la única solución es pasar los parámetros directamente en el atributo "<span class="codigo">href</span>", de la siguiente forma:<br />
<pre><code>
<s:a href="control.jsp?param1=%{'abc'}&param2=%{'123'}">Página control</s:a>
</code></pre><br />
Con esto se generará el siguiente enlace:<br />
<pre><code>
<a href="control.jsp?param1=abc&param2=123">Página control</a>
</code></pre><br />
El resto de los atributos de la etiqueta "<span class="codigo">s:a</span>" no funcionan cuando trabajamos con el atributo "<span class="codigo">href</span>", así que sólo veremos el código para el atributo "<span class="codigo">action</span>".<br />
<br />
Acabamos de ver cómo pasar parámetros a la etiqueta "<span class="codigo">s:a</span>", pero ¿qué pasa si recibimos los parámetros por la <span class="codigo">URL</span> o por algún otro medio o si no sabemos qué y cuántos parámetros vamos a recibir, y queremos pasarlos directamente al enlace? Para esos casos podemos usar el atributo "<span class="codigo">includeParams</span>". Si revisan la tabla anterior podrán ver que este atributo puede recibir tres valores: "<span class="codigo">none</span>" (su valor por default), "<span class="codigo">get</span>" y "<span class="codigo">all</span>". Cuando usamos "<span class="codigo">get</span>" se agregan todos los parámetros recibidos en la <span class="codigo">URL</span> (el queryString), y cuando usamos "<span class="codigo">all</span>" se agregan además los parámetros que se encuentren en el <span class="codigo">ValueStack</span>. En nuestro ejemplo, usaremos el valor "<span class="codigo">get</span>":<br />
<pre><code>
<s:a <span class="codigo">Action</span>="control" includeParams="get"><span class="codigo">Action</span> control</s:a>
</code></pre><br />
Y si entramos en la siguiente <span class="codigo">URL</span>:<br />
<pre><code>
<a href="http://localhost:8080/tags/datos.jsp?param4=456&param5=def">http://localhost:8080/tags/datos.jsp?param4=456&param5=def</a>
</code></pre><br />
Con lo que veremos que el enlace generado queda de la siguiente forma:<br />
<pre><code>
<a href="/tags/control.action?param4=456&param5=def">Action control</a>
</code></pre><br />
Vemos que los parámetros se pasan tal cuál al nuevo enlace.<br />
<br />
Podemos ver a lo largo de todos los ejemplos anteriores que en el enlace generado siempre es relativo, ¿qué pasaría si quisiéramos que fuera absoluto (que incluya el protocolo, host y puerto)? Para eso podemos usar el atributo "<span class="codigo">forceAddSchemeHostAndPort</span>" con un valor de "<span class="codigo">true</span>":<br />
<pre><code>
<s:a action="control" forceAddSchemeHostAndPort="true">Action control</s:a>
</code></pre><br />
Con esto, el enlace generado quedará de la siguiente forma:<br />
<pre><code>
<a href="http://localhost:8080/tags/control.action">Action control</a>
</code></pre><br />
Podemos ver que, efectivamente, la <span class="codigo">URL</span> ha pasado de ser relativa a ser absoluta.<br />
<br />
Podemos ver también, en los ejemplos anteriores, que el enlace generado incluye el contexto de la aplicación, en este caso "<span class="codigo">tag</span>". ¿Qué podemos hacer si no queremos que este contexto esté en el enlace? Usamos el atributo "<span class="codigo">includeContext</span>" con el valor de "<span class="codigo">false</span>":<br />
<br />
<pre><code>
<s:a action="control" includeContext="false">Action control</s:a>
</code></pre><br />
<br />
Con esto el enlace generado será el siguiente:<br />
<br />
<pre><code>
<a href="/control.action">Action control</a>
</code></pre><br />
<br />
El cual ya no incluye la parte de "<span class="codigo">tags</span>".<br />
<br />
Ahora otra pregunta, ¿si tenemos dos <span class="codigo">Action</span>s con el mismo nombre, pero en dos namespaces distintos? Para esos casos podemos usar el atributo "<span class="codigo">namespace</span>". En él indicamos el namespace en el que se encuentra el <span class="codigo">Action</span> para el que queremos generar el enlace. Si, por ejemplo, quisiéramos un enlace para un <span class="codigo">Action</span> que se encuentra en el namespace "<span class="codigo">test</span>", lo haríamos de la siguiente forma:<br />
<pre><code>
<s:a action="control" namespace="test">Action control</s:a>
</code></pre><br />
Con lo cual el enlace queda de la siguiente forma:<br />
<pre><code>
<a href="test/control.action">Action control</a>
</code></pre><br />
Finalmente, si queremos que se ejecute un método particular del <span class="codigo">Action</span>, podemos indicarlo usando el atributo "<span class="codigo">method</span>" de la etiqueta. Para el ejemplo, como sólo tenemos el método "<span class="codigo">execute</span>" indicaremos que debe ser este método el que se ejecute:<br />
<pre><code>
<s:a action="control" method="execute">Action control</s:a>
</code></pre><br />
Con lo que el enlace generado queda de la siguiente forma:<br />
<pre><code>
<a href="/tags/control!execute.action">Action control</a>
</code></pre><br />
Podemos ver el uso de esta etiqueta es muy sencillo y tiene muchos atributos que nos permiten indicar exactamente cómo debe ser el enlace que queremos que se genere.<br />
<br />
Ahora veremos cómo trabajar con la siguiente etiqueta.<br />
<br />
<h3 class="subtitulo"><a href="http://www.blogger.com/null" id="url"><span class="codigo">url</span></a></h3>Se usa para crear una <span class="codigo">URL</span> que puede ser usada en... bueno, en cualquier lado donde se use una <span class="codigo">URL</span>, por ejemplo para indicar dónde se encuentra una imagen, otro sitio web, para crear un enlace, etc.<br />
<br />
Al igual que con la etiqueta "<span class="codigo">s:a</span>", si queremos pasar algún parámetro podemos hacerlo con la etiqueta "<span class="codigo">s:param</span>".<br />
<br />
Muchos de los atributos de esta etiqueta son similares a los de la etiqueta "<span class="codigo">s:a</span>", ya que el propósito de estos es generar una <span class="codigo">URL</span> acertada para lo que estemos haciendo. No veremos ejemplos de todos los atributos de esta etiqueta ya que, de alguna manera, ya los vimos en los de la etiqueta "<span class="codigo">s:a</span>".<br />
<br />
Los atributos de esta etiqueta se muestran en la siguiente tabla:<br />
<br />
<table><thead>
<tr> <th>Nombre</th><th>Obligatorio</th><th>Valor Default</th><th>Tipo</th><th>Descripción</th> </tr>
</thead> <tbody>
<tr> <td><span class="codigo"><span class="codigo">action</span></span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>El <span class="codigo">Action</span> para el cual se generará el enlace.</td> </tr>
<tr> <td><span class="codigo">encode</span></td><td>No</td><td><span class="codigo">true</span></td><td><span class="codigo">Boolean</span></td><td>Indica si es necesario codificar los parámetros que se envían en el enlace.</td> </tr>
<tr> <td><span class="codigo">escapeAmp</span></td><td>No</td><td><span class="codigo">true</span></td><td><span class="codigo">Boolean</span></td><td>Especifica si se debe escapar el símbolo ampersand (<span class="codigo">&</span>) como <span class="codigo">&amp;</span> o no.</td> </tr>
<tr> <td><span class="codigo">forceAddSchemeHostAndPort</span></td><td>No</td><td><span class="codigo">false</span></td><td><span class="codigo">Boolean</span></td><td>Indica si se debe forzar el agregar el esquema, el host y el puerto en el enlace que se genera.</td> </tr>
<tr> <td><span class="codigo">includeContext</span></td><td>No</td><td><span class="codigo">true</span></td><td><span class="codigo">Boolean</span></td><td>Si el contexto actual debe ser incluido en la <span class="codigo">URL</span>.</td> </tr>
<tr> <td><span class="codigo">includeParams</span></td><td>No</td><td><span class="codigo">none</span></td><td><span class="codigo">String</span></td><td>Puede tener los valores "<span class="codigo">none</span>", "<span class="codigo">get</span>", y "<span class="codigo">all</span>", indicando qué parámetros deben ser incluidos en el enlace.</td> </tr>
<tr> <td><span class="codigo">namespace</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>El namespace que se usará al generar el enlace (en caso de que sea para un <span class="codigo">Action</span>).</td> </tr>
<tr> <td><span class="codigo">method</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>El método del <span class="codigo">Action</span> que se invocará.</td> </tr>
<tr> <td><span class="codigo">value</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>La dirección a la que apuntará esta <span class="codigo">URL</span> (si no se usa el atributo "<span class="codigo">action</span>").</td> </tr>
<tr> <td><span class="codigo">var</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>El nombre de la variable que será usada para referir a la <span class="codigo">URL</span>. Esta variable es puesta en la cima del <span class="codigo">ValueStack</span></td></tr>
<tr> <td colspan="5"><span class="codigo"><br />
anchor, portletMode, portletUrlType, scheme, windowState</span></td> </tr>
</tbody> </table><br />
<br />
Para el primer ejemplo de esta etiqueta necesitaremos una imagen que mostraremos al usuario. En mi caso usaré esta imagen:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj7WH-6UNBh3p7huiaG5J8g64nkTKV1f-GrC-WAjVH0ef4s2uzHdQBj7z18SbtBps8o88hv7Z94gNnWPa2OglETjrdYom81nMAjl9b8NnhspDyB9tLUI6T5__esDTwas6oNJAz-yWwAp7pt/s1600/JT_logo.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj7WH-6UNBh3p7huiaG5J8g64nkTKV1f-GrC-WAjVH0ef4s2uzHdQBj7z18SbtBps8o88hv7Z94gNnWPa2OglETjrdYom81nMAjl9b8NnhspDyB9tLUI6T5__esDTwas6oNJAz-yWwAp7pt/s400/JT_logo.png" /></a></div><br />
<br />
Esta imagen debe ser <span class="negritas">accesible desde el navegador</span>, por lo tanto lo colocaremos dentro del proyecto web, pero fuera del directorio <span class="codigo">WEB-INF</span>. En mi caso crearé un nuevo directorio llamado "<span class="codigo">img</span>":<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiwe82scwFfTnBNwiRGSTMFqjFLurK-9P1ao0XyeZ3rkw8bc0Ss7PI9lIY7G9PoRmN_uIOJWC3WsV6yCpoXLin0uoCosF4P724ON-KvjGo-CGcxMTva5IImpdaXUyM6solBahZlQl7AAOlf/s1600/S7_32.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiwe82scwFfTnBNwiRGSTMFqjFLurK-9P1ao0XyeZ3rkw8bc0Ss7PI9lIY7G9PoRmN_uIOJWC3WsV6yCpoXLin0uoCosF4P724ON-KvjGo-CGcxMTva5IImpdaXUyM6solBahZlQl7AAOlf/s400/S7_32.png" /></a></div><br />
<br />
Dentro de este directorio colocamos la imagen de la cual crearemos el enlace.<br />
<br />
Ahora que tenemos la imagen, podemos crear una <span class="codigo">URL</span> que apunte a ella y esta se muestre en el navegador, de la siguiente forma:<br />
<br />
<pre><code>
<s:url value="/img/JT_logo.png" />
</code></pre><br />
<br />
Si colocamos esta etiqueta dentro del atributo "<span class="codigo">src</span>" de una etiqueta "<span class="codigo">img</span>" de <span class="codigo">HTML</span>, de la siguiente forma:<br />
<br />
<pre><code>
<img src="<s:url value="/img/JT_logo.png" />" alt="logo de Java Tutoriales" />
</code></pre><br />
<br />
Esto nos genera la siguiente salida:<br />
<br />
<pre><code>
<img src="/tags/img/JT_logo.png" alt="logo de Java Tutoriales" />
</code></pre><br />
<br />
Que se ve así en el navegador:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjjgmM6C39AuAmRxeK5vv5GsVyLUNV6lH85AMGM21NtHztC6qKxcg0_oEZmZkf3peCUt1JFweyeE8Qq8Xhr0jYrYDKXbJ7hQIOd7JnpvpuW8grApfs16jJWA15yA71MxR2w9PUhS6lRqFAr/s1600/S7_33.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjjgmM6C39AuAmRxeK5vv5GsVyLUNV6lH85AMGM21NtHztC6qKxcg0_oEZmZkf3peCUt1JFweyeE8Qq8Xhr0jYrYDKXbJ7hQIOd7JnpvpuW8grApfs16jJWA15yA71MxR2w9PUhS6lRqFAr/s400/S7_33.png" /></a></div><br />
<br />
Algunos se preguntarán: ¿No es lo mismo que escribir directamente la ruta de la imagen? La respuesta es: en este caso sí, es lo mismo. A lo que se preguntarán: ¿Entonces por qué debería usar la etiqueta "<span class="codigo">s:url</span>" para esto, qué ventaja tiene? Y la respuesta es: Para este ejemplo que acabamos de ver: ninguna. Sin embargo la ventaja viene cuando debemos hacer algo más complejo, como por ejemplo pasar ciertos parámetros dinámicos a la <span class="codigo">URL</span> (digamos el id de una sesión o de un usuario).<br />
<br />
Para comprobar lo anterior podemos usar el atributo "<span class="codigo">includeParams</span>". Este actúa exactamente igual que el de la etiqueta "<span class="codigo">s:a</span>": coloca en el enlace los parámetros que le indiquemos ("<span class="codigo">none</span>", "<span class="codigo">get</span>" y "<span class="codigo">all</span>"). Si cambiamos la etiqueta de esta forma:<br />
<br />
<pre><code>
<img src="<s:url value="/img/JT_logo.png" includeParams="get" />" alt="logo de Java Tutoriales" />
</code></pre><br />
<br />
Y pasamos algunos parámetros a la URL, digamos que entramos de la siguiente manera:<br />
<br />
<pre><code>
<a href="http://localhost:8080/tags/datos.jsp?param1=a&param2=b">http://localhost:8080/tags/datos.jsp?param1=a&param2=b</a>
</code></pre><br />
<br />
La etiqueta generada quedará de la siguiente forma:<br />
<br />
<pre><code>
<img src="/tags/img/JT_logo.png?param1=a&param2=b" alt="logo de Java Tutoriales" />
</code></pre><br />
<br />
Es en estos casos donde la etiqueta "<span class="codigo">s:url</span>" puede ayudarnos (además de las necesidades particulares que nos resuelven sus demás atributos).<br />
<br />
Ahora, de los ejemplos anteriores algunos podrían estarse preguntando, si lo único que hacemos es colocar la etiqueta "<span class="codigo">s:url</span>" en el atributo "<span class="codigo">src</span>" de la etiqueta "<span class="codigo">img</span>" para que este muestre la <span class="codigo">URL</span> indicada, ¿eso quiere decir que la etiqueta "<span class="codigo">s:url</span>" imprime la <span class="codigo">URL</span> en la página de salida? Y la respuesta es: si no especificamos nada en el atributo "<span class="codigo">var</span>", sí. De lo contrario no mostrará nada y colocará la <span class="codigo">URL</span> en la variable que le indiquemos. Por lo tanto, si simplemente colocamos la etiqueta de esta forma:<br />
<br />
<pre><code>
<s:url value="/img/JT_logo.png" />
</code></pre><br />
<br />
Veremos la siguiente salida en la pantalla:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiWBkwLp0BZW33zriTumuSaOIOIRDVRkkYfQuGz9AYQE5I5lrn7a3NW1DpKmvXT0nugYFFa-d4q6ZyFk7-GRIlIU7GWBawL0msRfAH0ZArc7ElLtAGwmoc10dV0hifiPC5kae7wM5Fhy1Sf/s1600/S7_34.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiWBkwLp0BZW33zriTumuSaOIOIRDVRkkYfQuGz9AYQE5I5lrn7a3NW1DpKmvXT0nugYFFa-d4q6ZyFk7-GRIlIU7GWBawL0msRfAH0ZArc7ElLtAGwmoc10dV0hifiPC5kae7wM5Fhy1Sf/s400/S7_34.png" /></a></div><br />
<br />
Eso quiere decir que podemos usarla para generar un enlace a cualquier recurso que queramos. Imaginemos que queremos generar un enlace a Google para realizar una búsqueda, podemos hacerlo de la siguiente forma:<br />
<br />
<pre><code>
<s:url value="https://www.google.com">
</s:url>
</code></pre><br />
<br />
<span class="nota">*Nota: En los ejemplos anteriores, al crear el enlace, la etiqueta "<span class="codigo">s:url</span>" coloca el contexto de nuestra aplicación (a menos que le indiquemos lo contrario con el parámetro "<span class="codigo">includeContext</span>"). Sin embargo, cuando en una dirección colocamos "<span class="codigo">www</span>", "<span class="codigo">http</span>" o "<span class="codigo">https</span>", el enlace generado <span class="negritas">NO</span> incluirá el contexto.</span><br />
<br />
El parámetro con el que Google realiza sus búsquedas es "<span class="codigo">q</span>", supongo que por "<span class="codigo">query</span>". Así que colocamos un parámetro, con la etiqueta "<span class="codigo">s:param</span>", cuyo nombre sea "<span class="codigo">q</span>" y como valor tenga "<span class="codigo">java+tutoriales</span>", o cualquier otro valor que deseen:<br />
<br />
<pre><code>
<s:url value="https://www.google.com">
<s:param name="q" value="'Java+tutoriales'" />
</s:url>
</code></pre><br />
<br />
Si dejamos la etiqueta de la forma anterior terminaremos con la <span class="codigo">URL</span> impresa en el <span class="codigo">HTML</span> de salida. Lo que queremos es pasarla a un enlace para que el usuario pueda hacer clic en él, por lo tanto usaremos el atributo "<span class="codigo">var</span>" para crear una variable que haga referencia a la <span class="codigo">URL</span> que acabamos de crear:<br />
<br />
<pre><code>
<s:url value="https://www.google.com" var="enlace" >
<s:param name="q" value="'Java+tutoriales'" />
</s:url>
</code></pre><br />
<br />
La variable en este caso es "<span class="codigo">enlace</span>" y podemos pasarla directamente a una etiqueta "<span class="codigo">a</span>", usando la etiqueta "<span class="codigo">s:property</span>" o a una etiqueta "<span class="codigo">s:a</span>" usando su atributo "<span class="codigo">value</span>". Para ejemplificar lo haremos de las dos formas. Primero usando una etiqueta "<span class="codigo">a</span>" de <span class="codigo">HTML</span>. Para eso, debemos colocar en su atributo "<span class="codigo">href</span>" el valor de la variable "<span class="codigo">enlace</span>", usando una etiqueta "<span class="codigo">s:property</span>", de la siguiente forma:<br />
<br />
<pre><code>
<a href="<s:property value="enlace" />" target="_blank" >Búsqueda en Google</a>
</code></pre><br />
<br />
Con el valor "<span class="codigo">_blank</span>" en el atributo "<span class="codigo">target</span>", logramos que el enlace se abra en una nueva ventana o pestaña, depende de cómo tengamos configurado el navegador.<br />
<br />
La etiqueta anterior genera el siguiente enlace:<br />
<br />
<pre><code>
<a href="https://www.google.com?q=Java%2Btutoriales" target="_blank" >Búsqueda en Google</a>
</code></pre><br />
<br />
Que se ve de la siguiente forma en el navegador:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEghtJRq3karncNRO71HRrgRtAiCraG-Y0cK5F9ZEztv1-c3cnaAugL3RGYrpkuqBLjhYYjtMFlakIixihS2Bbtj2AjsrtoT7Req99eh3SSA-tt72GEgRk-yPU503dDXmFAWxPXVQEsgwbZR/s1600/S7_35.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEghtJRq3karncNRO71HRrgRtAiCraG-Y0cK5F9ZEztv1-c3cnaAugL3RGYrpkuqBLjhYYjtMFlakIixihS2Bbtj2AjsrtoT7Req99eh3SSA-tt72GEgRk-yPU503dDXmFAWxPXVQEsgwbZR/s400/S7_35.png" /></a></div><br />
<br />
Si hacemos clic en el enlace se abrirá una nueva pestaña con una búsqueda en Google para Java Tutoriales.<br />
<br />
Ahora veremos cómo usar la etiqueta "<span class="codigo">s:a</span>" para lograr la misma salida. En este caso debemos pasar el valor de la variable "<span class="codigo">enlace</span>" al atributo "<span class="codigo">value</span>", y debemos decirle que evalúe el contenido de esa variable, en otras palabras debemos colocarla entre los símbolos "<span class="codigo">%{"</span> y "<span class="codigo">}</span>", de la siguiente forma:<br />
<br />
<pre><code>
<s:a value="%{enlace}">Búsqueda en Google</s:a>
</code></pre><br />
<br />
Al hacer esto obtenemos el mismo enlace de la vez anterior:<br />
<br />
<pre><code>
<a href="https://www.google.com?q=Java%2Btutoriales" target="_blank" >Búsqueda en Google</a>
</code></pre><br />
<br />
Y por lo tanto la misma salida en pantalla.<br />
<br />
Podemos ver que esta etiqueta es muy flexible en cuanto a la generación de <span class="codigo">URL</span>s a la medida se refiere. Incluso podemos crear un enlace a un <span class="codigo">Action</span> de nuestra aplicación (usando el atributo "<span class="codigo">action</span>" de la etiqueta) y pasarle parámetros a través de la etiqueta "<span class="codigo">s:param</span>". Como ya vimos la manera de hacer esto con la etiqueta "<span class="codigo">s:a</span>" no lo volveremos a hacer aquí, pero queda al lector realizar este ejemplo si así lo desea.<br />
<br />
Ahora veremos cómo trabajar con la siguiente etiqueta de control.<br />
<br />
<h3 class="subtitulo"><a href="http://www.blogger.com/null" id="action"><span class="codigo">action</span></a></h3>Esta etiqueta permite ejecutar directamente un <span class="codigo">Action</span> dentro de una página <span class="codigo">JSP</span>. El método execute se invocará al encontrar la etiqueta de cierre, por lo que tendremos disponibles los valores del <span class="codigo">Action</span> después de la etiqueta.<br />
<br />
Aquí hay algo importante que explicar. Sabemos que todos los <span class="codigo">Action</span>s tienen un <span class="codigo">result</span> el cual normalmente nos envía a una nueva <span class="codigo">JSP</span>. Sin embargo esta etiqueta nos permite ejecutar un <span class="codigo">Action</span> dentro de una <span class="codigo">JSP</span>... entonces se preguntarán, ¿qué pasa con el <span class="codigo">result</span> del <span class="codigo">Action</span> que estamos ejecutando? Bien pues el <span class="codigo">result</span> del <span class="codigo">Action</span> se ignora dentro de la <span class="codigo">JSP</span> a menos que indiquemos que queremos que este sea mostrado. Para eso tenemos un parámetro llamado "<span class="codigo">executeResult</span>". Dentro de los ejemplos quedará más claro cómo funciona esto.<br />
<br />
Los atributos de esta etiqueta se muestran en la siguiente tabla:<br />
<br />
<table><thead>
<tr> <th>Nombre</th><th>Obligatorio</th><th>Valor Default</th><th>Tipo</th><th>Descripción</th> </tr>
</thead> <tbody>
<tr> <td><span class="codigo">name</span></td><td>Si</td><td> </td><td><span class="codigo">String</span></td><td>Nombre del <span class="codigo">Action</span> que será ejecutado (el nombre como está en la anotación o en el archivo de configuración).</td> </tr>
<tr> <td><span class="codigo">namespace</span></td><td>No</td><td>El namespace de la página donde se usa la etiqueta</td><td><span class="codigo">String</span></td><td>Namespace en el que se encuentra el <span class="codigo">Action</span>.</td> </tr>
<tr> <td><span class="codigo">executeResult</span></td><td>No</td><td><span class="codigo">false</span></td><td><span class="codigo">Boolean</span></td><td>Si el resultado de este <span class="codigo">Action</span> debe ser mostrado (la <span class="codigo">JSP</span> con el resultado).</td> </tr>
<tr> <td><span class="codigo">flush</span></td><td>No</td><td><span class="codigo">true</span></td><td><span class="codigo">Boolean</span></td><td>Si se le debe hacer flush al buffer de escritura al final de la etiqueta.</td> </tr>
<tr> <td><span class="codigo">ignoreContextParams</span></td><td>No</td><td><span class="codigo">false</span></td><td><span class="codigo">Boolean</span></td><td>Si los parámetros de la petición actual deben ser incluidos cuando la acción sea invocada.</td> </tr>
<tr> <td><span class="codigo">rethrowException</span></td><td>No</td><td><span class="codigo">false</span></td><td><span class="codigo">Boolean</span></td><td>En caso de que ocurra una excepción, si esta debe ser lanzada nuevamente.</td> </tr>
<tr> <td><span class="codigo">var</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>El nombre de la variable que será usada para referir al <span class="codigo">Action</span> después de su ejecución. Esta variable es puesta en la cima del <span class="codigo">ValueStack</span>.</td> </tr>
</tbody> </table><br />
<br />
Como podemos ver, esta etiqueta tiene pocos atributos, por lo que es muy sencilla de utilizar.<br />
<br />
Comencemos con el ejemplo. Como el único <span class="codigo">Action</span> que tenemos creado en este momento es el "<span class="codigo">ControlAction</span>", cuyo nombre es "<span class="codigo">control</span>", así que será este el que usaremos para el ejemplo.<br />
<br />
Lo primero que debemos hacer es colocar la etiqueta "<span class="codigo"><s:action></span>" indicando que el nombre del <span class="codigo">Action</span> que ejecutaremos será "<span class="codigo">control</span>". También debemos indicar el "<span class="codigo">namespace</span>" en que se encuentra este <span class="codigo">Action</span>, en este caso "<span class="codigo">/</span>":<br />
<br />
<pre><code>
<s:action name="control" namespace="/">
</s:action>
</code></pre><br />
<br />
También es necesario que indiquemos el nombre de la variable con la que haremos referencia a este <span class="codigo">Action</span>, usando el atributo "<span class="codigo">var</span>". En mi caso la variable será "<span class="codigo">controlAction</span>":<br />
<br />
<pre><code>
<s:action name="control" namespace="/" var="controlAction">
</s:action>
</code></pre><br />
<br />
Con esto tendremos a nuestra disposición el resultado del <span class="codigo">Action</span>, a través de la variable "<span class="codigo">controlAction</span>", pero sólo en el cierre de la etiqueta. Para comprobar esto colocaremos, tanto dentro del cuerpo de la etiqueta como fuera de él, una validación que nos indicará si la variable "<span class="codigo">controlAction</span>" es nula:<br />
<br />
<pre><code>
<s:property value="#controlAction == null" />
</code></pre><br />
<br />
De esta manera:<br />
<br />
<pre><code>
<s:action name="control" namespace="/" var="controlAction">
Dentro: <s:property value="#controlAction == null" /><br />
</s:action>
Fuera: <s:property value="#control<span class="codigo">Action</span> == null" />
</code></pre><br />
<br />
Al ejecutar la aplicación debemos ver la siguiente salida:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiMGTJRnYL105MzrLqrzcRUF2jMAnMiHbZ_MsPy6DFegKPKmTuc95nd1tbgp0t_98wdaWLtcYNimhm6Ur0aQeWc8weEW0bBCfNrPonGOvUmlDmWiHGqzAm-eSuXRCTc8NE3KQq8UUwk-L0m/s1600/S7_18.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiMGTJRnYL105MzrLqrzcRUF2jMAnMiHbZ_MsPy6DFegKPKmTuc95nd1tbgp0t_98wdaWLtcYNimhm6Ur0aQeWc8weEW0bBCfNrPonGOvUmlDmWiHGqzAm-eSuXRCTc8NE3KQq8UUwk-L0m/s400/S7_18.png" /></a></div><br />
<br />
Como podemos ver en la imagen anterior, la variable dentro del cuerpo de la etiqueta es nula, mientras que afuera de este deja de serlo. Por lo tanto, podemos comprobar que podremos hacer uso de esta variable hasta después de la etiqueta de cierre.<br />
<br />
Entonces ¿qué es lo que podemos colocar en el cuerpo de la etiqueta? Pues bien, es aquí donde colocamos parámetros que queramos pasar al <span class="codigo">Action</span>.<br />
<br />
Para ver un ejemplo debemos modificar un poco la clase "<span class="codigo">ControlAction</span>" para agregar un parámetro que podamos establecer. Para no complicar mucho las cosas, agregaremos un atributo, junto con sus <span class="codigo">setters</span> y <span class="codigo">getters</span>, de tipo <span class="codigo">String</span> llamado "<span class="codigo">parametro</span>":<br />
<br />
<pre><code>
private String parametro;
public String getParametro() {
return parametro;
}
public void setParametro(String parametro) {
this.parametro = parametro;
}
</code></pre><br />
<br />
Ahora, en la etiqueta, establecemos el valor de "<span class="codigo">parametro</span>" usando la etiqueta "<span class="codigo">s:param</span>". En su atributo "<span class="codigo">name</span>" colocamos el nombre del parámetro que queremos establecer, en este caso "<span class="codigo">parametro</span>", y en su atributo "<span class="codigo">value</span>" el valor que queramos colocarle al parámetro. Recuerden que si le pasamos una cadena, debemos encerrar esta entre comillas (ya sean dobles o simpes):<br />
<br />
<pre><code>
<s:action name="control" namespace="/" var="controlAction">
<s:param name="parametro" value="%{'abc123'}" />
</s:action>
</code></pre><br />
<br />
Ahora, para mostrar el valor de este parámetro, debemos usar la variable que definimos en el atributo "<span class="codigo">var</span>", seguido del nombre de atributo que queremos mostrar:<br />
<br />
<pre><code>
<s:action name="control" namespace="/" var="controlAction">
<s:param name="parametro" value="%{'abc123'}" />
</s:action>
Parametro: <s:property value="#control<span class="codigo">Action</span>.parametro" />
</code></pre><br />
<br />
Si ejecutamos la aplicación debemos ver la siguiente salida:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhmzoNOrCthlWMmCFGYy-bLxuiKYVlH3_HA9SSNAMbLtGSs7D-UzRLhK9c07ck3cF4p6NMabrTsS1119ZWkQjzZEO54dtXtrnEpEZhveNVFTdzrmp0lPbXK8Y94eSxPNOYS2gisTfqS_6br/s1600/S7_19.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhmzoNOrCthlWMmCFGYy-bLxuiKYVlH3_HA9SSNAMbLtGSs7D-UzRLhK9c07ck3cF4p6NMabrTsS1119ZWkQjzZEO54dtXtrnEpEZhveNVFTdzrmp0lPbXK8Y94eSxPNOYS2gisTfqS_6br/s400/S7_19.png" /></a></div><br />
<br />
Como podemos ver, de esta forma podemos acceder a cualquier parámetro del <span class="codigo">Action</span>. Por ejemplo, para obtener la lista de usuarios del <span class="codigo">Action</span>, lo hacemos de la siguiente forma:<br />
<br />
<pre><code>
Usuarios: <s:property value="#controlAction.usuarios" />
</code></pre><br />
<br />
Incluso podemos pasar estos valores a otras etiquetas:<br />
<br />
<pre><code>
<s:iterator value="#controlAction.usuarios">
<s:property value="nombre" />,
</s:iterator>
</code></pre><br />
<br />
Con el código anterior, si ejecutamos la aplicación, debemos ver la siguiente salida:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhT7oPfbAl3qGTdQFxkTwgJP2oa6LrZnss9oAyO7AF-pdu70s0_cMyS6VW0bDCZTX_TJr77cIRRlY-4R_3BAmfbwnCQrdZRH9yFnw3eZoQJo-tckwFp-b1k9PLgpe0C-HHDUyYdmzVMXN5Y/s1600/S7_20.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhT7oPfbAl3qGTdQFxkTwgJP2oa6LrZnss9oAyO7AF-pdu70s0_cMyS6VW0bDCZTX_TJr77cIRRlY-4R_3BAmfbwnCQrdZRH9yFnw3eZoQJo-tckwFp-b1k9PLgpe0C-HHDUyYdmzVMXN5Y/s400/S7_20.png" /></a></div><br />
<br />
Habíamos dicho anteriormente que es posible mostrar el resultado original del <span class="codigo">Action</span> que estamos ejecutando. Para eso debemos colocar el atributo "<span class="codigo">executeResult</span>" de la etiqueta "<span class="codigo">s:action</span>". El resultado será colocado en el lugar exacto en el que se haya colocado la etiqueta.<br />
<br />
<pre><code>
<s:action name="control" namespace="/" var="controlAction" executeResult="true" >
<s:param name="parametro" value="%{'abc123'}" />
</s:action>
</code></pre><br />
<br />
Como el resultado de la página anterior era la salida de la etiqueta "<span class="codigo">s:subset</span>", si ejecutamos la aplicación debemos ver la siguiente salida:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjFSDD3tfiKM9Duhvee37_ZJdCf62X_NFMrbcJrjMU2cRaK_q3if_BJzhwcAgBf3VElMjd0YA5MblKqH1hwihKmAA0AsQfVH04y5aIas32xFR1NA5g5odQE4kiZuHveideAw2a42R6EQkMn/s1600/S7_21.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjFSDD3tfiKM9Duhvee37_ZJdCf62X_NFMrbcJrjMU2cRaK_q3if_BJzhwcAgBf3VElMjd0YA5MblKqH1hwihKmAA0AsQfVH04y5aIas32xFR1NA5g5odQE4kiZuHveideAw2a42R6EQkMn/s400/S7_21.png" /></a></div><br />
<br />
Hemos visto cómo trabajar con instancias de clases <span class="codigo">Action</span> de <span class="codigo">Struts 2</span>, pero ¿qué pasa si queremos crear instancias de otras clases? Para ese caso podemos hacer uso de otra etiqueta: <span class="codigo">bean</span><br />
<br />
<h3 class="subtitulo"><a href="http://www.blogger.com/null" id="bean"><span class="codigo">bean</span></a></h3>Instancia una clase que siga la especificación <span class="codigo">JavaBeans</span>.<br />
<br />
Esta bean <span class="negritas">NO</span> se coloca en la cima del <span class="codigo">stack</span>, por lo que para hacer referencia al bean debemos usar el nombre que le demos, junto con el operador número (<span class="codigo">#</span>).<br />
<br />
Si queremos establecer algún parámetro del bean, podemos hacerlo en el cuerpo de la etiqueta.<br />
<br />
Los atributos de esta etiqueta se muestran en la siguiente tabla:<br />
<br />
<table><thead>
<tr> <th>Nombre</th><th>Obligatorio</th><th>Valor Default</th><th>Tipo</th><th>Descripción</th> </tr>
</thead> <tbody>
<tr> <td><span class="codigo">name</span></td><td>Si</td><td> </td><td><span class="codigo">String</span></td><td>El nombre completo (incluyendo paquete) de la clase que será instanciada.</td> </tr>
<tr> <td><span class="codigo">var</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>El nombre de la variable que será usada para referir al Bean.</td> </tr>
</tbody> </table><br />
<br />
Para ver un ejemplo de esta etiqueta usaremos nuestra clase "<span class="codigo">Usuario</span>", la cual conforma la especificación <span class="codigo">JavaBean</span>. En el atributo "<span class="codigo">name</span>" de la etiqueta "<span class="codigo">s:bean</span>" debemos colocar el nombre completo de la clase, en este caso "<span class="codigo">com.javatutoriales.struts2.tags.modelo.Usuario</span>":<br />
<br />
<pre><code>
<s:bean name="com.javatutoriales.struts2.tags.modelo.Usuario">
</s:bean>
</code></pre><br />
<br />
Y en el atributo "<span class="codigo">var</span>" colocamos el nombre que tendrá la variable con la que haremos referencia a este bean, en mi caso lo llamaré "<span class="codigo">beanUsuario</span>":<br />
<br />
<pre><code>
<s:bean name="com.javatutoriales.struts2.tags.modelo.Usuario" var="beanUsuario">
</s:bean>
</code></pre><br />
<br />
Esta clase tiene dos atributos: "<span class="codigo">nombre</span>" y "<span class="codigo">edad</span>". Establecemos estos atributos dentro del cuerpo de la etiqueta "<span class="codigo">s:bean</span>" usando la etiqueta "<span class="codigo">s:param</span>":<br />
<br />
<pre><code>
<s:bean name="com.javatutoriales.struts2.tags.modelo.Usuario" var="beanUsuario">
<s:param name="nombre" value="%{'Alex'}" />
<s:param name="edad" value="%{123}" />
</s:bean>
</code></pre><br />
<br />
Ahora, mostramos los valores de los atributos del bean usando la etiqueta "<span class="codigo">s:property</span>", indicando la variable que hace referencia al bean y el nombre del atributo que queremos mostrar:<br />
<br />
<pre><code>
Nombre: <s:property value="#beanUsuario.nombre" /> <br />
Edad: <s:property value="#beanUsuario.edad" />
</code></pre><br />
<br />
Si ejecutamos el código anterior debemos ver la siguiente salida:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhpwh4NpGZrAniXYYS7Um8_m7_HdC7g8ykOSWobO95ocljZ1E8gczpHI8mwn8aLZB7damzB7cBNVX4WZlXqS7Z-jCESvQIQhobuQ9ffyujoWgg1DAQoGPdzUN5boUwQArWYYAeYnXDtIVIy/s1600/S7_22.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhpwh4NpGZrAniXYYS7Um8_m7_HdC7g8ykOSWobO95ocljZ1E8gczpHI8mwn8aLZB7damzB7cBNVX4WZlXqS7Z-jCESvQIQhobuQ9ffyujoWgg1DAQoGPdzUN5boUwQArWYYAeYnXDtIVIy/s400/S7_22.png" /></a></div><br />
<br />
Podemos ver que la etiqueta "<span class="codigo">s:bean</span>" es muy fácil de utilizar. Podemos usar cualquier clase que siga la especificación <span class="codigo">JavaBean</span>, como por ejemplo "<span class="codigo">java.util.Date</span>".<br />
<br />
Ahora veremos la siguiente etiqueta<br />
<br />
<h3 class="subtitulo"><a href="http://www.blogger.com/null" id="date"><span class="codigo">date</span></a></h3>Permite formatear los objetos <span class="codigo">Date</span> de distintas maneras. Es posible especificar un formato propio como por ejemplo "<span class="codigo">dd/MM/yyyy hh:mm</span>", con una notación agradable (nice attribute) como "<span class="codigo">en 2 horas, 14 minutos</span>", o podemos usar un formato predefinido para todas las fechas de la aplicación, la cual se coloca en una de las propiedades con prefijo "<span class="codigo">struts.date.format</span>" (más adelante veremos estas propiedades).<br />
<br />
Los atributos de esta etiqueta se muestran en la siguiente tabla:<br />
<br />
<table><thead>
<tr> <th>Nombre</th><th>Obligatorio</th><th>Valor Default</th><th>Tipo</th><th>Descripción</th> </tr>
</thead> <tbody>
<tr> <td><span class="codigo">name</span></td><td>Si</td><td> </td><td><span class="codigo">String</span></td><td>El nombre del objeto tipo "<span class="codigo">java.util.Date</span>" al que se le dará formato.</td> </tr>
<tr> <td><span class="codigo">format</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>El formato que se le dará a la fecha.</td> </tr>
<tr> <td><span class="codigo">nice</span></td><td>No</td><td> </td><td><span class="codigo">Boolean</span></td><td>Si se debe imprimir la fecha con un formato "agradable"</td> </tr>
<tr> <td><span class="codigo">timezone</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>El timezone en el que se debe formatear la fecha.</td> </tr>
<tr> <td><span class="codigo">var</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>El nombre de la variable que será usada para referir a la fecha con formato. Esta variable es puesta en la cima del <span class="codigo">ValueStack</span>.</td> </tr>
</tbody> </table><br />
<br />
Para mostrar el uso de esta etiqueta, será necesario crear una pequeña clase de utilidad, la cual tendrá la función de regresar objetos de tipo "<span class="codigo">java.util.Date</span>" a través de sus distintos métodos. Esta case estará el paquete "<span class="codigo">com.javatutoriales.struts2.tags.util</span>" (el cual aún no existe, por lo que deberán crearlo previamente) y tendrá el nombre de "<span class="codigo">Fechas</span>".<br />
<br />
El primer método que pondremos a esta clase nos permitirá pasar un día, mes y año, y nos regresará un objeto "<span class="codigo">java.util.Date</span>" que represente esa fecha. Para esto es necesario hacer uso de la clase "<span class="codigo">java.util.Calendar</span>", de la siguiente forma:<br />
<br />
<pre><code>
private Date getDate(int anio, int mes, int dia) {
Calendar calendario = Calendar.getInstance();
calendario.setTime(new java.util.Date());
calendario.set(Calendar.YEAR, anio);
calendario.set(Calendar.MONTH, mes-1);
calendario.set(Calendar.DAY_OF_MONTH, dia);
return calendario.getTime();
}
</code></pre><br />
<br />
Ahora crearemos un segundo método que nos regrese la fecha "actual", y digo "actual" porque la fecha que regresaremos será el día que se colocó el primer tutorial de <span class="codigo">Struts 2</span>, o sea el 19 de Junio del 2011:<br />
<br />
<pre><code>
public Date getFechaActual()
{
return getDate(2011, 6, 19);
}
</code></pre><br />
<br />
La clase "<span class="codigo">Fecha</span>" de momento se ve así:<br />
<br />
<pre><code>
public class Fechas {
public Date getFechaActual() {
return getDate(2011, 6, 19);
}
private Date getDate(int anio, int mes, int dia) {
Calendar calendario = Calendar.getInstance();
calendario.setTime(new java.util.Date());
calendario.set(Calendar.YEAR, anio);
calendario.set(Calendar.MONTH, mes-1);
calendario.set(Calendar.DAY_OF_MONTH, dia);
return calendario.getTime();
}
}
</code></pre><br />
<br />
Iremos agregando más métodos a esta clase conforme avancemos en el uso de la etiqueta "<span class="codigo">date</span>".<br />
<br />
Para usar esta etiqueta haremos uso de <span class="codigo">OGNL</span>, como lo vimos en <a href="http://www.javatutoriales.com/2011/06/struts-2-parte-2-ognl.html">el segundo tutorial de la serie</a>.<br />
<br />
Ahora veamos cómo trabaja el atributo "<span class="codigo">format</span>". Este atributo funciona usando los patrones definidos en la clase <a href="http://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html">"<span class="codigo">SimpleDateFormat</span>" de java</a>. Con estos podemos especificar cualquier formato de fecha y hora que deseemos para que se ajuste a nuestras necesidades.<br />
<br />
Primero colocaremos la etiqueta pasando a su parámetro "<span class="codigo">name</span>" el objeto date al que le daremos formato. Como nosotros no tenemos ningún objeto "<span class="codigo">Date</span>" en el <span class="codigo">ValueStack</span> debemos pasar, en vez del nombre del objeto "<span class="codigo">Date</span>", una expresión en <span class="codigo">OGNL</span> para crear ese objeto (pueden ver una explicación más detallada de por qué funciona esto en el segundo tutorial de la serie). Nosotros invocaremos al método "<span class="codigo">getFechaActual</span>" de la clase "<span class="codigo">Fechas</span>":<br />
<br />
<pre><code>
<s:date name="new com.javatutoriales.struts2.tags.util.Fechas().getFechaActual()"/>
</code></pre><br />
<br />
Ahora en el parámetro "<span class="codigo">format</span>" pasaremos el formato que queramos darle al objeto "<span class="codigo">Date</span>" (la cual incluye fecha y hora). En este atributo debemos colocar las letras del patrón que queremos poner.<br />
<br />
Veremos una serie de pequeños ejemplos para ver las salidas que se generan con los distintos patrones (pueden ver la lista completa de letras de patrones y una explicación de cada uno en la documentación de la clase <a href="http://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html">SimpleDateFormat</a>.<br />
<br />
Si queremos mostrar la fecha en un formato de <span class="codigo">dd/mm/yyyy</span>, en donde el mes se muestre como número, debemos colocar el siguiente patrón:<br />
<br />
<pre><code>
dd/MM/yyyy
</code></pre><br />
<br />
Con lo que la fecha se mostrará de la siguiente forma:<br />
<br />
<pre><code>
19/06/2011
</code></pre><br />
<br />
Si queremos mostrar lo mismo, pero mostrando el nombre del mes, usamos el siguiente patrón:<br />
<br />
<pre><code>
dd/MMMM/yyyy
</code></pre><br />
<br />
Con lo que la fecha se muestra de la siguiente forma:<br />
<br />
<pre><code>
19/junio/2011
</code></pre><br />
<br />
Si en vez de las diagonales queremos usar la palabra "de" como separador, usamos el siguiente patrón:<br />
<br />
<pre><code>
dd 'de' MMMM 'de' yyyy
</code></pre><br />
<br />
La fecha se muestra de la siguiente forma:<br />
<br />
<pre><code>
19 de junio de 2011
</code></pre><br />
<br />
Ahora vamos con la hora. Si queremos mostrar la hora en formato de 0 a 24 hrs, usamos el siguiente patrón:<br />
<br />
<pre><code>
HH:mm
</code></pre><br />
<br />
Con lo que la hora se formateará de la siguiente forma:<br />
<br />
<pre><code>
21:12
</code></pre><br />
<br />
Si queremos mostrarla en formato de 0 a 12, agregando la marca de am/pm, usamos el siguiente patrón:<br />
<br />
<pre><code>
KK:mm a
</code></pre><br />
<br />
Con lo que la fecha se muestra de la siguiente forma:<br />
<br />
<pre><code>
09:12 PM
</code></pre><br />
<br />
También podemos combinar los patrones de fecha y hora. Por ejemplo si usamos el siguiente patrón:<br />
<br />
<pre><code>
dd/MMM/yy, KK:mm a
</code></pre><br />
<br />
Se mostrará la fecha de la siguiente forma:<br />
<br />
<pre><code>
19/jun/11, 09:22 PM
</code></pre><br />
<br />
Creo que la idea ya quedó clara. Ahora podemos ver cómo funciona el atributo "<span class="codigo">nice</span>".<br />
<br />
Este atributo es muy interesante. Permite mostrar la fecha con algo que llaman formato "agradable" pero, ¿qué significa esto? Una forma de entender esto es que (y créanme que no tiene nada que ver con formato) es que muestra el tiempo, que ha paso o que falta, de la fecha a actual a la fecha que recibe como parámetro…. ¿y esto qué quiere decir?<br />
<br />
Básicamente lo que hace "<span class="codigo">nice</span>" es mostrar de una forma clara el resultado de operaciones entre fechas. Por ejemplo, si han pasado sólo unos segundos la cadena que se mostrará será algo como "un instante", si han paso 2 días se mostrará la cadena "2 días", si faltan 3 meses se mostrará la cadena "en 90 días". Esto quedará más claro con un ejemplo.<br />
<br />
Primero, crearemos un nuevo método en la clase "<span class="codigo">Fechas</span>" que nos regresará una fecha en el futuro. En este caso, la fecha del primero de enero del 2100. Agregamos este nuevo método en la clase "<span class="codigo">Fechas</span>":<br />
<br />
<pre><code>
public Date getFechaFutura()
{
return getDate(2100, 10, 19);
}
</code></pre><br />
<br />
Ahora, de regreso a la página "<span class="codigo">datos.jsp</span>", agregaremos dos etiquetas "<span class="codigo">s:date</span>", a la primera le pasaremos en su atributo "<span class="codigo">name</span>" una invocación al método "<span class="codigo">getFechaActual()</span>", y a la segunda la invocación al método "<span class="codigo">getFechaFutura()</span>". En ambas etiquetas colocaremos el valor del atributo "<span class="codigo">nice</span>" en "<span class="codigo">true</span>", de la siguiente forma:<br />
<br />
<pre><code>
<s:date name="new com.javatutoriales.struts2.tags.util.Fechas().getFechaActual()" nice="true" />
<s:date name="new com.javatutoriales.struts2.tags.util.Fechas().getFechaFutura()" nice="true" />
</code></pre><br />
<br />
Si ahora actualizamos la página, veremos aparecer una salida más o menos como la que sigue:<br />
<br />
<pre><code>
2 years, 123 days ago
in 87 years, 21 days
</code></pre><br />
<br />
Esto es el tiempo que ha pasado, o que falta, de mi fecha actual (el momento en el que escribo este tutorial) a las dos fechas indicadas. Esto funciona tanto como para años, días, horas, minutos y segundos. Si ven en la lista anterior, no hay "<span class="codigo">meses</span>", eso quiere decir que si tenemos una diferencia de dos meses, esta se representará como "<span class="codigo">60 días</span>" (o el número de días correspondientes).<br />
Existen algunas propiedades pre-establecidas que indican los textos que deben mostrarse en cada caso. Estas propiedades se muestran en la siguiente tabla:<br />
<br />
<table><thead>
<tr> <th>Propiedad</th><th>Formato</th> </tr>
</thead> <tbody>
<tr> <td><span class="codigo">struts.date.format.past</span></td><td><span class="codigo">{0} ago</span></td> </tr>
<tr> <td><span class="codigo">struts.date.format.future</span></td><td><span class="codigo">in {0}</span></td> </tr>
<tr> <td><span class="codigo">struts.date.format.seconds</span></td><td><span class="codigo">an instant</span></td> </tr>
<tr> <td><span class="codigo">struts.date.format.minutes</span></td><td><span class="codigo">{0,choice,1#one minute|1<{0} minutes}</span></td> </tr>
<tr> <td><span class="codigo">struts.date.format.hours</span></td><td><span class="codigo">{0,choice,1#one hour|1<{0} hours}{1,choice,0#|1#, one minute|1<, {1} minutes}</span></td> </tr>
<tr> <td><span class="codigo">struts.date.format.days</span></td><td><span class="codigo">{0,choice,1#one day|1<{0} days}{1,choice,0#|1#, one hour|1<, {1} hours}</span></td> </tr>
<tr> <td><span class="codigo">struts.date.format.years</span></td><td><span class="codigo">{0,choice,1#one year|1<{0} years}{1,choice,0#|1#, one day|1<, {1} days}</span></td> </tr>
</tbody> </table><br />
<br />
Donde los números que aparecen entre llaves representan marcadores de posiciones para los datos de la fecha.<br />
<br />
Pero ¿qué pasa si queremos mostrar los textos en un idioma distinto o si queremos modificar el texto que se muestra por default? Por ejemplo, ¿qué pasa si queremos mostrar el tiempo que ha transcurrido desde una fecha usando el texto "<span class="codigo">hace {0}</span>" o "<span class="codigo">han pasado {0}</span>" o si queremos mostrar el número exacto de segundos que han transcurrido? Entonces tenemos que modificar los valores de las propiedades correspondientes.<br />
<br />
Para modificar estos valores es necesario hacer uso del mecanismo de <span class="codigo">i18n</span> (internacionalización) de <span class="codigo">Struts 2</span>. Hacer esto es muy sencillo, consiste en tres simples pasos:<br />
<br />
<ol><li>Crear un archivo que contendrá las propiedades (llave y valor) con los textos que se usarán en cada caso.</li>
<li>Colocar los textos en el archivo de propiedades.</li>
<li>Indicar a <span class="codigo">Struts 2</span> que debe usar este archivo usando la constante "<span class="codigo">struts.custom.i18n.resources</span>".</li>
</ol>Ya habíamos hecho esto anteriormente en <a href="http://www.javatutoriales.com/2011/10/struts-2-parte-3-trabajo-con.html">el tercer tutorial de la serie</a> cuando trabajamos en la carga de archivos al servidor.<br />
<br />
Vamos con el primer paso. Para crear este archivo hacemos clic derecho sobre el nodo "<span class="codigo">Source Package</span>" del panel de proyectos. En el menú contextual que aparece seleccionamos la opción "<span class="codigo">New -> Properties File...</span>" (si no tienen esa opción en el menú, seleccionen la opción "<span class="codigo">Other...</span>" y en la ventana que se abre seleccionen la categoría "<span class="codigo">Other</span>" y el tipo de archivo "<span class="codigo">Properties File</span>"):<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjmK3mi3PsPw19jLPL2nwS04huOOlpBaxDVlJbvccfL68Yu8QKnX1OnIEwe83l7cTgjdw6mZ6dluNr7L3OmRsVxe_2PNWpXeztBXnxP5QaQ0MvYip0bLdG2xPSnqewZlVbYDFlJ-CSnw9lL/s1600/S7_23.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjmK3mi3PsPw19jLPL2nwS04huOOlpBaxDVlJbvccfL68Yu8QKnX1OnIEwe83l7cTgjdw6mZ6dluNr7L3OmRsVxe_2PNWpXeztBXnxP5QaQ0MvYip0bLdG2xPSnqewZlVbYDFlJ-CSnw9lL/s400/S7_23.png" /></a></div><br />
<br />
Llamaremos a este archivo "<span class="codigo">struts-mensajes</span>" (el IDE se encargará de colocar automáticamente la extensión <span class="codigo">.properties</span>). Damos clic en el botón "<span class="codigo">Finish</span>" y veremos aparecer en el editor nuestro archivo de propiedades. En este archivo colocaremos los textos que los usuarios verán al mostrar la información de la diferencia de las fechas, por ejemplo podemos poner:<br />
<br />
<pre><code>
struts.date.format.past=han pasado {0}
struts.date.format.future=faltan {0}
struts.date.format.seconds=un instante
struts.date.format.minutes={0,choice,1#un minuto|1<{0} minutos}
struts.date.format.hours={0,choice,1#una hora|1<{0} horas}{1,choice,0#|1#, un minuto|1<, {1} minutos}
struts.date.format.days={0,choice,1#un dia|1<{0} dias}{1,choice,0#|1#, una hora|1<, {1} horas}
struts.date.format.years={0,choice,1#un año|1<{0} años}{1,choice,0#|1#, un dia|1<, {1} dias}
</code></pre><br />
<br />
Ya que tenemos definidos los mensajes, lo siguiente que debemos hacer es indicarle a <span class="codigo">Struts 2</span> dónde se localiza este archivo. Para ello usamos la constante "<span class="codigo">struts.custom.i18n.resources</span>" para indicar el nombre del archivo (el cual será buscado a partir del directorio raíz de los paquetes de la aplicación, o sea en el nodo "<span class="codigo">Source Packages</span>". Esta constante podemos colocarla, dependiendo si estamos trabajando con un archivo de configuración en <span class="codigo">XML</span> o con anotaciones, en el archivo "<span class="codigo">struts.xml</span>" o como un parámetro de inicialización del filtro de <span class="codigo">Struts 2</span> en el archivo "<span class="codigo">web.xml</span>".<br />
<br />
Si están usando el archivo "<span class="codigo">struts.xml</span>" la constante queda de la siguiente forma:<br />
<br />
<pre><code>
<constant name="struts.custom.i18n.resources" value="struts-mensajes" />
</code></pre><br />
<br />
Y si trabajan con anotaciones, la configuración del filtro queda así:<br />
<br />
<pre><code>
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
<init-param>
<param-name>struts.custom.i18n.resources</param-name>
<param-value>struts-mensajes</param-value>
</init-param>
</filter>
</code></pre><br />
<br />
Ahora, si volvemos a ejecutar la aplicación (en este caso es necesario detener el servidor y volver a iniciarlo) veremos la siguiente salida:<br />
<br />
<pre><code>
han pasado 2 años, 123 dias
faltan 87 años, 21 dias
</code></pre><br />
<br />
Con esto podemos comprobar que los valores que hemos colocado se respetan. <br />
<br />
Ahora veremos un poco sobre internacionalización, con la etiqueta "<span class="codigo">i18n</span>".<br />
<br />
<h3 class="subtitulo"><a href="http://www.blogger.com/null" id="i18n"><span class="codigo">i18n</span></a> y <a href="http://www.blogger.com/null" id="text"><span class="codigo">text</span></a></h3>Veremos dos etiquetas en esta sección, ya que ambas se usan juntas y una no tiene sentido sin la otra.<br />
<br />
Primero hablaremos de "<span class="codigo">i18n</span>". Esta etiqueta obtiene un resource boundle (un archivo de propiedades donde colocamos las llaves y los textos que representan) y lo coloca en el <span class="codigo">ValueStack</span>. Esto permite a la etiqueta de texto acceder a los mensajes del archivo.<br />
<br />
Los textos estarán disponibles únicamente dentro de cuerpo de la etiqueta.<br />
<br />
La etiqueta "<span class="codigo">i18n</span>" sólo tiene un atributo, el cual se muestra en la siguiente tabla:<br />
<br />
<table><thead>
<tr> <th>Nombre</th><th>Obligatorio</th><th>Valor Default</th><th>Tipo</th><th>Descripción</th> </tr>
</thead> <tbody>
<tr> <td><span class="codigo">name</span></td><td>Si</td><td> </td><td><span class="codigo">String</span></td><td>El nombre del resource boundle que se usará.</td> </tr>
</tbody> </table><br />
<br />
Ahora, la etiqueta "<span class="codigo">text</span>" se encarga de mostrar los textos que son cargados en el resource boundle con la etiqueta "<span class="codigo">i18n</span>". En otras palabras: con "<span class="codigo">i18n</span>" cargamos los textos, y con "<span class="codigo">text</span>" los mostramos. Esto siempre y cuando el texto no sea algún componente de <span class="codigo">HTML</span> como algún elemento de un formulario (para estos se usa el atributo "<span class="codigo">key</span>" del componente), sino un texto libre dentro de la página.<br />
<br />
Podemos indicar un valor por default para los casos en los que no se encuentre la llave indicada dentro del archivo de propiedades. Este texto lo colocamos dentro del cuerpo de la etiqueta (la llave se coloca en el atributo "<span class="codigo">name</span>" de la etiqueta).<br />
<br />
Los atributos de la etiqueta "<span class="codigo">text</span>" se muestran en la siguiente tabla:<br />
<br />
<table><thead>
<tr> <th>Nombre</th><th>Obligatorio</th><th>Valor Default</th><th>Tipo</th><th>Descripción</th> </tr>
</thead> <tbody>
<tr> <td><span class="codigo">name</span></td><td>Si</td><td> </td><td><span class="codigo">String</span></td><td>La llave relacionada con el texto que queremos mostrar.</td> </tr>
<tr> <td><span class="codigo">searchValueStack</span></td><td>No</td><td><span class="codigo">true</span></td><td><span class="codigo">Boolean</span></td><td>Busca en el <span class="codigo">ValueStack</span> en caso de que la llave indicada no sea encontrada en el resource boundle.</td> </tr>
<tr> <td><span class="codigo">var</span></td><td>no</td><td> </td><td><span class="codigo">String</span></td><td>El nombre de la variable que será usada para representar el valor, la cual será colocada en la cima del <span class="codigo">ValueStack</span>.</td> </tr>
</tbody> </table><br />
<br />
Para el ejemplo, lo primero que debemos hacer es crear un nuevo archivo de propiedades. Este puede estar en cualquier ubicación del código fuente de nuestro proyecto. Por orden, colocaré el archivo en el paquete "<span class="codigo">com.javatutoriales.struts2.tags.textos</span>". El nombre de este archivo será "<span class="codigo">textos.properties</span>":<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiEs6SbGDjgXQH44SdD8ZUnMkrEjKKRIuooMiIiMWZXMQYJVAD6efaZt9PsxSPgCyxHG7B8D6CXfVbki9am8WJi5Ao_o753-qqN2mWljvp7NzucGIkjd0VnEac5Hl7XP0RNPxDProEQNo88/s1600/S7_24.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiEs6SbGDjgXQH44SdD8ZUnMkrEjKKRIuooMiIiMWZXMQYJVAD6efaZt9PsxSPgCyxHG7B8D6CXfVbki9am8WJi5Ao_o753-qqN2mWljvp7NzucGIkjd0VnEac5Hl7XP0RNPxDProEQNo88/s400/S7_24.png" /></a></div><br />
<br />
Dentro de este archivo colocaremos tres textos, el primero será un texto con instrucciones para el usuario, el segundo será el texto de una etiqueta para un campo de texto de un formulario, y el tercero será el texto del botón de enviar de un formulario:<br />
<br />
<pre><code>
instrucciones=Por favor, coloque el nombre en el campo correspondiente
etiqueta=Nombre
boton=Confirmar
</code></pre><br />
<br />
Ahora debemos colocar la etiqueta "<span class="codigo">s:i18n</span>", y en su atributo "<span class="codigo">name</span>" colocamos la ruta hasta el archivo de propiedades (<span class="codigo">textos.properties</span>), pero sin colocar la extensión, de la siguiente forma:<br />
<br />
<pre><code>
<s:i18n name="com/javatutoriales/struts2/tags/textos/textos">
</s:i18n>
</code></pre><br />
<br />
Recordemos que para colocar un texto que no estará dentro de ningún componente del <span class="codigo">HTML</span>, podemos usar la etiqueta "<span class="codigo">s:text</span>", colocando en su atributo "<span class="codigo">name</span>" la llave del texto que queremos mostrar. En este caso mostraremos las instrucciones, por lo que el nombre de la llave que colocaremos será "<span class="codigo">instrucciones</span>":<br />
<br />
<pre><code>
<s:i18n name="com/javatutoriales/struts2/tags/textos/textos">
<s:text name="instrucciones" />
</s:i18n>
</code></pre><br />
<br />
Como los siguientes dos elementos son componentes de un formulario, debemos ponerlos dentro de una etiqueta "<span class="codigo">s:form</span>" que no tendrá ningún <span class="codigo">Action</span>. Dentro del formulario agregaremos una etiqueta, un campo de texto, y un botón para enviar el formulario:<br />
<br />
<pre><code>
<s:form>
<s:label /><s:textfield />
<s:submit />
</s:form>
</code></pre><br />
<br />
Aún no hemos colocado los textos a estos componentes. Para eso existen dos formas, en la primera usamos el atributo "<span class="codigo">value</span>" del elemento. Para obtener el texto que se colocaré en los elementos, cuando no se usa la etiqueta "<span class="codigo">s:text</span>", debemos usar la función "<span class="codigo">getText</span>" de <span class="codigo">OGNL</span>. Esta función recibe como parámetro la llave del texto que se mostrará:<br />
<br />
<pre><code>
<s:form>
<s:label value="%{getText('etiqueta')}" /><s:textfield />
<s:submit value="%{getText('boton')}" />
</s:form>
</code></pre><br />
<br />
Por lo tanto, nuestro ejemplo queda de la siguiente forma:<br />
<br />
<pre><code>
<s:i18n name="com/javatutoriales/struts2/tags/textos/textos">
<s:text name="instrucciones" />
<s:form>
<s:label value="%{getText('etiqueta')}" /><s:textfield />
<s:submit value="%{getText('boton')}" />
</s:form>
</s:i18n>
</code></pre><br />
<br />
Si ejecutamos la aplicación deberemos ver la siguiente salida:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjzH5CPMm_OmDPj5lHwksltBBcGEuYbXuswmUEePhRbaV4y56h4mF482FYfZDL6jF8dMDo-i7VpT3hWEWPhXlf-TKlPJU9odhMtxTArgmCOGiME-d2wYJmOTl1HXis0CpbuwvtbQpjQnAa-/s1600/S7_25.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjzH5CPMm_OmDPj5lHwksltBBcGEuYbXuswmUEePhRbaV4y56h4mF482FYfZDL6jF8dMDo-i7VpT3hWEWPhXlf-TKlPJU9odhMtxTArgmCOGiME-d2wYJmOTl1HXis0CpbuwvtbQpjQnAa-/s400/S7_25.png" /></a></div><br />
<br />
Podemos ver que se han tomado los textos que hemos especificado y se muestran en la página sin ningún problema.<br />
<br />
La segunda forma es usando el elemento "<span class="codigo">key</span>" del componente, por ejemplo, si modificamos el ejemplo anterior de la siguiente forma:<br />
<br />
<pre><code>
<s:i18n name="com/javatutoriales/struts2/tags/textos/textos">
<s:text name="instrucciones" />
<s:form>
<s:label key="etiqueta" />
<s:textfield />
<s:submit key="boton" />
</s:form>
</s:i18n>
</code></pre><br />
<br />
La salida será exactamente la misma que la vez anterior.<br />
<br />
Ahora veremos la siguiente etiqueta.<br />
<br />
<h3 class="subtitulo"><a href="http://www.blogger.com/null" id="include"><span class="codigo">include</span></a></h3>La etiqueta "<span class="codigo">include</span>" permite agregar la salida de un server (ya sea un <span class="codigo">Servlet</span> o una <span class="codigo">JSP</span>) en la página actual. Esto es útil si tenemos contenido que se repetirá en varias páginas o si queremos conjuntar el contenido de varias en una sola (como una especie de mashup).<br />
<br />
Esta etiqueta tiene sólo un atributo, el cual se muestra en la siguiente tabla:<br />
<br />
<table><thead>
<tr> <th>Nombre</th><th>Obligatorio</th><th>Valor Default</th><th>Tipo</th><th>Descripción</th> </tr>
</thead> <tbody>
<tr> <td><span class="codigo">value</span></td><td>Si</td><td> </td><td><span class="codigo">String</span></td><td>El <span class="codigo">Servlet</span> o <span class="codigo">JSP</span> que se incluirá.</td> </tr>
</tbody> </table><br />
<br />
Para este ejemplo, hagamos una página simple que tenga tan solo un texto que se mostrará en la página que la incluya. Agreguemos una nueva página llamada "<span class="codigo">contenido.jsp</span>" en la raíz de las páginas web del proyecto:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi4g38dUx3nsMvCJ8wao5iksVZPUB00zLxd7fzsVKNJE7sG0lIQ40Jl1HaEQu9hx6JCd-KxYoKKinn-6m10Fayg2zNLABvXUXMVDJ81ZfoRBOZ02J2Q8AKIlREGcGLmQNP-Vge_JcYJzPcG/s1600/S7_26.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi4g38dUx3nsMvCJ8wao5iksVZPUB00zLxd7fzsVKNJE7sG0lIQ40Jl1HaEQu9hx6JCd-KxYoKKinn-6m10Fayg2zNLABvXUXMVDJ81ZfoRBOZ02J2Q8AKIlREGcGLmQNP-Vge_JcYJzPcG/s400/S7_26.png" /></a></div><br />
<br />
Dentro de esta página pueden colocar los elementos que quieran para probar. Yo solo pondré un texto y un enlace a este mismo sitio web <span class="codigo">^_^</span>. El contenido de la página será el siguiente:<br />
<br />
<pre><code>
Bienvenido a <a href="http://javatutoriales.com/">Java Tutoriales</a>
</code></pre><br />
<br />
Ahora, de regreso a la página "<span class="codigo">datos.jsp</span>", lo único que debemos hacer es agregar la etiqueta "<span class="codigo">s:include</span>", y colocar en su atributo "<span class="codigo">value</span>" el nombre de la página que acabamos de crear, o sea "<span class="codigo">contenido.jsp</span>":<br />
<br />
<pre><code>
<s:include value="contenido.jsp" />
</code></pre><br />
<br />
Si actualizamos la página debemos ver la siguiente salida:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjYwMogEb6Vh-pjWzpLrhUObBS-j76P3HK9DpiSU3rlOw9EZ-7aSHPD0pd0N_kTl1vg-XXfnEXkfFAM5PyQB3Ff6WKohiALvAVSx_qc_q2X3hYePPaZcVsspkpbdexvw9RNhq0vvHVdMv83/s1600/S7_27.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjYwMogEb6Vh-pjWzpLrhUObBS-j76P3HK9DpiSU3rlOw9EZ-7aSHPD0pd0N_kTl1vg-XXfnEXkfFAM5PyQB3Ff6WKohiALvAVSx_qc_q2X3hYePPaZcVsspkpbdexvw9RNhq0vvHVdMv83/s400/S7_27.png" /></a></div><br />
<br />
¿Qué pasa si queremos pasar algún parámetro a la página que estamos incluyendo? Bien, en ese caso podemos usar, como ya deben estar imaginando, la etiqueta "<span class="codigo">s:param</span>", en cuyo atributo "<span class="codigo">name</span>" ponemos el nombre del parámetro que queremos pasar, y en el atributo "<span class="codigo">value</span>" el valor del atributo.<br />
<br />
Modifiquemos un poco la página "<span class="codigo">contenido.jsp</span>" para recibir un parámetro que será el nombre del usuario al que se le está mostrando la página. Como los parámetros son pasados como parámetros de petición, no podemos usar la etiqueta "<span class="codigo">s:property</span>" para obtener este valor. Sin embargo podemos usar el lenguaje de expresiones (Expression Language o <span class="codigo">EL</span>) de <span class="codigo">JSP</span> para obtenerlo. El contenido de la página "<span class="codigo">contenido.jsp</span>" queda de la siguiente forma:<br />
<br />
<pre><code>
Hola ${param.usuario}. Bienvenido a <a href="http://javatutoriales.com/">Java Tutoriales</a>
</code></pre><br />
<br />
Ahora en la página "<span class="codigo">datos.jsp</span>" agregamos, en el cuerpo de la etiqueta "<span class="codigo">s:include</span>" el parámetro llamado "<span class="codigo">usuario</span>". En este caso lo pondremos como un valor fijo para no complicar más el ejemplo:<br />
<br />
<pre><code>
<s:include value="contenido.jsp">
<s:param name="usuario" value="'Alex'" />
</s:include>
</code></pre><br />
<br />
Si actualizamos la página, deberemos ver la siguiente salida:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjcsI8JmTBWdKl-yWdrk1X466nv0RMOs_UPZNI8u-mW7HvGeEwMQePH3NEU3zF64MgmhiRCeiu77ItVpgqmFP_9s0CdqK8XvCvN8P-gNiBlyF_YEvkh3Em-g1wkZil_RMeA6j2vDn_5L7hG/s1600/S7_28.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjcsI8JmTBWdKl-yWdrk1X466nv0RMOs_UPZNI8u-mW7HvGeEwMQePH3NEU3zF64MgmhiRCeiu77ItVpgqmFP_9s0CdqK8XvCvN8P-gNiBlyF_YEvkh3Em-g1wkZil_RMeA6j2vDn_5L7hG/s400/S7_28.png" /></a></div><br />
<br />
Con lo que podemos ver que el parámetro se ha recibido de forma correcta.<br />
<br />
La siguiente etiqueta que veremos es justamente una que hemos estado usando bastante a lo largo del tutorial y que por lo tanto conocemos bien su funcionamiento "<span class="codigo">s:param</span>":<br />
<br />
<h3 class="subtitulo"><a href="http://www.blogger.com/null" id="param"><span class="codigo">param</span></a></h3>Esta etiqueta se usa para parametrizar otras etiquetas. Como hemos visto a lo largo del tutorial, cada una de las etiquetas que requiere un parámetro lo recibe a través de "<span class="codigo">s:param</span>".<br />
<br />
Esta etiqueta tiene dos atributos, que se muestran en la siguiente tabla:<br />
<br />
<table><thead>
<tr> <th>Nombre</th><th>Obligatorio</th><th>Valor Default</th><th>Tipo</th><th>Descripción</th> </tr>
</thead> <tbody>
<tr> <td><span class="codigo">name</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>El nombre del parámetro que se está estableciendo.</td> </tr>
<tr> <td><span class="codigo">value</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>El valor del parámetro.</td> </tr>
</tbody> </table><br />
<br />
No hay mucho más que decir de esta etiqueta, ya que la hemos estado usando de manera continua, sólo que existen dos formas de indicar el valor del parámetro que estamos estableciendo, y que hay una pequeña diferencia entre uno y otro.<br />
<br />
En la primer forma, que es la que hemos estado usando, pasamos el valor usando el atributo "<span class="codigo">value</span>", de la siguiente manera:<br />
<br />
<pre><code>
<s:param name=</code></pre></div>"parametro" value="valor" /> <br />
<br />
Cuando trabajamos de esta forma, el valor que se evalúa como un <span class="codigo">Object</span>, es decir que podemos pasar cualquier objeto y la etiqueta que estamos parametrizando recibirá este objeto tal cual. Es por esta razón que cuando establecemos cadenas debemos encerrarlas entre comillas simples o dobles.<br />
<br />
En la segunda forma en establecer el valor, este se coloca en el cuerpo de la etiqueta, de la siguiente forma:<br />
<br />
<pre><code>
<s:param name=</code></pre>"parametro">valor</s:param> <br />
<br />
Cuando hacemos esto, el parámetro se evalúa como un <span class="codigo">String</span>.<br />
<br />
No haremos un ejemplo de esta etiqueta ya que, nuevamente, la hemos estado usando a lo largo del tutorial; además de que no puede ser usada sin otras etiquetas.<br />
<br />
Ahora veremos la siguiente etiqueta, la cual también hemos estado usando bastante a lo largo de este tutorial.<br />
<br />
<h3 class="subtitulo"><a href="http://www.blogger.com/null" id="property"><span class="codigo">property</span></a></h3>Se usa para lo que ya hemos visto: especificamos el nombre de una propiedad de un objeto, el cual se espera que esté en la cima del <span class="codigo">stack</span>, y se obtiene su valor. Recordemos que si no especificamos un valor, esta etiqueta mostrará lo que sea que se encuentre en la cima del <span class="codigo">stack</span>.<br />
<br />
Los atributos de esta etiqueta se muestran en la siguiente tabla:<br />
<br />
<table><thead>
<tr> <th>Nombre</th><th>Obligatorio</th><th>Valor Default</th><th>Tipo</th><th>Descripción</th> </tr>
</thead> <tbody>
<tr> <td><span class="codigo">value</span></td><td>No</td><td>Lo que sea que se encuentre en la cima del <span class="codigo">stack</span></td><td><span class="codigo">Object</span></td><td>El valor a ser mostrado.</td> </tr>
<tr> <td><span class="codigo">default</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>El valor que se usará si el atributo "<span class="codigo">value</span>" es nulo.</td> </tr>
<tr> <td><span class="codigo">escapeCsv</span></td><td>No</td><td> </td><td><span class="codigo">Boolean</span></td><td>Indica si se debe escapar información en <span class="codigo">CSV</span> (mostrarse en vez de evaluarse).</td> </tr>
<tr> <td><span class="codigo">escapeHtml</span></td><td>No</td><td> </td><td><span class="codigo">Boolean</span></td><td>Indica si se debe escapar información en <span class="codigo">HTML</span> (mostrarse en vez de evaluarse).</td> </tr>
<tr> <td><span class="codigo">escapeJavaScript</span></td><td>No</td><td> </td><td><span class="codigo">Boolean</span></td><td>Indica si se debe escapar información en <span class="codigo">JavaScript</span> (mostrarse en vez de evaluarse).</td> </tr>
<tr> <td><span class="codigo">escapeXml</span></td><td>No</td><td> </td><td><span class="codigo">Boolean</span></td><td>Indica si se debe escapar información en <span class="codigo">XML</span> (mostrarse en vez de evaluarse).</td> </tr>
</tbody> </table><br />
<br />
Esta etiqueta también le hemos estado usando a lo largo de todo el tutorial, por lo que tampoco veremos un ejemplo y pasaremos directamente a la siguiente etiqueta.<br />
<br />
<h3 class="subtitulo"><a href="http://www.blogger.com/null" id="push"><span class="codigo">push</span></a></h3>Pone un objeto momentáneamente en la cima del <span class="codigo">stack</span>.<br />
<br />
Esto quiere decir que el objeto que queramos colocar ya debe existir, y sólo estará en la cima del <span class="codigo">stack</span> en el scope del cuerpo de la etiqueta. Una vez que la etiqueta se cierre, el objeto será removido de la cima del <span class="codigo">stack</span>.<br />
<br />
Esta etiqueta sólo tiene un atributo, el cual se muestra en la siguiente tabla:<br />
<br />
<table><thead>
<tr> <th>Nombre</th><th>Obligatorio</th><th>Valor Default</th><th>Tipo</th><th>Descripción</th> </tr>
</thead> <tbody>
<tr> <td><span class="codigo">value</span></td><td>Si</td><td> </td><td><span class="codigo">String</span></td><td>El nombre del objeto que se colocará en la cima del <span class="codigo">stack</span>.</td></tr>
</tbody> </table><br />
<br />
Para este ejemplo, haremos uso nuevamente de la etiqueta "<span class="codigo">s:bean</span>" para crear un objeto el cual posteriormente pondremos en el <span class="codigo">stack</span>. Recordemos que "<span class="codigo">s:bean</span>" crea un objeto, pero no lo pone en el <span class="codigo">stack</span> por si sólo, así que para hacer referencia a él debemos usar el operador número (<span class="codigo">#</span>).<br />
<br />
<br />
Entonces, lo primero que haremos es crear un nuevo bean, de la siguiente forma:<br />
<br />
<pre><code>
<s:bean name="com.javatutoriales.struts2.tags.modelo.Usuario" var="usuario">
<s:param name="nombre" value="'Alex'" />
<s:param name="edad" value="123" />
</s:bean>
</code></pre><br />
<br />
Nuevamente: este bean <span class="negritas">NO es colocado en la cima del <span class="codigo">ValueStack</span></span>, por lo que para hacer uso de él tendíamos que usar su nombre (en este caso "<span class="codigo">usuario</span>") junto con el operador número (<span class="codigo">#</span>).<br />
<br />
Comprobemos que lo anterior es cierto usando la etiqueta "<span class="codigo">s:property</span>". Recordemos que si no indicamos ningún valor para "<span class="codigo">s:property</span>", esta mostrará lo que se encuentre en la cima del <span class="codigo">stack</span>. Si colocamos la etiqueta de esta forma:<br />
<br />
<pre><code>
<s:property />
</code></pre><br />
<br />
Debemos ver, más o menos, la siguiente salida:<br />
<br />
<pre><code>
com.opensymphony.xwork2.DefaultTextProvider@1ef4d22
</code></pre><br />
<br />
¿Qué es este objeto? No lo sé (pero no importa).<br />
<br />
Ahora usemos la etiqueta "<span class="codigo">s:push</span>", colocando en su atributo "<span class="codigo">value</span>" el nombre del objeto que queremos colocar en la cima del <span class="codigo">stack</span>, o sea nuestro bean llamado "<span class="codigo">usuario</span>":<br />
<br />
<pre><code>
<s:push value="usuario">
</s:push>
</code></pre><br />
<br />
Si dentro del cuerpo de la etiqueta colocamos nuevamente la etiqueta "<span class="codigo">s:property</span>", de la siguiente forma:<br />
<br />
<pre><code>
<s:push value="usuario">
<s:property />
</s:push>
</code></pre><br />
<br />
Debemos ver la siguiente salida:<br />
<br />
<pre><code>
com.javatutoriales.struts2.tags.modelo.Usuario@d364bc
</code></pre><br />
<br />
Como ahora el objeto "<span class="codigo">usuario</span>" está en la cima del <span class="codigo">stack</span>, podemos obtener directamente cualquiera de sus propiedades, simplemente poniendo el nombre de la propiedad en la etiqueta "<span class="codigo">s:property</span>". Por ejemplo, para mostrar el <span class="codigo">nombre</span> y la <span class="codigo">edad</span>, lo hacemos de la siguiente manera:<br />
<br />
<pre><code>
<s:push value="usuario">
Nombre: <s:property value="nombre" /><br />
Edad: <s:property value="edad" />
</s:push>
</code></pre><br />
<br />
Con lo que debemos ver la siguiente salida:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiZ3axYcJgAFyY9DziCcSV-zZFplPnTpjlEljVi0tqpAMPlbsG43cmIMdSUcTda1iA78TuIwhqsITdZaSn7npJ4UXpnJlr0wGe35GtgzEBc6aFFwqaT4LNmDbx-P0Y1N-EynN3WeAgOWU1n/s1600/S7_29.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiZ3axYcJgAFyY9DziCcSV-zZFplPnTpjlEljVi0tqpAMPlbsG43cmIMdSUcTda1iA78TuIwhqsITdZaSn7npJ4UXpnJlr0wGe35GtgzEBc6aFFwqaT4LNmDbx-P0Y1N-EynN3WeAgOWU1n/s400/S7_29.png" /></a></div><br />
<br />
Si volvemos a colocar la etiqueta "<span class="codigo">s:propery</span>" fuera del cuerpo de "<span class="codigo">s:push</span>" debemos ver la misma salida de antes.<br />
<br />
¿Para qué puede servirnos esta etiqueta? Como una utilidad. Por ejemplo si el objeto "<span class="codigo">usuario</span>" tuviera muchos atributos que tuviéramos que mostrar, sería más fácil ponerlo en la cima del <span class="codigo">stack</span> y hacer referencia simplemente al nombre de cada atributo, sin tener que precerlos con el nombre del objeto. O si tenemos un objeto compuesto por otros objetos y queremos poder tener alguno de los "sub-objetos" en la cima por algún tiempo.<br />
<br />
Ahora veamos la siguiente etiqueta:<br />
<br />
<h3 class="subtitulo"><a href="http://www.blogger.com/null" id="set"><span class="codigo">set</span></a></h3><span class="codigo">set</span> es otra de esas etiquetas simples pero muy útiles. Permite asignar un valor a una variable en un scope especificado. Es útil cuando queremos asignar el resultado de una expresión compleja o larga a una variable para usar esta en vez de la expresión. Esto es útil en varios casos, por ejemplo cuando la expresión toma tiempo en evaluarse (mejora de rendimiento) y cuando es difícil de leer (mejora en la legibilidad).<br />
<br />
También sirve, aunque esta funcionalidad no está documentada, para poder colocar valores que pueden ser leídos desde el <span class="codigo">EL</span> de <span class="codigo">JSP</span> en el <span class="codigo">ValueStack</span> de <span class="codigo">OGNL</span> para que podamos usarlas desde <span class="codigo">Struts 2</span>.<br />
<br />
Los scopes disponibles son básicamente los mismos que la especificación de <span class="codigo">servlets</span> (<span class="codigo">application</span>, <span class="codigo">session</span>, <span class="codigo">request</span> y <span class="codigo">page</span>) más el scope "<span class="codigo">action</span>", que establece la variable en el scope del <span class="codigo">ActionContext</span> de <span class="codigo">Struts 2</span>.<br />
<br />
Si no se especifica ningún scope, por default la variable se coloca en el scope "<span class="codigo">action</span>".<br />
<br />
Los atributos de esta etiqueta se muestran en la siguiente tabla:<br />
<br />
<table><thead>
<tr> <th>Nombre</th><th>Obligatorio</th><th>Valor Default</th><th>Tipo</th><th>Descripción</th> </tr>
</thead> <tbody>
<tr> <td><span class="codigo">var</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>El nombre que tendrá la variable usada para referenciar al valor que es colocado en el <span class="codigo">ValueStack</span>.</td>
</tr>
<tr> <td><span class="codigo">value</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>El valor que se le asignará a la variable.</td> </tr>
<tr> <td><span class="codigo">scope</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>El scope en el que se asignará la variable.</td> </tr>
</tbody> </table><br />
<br />
Para el primer ejemplo, haremos de cuenta que hay un dato que se repite mucho en la página o que es muy largo (puede ser una fecha, un nombre, algún valor, un número de teléfono, etc.), y queremos manejarlo con una variable más corta o que si este valor cambiara sólo tuviéramos que moverlo en un lugar y que se refleje en todos los lugares donde la usamos.<br />
<br />
Lo primero que haremos será coloca la etiqueta "<span class="codigo">s:set</span>", en su atributo "<span class="codigo">var</span>" pondremos el nombre de la variable, que en este caso será "<span class="codigo">datos</span>":<br />
<br />
<pre><code>
<s:set var="datos" />
</code></pre><br />
<br />
En su atributo "<span class="codigo">value</span>" colocaremos una cadena de texto muy larga, la cual contendrá mis datos (nombre, sitio web y correo electrónico), de la siguiente forma (no olviden colocar el valor entre comillas simples dentro de las comillas dobles):<br />
<br />
<pre><code>
<s:set var="datos" value="'Programador Java: www.javatutoriales.com, programadorjavablog@gmail.com'" />
</code></pre><br />
<br />
Ahora, para mostrar el valor de esta variable usamos la etiqueta "<span class="codigo">s:property</span>", indicando en su atributo "<span class="codigo">value</span>" el nombre de la variable, en este caso "<span class="codigo">datos</span>":<br />
<br />
<pre><code>
<s:property value="datos" />
</code></pre><br />
<br />
Al ejecutar la aplicación debemos ver la siguiente salida:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi_QLQyeApNIhvzBQF_NPQrIVRgyA_1MVO7uOVUGMlEBL9GjTJMVXGlBhGUNGfB0QgJLZ0A80wutTmr0YZLU3FcyvCoRy1IJ0M_XzDT_TumtRWIcLvTphChUTDJD6HuSvXqU0ZWw4bLDXDl/s1600/S7_30.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi_QLQyeApNIhvzBQF_NPQrIVRgyA_1MVO7uOVUGMlEBL9GjTJMVXGlBhGUNGfB0QgJLZ0A80wutTmr0YZLU3FcyvCoRy1IJ0M_XzDT_TumtRWIcLvTphChUTDJD6HuSvXqU0ZWw4bLDXDl/s400/S7_30.png" /></a></div><br />
<br />
Para el siguiente ejemplo haremos de cuenta que hacemos un cálculo complejo y lo almacenamos en una variable. Estos valores los recibiremos usando el Expression Language (<span class="codigo">EL</span>) de <span class="codigo">JSP</span>. Lo primero que haremos es colocar una etiqueta "<span class="codigo">s:set</span>", y en su atributo "<span class="codigo">var</span>" colocamos "<span class="codigo">calculo</span>":<br />
<br />
<pre><code>
<s:set var="calculo">
</s:set>
</code></pre><br />
<br />
El cálculo que haremos tendrá valores constantes para facilitar el ejemplo, y en esta ocasión colocaremos el valor en el cuerpo de la etiqueta:<br />
<br />
<pre><code>
<s:set var="calculo">${1 + 2 + 3 + 4 + 5}</s:set>
</code></pre><br />
<br />
Para mostrar el valor de la variable (el resultado del cálculo), usaremos nuevamente la etiqueta "<span class="codigo">s:property</span>":<br />
<br />
<pre><code>
<s:property value="calculo" />
</code></pre><br />
<br />
Al ejecutar nuevamente la aplicación debemos ver la siguiente salida:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgZVVxtSLA8L-Wy6i3s3aPvbOnZS5C_O8FvEYv-_VdfWOYx19UKhp7i4Py-8lHSS0oXFr6JgI8CrqKA8q3QwFHaJ1aVkD2NUH8E85hpb2v5V1yAqEcogmehNJG1cN5DZY3P-ubInRPw_s56/s1600/S7_31.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgZVVxtSLA8L-Wy6i3s3aPvbOnZS5C_O8FvEYv-_VdfWOYx19UKhp7i4Py-8lHSS0oXFr6JgI8CrqKA8q3QwFHaJ1aVkD2NUH8E85hpb2v5V1yAqEcogmehNJG1cN5DZY3P-ubInRPw_s56/s400/S7_31.png" /></a></div><br />
<br />
Con lo que podemos ver que el resultado es correcto.<br />
<br />
Una pregunta que oigo (y me hago a mí mismo) de forma constante, es si es posible cambiar el valor de una propiedad de un bean, creado con "<span class="codigo">s:bean</span>", usando esta etiqueta. La respuesta es muy simple: <span class="negritas">NO</span>. Esta etiqueta no establece el valor de un bean, sino que crea una nueva variable y le coloca el valor establecido. Veamos un ejemplo de esto. Crearemos nuevamente un bean de la clase "<span class="codigo">Usuario</span>", al que le daremos el nombre de "<span class="codigo">usuario</span>", y estableceremos algunos de sus parámetros:<br />
<br />
<pre><code>
<s:bean name="com.javatutoriales.struts2.tags.modelo.Usuario" var="usuario">
<s:param name="nombre" value="%{'Alex'}" />
<s:param name="edad" value="%{123}" />
</s:bean>
</code></pre><br />
<br />
Ahora, mostraremos el valor del atributo "<span class="codigo">nombre</span>", usando "<span class="codigo">s:property</span>":<br />
<br />
<pre><code>
<s:property value="#usuario.nombre" />
</code></pre><br />
<br />
Con esto obtendremos en la pantalla el nombre que establecimos al "<span class="codigo">usuario</span>":<br />
<br />
<pre><code>
Alex
</code></pre><br />
<br />
Hasta aquí todo normal y funcionando como lo hemos visto en el tutorial. Si quisiéramos cambiar el valor de la variable nombre, tal vez podríamos pensar en usar la etiqueta "<span class="codigo">s:set</span>" de la siguiente forma:<br />
<br />
<pre><code>
<s:set var="#usuario.nombre" value="'Otro'" />
</code></pre><br />
<br />
Pero si volvemos a mostrar el nombre, volveremos a obtener la misma salida de la vez anterior:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhz6tGVkSM7zAo01J4jQiqtx3mkiKVq03fcFVw4oxn-eRfmPgdfMAlRE_WVDAtH3taORKsfrUqEsl5KvJkkOcXXN7MqLYlZZHMFWRdUXyT1AlK0K0mwKcg5_jdkANxbmJmhQ_aWPafgR_au/s1600/S7_36.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhz6tGVkSM7zAo01J4jQiqtx3mkiKVq03fcFVw4oxn-eRfmPgdfMAlRE_WVDAtH3taORKsfrUqEsl5KvJkkOcXXN7MqLYlZZHMFWRdUXyT1AlK0K0mwKcg5_jdkANxbmJmhQ_aWPafgR_au/s400/S7_36.png" /></a></div><br />
<br />
Podemos pensar en varias formas de intentar hacer esto, pero el valor no cambiará. Revisando el código fuente de la etiqueta podemos ver que lo único que hace es establecer atributos en el <span class="codigo">ValueStack</span>, no toma nunca ningún objeto. Lo que si podemos hacer es crear una variable que contenga el valor del nombre del usuario:<br />
<br />
<pre><code>
<s:set var="nombre" value="#usuario.nombre" />
<s:property value="nombre" />
</code></pre><br />
<br />
Que es un ejemplo más de mejora de legibilidad.<br />
<br />
Ahora veremos la última etiqueta de control.<br />
<br />
<h3 class="subtitulo"><a href="http://www.blogger.com/null" id="debug"><span class="codigo">debug</span></a></h3>Esta es una etiqueta que <span class="negritas">no usaremos en producción, sino sólo durante la etapa de desarrollo</span>. Su propósito es imprimir el valor del <span class="codigo">ValueStack</span> y del <span class="codigo">StackContext</span> en una página, de forma que podamos ver su contenido actual.<br />
Esta etiqueta no tiene ningún atributo por lo que para usarla sólo debemos colocarla en el punto de la página donde queramos analizar el contenido del <span class="codigo">stack</span>:<br />
<br />
<pre><code>
<s:debug />
</code></pre><br />
<br />
Si la colocamos al final de la página "<span class="codigo">datos.jsp</span>" y actualizamos la página veremos que aparece el siguiente enlace:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgNvCr9sU52M6UWLygSdi4l9Z3dHvIxIg-oTmBNEGatc1v4m8PSgeiTYPM3jky2zcIMcELNgbBqgj0wCybg3gSfBr5e7Vo9B_tNWWOJ7YW4Ww1z8DfdxmM_ZYy7LA7DMIAXmzQ6t48FQapo/s1600/S7_37.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgNvCr9sU52M6UWLygSdi4l9Z3dHvIxIg-oTmBNEGatc1v4m8PSgeiTYPM3jky2zcIMcELNgbBqgj0wCybg3gSfBr5e7Vo9B_tNWWOJ7YW4Ww1z8DfdxmM_ZYy7LA7DMIAXmzQ6t48FQapo/s400/S7_37.png" /></a></div><br />
<br />
Si hacemos clic en él aparecerá el siguiente contenido en la página:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjBsXE3IsBLV5LXhzcvT5qRFUL40SYD0q_hy9DWK_BZpxcIOf9jKUujuJuXdrFvx_xptvriGIqkKkfag4B43fhrpEaf_Wmm_-gRpRfYNWIATAjfy7TcF_Q0xx4AarPW8tY0b0guKn2HDbhF/s1600/S7_38.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjBsXE3IsBLV5LXhzcvT5qRFUL40SYD0q_hy9DWK_BZpxcIOf9jKUujuJuXdrFvx_xptvriGIqkKkfag4B43fhrpEaf_Wmm_-gRpRfYNWIATAjfy7TcF_Q0xx4AarPW8tY0b0guKn2HDbhF/s400/S7_38.png" /></a></div><br />
<br />
De esta manera podemos revisar los valores actuales en el <span class="codigo">stack</span> y verificar que todo está funcionando correctamente.<br />
<br />
Como la página en la que estamos trabajando no tiene asociado ningún <span class="codigo">Action</span>, no hay nada interesante en la sección del <span class="codigo">ValueStack</span>, pero si bajamos un poco, en el <span class="codigo">StackContext</span> podremos encontrar, por ejemplo, el valor de la variable que definimos con "<span class="codigo">s:set</span>":<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhQlrJOx2wQOHL_7w7hN8m53EKfPKcmqM9s_2CfAzWkDKjpiKKFnZSO6wIVaxtEvvXv9GrnJCO9P62Mojdh-a1qgQcc0JC8QNLRIURfrKtWrfIizeh7kEfuYjv0EDjUp_UrY7A1HyhWuSWQ/s1600/S7_39.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhQlrJOx2wQOHL_7w7hN8m53EKfPKcmqM9s_2CfAzWkDKjpiKKFnZSO6wIVaxtEvvXv9GrnJCO9P62Mojdh-a1qgQcc0JC8QNLRIURfrKtWrfIizeh7kEfuYjv0EDjUp_UrY7A1HyhWuSWQ/s400/S7_39.png" /></a></div><br />
<br />
Si colocamos esta misma etiqueta en "<span class="codigo">control.jsp</span>" podremos ver los atributos del "<span class="codigo">ControlAction</span>" asociado con la página:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjkG3fb_c45aHVsMKbxu9dU5pyzS1Yw6ZDdY8nWqikCHdN5ZeB-xsmROgwU0KfKCXfqX_EHf1p5cezlxNnvUHyRVISdQ8KCPeo6YV0YU6581u49ivMppKfd4Mu1Olf2NlN1KXt4mBWfINZL/s1600/S7_40.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjkG3fb_c45aHVsMKbxu9dU5pyzS1Yw6ZDdY8nWqikCHdN5ZeB-xsmROgwU0KfKCXfqX_EHf1p5cezlxNnvUHyRVISdQ8KCPeo6YV0YU6581u49ivMppKfd4Mu1Olf2NlN1KXt4mBWfINZL/s400/S7_40.png" /></a></div><br />
<br />
Con esto terminamos con las etiquetas de control y veremos siguiente tipo de etiqueta, las etiquetas de formulario.<br />
<br />
<h2 class="titulo"><a href="http://www.blogger.com/null" id="formulario">Etiquetas de Formulario</a></h2>Este tipo de etiqueta se usa para generar los elementos de los formularios del sitio web. Algunas de las etiquetas generan como salida el componente indicado (como un campo de texto) mientras que otros colocan componentes con comportamientos más complejos, como listas con opciones transferibles entre ellas.<br />
<br />
Las etiquetas de esta categoría son:<br />
<br />
<ul><li><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#form">form</a></li>
<li><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#label">label</a></li>
<li><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#head">head</a></li>
<li><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#file">file</a></li>
<li><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#checkbox">checkbox</a></li>
<li><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#checkboxlist">checkboxlist</a></li>
<li><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#radio">radio</a></li>
<li><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#select">select</a></li>
<li><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#optgroup">optgroup</a></li>
<li><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#textfield">textfield</a></li>
<li><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#password">password</a></li>
<li><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#hidden">hidden</a></li>
<li><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#textarea">textarea</a></li>
<li><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#combobox">combobox</a></li>
<li><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#doubleselect">doubleselect</a></li>
<li><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#updownselect">updownselect</a></li>
<li><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#inputtransferselect">inputtransferselect</a></li>
<li><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#optiontransferselect">optiontransferselect</a></li>
<li><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#reset">reset</a></li>
<li><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#submit">submit</a></li>
<li><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#token">token</a></li>
</ul><br />
<br />
Para los ejemplos de esta categoría no crearemos ningún <span class="codigo">Action</span>, ya que dedicamos <a href="http://www.javatutoriales.com/2011/10/struts-2-parte-3-trabajo-con.html">un tutorial completo al trabajo con formularios</a> y cómo obtener en un <span class="codigo">Action</span> los valores que capturamos a través de este. Sólo crearemos una nueva página, llamada "<span class="codigo">formulario.jsp</span>", en donde iremos colocando estas etiquetas:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh39_6Jt58-Ys_FN9IQWzEiXO_kH7Mp2eipPSMcmSEuGG_ogblcuaaPgKB7Pi5siTTfcksPB6xt5msc05yFWpRfh3Plfs7Bwb_-paixz56D5t7kF6pCC1rGcZLCKzqYdj9liyGMR0HkllM8/s1600/S7_41.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh39_6Jt58-Ys_FN9IQWzEiXO_kH7Mp2eipPSMcmSEuGG_ogblcuaaPgKB7Pi5siTTfcksPB6xt5msc05yFWpRfh3Plfs7Bwb_-paixz56D5t7kF6pCC1rGcZLCKzqYdj9liyGMR0HkllM8/s400/S7_41.png" /></a></div><br />
<br />
En esta nueva página indicamos que usaremos las etiquetas de <span class="codigo">Struts 2</span> usando la directiva "<span class="codigo">@taglib</span>":<br />
<br />
<pre><code>
<%@taglib uri="/struts-tags" prefix="s" %>
</code></pre><br />
<br />
<div class="nota">*Nota: La manera en la que los elementos del formulario se muestran dependen del tema que se esté utilizando (<span class="codigo">css_xhtml</span>, <span class="codigo">xhtml</span>, <span class="codigo">simple</span>), como lo vimos al principio del tutorial. Por default al colocar un elemento este tendrá una etiqueta asociada, la cual muestra el texto indicado en su atributo "<span class="codigo">label</span>", pero como indicamos que usaremos el tema "<span class="codigo">simple</span>", y este no genera esta etiqueta, tendremos que colocar la etiqueta de forma manual.</div><br />
<br />
<div class="nota">*Nota: Como la mayoría de los atributos de estas etiquetas se derivan directamente de la etiqueta de <span class="codigo">HTML</span> o son heredados del componente que <span class="codigo">Struts 2</span> usa como base, sólo se explicarán los atributos que tengan sentido para la etiqueta que estemos viendo. Los demás atributos (y que ya no colocaremos en las tablas) son:</div><br />
<br />
<span class="codigo"> accesskey, cssClass, cssErrorClass, cssErrorStyle, cssStyle, disabled, errorPosition, id, javascriptTooltip, key, label, labelSeparator, labelposition, listCssClass, listCssStyle, name, onblur, onchange, onclick, ondblclick, onfocus, onkeydown, onkeypress, onkeyup, onmousedown, onmousemove, onmouseout, onmouseover, onmouseup, onselect, requiredLabel, requiredPosition, tabindex, template, templateDir, theme, title, tooltip, tooltipConfig, tooltipCssClass, tooltipDelay, tooltipIconPath.</span><br />
<table><tbody> </tbody> </table><br />
<br />
<div class="nota">*Nota: Todas las etiquetas de este grupo deben estar dentro de un formulario. No lo indicaremos de forma explícita en los ejemplos, pero todas las etiquetas estarán contenidas dentro de las siguientes:</div><pre><code>
<s:form>
</s:form>
</code></pre><br />
Comencemos a ver la primera etiqueta:<br />
<br />
<h3 class="subtitulo"><a href="http://www.blogger.com/null" id="form"><span class="codigo">form</span></a></h3>Muestra un formulario en <span class="codigo">HTML</span>... y eso es todo. No hay mucho más que decir de esta etiqueta, ya que el resto del trabajo lo hacen los componentes del formulario.<br />
<br />
Los atributos de esta etiqueta se muestran en la siguiente tabla:<br />
<br />
<table><thead>
<tr> <th>Nombre</th><th>Obligatorio</th><th>Valor Default</th><th>Tipo</th><th>Descripción</th> </tr>
</thead> <tbody>
<tr> <td><span class="codigo">action</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>El nombre del <span class="codigo">Action</span> al que este formulario será enviado.</td> </tr>
<tr> <td><span class="codigo">includeContext</span></td><td>No</td><td><span class="codigo">true</span></td><td><span class="codigo">Boolean</span></td><td>Indica si el contexto actual debe ser incluido en la <span class="codigo">URL</span> del <span class="codigo">Action</span> al que se envirará el formulario.</td> </tr>
<tr> <td><span class="codigo">namespace</span></td><td>No</td><td>El namespace actual</td><td><span class="codigo">String</span></td><td>El namespace del <span class="codigo">Action</span> al que este formulario será enviado.</td> </tr>
<tr> <td colspan="5"><span class="codigo">acceptcharset, enctype, focusElement, method, onreset, onsubmit, openTemplate, portletMode, target, validate, value, windowState</span>.</td> </tr>
</tbody> </table><br />
<br />
Para esta etiqueta no proporcionaremos ningún ejemplo, ya que por sí sola no tiene ninguna utilidad, así que pasaremos directamente a al siguiente etiqueta.<br />
<br />
<h3 class="subtitulo"><a href="http://www.blogger.com/null" id="label"><span class="codigo">label</span></a></h3>Muestra una etiqueta "<span class="codigo">label</span>" de <span class="codigo">HTML</span>, la cual se usa para colocar un texto que identifica a un elemento de un formulario. Por lo regular al hacer clic sobre una etiqueta se hace clic también sobre el elemento con el que está relacionado. Para indicar esta relación se usa el atributo "<span class="codigo">for</span>" de la etiqueta (el cual es un atributo de <span class="codigo">HTML</span>), indicando el identificador del elemento con el que se relacionará.<br />
<br />
Los atributos de esta etiqueta se muestran a continuación:<br />
<br />
<table><thead>
<tr> <th>Nombre</th><th>Obligatorio</th><th>Valor Default</th><th>Tipo</th><th>Descripción</th> </tr>
</thead> <tbody>
<tr> <td><span class="codigo">value</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>El texto que mostrará la etiqueta.</td> </tr>
<tr> <td colspan="6"><span class="codigo">for</span></td> </tr>
</tbody> </table><br />
<br />
Tampoco veremos ejemplo de esta etiqueta, ya que se requiere de otro elemento para que esta tenga sentido.<br />
<br />
<h3 class="subtitulo"><a href="http://www.blogger.com/null" id="head"><span class="codigo">head</span></a></h3>Agrega elementos necesarios para el funcionamiento y apariencia visual de los elementos de la página, en el encabezado de esta.<br />
<br />
Los archivos que se agreguen dependerán del tema que estemos usando.<br />
<br />
Esta etiqueta no tiene ningún atributo.<br />
<br />
Si colocamos la etiqueta de esta forma:<br />
<br />
<pre><code>
<head>
<s:head />
</head>
</code></pre><br />
<br />
Obtendremos el siguiente código:<br />
<br />
<pre><code>
<head>
<script src="/tags/struts/utils.js" type="text/javascript"></script>
</head>
</code></pre><br />
<br />
Un detalle importante de esta etiqueta es que agregará tanto scripts como hojas de estilo en el encabezado de la página. Sin embargo, las "buenas prácticas" dicen que para mejorar el rendimiento del sitio, siempre se deben poner los <span class="codigo">.css</span> en el encabezado de la página, y los archivos <span class="codigo">.js</span> en la parte inferior de la página.<br />
<br />
<br />
<h3 class="subtitulo"><a href="http://www.blogger.com/null" id="file"><span class="codigo">file</span></a></h3>Muestra un componente para cargar un archivo al servidor.<br />
<br />
Ya habíamos hablado de esta etiqueta en el tutorial de trabajo con formularios en donde explicamos detalladamente cómo usar este componente.<br />
<br />
Los atributos de esta etiqueta se muestran en la siguiente tabla:<br />
<br />
<br />
<table><thead>
<tr> <th>Nombre</th><th>Obligatorio</th><th>Valor Default</th><th>Tipo</th><th>Descripción</th> </tr>
</thead> <tbody>
<tr> <td><span class="codigo">accept</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>Atributo "<span class="codigo">accept</span>" de <span class="codigo">HTML</span> indicando los mime types aceptados por el componente.</td> </tr>
<tr> <td><span class="codigo">value</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>El valor pre-establecido que tendrá el componente. Este valor debería venir desde un atributo de un <span class="codigo">Action</span>.</td> </tr>
</tbody> </table><br />
<br />
Veamos un pequeño ejemplo. Colocaremos la etiqueta para que sólo acepte imágenes de cualquier tipo, de la siguiente forma:<br />
<br />
<pre><code>
<s:file accept="image/*" name="fileTest" id="fileTest" />
</code></pre><br />
<br />
La etiqueta anterior genera el siguiente código:<br />
<br />
<pre><code>
<input type="file" name="fileTest" value="" accept="image/*" id="fileTest"/>
</code></pre><br />
<br />
Que se ve así en el navegador.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiJVLdK2th2mtWWcmyrUz3PNOt1w6tCf7vwdCaYoSAOA2dPB8AuwLX8s840QFt5HADBkM7OZF56OGxQhudcRPb03yHnT6IIQ_0vIklysy6icmoLixCycGuhPBVpldS8Lc286AN5qmDTXkpX/s1600/S7_64.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiJVLdK2th2mtWWcmyrUz3PNOt1w6tCf7vwdCaYoSAOA2dPB8AuwLX8s840QFt5HADBkM7OZF56OGxQhudcRPb03yHnT6IIQ_0vIklysy6icmoLixCycGuhPBVpldS8Lc286AN5qmDTXkpX/s400/S7_64.png" /></a></div><br />
<br />
Si presionamos el botón "<span class="codigo">Examinar...</span>" y navegamos a un directorio que tenga varios tipos de archivos, veremos que sólo se nos muestran las imágenes (claro, esto depende del navegador, en IE menor a 10, para variar, esto no funciona bien <span class="codigo">^_^</span>).<br />
<br />
Si queremos modificar esta etiqueta para que sólo acepte archivos de texto plano y html podemos colocarla de esta forma:<br />
<br />
<pre><code>
<s:file accept="text/html,text/plain" name="fileTest" id="fileTest" />
</code></pre><br />
<br />
Que genera el siguiente código:<br />
<br />
<pre><code>
<input type="file" name="fileTest" value="" accept="text/html,text/plain" id="fileTest"/>
</code></pre><br />
<br />
Que visualmente es igual al caso anterior.<br />
<br />
<h3 class="subtitulo"><a href="http://www.blogger.com/null" id="checkbox"><span class="codigo">checkbox</span></a></h3>Muestra un elemento input de tipo <span class="codigo">checkbox</span>.<br />
<br />
Como en <span class="codigo">HTML</span> (al menos hasta antes de la versión 5) si un <span class="codigo">checkbox</span> no está seleccionado este no se envía al formulario, <span class="codigo">Struts 2</span> agrega un elemento "<span class="codigo">hidden</span>" extra por cada <span class="codigo">checkbox</span> que colocamos. De esta manera un interceptor especial determina si el valor es <span class="codigo">true</span> o <span class="codigo">false</span>, y lo establece correctamente en el <span class="codigo">Action</span>.<br />
<br />
Los atributos de esta etiqueta son:<br />
<br />
<table><thead>
<tr> <th>Nombre</th><th>Obligatorio</th><th>Valor Default</th><th>Tipo</th><th>Descripción</th> </tr>
</thead> <tbody>
<tr> <td><span class="codigo">value</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>El valor pre-establecido que tendrá el <span class="codigo">checkbox</span> (<span class="codigo">true</span> o <span class="codigo">false</span>). Este valor debería venir desde un atributo de un <span class="codigo">Action</span>.</td> </tr>
</tbody> </table><br />
<br />
Para el ejemplo crearemos un <span class="codigo">checkbox</span> cuyo <span class="codigo">nombre</span> e <span class="codigo">id</span> serán "<span class="codigo">checkboxTest</span>", y una etiqueta para este componente:<br />
<br />
<pre><code>
<s:label for="checkboxTest" value="Checkbox: " />
<s:checkbox id="checkboxTest" name="checkboxTest" value="false" />
</code></pre><br />
<br />
Con esto el <span class="codigo">HTML</span> generado es el siguiente:<br />
<br />
<pre><code>
<label for="checkboxTest">Checkbox: </label>
<input type="checkbox" name="checkboxTest" value="true" id="checkboxTest"/>
<input type="hidden" id="__checkbox_checkboxTest" name="__checkbox_checkboxTest" value="true" />
</code></pre><br />
<br />
Y se ve de la siguiente forma en el navegador:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgB-wUHDzlXUUt3eKmkdTEutamakg_Ow86_WzW5EcNRu-NR6_AwdkgX6YU6Hq3GpOgoS52JyJvtq5cPL59RlLPIWC9_to3azWB96t4HQ2OsT1ZWT0w93d41lscmZ_PTy-TmeOrtL6lF6By8/s1600/S7_42.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgB-wUHDzlXUUt3eKmkdTEutamakg_Ow86_WzW5EcNRu-NR6_AwdkgX6YU6Hq3GpOgoS52JyJvtq5cPL59RlLPIWC9_to3azWB96t4HQ2OsT1ZWT0w93d41lscmZ_PTy-TmeOrtL6lF6By8/s400/S7_42.png" /></a></div><br />
<br />
Vemos que se ha agregado el elemento de tipo "<span class="codigo">hidden</span>" que mencionamos anteriormente.<br />
<br />
Ahora veamos la siguiente etiqueta.<br />
<br />
<h3 class="subtitulo"><a href="http://www.blogger.com/null" id="checkboxlist"><span class="codigo">checkboxlist</span></a></h3>Crea una serie de <span class="codigo">checkbox</span>es desde una lista o un mapa.<br />
<br />
Los atributos de esta etiqueta son:<br />
<br />
<table><thead>
<tr> <th>Nombre</th><th>Obligatorio</th><th>Valor Default</th><th>Tipo</th><th>Descripción</th> </tr>
</thead> <tbody>
<tr> <td><span class="codigo">list</span></td><td>Si</td><td> </td><td><span class="codigo">String</span></td><td>Una fuente iterable (una lista o un mapa) de elementos que llenarán la lista.</td> </tr>
<tr> <td><span class="codigo">listKey</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>La propiedad del objeto que se usará para mostrar <span class="negritas">el valor</span> que tendrá el elemento de la lista.</td> </tr>
<tr> <td><span class="codigo">listTitle</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>La propiedad del objeto que se usará para mostrar <span class="negritas">el título</span> del elemento de la lista.</td> </tr>
<tr> <td><span class="codigo">listValue</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>La propiedad del objeto que se usará para mostrar <span class="negritas">la etiqueta</span> del elemento de la lista.</td> </tr>
<tr> <td><span class="codigo">value</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>El valor pre-establecido que tendrá el <span class="codigo">checkboxlist</span> (los elementos que estarán seleccionados). Este valor debería venir desde un atributo de un <span class="codigo">Action</span>.</td> </tr>
</tbody> </table><br />
<br />
Para el primer ejemplo crearemos una lista que colocaremos directamente en el atributo "<span class="codigo">list</span>" de la etiqueta. Esta será una lista de cadenas con los valores del uno al cinco. Daremos a la etiqueta un id y un nombre de "<span class="codigo">checkboxlistTest</span>":<br />
<br />
<pre><code>
<s:checkboxlist list="{'uno', 'dos', 'tres', 'cuatro', 'cinco'}" id="checkboxlistTest" name="checkboxlistTest" />
</code></pre><br />
<br />
Cuando trabajamos con listas de cadenas, como en este caso, el valor y la etiqueta de cada elemento es el mismo, el texto de la cadena. Por lo que el código generado es el siguiente:<br />
<br />
<pre><code>
<input type="checkbox" name="checkboxlistTest" value="uno" id="checkboxlistTest-1" />
<label for="checkboxlistTest-1" class="checkboxLabel">uno</label>
<input type="checkbox" name="checkboxlistTest" value="dos" id="checkboxlistTest-2" />
<label for="checkboxlistTest-2" class="checkboxLabel">dos</label>
<input type="checkbox" name="checkboxlistTest" value="tres"id="checkboxlistTest-3" />
<label for="checkboxlistTest-3" class="checkboxLabel">tres</label>
<input type="checkbox" name="checkboxlistTest" value="cuatro" id="checkboxlistTest-4" />
<label for="checkboxlistTest-4" class="checkboxLabel">cuatro</label>
<input type="checkbox" name="checkboxlistTest" value="cinco" id="checkboxlistTest-5" />
<label for="checkboxlistTest-5" class="checkboxLabel">cinco</label>
<input type="hidden" id="__multiselect_checkboxlistTest" name="__multiselect_checkboxlistTest" value="" />
</code></pre><br />
<br />
Que se ve de la siguiente forma en el navegador:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEizP16zRZ4G8g2mUD9WUL_zfxCvvpo0VhVXVeU4pZxq6Na4LA0zpn6Ipauy2H8JB_YZ1ilVSG3v1y-0CVJr30L1jatbni31zcQU_wvVFsas4dnpFAKxDEGTXtv5OR_ANSWaNcyA7rVMk495/s1600/S7_43.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEizP16zRZ4G8g2mUD9WUL_zfxCvvpo0VhVXVeU4pZxq6Na4LA0zpn6Ipauy2H8JB_YZ1ilVSG3v1y-0CVJr30L1jatbni31zcQU_wvVFsas4dnpFAKxDEGTXtv5OR_ANSWaNcyA7rVMk495/s400/S7_43.png" /></a></div><br />
<br />
¿Qué pasa si queremos usar valores distintos para la etiqueta y el valor? En ese caso podemos usar un mapa, del cual la llave se tomará como valor y el valor se tomará como etiqueta, en el atributo "<span class="codigo">list</span>" de la etiqueta.<br />
<br />
Modifiquemos el ejemplo para que ahora el valor sea el número y la etiqueta el nombre del número, de la siguiente forma:<br />
<br />
<pre><code>
<s:checkboxlist list="#{'1':'uno', '2':'dos', '3':'tres', '4':'cuatro', '5':'cinco'}" id="checkboxlistTest" name="checkboxlistTest" />
</code></pre><br />
<br />
Lo cual genera el siguiente código:<br />
<br />
<pre><code>
<input type="checkbox" name="checkboxlistTest" value="1" id="checkboxlistTest-1" />
<label for="checkboxlistTest-1" class="checkboxLabel">uno</label>
<input type="checkbox" name="checkboxlistTest" value="2" id="checkboxlistTest-2" />
<label for="checkboxlistTest-2" class="checkboxLabel">dos</label>
<input type="checkbox" name="checkboxlistTest" value="3" id="checkboxlistTest-3" />
<label for="checkboxlistTest-3" class="checkboxLabel">tres</label>
<input type="checkbox" name="checkboxlistTest" value="4" id="checkboxlistTest-4" />
<label for="checkboxlistTest-4" class="checkboxLabel">cuatro</label>
<input type="checkbox" name="checkboxlistTest" value="5" id="checkboxlistTest-5" />
<label for="checkboxlistTest-5" class="checkboxLabel">cinco</label>
<input type="hidden" id="__multiselect_checkboxlistTest" name="__multiselect_checkboxlistTest" value="" />
</code></pre><br />
<br />
Y que se ve de la siguiente forma en el navegador:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg6k6vh9S3bsgxxOt5kOnoPYg6dr6xTytN8fnH-b676lsCSXmKAo3Yh_pdYdhHdCg4znapN4WXlrHsyD557KkvRiDsZ5HU0ou0iyilVPZzNs4MlzDKQSvrLfaGVZytRUuK5GREOK2IfYMEB/s1600/S7_44.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg6k6vh9S3bsgxxOt5kOnoPYg6dr6xTytN8fnH-b676lsCSXmKAo3Yh_pdYdhHdCg4znapN4WXlrHsyD557KkvRiDsZ5HU0ou0iyilVPZzNs4MlzDKQSvrLfaGVZytRUuK5GREOK2IfYMEB/s400/S7_44.png" /></a></div><br />
<br />
Aunque, siendo realistas, casi nunca vamos a generar componentes con elementos estáticos. Lo más normal es generarlos a través de una lista de objetos, típicamente recibida del atributo de un bean. Cuando hacemos esto, debemos indicar qué atributo del objeto se usará como valor y cuál como etiqueta. <br />
Veamos un ejemplo del caso anterior, creando una lista de objetos "<span class="codigo">Usuario</span>". Tomaremos el atributo "<span class="codigo">nombre</span>" como etiqueta y, a falta de un atributo mejor, "<span class="codigo">edad</span>" como valor.<br />
<br />
Lo primero es crear unos cuantos objetos usando la etiqueta "<span class="codigo">s:bean</span>":<br />
<br />
<pre><code>
<s:bean name="com.javatutoriales.struts2.tags.modelo.Usuario" var="usuario1">
<s:param name="nombre" value="%{'Alex1'}" />
<s:param name="edad" value="%{1}" />
</s:bean>
<s:bean name="com.javatutoriales.struts2.tags.modelo.Usuario" var="usuario2">
<s:param name="nombre" value="%{'Alex2'}" />
<s:param name="edad" value="%{2}" />
</s:bean>
<s:bean name="com.javatutoriales.struts2.tags.modelo.Usuario" var="usuario3">
<s:param name="nombre" value="%{'Alex3'}" />
<s:param name="edad" value="%{3}" />
</s:bean>
</code></pre><br />
<br />
Ahora creamos la lista de <span class="codigo">checkbox</span>es. En su atributo "<span class="codigo">list</span>" creamos una lista a la cual pasamos los tres objetos anteriores:<br />
<br />
<pre><code>
<s:checkboxlist list="{#usuario1, #usuario2, #usuario3}" id="checkboxlistTest" name="checkboxlistTest" />
</code></pre><br />
<br />
El último paso es indicar cuáles atributos servirán como valor y como etiqueta. Para indicar cuál será el valor usamos el atributo "<span class="codigo">listKey</span>", y para la etiqueta el atributo "<span class="codigo">listValue</span>":<br />
<br />
<pre><code>
<s:checkboxlist list="{#usuario1, #usuario2, #usuario3}" id="checkboxlistTest" name="checkboxlistTest" listKey="edad" listValue="nombre" />
</code></pre><br />
<br />
El ejemplo anterior genera el siguiente código:<br />
<br />
<pre><code>
<input type="checkbox" name="test" value="1" id="test-1" />
<label for="test-1" class="checkboxLabel">Alex1</label>
<input type="checkbox" name="test" value="2" id="test-2" />
<label for="test-2" class="checkboxLabel">Alex2</label>
<input type="checkbox" name="test" value="3" id="test-3" />
<label for="test-3" class="checkboxLabel">Alex3</label>
<input type="hidden" id="__multiselect_test" name="__multiselect_test" value="" />
</code></pre><br />
<br />
Que se ve así en el navegador:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhWsyqo8_ZWo5MZmsTCuFXtX3hF0UB-uNlFeRxtLoYs8oVjxPYGsIyGoJz3Pgho0rYI9CpQ-WEK-TYsa8nb3bV6DIL7AJL32-mOfGCmodk5kbqOT955H6_LBBO4MqQbIpRKWWkf43VScFLV/s1600/S7_45.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhWsyqo8_ZWo5MZmsTCuFXtX3hF0UB-uNlFeRxtLoYs8oVjxPYGsIyGoJz3Pgho0rYI9CpQ-WEK-TYsa8nb3bV6DIL7AJL32-mOfGCmodk5kbqOT955H6_LBBO4MqQbIpRKWWkf43VScFLV/s400/S7_45.png" /></a></div><br />
<br />
Esta misma lista de objetos que hemos generado de una forma estática, podríamos obtenerla de forma dinámica a través de un <span class="codigo">Action</span>.<br />
<br />
<h3 class="subtitulo"><a href="http://www.blogger.com/null" id="radio"><span class="codigo">radio</span></a></h3>Muestra un campo de entrada de tipo radio.<br />
<br />
Esta etiqueta funciona de forma muy parecida a "<span class="codigo">s:checkboxlist</span>", recibe una lista de elementos que mostrará como radio buttons. <br />
<br />
Los atributos de esta etiqueta se muestran en la siguiente tabla:<br />
<br />
<table><thead>
<tr> <th>Nombre</th><th>Obligatorio</th><th>Valor Default</th><th>Tipo</th><th>Descripción</th> </tr>
</thead> <tbody>
<tr> <td><span class="codigo">list</span></td><td>Si</td><td> </td><td><span class="codigo">String</span></td><td>Una fuente iterable (una lista o un mapa) de elementos que llenarán la lista.</td> </tr>
<tr> <td><span class="codigo">listKey</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>La propiedad del objeto que se usará para mostrar <span class="negritas">el valor</span> que tendrá el elemento de la lista.</td> </tr>
<tr> <td><span class="codigo">listTitle</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>La propiedad del objeto que se usará para mostrar <span class="negritas">el título</span> del elemento de la lista.</td> </tr>
<tr> <td><span class="codigo">listValue</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>La propiedad del objeto que se usará para mostrar <span class="negritas">la etiqueta</span> del elemento de la lista.</td> </tr>
<tr> <td><span class="codigo">value</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>El valor pre-establecido que tendrá la lista de radio buttons (el elemento que estará seleccionado). Este valor debería venir desde un atributo de un <span class="codigo">Action</span>.</td> </tr>
</tbody> </table><br />
<br />
Como esta etiqueta funciona igual a la anterior, saltaremos directo al último ejemplo de la vez anterior, y crearemos una serie de radio buttons basado en una lista de usuarios que crearemos con la etiqueta "<span class="codigo">s:bean</span>". Primero los beans:<br />
<br />
<pre><code>
<s:bean name="com.javatutoriales.struts2.tags.modelo.Usuario" var="usuario1">
<s:param name="nombre" value="%{'Alex1'}" />
<s:param name="edad" value="%{1}" />
</s:bean>
<s:bean name="com.javatutoriales.struts2.tags.modelo.Usuario" var="usuario2">
<s:param name="nombre" value="%{'Alex2'}" />
<s:param name="edad" value="%{2}" />
</s:bean>
<s:bean name="com.javatutoriales.struts2.tags.modelo.Usuario" var="usuario3">
<s:param name="nombre" value="%{'Alex3'}" />
<s:param name="edad" value="%{3}" />
</s:bean>
</code></pre><br />
<br />
Y luego la etiqueta "<span class="codigo">s:radio</span>":<br />
<br />
<pre><code>
<s:radio list="{#usuario1, #usuario2, #usuario3}" id="radioTest" name="radioTest" listKey="edad" listValue="nombre" />
</code></pre><br />
<br />
La cual genera el siguiente código:<br />
<br />
<pre><code>
<input type="radio" name="radioTest" id="radioTest1" value="1"/><label for="radioTest1">Alex1</label>
<input type="radio" name="radioTest" id="radioTest1" value="2"/><label for="radioTest1">Alex2</label>
<input type="radio" name="radioTest" id="radioTest1" value="3"/><label for="radioTest1">Alex3</label>
</code></pre><br />
<br />
Que se ve así en el navegador:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMc4uJc8ur4kci_a0Xk90TAP-7TlngBH6OH-IjRmWRM_F1RY5VdtZb-UByBE8TS_l1eNh4stfibojk8uyLDCscgVEN8kc2c9Fgt7G9amBVlhVDo_3F3JxlZiImKVo3MeDgslVfSg_zrnA3/s1600/S7_46.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMc4uJc8ur4kci_a0Xk90TAP-7TlngBH6OH-IjRmWRM_F1RY5VdtZb-UByBE8TS_l1eNh4stfibojk8uyLDCscgVEN8kc2c9Fgt7G9amBVlhVDo_3F3JxlZiImKVo3MeDgslVfSg_zrnA3/s400/S7_46.png" /></a></div><br />
<br />
Podemos ver que en este caso no se generan campos "<span class="codigo">hidden</span>". Además, a diferencia de los <span class="codigo">checkbox</span>, aquí no hay una forma de generar sólo un radio button (a menos que se use una lista con un solo elemento).<br />
<br />
<h3 class="subtitulo"><a href="http://www.blogger.com/null" id="select"><span class="codigo">select</span></a></h3>Genera una etiqueta de tipo "<span class="codigo">select</span>" y una serie de etiquetas "<span class="codigo">option</span>" llenas con la lista de valores que recibe como parámetro.<br />
<br />
Los atributos de esta etiqueta se muestran en la siguiente tabla:<br />
<br />
<table><thead>
<tr> <th>Nombre</th><th>Obligatorio</th><th>Valor Default</th><th>Tipo</th><th>Descripción</th> </tr>
</thead> <tbody>
<tr> <td><span class="codigo">list</span></td><td>Si</td><td> </td><td><span class="codigo">String</span></td><td>La fuente de datos iterable (un iterador, una lista o un mapa) que se usará para llenar los elementos "<span class="codigo">option</span>" de la etiqueta.</td> </tr>
<tr> <td><span class="codigo">listKey</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>La propiedad del objeto que se usará para mostrar <span class="negritas">el valor</span> que tendrá cada elemento de la lista.</td> </tr>
<tr> <td><span class="codigo">listTitle</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>La propiedad del objeto que se usará para mostrar <span class="negritas">el título</span> de cada elemento de la lista.</td> </tr>
<tr> <td><span class="codigo">listValue</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>La propiedad del objeto que se usará para mostrar <span class="negritas">la etiqueta</span> de cada elemento de la lista.</td> </tr>
<tr> <td><span class="codigo">headerKey</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>El valor que tendrá el primer elemento de la lista.</td> </tr>
<tr> <td><span class="codigo">headerValue</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>La etiqueta que tendrá el primer elemento de la lista.</td> </tr>
<tr> <td><span class="codigo">multiple</span></td><td>No</td><td><span class="codigo">false</span></td><td><span class="codigo">Boolean</span></td><td>Indica si debe permitirse seleccionar múltiples elementos de la lista. Si queremos pre-seleccionar varios elementos, debemos pasar un arreglo o una lista al atributo "<span class="codigo">value</span>" de la etiqueta.</td> </tr>
<tr> <td><span class="codigo">value</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>El valor del elemento o elementos que estarán pre-seleccionados. Este valor debería venir desde un atributo de un <span class="codigo">Action</span>.</td> </tr>
<tr> <td colspan="6"><span class="codigo">listCssClass, listCssStyle, size</span></td> </tr>
</tbody> </table><br />
<br />
Esta etiqueta funciona de manera muy similar a "<span class="codigo">checkboxlist</span>" y "<span class="codigo">radio</span>": En su atributo "<span class="codigo">list</span>" puede recibir una lista o un mapa de elementos (los cuales pueden ser cualquier tipo de objeto).<br />
<br />
Como a esta altura ya entendimos como poner estos elementos en el atributo "<span class="codigo">list</span>" veremos, primero, cómo pasar un mapa con datos estáticos.<br />
<br />
Si colocamos la siguiente etiqueta:<br />
<br />
<pre><code>
<s:label for="selectTest" value="Select: " />
<s:select list="#{'1':'uno', '2':'dos', '3':'tres', '4':'cuatro', '5':'cinco'}" id="selectTest" name="selectTest" headerKey="-1" headerValue="Selecciona una opcion" />
</code></pre><br />
<br />
Obtendremos el siguiente código:<br />
<br />
<pre><code>
<label for="selectTest">Select: </label>
<select name="selectTest" id="selectTest">
<option value="-1">Selecciona una opcion</option>
<option value="1">uno</option>
<option value="2">dos</option>
<option value="3">tres</option>
<option value="4">cuatro</option>
<option value="5">cinco</option>
</select>
</code></pre><br />
<br />
Que se ve de la siguiente forma en el navegador:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhjY1uG3HZGhYzRmwzoEfwKSj8da3tOlvmvTHcoM_KpZfLkQAvuiHEXZjJzcoQnBMGJcbOcRz1H9gPdS5aiomg84b1cbzNS6LdCwKgYkBvFzn8qqfSZNUHIDlotmlcGlOskWoQvL5-wUqtv/s1600/S7_51.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhjY1uG3HZGhYzRmwzoEfwKSj8da3tOlvmvTHcoM_KpZfLkQAvuiHEXZjJzcoQnBMGJcbOcRz1H9gPdS5aiomg84b1cbzNS6LdCwKgYkBvFzn8qqfSZNUHIDlotmlcGlOskWoQvL5-wUqtv/s400/S7_51.png" /></a></div><br />
<br />
Ahora, en vez de usar un mapa, lo haremos con una lista de objetos, así que lo primero que haremos es crear tres beans con la etiqueta "<span class="codigo">s:bean</span>":<br />
<br />
<pre><code>
<s:bean name="com.javatutoriales.struts2.tags.modelo.Usuario" var="usuario1">
<s:param name="nombre" value="%{'Alex1'}" />
<s:param name="edad" value="%{1}" />
</s:bean>
<s:bean name="com.javatutoriales.struts2.tags.modelo.Usuario" var="usuario2">
<s:param name="nombre" value="%{'Alex2'}" />
<s:param name="edad" value="%{2}" />
</s:bean>
<s:bean name="com.javatutoriales.struts2.tags.modelo.Usuario" var="usuario3">
<s:param name="nombre" value="%{'Alex3'}" />
<s:param name="edad" value="%{3}" />
</s:bean>
</code></pre><br />
<br />
Y luego creamos una lista con estos objetos, la cual pasamos al atributo "<span class="codigo">list</span>" de la etiqueta "<span class="codigo">s:select</span>". También indicamos cuáles atributos servirán como valor y como etiqueta. Para indicar cuál será el valor usamos el atributo "<span class="codigo">listKey</span>", y para la etiqueta el atributo "<span class="codigo">listValue</span>", de la siguiente forma:<br />
<br />
<pre><code>
<s:label for="selectTest" value="Select: " />
<s:select list="{#usuario1, #usuario2, #usuario3}" id="selectTest" name="selectTest" listKey="edad" listValue="nombre" headerKey="-1" headerValue="Seleccione una opcion" />
</code></pre><br />
<br />
La etiqueta anterior genera el siguiente código:<br />
<br />
<pre><code>
<label for="selectTest">Select: </label>
<select name="selectTest" id="selectTest">
<option value="-1">Seleccione una opcion</option>
<option value="1">Alex1</option>
<option value="2">Alex2</option>
<option value="3">Alex3</option>
</select>
</code></pre><br />
<br />
Que se ve así en el navegador:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEig7oJW_EJvM9uU0qVRVYelGii_tHpGgE-9k3grYZqrGKFhvG1urjCy1H3rrZpDClg5aNlAHgvEzCTdVEf2JBbkhSfHL7KysmiYwc5QDcEB7q2fZ_R6wzicv3vyFH4nMaX_WqhXsyUn3oz2/s1600/S7_52.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEig7oJW_EJvM9uU0qVRVYelGii_tHpGgE-9k3grYZqrGKFhvG1urjCy1H3rrZpDClg5aNlAHgvEzCTdVEf2JBbkhSfHL7KysmiYwc5QDcEB7q2fZ_R6wzicv3vyFH4nMaX_WqhXsyUn3oz2/s400/S7_52.png" /></a></div><br />
<br />
Ahora veamos una etiqueta un poco distinta.<br />
<br />
<h3 class="subtitulo"><a href="http://www.blogger.com/null" id="optgroup"><span class="codigo">optgroup</span></a></h3>Crea, dentro de una etiqueta "<span class="codigo">select</span>", una etiqueta <span class="codigo">optgroup</span> de <span class="codigo">HTML</span>, que marca una serie de categorías con sus elementos. Sirven como una forma de agrupar valores como ayuda visual para el usuario. Por lo tanto esta etiqueta sólo puede ser usada dentro de una etiqueta "<span class="codigo">select</span>".<br />
<br />
Los atributos de esta etiqueta se muestran en la siguiente tabla:<br />
<br />
<table><thead>
<tr> <th>Nombre</th><th>Obligatorio</th><th>Valor Default</th><th>Tipo</th><th>Descripción</th> </tr>
</thead> <tbody>
<tr> <td><span class="codigo">list</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>La fuente de datos iterable (un mapa o una lista de objetos) que se usará para llenar los elementos "<span class="codigo">option</span>" de la etiqueta.</td> </tr>
<tr> <td><span class="codigo">listKey</span></td><td>No</td><td> </td><td>No</td><td>La propiedad del objeto que se usará para mostrar <span class="negritas">el valor</span> que tendrá cada elemento de la lista.</td> </tr>
<tr> <td><span class="codigo">listValue</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>La propiedad del objeto que se usará para mostrar <span class="negritas">la etiqueta</span> de cada elemento de la lista.</td> </tr>
<tr> <td><span class="codigo">label</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>El nombre de la categoría que se mostrará en el componente <span class="codigo">select</span>. Este valor no puede ser seleccionado.</td> </tr>
</tbody> </table><br />
<br />
A diferencia de los casos anteriores, esta etiqueta sólo puede recibir los valores de sus opciones a través de un mapa o de una lista de objetos, siempre y cuando estos NO sean cadenas. <br />
<br />
<br />
Para el ejemplo lo primero que debemos hacer es tener una etiqueta "<span class="codigo">s:select</span>", en esta pondremos algunas de las tecnologías existentes de Java:<br />
<br />
<pre><code>
<s:select id="selectTest" name="selectTest" list="{'JSE', 'JEE', 'JME'}">
</s:select>
</code></pre><br />
<br />
Ahora, en el cuerpo de la etiqueta anterior, colocamos la etiqueta "<span class="codigo">s:optgroup</span>". El primer grupo que crearemos será de servidores de aplicaciones Java. Por lo que primero colocaremos la etiqueta (esta es como el nombre de la categoría o del grupo, esta etiqueta se muestra de un color más obscuro y no se puede seleccionar), la cual será "<span class="codigo">Servidores</span>":<br />
<br />
<pre><code>
<s:select id="selectTest" name="selectTest" list="{'JSE', 'JEE', 'JME'}">
<s:optgroup label="Servidores" />
</s:select>
</code></pre><br />
<br />
Lo siguiente que haremos será colocar la lista de opciones con un mapa. Aquí pondremos una lista con 4 servidores de aplicaciones. Aquí la llave (lo que se usará como valor) será un número que servirá a modo de identificador, y el valor (lo que se muestra como etiqueta) será el nombre el servidor:<br />
<br />
<pre><code>
<s:select id="selectTest" name="selectTest" list="{'JSE', 'JEE', 'JME'}">
<s:optgroup label="Servidores" list="#{'1':'Glassfish', '2':'JBoss', '3':'WebLogic', '4':'WebSphere'}" />
</s:select>
</code></pre><br />
<br />
Colocaremos un segundo grupo, de bases de datos, sólo para ver cómo se ven estos en la interfaz de usuario:<br />
<br />
<pre><code>
<s:select id="selectTest" name="selectTest" list="{'JSE', 'JEE', 'JME'}">
<s:optgroup label="Servidores" list="#{'1':'Glassfish', '2':'JBoss', '3':'WebLogic', '4':'WebSphere'}" />
<s:optgroup label="Bases de Datos" list="#{'5':'Oracle', '6':'MySQL', '7':'SQL Server', '8':'PosgreSQL'}" />
</s:select>
</code></pre><br />
<br />
La etiqueta anterior genera el siguiente código:<br />
<br />
<pre><code>
<select name="selectTest" id="selectTest">
<option value="JSE">JSE</option>
<option value="JEE">JEE</option>
<option value="JME">JME</option>
<optgroup label="Servidores">
<option value="1">Glassfish</option>
<option value="2">JBoss</option>
<option value="3">WebLogic</option>
<option value="4">WebSphere</option>
</optgroup>
<optgroup label="Bases de Datos">
<option value="5">Oracle</option>
<option value="6">MySQL</option>
<option value="7">SQL Server</option>
<option value="8">PosgreSQL</option>
</optgroup>
</select>
</code></pre><br />
<br />
Que se ve así en el navegador:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhg2eUVHW4jwYied6RidWGBDy0PIr1ot0EPOoFkn9pXxjs2F_r2bcKsiVMurUddq7b6KBuI3QnOh3u-RW-EoFljIIDaF6ZvsUejVYaT83r87XtNd05wRMakAC0JnJL2nqrWHxlzoCsY_ZbB/s1600/S7_58.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhg2eUVHW4jwYied6RidWGBDy0PIr1ot0EPOoFkn9pXxjs2F_r2bcKsiVMurUddq7b6KBuI3QnOh3u-RW-EoFljIIDaF6ZvsUejVYaT83r87XtNd05wRMakAC0JnJL2nqrWHxlzoCsY_ZbB/s400/S7_58.png" /></a></div><br />
<br />
Para el siguiente ejemplo, veremos cómo tomar la lista de opciones para la etiqueta "<span class="codigo">s:optgroup</span>". Como el único bean que tenemos en la aplicación es el bean <span class="codigo">Usuario</span>, haremos uso de este para nuestra lista de opciones.<br />
<br />
Lo primero que haremos es crear tres beans, cada uno con un nombre y una edad distinta:<br />
<br />
<pre><code>
<s:bean name="com.javatutoriales.struts2.tags.modelo.Usuario" var="usuario1">
<s:param name="nombre" value="%{'Alex1'}" />
<s:param name="edad" value="%{1}" />
</s:bean>
<s:bean name="com.javatutoriales.struts2.tags.modelo.Usuario" var="usuario2">
<s:param name="nombre" value="%{'Alex2'}" />
<s:param name="edad" value="%{2}" />
</s:bean>
<s:bean name="com.javatutoriales.struts2.tags.modelo.Usuario" var="usuario3">
<s:param name="nombre" value="%{'Alex3'}" />
<s:param name="edad" value="%{3}" />
</s:bean>
</code></pre><br />
<br />
Ahora crearemos la etiqueta "<span class="codigo">s:select</span>" como la teníamos anteriormente:<br />
<br />
<pre><code>
<s:select id="selectTest" name="selectTest" list="{'JSE', 'JEE', 'JME'}">
</s:select>
</code></pre><br />
<br />
Dentro del cuerpo de esta colocaremos el primer grupo, que será nuevamente el de "<span class="codigo">Base de Datos</span>":<br />
<br />
<pre><code>
<s:select id="selectTest" name="selectTest" list="{'JSE', 'JEE', 'JME'}">
<s:optgroup label="Bases de Datos" list="#{'Oracle':'5', 'MySQL':'6', 'SQL Server':'7', 'PosgreSQL':'8'}" />
</s:select>
</code></pre><br />
<br />
Y como segundo grupo agregaremos uno llamado "<span class="codigo">Usuario</span>" en la que pasaremos una lista con los objetos que creamos anteriormente. También indicamos cuáles atributos servirán como valor y como etiqueta. Para indicar cuál será el valor usamos el atributo "<span class="codigo">listKey</span>", y para la etiqueta el atributo "<span class="codigo">listValue</span>", de la siguiente forma:<br />
<br />
<pre><code>
<s:select id="selectTest" name="selectTest" list="{'JSE', 'JEE', 'JME'}">
<s:optgroup label="Bases de Datos" list=" #{'5':'Oracle', '6':'MySQL', '7':'SQL Server', '8':'PosgreSQL'}" />
<s:optgroup label="Usuarios" listKey="edad" listValue="nombre" list="{#usuario1, #usuario2, #usuario3}" />
</s:select>
</code></pre><br />
<br />
La etiqueta anterior genera el siguiente código:<br />
<br />
<pre><code>
<select name="selectTest" id="selectTest">
<option value="JSE">JSE</option>
<option value="JEE">JEE</option>
<option value="JME">JME</option>
<optgroup label="Bases de Datos">
<option value="5">Oracle</option>
<option value="6">MySQL</option>
<option value="7">SQL Server</option>
<option value="8">PosgreSQL</option>
</optgroup>
<optgroup label="Usuarios">
<option value="1">Alex1</option>
<option value="2">Alex2</option>
<option value="3">Alex3</option>
</optgroup>
</select>
</code></pre><br />
<br />
Que se ve así en el navegador:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEipDoadAVPnArdYA1njxxFdlCkLj2FdZRl3LTW5lNY3fsDWEOCcwdgHl-DK_lSuhkEssF46trTj8mI8hsjuJXT3hsl6DQHDlsYFXJfZ6HNG2TcAK1SSvkEBNF8R06DhYithnaqCpDF2TFnt/s1600/S7_59.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEipDoadAVPnArdYA1njxxFdlCkLj2FdZRl3LTW5lNY3fsDWEOCcwdgHl-DK_lSuhkEssF46trTj8mI8hsjuJXT3hsl6DQHDlsYFXJfZ6HNG2TcAK1SSvkEBNF8R06DhYithnaqCpDF2TFnt/s400/S7_59.png" /></a></div><br />
<br />
Como ven usar esta etiqueta es muy sencillo y genera una ayuda visual para clasificar cosas.<br />
<br />
Ahora veamos la siguiente etiqueta.<br />
<br />
<h3 class="subtitulo"><a href="http://www.blogger.com/null" id="textfield"><span class="codigo">textfield</span></a></h3>Esta etiqueta muestra un elemento <span class="codigo">input</span> de tipo texto, o sea un campo de texto.<br />
<br />
Los atributos de esta etiqueta se muestran en la siguiente tabla:<br />
<br />
<table><thead>
<tr> <th>Nombre</th><th>Obligatorio</th><th>Valor Default</th><th>Tipo</th><th>Descripción</th> </tr>
</thead> <tbody>
<tr> <td><span class="codigo">value</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>El valor pre-establecido que tendrá el campo de texto. Este valor debería venir desde un atributo de un <span class="codigo">Action</span>.</td> </tr>
<tr> <td colspan="6"><span class="codigo">maxlength, readonly, size</span></td> </tr>
</tbody> </table><br />
<br />
Este será un ejemplo sencillo. Colocamos la etiqueta de la siguiente forma:<br />
<br />
<pre><code>
<s:label for="textfieldTest" value="Texto: " />
<s:textfield id="textfieldTest" name="textfieldTest" value="texto" />
</code></pre><br />
<br />
Con lo que obtenemos el siguiente código:<br />
<br />
<pre><code>
<label for="textfieldTest">Texto: </label>
<input type="text" name="textfieldTest" value="texto" id="textfieldTest"/>
</code></pre><br />
<br />
Que se ve de la siguiente forma en el navegador:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjavgz2h4utk_6OKUvQY0tOkVAn_1ytmXPc_j32adZvC6ht_SMW80AjZ4wFCgb4VhTl1OBRl7zzS8EFM7fq7y1Vtw4CefTQS6ujY2SGaLNSo9sdcSoRHalPampL0fhxJv_aeL9FgdpWYiUr/s1600/S7_47.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjavgz2h4utk_6OKUvQY0tOkVAn_1ytmXPc_j32adZvC6ht_SMW80AjZ4wFCgb4VhTl1OBRl7zzS8EFM7fq7y1Vtw4CefTQS6ujY2SGaLNSo9sdcSoRHalPampL0fhxJv_aeL9FgdpWYiUr/s400/S7_47.png" /></a></div><br />
<br />
Así de fácil <span class="codigo">^_^!</span><br />
<br />
<h3 class="subtitulo"><a href="http://www.blogger.com/null" id="password"><span class="codigo">password</span></a></h3>Muestra una etiqueta <span class="codigo">input</span> de tipo password, esos que lo que escribamos lo muestran con una serie de asteriscos.<br />
<br />
Los atributos de esta etiqueta se muestran en la siguiente tabla:<br />
<br />
venir desde un atributo de un <span class="codigo">Action</span>. <br />
<table><thead>
<tr> <th>Nombre</th><th>Obligatorio</th><th>Valor Default</th><th>Tipo</th><th>Descripción</th> </tr>
</thead> <tbody>
<tr> <td><span class="codigo">value</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>El valor pre-establecido que tendrá el campo de texto. Este valor debería venir desde un atributo de un <span class="codigo">Action</span>.</td></tr>
<tr> <td><span class="codigo">showPassword</span></td><td>No</td><td><span class="codigo">false</span></td><td><span class="codigo">Boolean</span></td><td>Si se debe mostrar o no, en el código fuente de la página, el valor del password.</td> </tr>
<tr> <td colspan="6"><span class="codigo">maxlength, readonly, size</span></td> </tr>
</tbody> </table><br />
<br />
El primer ejemplo será muy sencillo. Si colocamos la siguiente etiqueta:<br />
<br />
<pre><code>
<s:label for="passwordTest" value="Password: " />
<s:password id="passwordTest" name="passwordTest" value="password" />
</code></pre><br />
<br />
Obtenemos el siguiente código:<br />
<br />
<pre><code>
<label for="passwordTest">Password: </label>
<input type="password" name="passwordTest" id="passwordTest"/>
</code></pre><br />
<br />
Que se ve así en el navegador:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgWZdTkiTi4x8Z9MRQzrC_AZHdZi77AJsxs7WTxe7qs1GIwNO4mdJoOqptPGnvvJ6vACuATTezJaJjsxK60w6_rbbl04NvFfs1vEG33dh1ZZ8VcvsDWom06hRtARlLdgLJI6tiyNFYkIWca/s1600/S7_48.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgWZdTkiTi4x8Z9MRQzrC_AZHdZi77AJsxs7WTxe7qs1GIwNO4mdJoOqptPGnvvJ6vACuATTezJaJjsxK60w6_rbbl04NvFfs1vEG33dh1ZZ8VcvsDWom06hRtARlLdgLJI6tiyNFYkIWca/s400/S7_48.png" /></a></div><br />
<br />
En el ejemplo anterior podemos notar que no se ve el valor que pusimos en el atributo "<span class="codigo">value</span>". Esto es una medida de seguridad, ya que si permitimos que se muestre el valor, aunque en la interfaz de usuario lo veríamos como una serie de asteriscos, en el código fuente este se vería de forma clara; y algún usuario "listo" podría obtener el valor de este preciado recurso.<br />
<br />
Aun así, podría existir alguna razón por la que quisiéramos mostrar el valor del password. Para este caso lo único que debemos hacer es poner en <span class="codigo">true</span> el valor del atributo "<span class="codigo">showPassword</span>", de la siguiente forma:<br />
<br />
<pre><code>
<s:label for="passwordTest" value="Password: " />
<s:password id="passwordTest" name="passwordTest" value="password" showPassword="true" />
</code></pre><br />
<br />
Con lo que obtendremos el siguiente código (noten en valor del atributo "<span class="codigo">value</span>", ¡ups!):<br />
<br />
<pre><code>
<label for="passwordTest">Password: </label>
<input type="password" name="passwordTest" value="password" id="passwordTest"/>
</code></pre><br />
<br />
Que se ve de esta forma en el navegador:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhGT7R3zeL2yqE5iXiTAJCBUQweiB38x8Djv2rOQvfRgTUqZC69Rc-xjlzHoSTmtL30XkA2z2qG02uFNrmNU7PeDrHe74WVk6MD01VrB1N2DdYWWk0g244GrKTu0XAqYdYi3ATONAm_scjV/s1600/S7_49.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhGT7R3zeL2yqE5iXiTAJCBUQweiB38x8Djv2rOQvfRgTUqZC69Rc-xjlzHoSTmtL30XkA2z2qG02uFNrmNU7PeDrHe74WVk6MD01VrB1N2DdYWWk0g244GrKTu0XAqYdYi3ATONAm_scjV/s400/S7_49.png" /></a></div><br />
<br />
En la vista no tenemos nada de qué preocuparnos.<br />
<br />
La siguiente etiqueta también será de las simples.<br />
<br />
<h3 class="subtitulo"><a href="http://www.blogger.com/null" id="hidden"><span class="codigo">hidden</span></a></h3>Muestra una etiqueta <span class="codigo">input</span> de tipo "<span class="codigo">hidden</span>".<br />
<br />
Los atributos de esta etiqueta se muestran en la siguiente tabla:<br />
<br />
<table><thead>
<tr> <th>Nombre</th><th>Obligatorio</th><th>Valor Default</th><th>Tipo</th><th>Descripción</th> </tr>
</thead> <tbody>
<tr> <td><span class="codigo">value</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>El valor pre-establecido que tendrá el campo de texto. Este valor debería venir desde un atributo de un <span class="codigo">Action</span>.</td> </tr>
</tbody> </table><br />
<br />
Este ejemplo también será muy sencillo. Si colocamos la siguiente etiqueta:<br />
<br />
<pre><code>
<s:hidden name="hiddenTest" value="abc123" />
</code></pre><br />
<br />
Obtenemos el siguiente código:<br />
<br />
<pre><code>
<input type="hidden" name="hiddenTest" value="abc123" id="hiddenTest"/>
</code></pre><br />
<br />
El cual no genera ninguna salida en pantalla.<br />
<br />
<h3 class="subtitulo"><a href="http://www.blogger.com/null" id="textarea"><span class="codigo">textarea</span></a></h3>Muestra un área de texto donde el usuario puede escribir texto de forma libre, incluyendo saltos de línea.<br />
<br />
Los atributos de esta etiqueta se muestran en la siguiente tabla:<br />
<br />
<table><thead>
<tr> <th>Nombre</th><th>Obligatorio</th><th>Valor Default</th><th>Tipo</th><th>Descripción</th> </tr>
</thead> <tbody>
<tr> <td><span class="codigo">value</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>El valor pre-establecido que tendrá el área de texto. Este valor debería venir desde un atributo de un <span class="codigo">Action</span>.</td> </tr>
<tr> <td colspan="6"><span class="codigo">cols, readonly, rows, wrap</span></td> </tr>
</tbody> </table><br />
<br />
Los tributos <span class="codigo">cols</span> (número de columnas) y <span class="codigo">rows</span> (número de filas) determinan el tamaño visible del área de texto.<br />
<br />
El valor del área de texto se coloca en su atributo "<span class="codigo">value</span>". Este atributo por lo regular vendrá desde un <span class="codigo">Action</span>, pero si vamos a definir un texto estático de gran longitud, podemos ponerlo en una variable con la etiqueta "<span class="codigo">s:set</span>" y posteriormente pasar esta variable a la etiqueta "<span class="codigo">s:textarea</span>".<br />
<br />
El ejemplo será sencillo, si colocamos la siguiente etiqueta:<br />
<br />
<pre><code>
<s:label for="textareaTest" value="Textarea: " />
<s:textarea cols="20" rows="5" value="Texto que se encontrara en el area" id="textareaTest" name="textareaTest" />
</code></pre><br />
<br />
Obtendremos el siguiente código:<br />
<br />
<pre><code>
<label for="textareaTest">Textarea: </label>
<textarea name="textareaTest" cols="20" rows="5" id="textareaTest">Texto que se encontrara en el area</textarea>
</code></pre><br />
<br />
Que se verá así en el navegador:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh6Lw-MeoaGN1sXFC6A4G16jB6TCdIDNcxcJKJFSwHfZF05hroWs5VdSqitCB-O5ID-ptCEjGRT4ZfaV5gwCZY_cnHtzFm7hlWXi2UWsI4k4dqNuYSwhdaGsOYaqfTvGV4CRw52grdlRdbK/s1600/S7_50.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh6Lw-MeoaGN1sXFC6A4G16jB6TCdIDNcxcJKJFSwHfZF05hroWs5VdSqitCB-O5ID-ptCEjGRT4ZfaV5gwCZY_cnHtzFm7hlWXi2UWsI4k4dqNuYSwhdaGsOYaqfTvGV4CRw52grdlRdbK/s400/S7_50.png" /></a></div><br />
<br />
No hay más que decir de esta etiqueta<br />
<br />
A partir de aquí veremos una serie de componentes especiales que hacen uso de <span class="codigo">JavaScript</span> para agregar comportamiento dinámico.<br />
<br />
<h3 class="subtitulo"><a href="http://www.blogger.com/null" id="combobox"><span class="codigo">combobox</span></a></h3>Este componente es una mezcla de un <span class="codigo">input</span> de tipo text y un componente <span class="codigo">select</span> puestos juntos. Esto permite colocar algún texto en el <span class="codigo">input</span> seleccionándolo del <span class="codigo">select</span>, o escribiéndolo directamente en el espacio correspondiente.<br />
<br />
Los atributos de esta etiqueta se muestran a continuación:<br />
<br />
<table><thead>
<tr> <th>Nombre</th><th>Obligatorio</th><th>Valor Default</th><th>Tipo</th><th>Descripción</th> </tr>
</thead> <tbody>
<tr> <td><span class="codigo">list</span></td><td>Si</td><td> </td><td><span class="codigo">String</span></td><td>La fuente de datos iterable (un iterador, una lista o un mapa) que se usará para llenar los elementos "<span class="codigo">option</span>" de la etiqueta.</td> </tr>
<tr> <td><span class="codigo">listKey</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>La propiedad del objeto que se usará para mostrar <span class="negritas">el valor</span> que tendrá cada elemento de la lista.</td> </tr>
<tr> <td><span class="codigo">listTitle</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>La propiedad del objeto que se usará para mostrar <span class="negritas">el título</span> de cada elemento de la lista.</td> </tr>
<tr> <td><span class="codigo">listValue</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>La propiedad del objeto que se usará para mostrar <span class="negritas">la etiqueta</span> de cada elemento de la lista.</td> </tr>
<tr> <td><span class="codigo">headerKey</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>El valor que tendrá el primer elemento de la lista.</td> </tr>
<tr> <td><span class="codigo">headerValue</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>La etiqueta que tendrá el primer elemento de la lista.</td> </tr>
<tr> <td><span class="codigo">emptyOption</span></td><td>No</td><td><span class="codigo">false</span></td><td><span class="codigo">String</span></td><td>Indica si insertar un elemento <span class="codigo">option</span> vacío.</td> </tr>
<tr> <td><span class="codigo">value</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>El valor pre-establecido del <span class="codigo">input</span>. Este valor debería venir desde un atributo de un <span class="codigo">Action</span>.</td> </tr>
<tr> <td colspan="6"><span class="codigo">listCssClass, listCssStyle, maxLength, maxlength, readonly, size</span></td> </tr>
</tbody> </table><br />
<br />
Para llenar el <span class="codigo">input</span>, la etiqueta genera un pequeño código en <span class="codigo">JavaScript</span>. Por el resto, esta etiqueta funciona exactamente igual que una etiqueta "<span class="codigo">s:select</span>" normal, por lo mismo sólo veremos dos ejemplos. El primero usaremos un mapa de opciones:<br />
<br />
<pre><code>
<s:label for="comboboxTest" value="Combobox: " />
<s:combobox list="#{'1':'uno', '2':'dos', '3':'tres', '4':'cuatro', '5':'cinco'}" id="comboboxTest" name="comboboxTest" headerKey="-1" headerValue="Seleccione un elemento" emptyOption="true" />
</code></pre><br />
<br />
Esta etiqueta genera el siguiente código, en el que podemos ver el <span class="codigo">JavaScript</span> usado para llenar el campo de texto:<br />
<br />
<pre><code>
<label for="comboboxTest">Combobox: </label>
<script type="text/javascript">
function autoPopulate_comboboxTest(targetElement) {
if (targetElement.options[targetElement.selectedIndex].value == '-1') {
return;
}
if (targetElement.options[targetElement.selectedIndex].value == '') {
return;
}
targetElement.form.elements['comboboxTest'].value=targetElement.options[targetElement.selectedIndex].value;
}
</script>
<input type="text" name="comboboxTest" value="" id="comboboxTest"/><br />
<select onChange="autoPopulate_comboboxTest(this);">
<option value="-1">Seleccione un elemento</option>
<option value=""></option>
<option value="1">uno</option>
<option value="2">dos</option>
<option value="3">tres</option>
<option value="4">cuatro</option>
<option value="5">cinco</option>
</select>
</code></pre><br />
<br />
Esto se ve de la siguiente forma en el navegador:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhAD9XSkK93iCBV2Locf9h4XnyKK9svkN78woWGTGMR1tqf4FM-BmA-M_m50AaMklaMQ6Eze1osauxHY3N7AScWQYhsZSJqlvht5wtlisCUx52oEI550UuCWUb6sT-uTR9hXQqtfGLyzzv0/s1600/S7_53.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhAD9XSkK93iCBV2Locf9h4XnyKK9svkN78woWGTGMR1tqf4FM-BmA-M_m50AaMklaMQ6Eze1osauxHY3N7AScWQYhsZSJqlvht5wtlisCUx52oEI550UuCWUb6sT-uTR9hXQqtfGLyzzv0/s400/S7_53.png" /></a></div><br />
<br />
Al seleccionar cualquier elemento del <span class="codigo">select</span>, el valor (en este caso el número) aparecerá en el <span class="codigo">input</span>.<br />
<br />
Veamos el siguiente ejemplo, en el en vez de usar un mapa, lo haremos con una lista de objetos, así que lo primero que haremos es crear tres beans con la etiqueta "<span class="codigo">s:bean</span>":<br />
<br />
<pre><code>
<s:bean name="com.javatutoriales.struts2.tags.modelo.Usuario" var="usuario1">
<s:param name="nombre" value="%{'Alex1'}" />
<s:param name="edad" value="%{1}" />
</s:bean>
<s:bean name="com.javatutoriales.struts2.tags.modelo.Usuario" var="usuario2">
<s:param name="nombre" value="%{'Alex2'}" />
<s:param name="edad" value="%{2}" />
</s:bean>
<s:bean name="com.javatutoriales.struts2.tags.modelo.Usuario" var="usuario3">
<s:param name="nombre" value="%{'Alex3'}" />
<s:param name="edad" value="%{3}" />
</s:bean>
</code></pre><br />
<br />
Y luego creamos una lista con estos objetos, la cual pasamos al atributo "<span class="codigo">list</span>" de la etiqueta "<span class="codigo">s:combobox</span>". También indicamos cuáles atributos servirán como valor y como etiqueta. Para indicar cuál será el valor usamos el atributo "<span class="codigo">listKey</span>", y para la etiqueta el atributo "<span class="codigo">listValue</span>", de la siguiente forma:<br />
<br />
<pre><code>
<s:label for="selectTest" value="Select: " />
<s:combobox list="{#usuario1, #usuario2, #usuario3}" id="selectTest" name="selectTest" listKey="edad" listValue="nombre" headerKey="-1" headerValue="Seleccione una opcion" emptyOption="true" />
</code></pre><br />
<br />
Con la etiqueta anterior se genera el siguiente código:<br />
<br />
<pre><code>
<label for="comboboxTest">Combobox: </label>
<script type="text/javascript">
function autoPopulate_comboboxTest(targetElement) {
if (targetElement.options[targetElement.selectedIndex].value == '-1') {
return;
}
if (targetElement.options[targetElement.selectedIndex].value == '') {
return;
}
targetElement.form.elements['comboboxTest'].value=targetElement.options[targetElement.selectedIndex].value;
}
</script>
<input type="text" name="comboboxTest" value="" id="comboboxTest"/><br />
<select onChange="autoPopulate_comboboxTest(this);">
<option value="-1">Seleccione una opcion</option>
<option value=""></option>
<option value="1">Alex1</option>
<option value="2">Alex2</option>
<option value="3">Alex3</option>
</select>
</code></pre><br />
<br />
Que se ve así en el navegador:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgaUh4MjizAe9NHIr_NNHvEzEHhN_LESo-a3ufakNQBEFf957A4ZhOmhLdAPOI7FkxPcd5xjJvPQMKqOevBLJAlsCOrb_v1Enuq1YUA1a-E3Bh6jl-UHeSa2ktpXUks7_LPTWt1S2huNJaz/s1600/S7_54.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgaUh4MjizAe9NHIr_NNHvEzEHhN_LESo-a3ufakNQBEFf957A4ZhOmhLdAPOI7FkxPcd5xjJvPQMKqOevBLJAlsCOrb_v1Enuq1YUA1a-E3Bh6jl-UHeSa2ktpXUks7_LPTWt1S2huNJaz/s400/S7_54.png" /></a></div><br />
<br />
De igual forma, al seleccionar un elemento del <span class="codigo">select</span>, su valor aparece en el campo de texto.<br />
<br />
<h3 class="subtitulo"><a href="http://www.blogger.com/null" id="doubleselect"><span class="codigo">doubleselect</span></a></h3>Esta es otra de las etiquetas muy útiles e interesantes. Se trata de dos listas ligadas, o dicho de otra forma, dos etiquetas <span class="codigo">select</span> donde los valores de la segunda dependen del elemento que esté seleccionado en la primera.<br />
<br />
Para generar el funcionamiento de este componente, <span class="codigo">Struts 2</span> generará un "<span class="codigo">pequeño</span>" código en <span class="codigo">JavaScript</span> que llenará los valores del segundo <span class="codigo">select</span>.<br />
<br />
Para los atributos de esta etiqueta podemos decir que ambos elementos tienen los mismos atributos, pero los atributos del segundo son antecedidos por la palabra "<span class="codigo">double</span>" (desconozco porque habrán decidido llamarlo "double" y no algo más claro como "second" o algo así). Por ejemplo, para establecer el "<span class="codigo">id</span>" del segundo <span class="codigo">select</span> usamos el atributo "<span class="codigo">doubleId</span>", para su atributo "<span class="codigo">onclic</span>" usamos "<span class="codigo">doubleOnclick</span>", etc.<br />
<br />
Los atributos de esta etiqueta se muestran en la siguiente tabla:<br />
<br />
<table><thead>
<tr> <th>Nombre</th><th>Obligatorio</th><th>Valor Default</th><th>Tipo</th><th>Descripción</th> </tr>
</thead> <tbody>
<tr> <td><span class="codigo">list</span></td><td>Si</td><td> </td><td><span class="codigo">String</span></td><td>La fuente de datos iterable (un iterador, una lista o un mapa) que se usará para llenar los elementos "<span class="codigo">option</span>" del primer <span class="codigo">select</span>.</td> </tr>
<tr> <td><span class="codigo">doubleList</span></td><td>Si</td><td> </td><td><span class="codigo">String</span></td><td>Los elementos que tendrá el segundo <span class="codigo">select</span>. Por lo regular es una expresión que indica que elementos mostrar basado en lo que esté seleccionado en el primer <span class="codigo">select</span> (quedará más claro con el ejemplo).</td> </tr>
<tr> <td><span class="codigo">name</span></td><td>Si</td><td> </td><td><span class="codigo">String</span></td><td>El nombre que tendrá el primer <span class="codigo">select</span>. En este caso es obligatorio porque se usará para el <span class="codigo">JavaScript</span> que se genere.</td> </tr>
<tr> <td><span class="codigo">doubleName</span></td><td>Si</td><td> </td><td><span class="codigo">String</span></td><td>El nombre que tendrá el segundo <span class="codigo">select</span>. En este caso es obligatorio porque se usará para el <span class="codigo">JavaScript</span> que se genere.</td> </tr>
<tr> <td><span class="codigo">listKey</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>La propiedad del objeto que se usará para mostrar <span class="negritas">el valor que tendrá cada elemento de la primer lista</span> (en caso de que trabajemos con un objeto que no sea un <span class="codigo">String</span>).</td> </tr>
<tr> <td><span class="codigo">doubleListKey</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>La propiedad del objeto que se usará para mostrar <span class="negritas">el valor que tendrá cada elemento de la segunda lista</span> (en caso de que trabajemos con un objeto que no sea un <span class="codigo">String</span>).</td> </tr>
<tr> <td><span class="codigo">listTitle</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>La propiedad del objeto que se usará para mostrar <span class="negritas">el título de cada elemento de la primer lista</span> (en caso de que trabajemos con un objeto que no sea un <span class="codigo">String</span>).</td> </tr>
<tr> <td><span class="codigo">doubleListTitle</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>La propiedad del objeto que se usará para mostrar <span class="negritas">el título de cada elemento de la segunda lista</span> (en caso de que trabajemos con un objeto que no sea un <span class="codigo">String</span>).</td> </tr>
<tr> <td><span class="codigo">listValue</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>La propiedad del objeto que se usará para mostrar <span class="negritas">la etiqueta de cada elemento de la primer lista</span> (en caso de que trabajemos con un objeto que no sea un <span class="codigo">String</span>).</td> </tr>
<tr> <td><span class="codigo">doubleListValue</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>La propiedad del objeto que se usará para mostrar <span class="negritas">la etiqueta de cada elemento de la segunda lista</span> (en caso de que trabajemos con un objeto que no sea un <span class="codigo">String</span>).</td> </tr>
<tr> <td><span class="codigo">emptyOption</span></td><td>No</td><td><span class="codigo">false</span></td><td><span class="codigo">Boolean</span></td><td>Indica si insertar un elemento option vacío en el primer <span class="codigo">select</span>.</td> </tr>
<tr> <td><span class="codigo">doubleEmptyOption</span></td><td>No</td><td><span class="codigo">false</span></td><td><span class="codigo">Boolean</span></td><td>Indica si insertar un elemento option vacío en el segundo <span class="codigo">select</span>.</td> </tr>
<tr> <td><span class="codigo">headerKey</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>El valor que tendrá el primer elemento de la lista del primer <span class="codigo">select</span>.</td> </tr>
<tr> <td><span class="codigo">doubleHeaderKey</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>El valor que tendrá el primer elemento de la lista del segundo <span class="codigo">select</span>.</td> </tr>
<tr> <td><span class="codigo">headerValue</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>La etiqueta que tendrá el primer elemento de la lista del primer <span class="codigo">select</span>.</td> </tr>
<tr> <td><span class="codigo">doubleHeaderValue</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>La etiqueta que tendrá el primer elemento de la lista del segundo <span class="codigo">select</span>.</td> </tr>
<tr> <td><span class="codigo">multiple</span></td><td>No</td><td><span class="codigo">false</span></td><td><span class="codigo">Boolean</span></td><td>Indica si debe permitirse seleccionar múltiples elementos de la primer lista. Si queremos pre-seleccionar varios elementos, debemos pasar un arreglo o una lista al atributo "<span class="codigo">value</span>" de la etiqueta.</td> </tr>
<tr> <td><span class="codigo">doubleMultiple</span></td><td>No</td><td><span class="codigo">false</span></td><td><span class="codigo">Boolean</span></td><td>Indica si debe permitirse seleccionar múltiples elementos de la segunda lista. Si queremos pre-seleccionar varios elementos, debemos pasar un arreglo o una lista al atributo "<span class="codigo">value</span>" de la etiqueta.</td> </tr>
<tr> <td><span class="codigo">value</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>El valor del elemento o elementos que estarán pre-seleccionados en el primer <span class="codigo">select</span>. Este valor debería venir desde un atributo de un <span class="codigo">Action</span>.</td> </tr>
<tr> <td><span class="codigo">doubleValue</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>El valor del elemento o elementos que estarán pre-seleccionados en el segundo <span class="codigo">select</span>. Este valor debería venir desde un atributo de un <span class="codigo">Action</span>.</td> </tr>
<tr> <td colspan="6"><span class="codigo">listCssClass, listCssStyle, size, doubleSize</span></td> </tr>
</tbody> </table><br />
<br />
La información de qué elemento está seleccionado en la primer lista queda disponible en una variable llamada "<span class="codigo">top</span>". Esta puede ser usada en la expresión de la segunda lista que indicará qué valores mostrar.<br />
<br />
Comencemos con el ejemplo sencillo, colocando los valores de los elementos desde una lista.<br />
<br />
Lo primero que debemos hacer es colocar nuestra etiqueta "<span class="codigo">s:doubleselect</span>", aprovecharemos para darle un nombre y un id a ambos componentes:<br />
<br />
<pre><code>
<s:doubleselect name="primerLista" id="doubleselecList1Test"
doubleName="segundaLista" doubleId="doubleselecList2Test" />
</code></pre><br />
<br />
Ahora agregaremos los elementos de la primer lista. En esta colocaremos dos categorías, la primera será especialidad y la segunda el lenguaje de programación.<br />
<br />
<pre><code>
<s:doubleselect name="primerLista" id="doubleselecList1Test"
doubleName="segundaLista" doubleId="doubleselecList2Test"
list="{'especialidad','lenguaje'}" />
</code></pre><br />
<br />
Y ahora viene la parte interesante, agregaremos una expresión para indicar qué elementos estarán en la segunda lista y en qué caso se mostrará qué. Digamos que cuando esté seleccionado el valor "<span class="codigo">especialidad</span>" queremos mostrar los valores "<span class="codigo">comunicación</span>", "<span class="codigo">bases de datos</span>" y "<span class="codigo">criptografía</span>", y que al seleccionar el valor "<span class="codigo">lenguaje</span>" queremos mostrar los valores "<span class="codigo">java</span>", "<span class="codigo">php</span>" y "<span class="codigo">.net</span>". La expresión queda de la siguiente forma (recuerden que se usa la variable "<span class="codigo">top</span>" para saber el valor que está seleccionado en la primer lista):<br />
<br />
<pre><code>
top == 'especialidad' ? {'comunicacion', 'bases de datos','criptografia'} : {'java', 'php', '.net'}
</code></pre><br />
<br />
Y la etiqueta queda de la siguiente forma:<br />
<br />
<pre><code>
<s:doubleselect name="primerLista" id="doubleselecList1Test"
doubleName="segundaLista" doubleId="doubleselecList2Test"
list="{'especialidad','lenguaje'}"
doubleList="top == 'especialidad' ? {'comunicacion', 'bases de datos','criptografia'} : {'java', 'php', '.net'}" />
</code></pre><br />
<br />
La etiqueta anterior genera el siguiente código:<br />
<br />
<pre><code>
<select name="primerLista" id="doubleselecList1Test" onchange="doubleselecList1TestRedirect(this.selectedIndex)">
<option value="especialidad">especialidad</option>
<option value="lenguaje">lenguaje</option>
</select>
<br/>
<select name="segundaLista" id="doubleselecList2Test">
</select>
<script type="text/javascript">
var doubleselecList1TestGroup = new Array(2 + 0);
for (i = 0; i < (2 + 0); i++)
doubleselecList1TestGroup[i] = new Array();
doubleselecList1TestGroup[0][0] = new Option("servidores", "servidores");
doubleselecList1TestGroup[0][1] = new Option("bases de datos", "bases de datos");
doubleselecList1TestGroup[0][2] = new Option("programacion", "programacion");
doubleselecList1TestGroup[1][0] = new Option("java", "java");
doubleselecList1TestGroup[1][1] = new Option("php", "php");
doubleselecList1TestGroup[1][2] = new Option(".net", ".net");
var doubleselecList1TestTemp = document.form.doubleselecList2Test;
doubleselecList1TestRedirect(0);
function doubleselecList1TestRedirect(x) {
var selected = false;
for (m = doubleselecList1TestTemp.options.length - 1; m >= 0; m--) {
doubleselecList1TestTemp.remove(m);
}
for (i = 0; i < doubleselecList1TestGroup[x].length; i++) {
doubleselecList1TestTemp.options[i] = new Option(doubleselecList1TestGroup[x][i].text, doubleselecList1TestGroup[x][i].value);
}
if ((doubleselecList1TestTemp.options.length > 0) && (! selected)) {
doubleselecList1TestTemp.options[0].selected = true;
}
}
</script>
</code></pre><br />
<br />
En este caso podemos ver mucho código generado, este básicamente lo que hace es crear un arreglo bidimensional en el que coloca los elementos que estarán en la segunda lista, y luego el código para cambiar los elementos de la segunda lista.<br />
<br />
Esto se ve así en el navegador:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgfNYf2vXlJoBmrr3q2gcMvBjEt4S5MfC7JTTite52egC9K2UkkaB2iLvoMGlEXquJ_EHzm9WEh0AgIW2dO9u0wV8a4-3Va5l1EXQ-lTNWmUfGQsCtylWhsemodaYbH0lD_ZEf91UcukigC/s1600/S7_55.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgfNYf2vXlJoBmrr3q2gcMvBjEt4S5MfC7JTTite52egC9K2UkkaB2iLvoMGlEXquJ_EHzm9WEh0AgIW2dO9u0wV8a4-3Va5l1EXQ-lTNWmUfGQsCtylWhsemodaYbH0lD_ZEf91UcukigC/s400/S7_55.png" /></a></div><br />
<br />
Ahora ¿qué pasa si intentamos llenar la lista en base a un mapa, recordando que la finalidad de esto es que podamos tener valores diferentes para la etiqueta (lo que verá el usuario) y el código de la selección (lo que enviaremos al servidor)? Basándonos en los ejemplos anteriores, podríamos pensar que la forma de generar esta etiqueta sería la siguiente:<br />
<br />
<pre><code>
<s:doubleselect name="primerLista" id="doubleselecList1Test"
doubleName="segundaLista" doubleId="doubleselecList2Test"
list="#{'especialidad':'Especialidad', 'lenguaje':'Lenguaje de Programacion'}"
doubleList="top == 'especialidad' ? {'comunicacion', 'bases de datos','criptografia'} : {'java', 'php', '.net'}" />
</code></pre><br />
<br />
Sin embargo esta etiqueta genera el siguiente código:<br />
<br />
<pre><code>
<select name="primerLista" id="doubleselecList1Test" onchange="doubleselecList1TestRedirect(this.selectedIndex)">
<option value="especialidad">Especialidad</option>
<option value="lenguaje">Lenguaje de Programacion</option>
</select>
<br/>
<select name="segundaLista" id="doubleselecList2Test" >
</select>
<script type="text/javascript">
var doubleselecList1TestGroup = new Array(2 + 0);
for (i = 0; i < (2 + 0); i++)
doubleselecList1TestGroup[i] = new Array();
var doubleselecList1TestTemp = document.form.doubleselecList2Test;
doubleselecList1TestRedirect(0);
function doubleselecList1TestRedirect(x) {
var selected = false;
for (m = doubleselecList1TestTemp.options.length - 1; m >= 0; m--) {
doubleselecList1TestTemp.remove(m);
}
for (i = 0; i < doubleselecList1TestGroup[x].length; i++) {
doubleselecList1TestTemp.options[i] = new Option(doubleselecList1TestGroup[x][i].text, doubleselecList1TestGroup[x][i].value);
}
if ((doubleselecList1TestTemp.options.length > 0) && (! selected)) {
doubleselecList1TestTemp.options[0].selected = true;
}
}
</script>
</code></pre><br />
<br />
Si se fijan bien, el código anterior no genera el arreglo bidimensional de elementos parael segundo arreglo, por lo tanto este está vacío todo el tiempo:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjgz_qZWpaxcUP2o8nNij2MqL5bp7AxwujrnVR2tjU8OQii4MN1OSVrM4KR5O-akGcAH4tseg12ie3994yLwXfICx1VBTx-3-631tNe4ujbAOE59X2JGFEiJDAZ3fbS1eQw0THj2TNW4pE-/s1600/S7_56.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjgz_qZWpaxcUP2o8nNij2MqL5bp7AxwujrnVR2tjU8OQii4MN1OSVrM4KR5O-akGcAH4tseg12ie3994yLwXfICx1VBTx-3-631tNe4ujbAOE59X2JGFEiJDAZ3fbS1eQw0THj2TNW4pE-/s400/S7_56.png" /></a></div><br />
<br />
Entonces ¿cómo hacemos para usar un mapa? En realidad… no es posible llenar estas listas usando un mapa, el <span class="codigo">API</span> de <span class="codigo">Struts 2</span> en este caso no lo permite.<br />
<br />
Lo que podemos hacer es simplificar la horrible expresión del ejemplo anterior. ¿Para qué? Imaginen que en el ejemplo anterior, en vez de tener dos clasificaciones o elementos en la primer lista, tuviéramos tres. Tendríamos que agregar más condiciones en la expresión para la segunda lista. Si esto ya sería in-manejable (dependiendo del número de elementos para cada clasificación), imaginen que fueran cuatro o cinco clasificaciones. No solo sería ilegible, sino que sería propenso a errores.<br />
<br />
¿Qué podemos hacer en este caso? Pues usar un mapa <span class="codigo">^_^!</span> (sé que acabo de decir que no se puede, pero es un mapa un poco distinto a los anteriores). En vez de usar un mapa en cuya llave y valor son ambos cadenas (una para usar como etiqueta y otra como valor), necesitamos uno en donde la llave sea una cadena, que se mostrará en la primer lista, y el valor será una lista de cadenas con los elementos que se mostrarán en la segunda lista.<br />
<br />
Para hacer esto en <span class="codigo">OGNL</span> podemos hacerlo de la siguiente forma:<br />
<br />
<pre><code>
#{'Especialidad': {'Comunicación', 'Bases de Datos', 'Criptografía'},
'Lenguajes': {'Java', 'PHP', '.Net'},
'Conocimientos': {'Básico', 'Intermedio', 'Avanzado'}
}
</code></pre><br />
<br />
Para poder hacer una selección más simple, podemos poner este mapa de valores en una variable, que llamaremos en este caso "<span class="codigo">mapaValores</span>", usando la etiqueta "<span class="codigo">s:set</span>", de esta forma:<br />
<br />
<pre><code>
<s:set var="mapaValores" value="#{'Especialidad': {'Comunicación', 'Bases de Datos', 'Criptografía'},
'Lenguajes': {'Java', 'PHP', '.Net'},
'Conocimientos': {'Básico', 'Intermedio', 'Avanzado'}}" />
</code></pre><br />
<br />
Ahora podemos usar esta variable en la etiqueta "<span class="codigo">s:doubleselect</span>". Primero colocamos la etiqueta dando un nombre y un id a cada una de las listas:<br />
<br />
<pre><code>
<s:doubleselect name="primerLista" id="doubleselecList1Test"
doubleName="segundaLista" doubleId="doubleselecList2Test" />
</code></pre><br />
<br />
Para colocar los valores de la primer lista (los cuales recuerden que deben ser una fuente de datos iterable), obtenemos un "<span class="codigo">set</span>" con las llaves de la lista, de la siguiente forma:<br />
<br />
<pre><code>
<s:doubleselect name="primerLista" id="doubleselecList1Test"
doubleName="segundaLista" doubleId="doubleselecList2Test"
list="#mapaValores.keySet()" />
</code></pre><br />
<br />
Y, finalmente, en la expresión para mostrar los valores de la segunda lista, es tan simple como indicar que los elementos estarán en la lista cuya llave estamos seleccionando actualmente, de la siguiente forma:<br />
<br />
<pre><code>
<s:doubleselect name="primerLista" id="doubleselecList1Test"
doubleName="segundaLista" doubleId="doubleselecList2Test"
list="#mapaValores.keySet()"
doubleList="#mapaValores[top]" />
</code></pre><br />
<br />
La etiqueta anterior genera el siguiente código:<br />
<br />
<pre><code>
<select name="primerLista" id="doubleselecList1Test" onchange="doubleselecList1TestRedirect(this.selectedIndex)">
<option value="Especialidad">Especialidad</option>
<option value="Lenguajes">Lenguajes</option>
<option value="Conocimientos">Conocimientos</option>
</select>
<br/>
<select name="segundaLista" id="doubleselecList2Test" >
</select>
<script type="text/javascript">
var doubleselecList1TestGroup = new Array(3 + 0);
for (i = 0; i < (3 + 0); i++)
doubleselecList1TestGroup[i] = new Array();
doubleselecList1TestGroup[0][0] = new Option("Comunicación", "Comunicación");
doubleselecList1TestGroup[0][1] = new Option("Bases de Datos", "Bases de Datos");
doubleselecList1TestGroup[0][2] = new Option("Criptografía", "Criptografía");
doubleselecList1TestGroup[1][0] = new Option("Java", "Java");
doubleselecList1TestGroup[1][1] = new Option("PHP", "PHP");
doubleselecList1TestGroup[1][2] = new Option(".Net", ".Net");
doubleselecList1TestGroup[2][0] = new Option("Básico", "Básico");
doubleselecList1TestGroup[2][1] = new Option("Intermedio", "Intermedio");
doubleselecList1TestGroup[2][2] = new Option("Avanzado", "Avanzado");
var doubleselecList1TestTemp = document.formulario.doubleselecList2Test;
doubleselecList1TestRedirect(0);
function doubleselecList1TestRedirect(x) {
var selected = false;
for (m = doubleselecList1TestTemp.options.length - 1; m >= 0; m--) {
doubleselecList1TestTemp.remove(m);
}
for (i = 0; i < doubleselecList1TestGroup[x].length; i++) {
doubleselecList1TestTemp.options[i] = new Option(doubleselecList1TestGroup[x][i].text, doubleselecList1TestGroup[x][i].value);
}
if ((doubleselecList1TestTemp.options.length > 0) && (! selected)) {
doubleselecList1TestTemp.options[0].selected = true;
}
}
</script>
</code></pre><br />
<br />
Que se ve así en el navegador:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi8_BlqMVtUKQeFHm5xT7rpO6dn9FWwlQLNxydQWsqdx5Ui_VtqdEFRW-P2fXv2yFIcCoQKr8ZNq-PjUZhe94RPs1u2DP2F2yQBM4Dq_NJs43AcIlTS-cuDZGdZSvTuS5tJfSjMB0wqoQyR/s1600/S7_57.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi8_BlqMVtUKQeFHm5xT7rpO6dn9FWwlQLNxydQWsqdx5Ui_VtqdEFRW-P2fXv2yFIcCoQKr8ZNq-PjUZhe94RPs1u2DP2F2yQBM4Dq_NJs43AcIlTS-cuDZGdZSvTuS5tJfSjMB0wqoQyR/s400/S7_57.png" /></a></div><br />
<br />
De esta manera tenemos una etiqueta más mantenible y que puede manejar más categorías y elementos de forma sencilla.<br />
<br />
Si quisiéramos regresar esta lista desde el atributo de un bean, este tendría que ser un mapa cuya llave sea una cadena, en su valor una lista de cadenas, de esta forma:<br />
<br />
<pre><code>
Map<<span class="codigo">String</span>, List<<span class="codigo">String</span>>>
</code></pre><br />
<br />
El resto de código funciona de la misma forma.<br />
<br />
<h3 class="subtitulo"><a href="http://www.blogger.com/null" id="updownselect"><span class="codigo">updownselect</span></a></h3>Coloca un componente "<span class="codigo">select</span>", con botones para mover sus elementos arriba y abajo dentro de la lista. Cuando el formulario sea enviado, los elementos seleccionados de la lista serán enviados en el orden en el que se encuentren (de arriba abajo).<br />
<br />
Los atributos de esta etiqueta se muestran en la siguiente tabla:<br />
<br />
<table><thead>
<tr> <th>Nombre</th><th>Obligatorio</th><th>Valor Default</th><th>Tipo</th><th>Descripción</th> </tr>
</thead> <tbody>
<tr> <td><span class="codigo">list</span></td><td>Si</td><td> </td><td><span class="codigo">String</span></td><td>La fuente de datos iterable (un iterador, una lista o un mapa) que se usará para llenar los elementos "<span class="codigo">option</span>" de la etiqueta.</td> </tr>
<tr> <td><span class="codigo">value</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>El valor del elemento o elementos que estarán pre-seleccionados. Este valor debería venir desde un atributo de un <span class="codigo">Action</span>.</td> </tr>
<tr> <td><span class="codigo">allowMoveDown</span></td><td>No</td><td><span class="codigo">true</span></td><td><span class="codigo">Boolean</span></td><td>Indica si se mostrará el botón para bajar los elementos en la lista.</td> </tr>
<tr> <td><span class="codigo">allowMoveUp</span></td><td>No</td><td><span class="codigo">true</span></td><td><span class="codigo">Boolean</span></td><td>Indica si se mostrará el botón para subir los elementos de la lista.</td> </tr>
<tr> <td><span class="codigo">allowSelectAll</span></td><td>No</td><td><span class="codigo">true</span></td><td><span class="codigo">Boolean</span></td><td>Indica si se mostrará el botón para seleccionar todos los elementos de la lista.</td> </tr>
<tr> <td><span class="codigo">emptyOption</span></td><td>No</td><td><span class="codigo">false</span></td><td><span class="codigo">Boolean</span></td><td>Indica si se debe agregar una opción vacía después del encabezado (header) de la lista.</td> </tr>
<tr> <td><span class="codigo">moveDownLabel</span></td><td>No</td><td><span class="codigo">v</span></td><td><span class="codigo">String</span></td><td>El texto que se mostrará en la etiqueta para bajar un elemento de la lista.</td> </tr>
<tr> <td><span class="codigo">moveUpLabel</span></td><td>No</td><td><span class="codigo">^</span></td><td><span class="codigo">String</span></td><td>El texto que se mostrará en la etiqueta para subir un elemento de la lista.</td> </tr>
<tr> <td><span class="codigo">selectAllLabel</span></td><td>No</td><td><span class="codigo">*</span></td><td><span class="codigo">String</span></td><td>El texto que se mostrará en la etiqueta para seleccionar todos los elementos de la lista.</td> </tr>
<tr> <td><span class="codigo">headerKey</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>El valor que tendrá el primer elemento de la lista.</td> </tr>
<tr> <td><span class="codigo">headerValue</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>La etiqueta que tendrá el primer elemento de la lista.</td> </tr>
<tr> <td><span class="codigo">listKey</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>La propiedad del objeto que se usará para mostrar <span class="negritas">el valor</span> que tendrá cada elemento de la lista.</td> </tr>
<tr> <td><span class="codigo">listTitle</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>La propiedad del objeto que se usará para mostrar <span class="negritas">el título</span> de cada elemento de la lista.</td> </tr>
<tr> <td><span class="codigo">listValue</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>La propiedad del objeto que se usará para mostrar <span class="negritas">la etiqueta</span> de cada elemento de la lista.</td> </tr>
<tr> <td><span class="codigo">multiple</span></td><td>No</td><td><span class="codigo">true</span></td><td><span class="codigo">Boolean</span></td><td>Indica si debe permitirse seleccionar múltiples elementos de la lista. Si queremos pre-seleccionar varios elementos, debemos pasar un arreglo o una lista al atributo "<span class="codigo">value</span>" de la etiqueta.</td> </tr>
<tr> <td colspan="6"><span class="codigo">buttonCssClass, buttonCssStyle, listCssClass, listCssStyle, size</span></td> </tr>
</tbody> </table><br />
<br />
Para el primer ejemplo pondremos los valores que se mostrarán en el <span class="codigo">select</span>, haciendo uso de un mapa.<br />
<br />
Si colocamos la siguiente etiqueta:<br />
<br />
<pre><code>
<s:updownselect
list="#{'java':'Java', 'php':'PHP', 'puntoNet':'.Net'}"
name="updownselectTest" id="updownselectTest"
headerKey="-1"
headerValue="--- Selecciona los elementos en el orden deseado ---"
emptyOption="true" />
</code></pre><br />
<br />
Obtendremos el siguiente código:<br />
<br />
<pre><code>
<script type="text/javascript" src="/tags/struts/optiontransferselect.js"></script>
<table>
<tr>
<td>
<select name="updownselectTest" size="5" id="updownselectTest" multiple="multiple">
<option value="-1">--- Selecciona los elementos en el orden deseado ---</option>
<option value=""></option>
<option value="java">Java</option>
<option value="php">PHP</option>
<option value="puntoNet">.Net</option>
</select>
<input type="hidden" id="__multiselect_updownselectTest" name="__multiselect_updownselectTest" value="" />
</td>
</tr>
<tr>
<td>
<input type="button" value="^" onclick="moveOptionUp(document.getElementById('updownselectTest'), 'key', '-1');" />
<input type="button" value="v" onclick="moveOptionDown(document.getElementById('updownselectTest'), 'key', '-1');" />
<input type="button" value="*" onclick="selectAllOptionsExceptSome(document.getElementById('updownselectTest'), 'key', '-1');" />
</td>
</tr>
</table>
<script type="text/javascript">
var containingForm = document.getElementById("formulario");
StrutsUtils.addEventListener(containingForm, "submit", function(evt) {
var updownselectObj = document.getElementById("updownselectTest");
selectAllOptionsExceptSome(updownselectObj, "key", "-1");
}, true);
</script>
</code></pre><br />
<br />
Que se ve así en el navegador:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwoXU6HUSa-BMc4cD8R3_QaX4sdteX0cjY1ERk_FTceepC3815p-ZL7tX9ejHUUn0ZDBsFQ9K6DFdiy3mxQlwZXo8nqmptMijKy03mBvSMwbYomzmv98bS5VtZIJhevGLtAGyga741K25L/s1600/S7_62.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwoXU6HUSa-BMc4cD8R3_QaX4sdteX0cjY1ERk_FTceepC3815p-ZL7tX9ejHUUn0ZDBsFQ9K6DFdiy3mxQlwZXo8nqmptMijKy03mBvSMwbYomzmv98bS5VtZIJhevGLtAGyga741K25L/s400/S7_62.png" /></a></div><br />
<br />
Como a esta altura ya sabemos cómo hacer lo mismo, tomando los elementos del <span class="codigo">select</span> a partir de una lista, tanto de valores fijos como de objetos, no veremos otro ejemplo de esta etiqueta.<br />
<br />
<h3 class="subtitulo"><a href="http://www.blogger.com/null" id="inputtransferselect"><span class="codigo">inputtransferselect</span></a></h3>Esta es otra etiqueta que es una combinación de dos componentes. El primero es una etiqueta <span class="codigo">select</span> que permite seleccionar múltiples valores, además de que permite ordenar los elementos de la lista y eliminar uno o varios elementos.<br />
<br />
El segundo componente es un campo de entrada de texto que permite agregar valores a la primer lista.<br />
<br />
Antes de hacer un submit del formulario, todos los elementos del componente "<span class="codigo">select</span>" serán seleccionados y enviados al servidor.<br />
<br />
Los atributos de esta etiqueta se muestran en la siguiente tabla:<br />
<br />
<table><thead>
<tr> <th>Nombre</th><th>Obligatorio</th><th>Valor Default</th><th>Tipo</th><th>Descripción</th> </tr>
</thead> <tbody>
<tr> <td><span class="codigo">list</span></td><td>Si</td><td> </td><td><span class="codigo">String</span></td><td>La fuente de datos iterable (un iterador, una lista o un mapa) que se usará para llenar los elementos "<span class="codigo">option</span>" de la etiqueta.</td> </tr>
<tr> <td><span class="codigo">value</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>El valor del elemento o elementos que estarán pre-seleccionados. Este valor debería venir desde un atributo de un <span class="codigo">Action</span>.</td> </tr>
<tr> <td><span class="codigo">allowRemoveAll</span></td><td>No</td><td><span class="codigo">true</span></td><td><span class="codigo">Boolean</span></td><td>Indica si se mostrará el botón para eliminar todos los elementos de la lista.</td> </tr>
<tr> <td><span class="codigo">allowUpDown</span></td><td>No</td><td><span class="codigo">true</span></td><td><span class="codigo">Boolean</span></td><td>Indica si se mostraran los botones para ordenar los elementos.</td> </tr>
<tr> <td><span class="codigo">addLabel</span></td><td>No</td><td><span class="codigo">-></span></td><td><span class="codigo">String</span></td><td>La etiqueta usada para el botón de agregar elementos.</td> </tr>
<tr> <td><span class="codigo">upLabel</span></td><td>No</td><td><span class="codigo">^</span></td><td><span class="codigo">String</span></td><td>La etiqueta usada para el botón de subir elemento.</td> </tr>
<tr> <td><span class="codigo">downLabel</span></td><td>No</td><td><span class="codigo">v</span></td><td><span class="codigo">String</span></td><td>La etiqueta usada para el botón de bajar elemento.</td> </tr>
<tr> <td><span class="codigo">removeLabel</span></td><td>No</td><td><span class="codigo"><-</span></td><td><span class="codigo">String</span></td><td>La etiqueta utilizada para el botón de eliminar un elemento.</td> </tr>
<tr> <td><span class="codigo">removeAllLabel</span></td><td>No</td><td><span class="codigo"><<-</span></td><td><span class="codigo">String</span></td><td>La etiqueta utilizada para el botón de eliminar todos los elementos.</td> </tr>
<tr> <td><span class="codigo">headerKey</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>El valor que tendrá el primer elemento de la lista.</td> </tr>
<tr> <td><span class="codigo">headerValue</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>La etiqueta que tendrá el primer elemento de la lista.</td> </tr>
<tr> <td><span class="codigo">listKey</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>La propiedad del objeto que se usará para mostrar <span class="negritas">el valor</span> que tendrá cada elemento de la lista.</td> </tr>
<tr> <td><span class="codigo">listTitle</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>La propiedad del objeto que se usará para mostrar <span class="negritas">el título</span> de cada elemento de la lista.</td> </tr>
<tr> <td><span class="codigo">listValue</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>La propiedad del objeto que se usará para mostrar <span class="negritas">la etiqueta</span> de cada elemento de la lista.</td> </tr>
<tr> <td><span class="codigo">multiple</span></td><td>No</td><td><span class="codigo">true</span></td><td><span class="codigo">Boolean</span></td><td>Indica si debe permitirse seleccionar múltiples elementos de la lista. Si queremos pre-seleccionar varios elementos, debemos pasar un arreglo o una lista al atributo "<span class="codigo">value</span>" de la etiqueta.</td> </tr>
<tr> <td colspan="6"><span class="codigo">buttonCssClass, buttonCssStyle, listCssClass, listCssStyle, size</span></td> </tr>
</tbody> </table><br />
<br />
Veremos sólo dos ejemplos. En el primero llenaremos la lista de elementos con una lista, y dejaremos el resto de los valores por default. Lo primero que haremos es colocar la etiqueta "<span class="codigo">s:inputtransferselect</span>", a la cual pondremos un nombre y un id:<br />
<br />
<pre><code>
<s:inputtransferselect
name="inputselectTest"
id="inputselectTest" />
</code></pre><br />
<br />
Ahora colocaremos, en el atributo "<span class="codigo">list</span>", una lista con las opciones iniciales que estarán en el <span class="codigo">select</span>, en nuestro caso podremos los sistemas operativos que utilizamos:<br />
<br />
<pre><code>
<s:inputtransferselect
name="inputselectTest"
id="inputselectTest"
list="{'Linux', 'Mac OS', 'Windows'}"/>
</code></pre><br />
<br />
Esto es todo. La etiqueta anterior genera el siguiente código:<br />
<br />
<pre><code>
<script type="text/javascript" src="/tags/struts/inputtransferselect.js"></script>
<table border="0">
<tr>
<td>
<input type="text" name="inputselectTest_input" id="inputselectTest_input"/>
</td>
<td valign="middle" align="center">
<input type="button" value="->" onclick="addOption(document.getElementById('inputselectTest_input'), document.getElementById('inputselectTest'))" />
<br />
<br />
<input type="button" value="<-" onclick="removeOptions(document.getElementById('inputselectTest'))" />
<br />
<br />
<input type="button" value="<<--" onclick="removeAllOptions(document.getElementById('inputselectTest'))" />
<br />
<br />
</td>
<td>
<select name="inputselectTest" size="5" id="inputselectTest" multiple="multiple">
<option value="Linux">Linux</option>
<option value="Mac OS">Mac OS</option>
<option value="Windows">Windows</option>
</select>
<input type="hidden" id="__multiselect_inputselectTest" name="__multiselect_inputselectTest" value="" />
<input type="button" onclick="moveOptionDown(document.getElementById('inputselectTest'), 'key', '');" value="v" />
<input type="button" onclick="moveOptionUp(document.getElementById('inputselectTest'), 'key', '');" value="^" />
</td>
</tr>
</table>
</code></pre><br />
<br />
Como podemos ver, a pesar de nuestros mejores esfuerzos, la etiqueta ordena todos los elementos en una tabla.<br />
<br />
El código anterior se ve así en el navegador:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhUyBxPZ42UinFM4cWvA-ftew0nXshyphenhyphenLjg2FuHf74KMacgXEqdek3WxyN5J3buk9kREZUzamVhX_gbsvNvW0chAI9UUo94Xm0Z6RYjG23Oo5HxG3iwVOJjDH-jV4m0GLnyack47XvONvvq8/s1600/S7_60.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhUyBxPZ42UinFM4cWvA-ftew0nXshyphenhyphenLjg2FuHf74KMacgXEqdek3WxyN5J3buk9kREZUzamVhX_gbsvNvW0chAI9UUo94Xm0Z6RYjG23Oo5HxG3iwVOJjDH-jV4m0GLnyack47XvONvvq8/s400/S7_60.png" /></a></div><br />
<br />
Pueden ingresar un nuevo nombre en el campo de texto y luego presionar el botón de agregar a la lista (<span class="codigo">-></span>) y este aparecerá en la parte inferior del <scpan class="code">select</scpan>.<br />
<br />
Veamos el segundo ejemplo, en el que llenaremos la lista usando un mapa (creo que ya quedó claro cómo hacerlo con una lista de objetos).<br />
<br />
En este ejemplo modificaremos los textos de los botones para hacerlos más claros.<br />
<br />
Si colocamos la siguiente etiqueta:<br />
<br />
<pre><code>
<s:inputtransferselect name="inputselectTest" id="inputselectTest"
addLabel="Agregar" removeLabel="Eliminar" removeAllLabel="Eliminar todos"
upLabel="Subir" downLabel="Bajar"
list="#{'1':'Linux', '2':'Mac OS', '3':'Windows'}"/>
</code></pre><br />
<br />
Obtendremos el siguiente código:<br />
<br />
<pre><code>
<script type="text/javascript" src="/tags/struts/inputtransferselect.js"></script>
<table border="0">
<tr>
<td>
<input type="text" name="inputselectTest_input" id="inputselectTest_input"/>
</td>
<td valign="middle" align="center">
<input type="button" value="Agregar" onclick="addOption(document.getElementById('inputselectTest_input'), document.getElementById('inputselectTest'))" />
<br />
<br />
<input type="button" value="Eliminar" onclick="removeOptions(document.getElementById('inputselectTest'))" />
<br />
<br />
<input type="button" value="Eliminar todos" onclick="removeAllOptions(document.getElementById('inputselectTest'))" />
<br />
<br />
</td>
<td>
<select name="inputselectTest" size="5" id="inputselectTest" multiple="multiple">
<option value="1">Linux</option>
<option value="2">Mac OS</option>
<option value="3">Windows</option>
</select>
<input type="hidden" id="__multiselect_inputselectTest" name="__multiselect_inputselectTest" value="" />
<input type="button" onclick="moveOptionDown(document.getElementById('inputselectTest'), 'key', '');" value="Bajar" />
<input type="button" onclick="moveOptionUp(document.getElementById('inputselectTest'), 'key', '');" value="Subir" />
</td>
</tr>
</table>
<script type="text/javascript">
var containingForm = document.getElementById("formulario");
StrutsUtils.addEventListener(containingForm, "submit", function(evt) {
var selectObj = document.getElementById("inputselectTest");
selectAllOptionsExceptSome(selectObj, "key", "");
}, true);
</script>
</code></pre><br />
<br />
Que se ve así en el navegador:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhlD7ovBoRGRl4d5vyzEBVA8AWwOhNRA1sZ4menDtMRRc2UDtbKdFHTMV9_xIwFiI56YoYHCb_Dow2JAvq0GtB__Yuh_3ypeT_viviqrLVMhVaxy9tRSRrBVaaIuVVD8gIWPLbJ0x6MGyf6/s1600/S7_61.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhlD7ovBoRGRl4d5vyzEBVA8AWwOhNRA1sZ4menDtMRRc2UDtbKdFHTMV9_xIwFiI56YoYHCb_Dow2JAvq0GtB__Yuh_3ypeT_viviqrLVMhVaxy9tRSRrBVaaIuVVD8gIWPLbJ0x6MGyf6/s400/S7_61.png" /></a></div><br />
<br />
<h3 class="subtitulo"><a href="http://www.blogger.com/null" id="optiontransferselect"><span class="codigo">optiontransferselect</span></a></h3>Esta etiqueta coloca dos etiquetas "<span class="codigo">select</span>", que permiten mover los elementos de cada una de las listas entre ellas.<br />
<br />
Antes de hacer un submit del formulario, todos los elementos del segundo "<span class="codigo">select</span>" serán seleccionados y enviados al servidor.<br />
<br />
Para los atributos de esta etiqueta podemos decir que ambos componentes tienen los mismos atributos, pero los atributos del segundo son antecedidos por la palabra "<span class="codigo">double</span>" (desconozco porque habrán decidido llamarlo "double" y no algo más claro como "second" o algo así). Por ejemplo, para establecer el "<span class="codigo">id</span>" del segundo <span class="codigo">select</span> usamos el atributo "<span class="codigo">doubleId</span>", para su atributo "<span class="codigo">onclic</span>" usamos "<span class="codigo">doubleOnclick</span>", etc. Como la lista crecería mucho si colocamos los atributos de ambas listas, sólo colocaremos los más importantes.<br />
<br />
Los atributos de esta etiqueta se muestran en la siguiente tabla:<br />
<br />
<table><thead>
<tr> <th>Nombre</th><th>Obligatorio</th><th>Valor Default</th><th>Tipo</th><th>Descripción</th> </tr>
</thead> <tbody>
<tr> <td><span class="codigo">list</span></td><td>Si</td><td> </td><td><span class="codigo">String</span></td><td>La fuente de datos iterable (un iterador, una lista o un mapa) que se usará para llenar los elementos "<span class="codigo">option</span>" del <span class="codigo">select</span> de la izquierda.</td> </tr>
<tr> <td><span class="codigo">doubleList</span></td><td>Si</td><td> </td><td><span class="codigo">String</span></td><td>La fuente de datos iterable (un iterador, una lista o un mapa) que se usará para llenar los elementos "<span class="codigo">option</span>" del <span class="codigo">select</span> de la derecha.</td> </tr>
<tr> <td><span class="codigo">value</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>El valor del elemento o elementos que estarán pre-seleccionados. Este valor debería venir desde un atributo de un <span class="codigo">Action</span>.</td> </tr>
<tr> <td><span class="codigo">allowAddAllToLeft</span></td><td>No</td><td><span class="codigo">true</span></td><td><span class="codigo">Boolean</span></td><td>Indica si se mostrará el botón para mover todos los elementos a la lista de la izquierda.</td> </tr>
<tr> <td><span class="codigo">allowAddAllToRight</span></td><td>No</td><td><span class="codigo">true</span></td><td><span class="codigo">Boolean</span></td><td>Indica si se mostrará el botón para mover todos los elementos a la lista de la derecha.</td> </tr>
<tr> <td><span class="codigo">allowAddToLeft</span></td><td>No</td><td><span class="codigo">true</span></td><td><span class="codigo">Boolean</span></td><td>Indica si se mostrará el botón para mover un elemento a la lista de la izquierda.</td> </tr>
<tr> <td><span class="codigo">allowAddToRight</span></td><td>No</td><td><span class="codigo">true</span></td><td><span class="codigo">Boolean</span></td><td>Indica si se mostrará el botón para mover un elemento a la lista de la derecha.</td> </tr>
<tr> <td><span class="codigo">allowSelectAll</span></td><td>No</td><td><span class="codigo">true</span></td><td><span class="codigo">Boolean</span></td><td>Indica si se mostrará el botón para seleccionar todos los elementos de las dos listas.</td> </tr>
<tr> <td><span class="codigo">allowUpDownOnLeft</span></td><td>No</td><td><span class="codigo">true</span></td><td><span class="codigo">Boolean</span></td><td>Indica si se mostrará el botón para mover los elementos de arriba a abajo en la lista de la izquierda.</td> </tr>
<tr> <td><span class="codigo">allowUpDownOnRight</span></td><td>No</td><td><span class="codigo">true</span></td><td><span class="codigo">Boolean</span></td><td>Indica si se mostrará el botón para mover los elementos de arriba a abajo en la lista de la derecha.</td> </tr>
<tr> <td><span class="codigo">addAllToLeftLabel</span></td><td>No</td><td><span class="codigo"><<-</span></td><td><span class="codigo">String</span></td><td>La etiqueta utilizada para el botón de mover todos los elementos a la lista de la izquierda.</td> </tr>
<tr> <td><span class="codigo">addAllToRightLabel</span></td><td>No</td><td><span class="codigo">->></span></td><td><span class="codigo">String</span></td><td>La etiqueta utilizada para el botón de mover todos los elementos a la lista de la derecha.</td> </tr>
<tr> <td><span class="codigo">addToLeftLabel</span></td><td>No</td><td><span class="codigo"><-</span></td><td><span class="codigo">String</span></td><td>La etiqueta utilizada para el botón de mover un elemento a la lista de la izquierda.</td> </tr>
<tr> <td><span class="codigo">addToRightLabel</span></td><td>No</td><td><span class="codigo">-></span></td><td><span class="codigo">String</span></td><td>La etiqueta utilizada para el botón de mover un elemento a la lista de la derecha.</td> </tr>
<tr> <td><span class="codigo">leftDownLabel</span></td><td>No</td><td><span class="codigo">v</span></td><td><span class="codigo">String</span></td><td>La etiqueta utilizada para el botón de bajar un elemento en la lista de la izquierda.</td> </tr>
<tr> <td><span class="codigo">leftUpLabel</span></td><td>No</td><td><span class="codigo">^</span></td><td><span class="codigo">String</span></td><td>La etiqueta utilizada para el botón de subir un elemento en la lista de la izquierda.</td> </tr>
<tr> <td><span class="codigo">rightDownLabel</span></td><td>No</td><td><span class="codigo">v</span></td><td><span class="codigo">String</span></td><td>La etiqueta utilizada para el botón de bajar un elemento en la lista de la derecha.</td> </tr>
<tr> <td><span class="codigo">rightUpLabel</span></td><td>No</td><td><span class="codigo">^</span></td><td><span class="codigo">String</span></td><td>La etiqueta utilizada para el botón de subir un elemento en la lista de la derecha.</td> </tr>
<tr> <td><span class="codigo">selectAllLabel</span></td><td>No</td><td><span class="codigo"><*></span></td><td><span class="codigo">String</span></td><td>La etiqueta utilizada para el botón de seleccionar todos los elementos de ambas listas.</td> </tr>
<tr> <td><span class="codigo">addAllToLeftOnclick</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td><span class="codigo">JavaScript</span> que se ejecutará después de que se ha presionado el botón para enviar todos los elementos a la lista de la izquierda.</td> </tr>
<tr> <td><span class="codigo">addAllToRightOnclick</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td><span class="codigo">JavaScript</span> que se ejecutará después de que se ha presionado el botón para enviar todos los elementos a la lista de la derecha.</td> </tr>
<tr> <td><span class="codigo">addToLeftOnclick</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td><span class="codigo">JavaScript</span> que se ejecutará después de que se ha presionado el botón para enviar un elemento a la lista de la izquierda.</td> </tr>
<tr> <td><span class="codigo">addToRightOnclick</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td><span class="codigo">JavaScript</span> que se ejecutará después que se ha presionado el botón para enviar un elemento a la lista de la derecha.</td> </tr>
<tr> <td><span class="codigo">upDownOnLeftOnclick</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td><span class="codigo">JavaScript</span> que se ejecutará después que se ha presionado el botón para subir/bajar un elemento en la lista de la izquierda.</td> </tr>
<tr> <td><span class="codigo">upDownOnRightOnclick</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td><span class="codigo">JavaScript</span> que se ejecutará después que se ha presionado el botón para subir/bajar un elemento en la lista de la derecha.</td> </tr>
<tr> <td><span class="codigo">selectAllOnclick</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td><span class="codigo">JavaScript</span> que se ejecutará después de que se hace clic en el botón para seleccionar todos los elementos de las listas.</td> </tr>
<tr> <td><span class="codigo">headerKey</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>El valor que tendrá el primer elemento de la lista.</td> </tr>
<tr> <td><span class="codigo">headerValue</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>La etiqueta que tendrá el primer elemento de la lista.</td> </tr>
<tr> <td><span class="codigo">listKey</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>La propiedad del objeto que se usará para mostrar el valor que tendrá cada elemento de la lista.</td> </tr>
<tr> <td><span class="codigo">listTitle</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>La propiedad del objeto que se usará para mostrar el título de cada elemento de la lista.</td> </tr>
<tr> <td><span class="codigo">listValue</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>La propiedad del objeto que se usará para mostrar la etiqueta de cada elemento de la lista.</td> </tr>
<tr> <td><span class="codigo">emptyOption</span></td><td>No</td><td><span class="codigo">false</span></td><td><span class="codigo">Boolean</span></td><td>Indica si se debe agregar una opción vacía después del encabezado (header) de la lista de la izquierda.</td> </tr>
<tr> <td><span class="codigo">multiple</span></td><td>No</td><td><span class="codigo">true</span></td><td><span class="codigo">Boolean</span></td><td>Indica si debe permitirse seleccionar múltiples elementos de la lista. Si queremos pre-seleccionar varios elementos, debemos pasar un arreglo o una lista al atributo "<span class="codigo">value</span>" de la etiqueta.</td> </tr>
<tr> <td colspan="6"><span class="codigo">buttonCssClass, buttonCssStyle, listCssClass, listCssStyle, size, doubleListCssClass, doubleListCssStyle, doubleSize</span></td> </tr>
</tbody> </table><br />
<br />
Como ya hemos entendido la mecánica de cómo llenar los elementos de la lista, sólo veremos un ejemplo en el que usaremos un mapa.<br />
<br />
Si colocamos la siguiente etiqueta:<br />
<br />
<pre><code>
<s:optiontransferselect
name="optiontransferselect1Test" id="optiontransferselect1Test"
doubleName="optiontransferselect2Test" doubleId="optiontransferselect2Test"
list="#{'1':'PHP', '2':'.Net', '3':'Phyton'}"
doubleList="#{'4':'Java', '5':'Scala', '6':'Groovy'}" />
</code></pre><br />
<br />
Obtendremos el siguiente código:<br />
<br />
<pre><code>
<script type="text/javascript" src="/tags/struts/optiontransferselect.js"></script>
<table border="0">
<tr>
<td>
<select name="optiontransferselect1Test" size="15" id="optiontransferselect1Test" multiple="multiple">
<option value="1">PHP</option>
<option value="2">.Net</option>
<option value="3">Phyton</option>
</select>
<input type="hidden" id="__multiselect_optiontransferselect1Test" name="__multiselect_optiontransferselect1Test" value="" />
<input type="button" onclick="moveOptionDown(document.getElementById('optiontransferselect1Test'), 'key', '');" value="v" />
<input type="button" onclick="moveOptionUp(document.getElementById('optiontransferselect1Test'), 'key', '');" value="^" />
</td>
<td valign="middle" align="center">
<input type="button" value="<-" onclick="moveSelectedOptions(document.getElementById('optiontransferselect2Test'), document.getElementById('optiontransferselect1Test'), false, '');" /><br /><br />
<input type="button" value="->" onclick="moveSelectedOptions(document.getElementById('optiontransferselect1Test'), document.getElementById('optiontransferselect2Test'), false, '');" /><br /><br />
<input type="button" value="<<--" onclick="moveAllOptions(document.getElementById('optiontransferselect2Test'), document.getElementById('optiontransferselect1Test'), false, '');" /><br /><br />
<input type="button" value="-->>" onclick="moveAllOptions(document.getElementById('optiontransferselect1Test'), document.getElementById('optiontransferselect2Test'), false, '');" /><br /><br />
<input type="button" value="<*>" onclick="selectAllOptions(document.getElementById('optiontransferselect1Test'));selectAllOptions(document.getElementById('optiontransferselect2Test'));" /><br /><br />
</td>
<td>
<select name="optiontransferselect2Test" size="15" multiple="multiple" id="optiontransferselect2Test" >
<option value="4">Java</option>
<option value="5">Scala</option>
<option value="6">Groovy</option>
</select>
<input type="hidden" id="__multiselect_optiontransferselect2Test" name="__multiselect_optiontransferselect2Test" value="" />
<input type="button" onclick="moveOptionDown(document.getElementById('optiontransferselect2Test'), 'key', '');" value="v" />
<input type="button" onclick="moveOptionUp(document.getElementById('optiontransferselect2Test'), 'key', '');" value="^" />
</td>
</tr>
</table>
<script type="text/javascript">
var containingForm = document.getElementById("formulario");
StrutsUtils.addEventListener(containingForm, "submit", function(evt) {
var selectObj = document.getElementById("optiontransferselect1Test");
selectAllOptionsExceptSome(selectObj, "key", "");
}, true);
var containingForm = document.getElementById("formulario");
StrutsUtils.addEventListener(containingForm, "submit", function(evt) {
var selectObj = document.getElementById("optiontransferselect2Test");
selectAllOptionsExceptSome(selectObj, "key", "");
}, true);
</script>
</code></pre><br />
<br />
Que se ve así en el navegador:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhGvbINgVCnSYd90MJcHgutbgnPBaIYg2qUPNyD3cPqf9nxECzopbldZH7duRvVypaGyP8NvrE1kOO7uiqShIOagvAQH2bJ8WdPmyEoJWFp7VYxSbghlKA-IVWDgjSgEl7VrwVGm8lF6hGB/s1600/S7_63.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhGvbINgVCnSYd90MJcHgutbgnPBaIYg2qUPNyD3cPqf9nxECzopbldZH7duRvVypaGyP8NvrE1kOO7uiqShIOagvAQH2bJ8WdPmyEoJWFp7VYxSbghlKA-IVWDgjSgEl7VrwVGm8lF6hGB/s400/S7_63.png" /></a></div><br />
<br />
Ahora que ya hemos visto los componentes, veamos cómo limpiar y enviar los datos del formulario.<br />
<br />
<h3 class="subtitulo"><a href="http://www.blogger.com/null" id="reset"><span class="codigo">reset</span></a></h3>Muestra un botón que permite limpiar todos los datos que se hayan colocado en el formulario.<br />
<br />
Este componente puede mostrarse de dos formas, la primera es como un componente "<span class="codigo">input</span>" (<span class="codigo"><input type="reset"...></span>), y la segunda como un botón (<span class="codigo"><button type="reset"...></span>).<br />
<br />
Los atributos de esta etiqueta se muestran en la siguiente tabla:<br />
<br />
<table><thead>
<tr> <th>Nombre</th><th>Obligatorio</th><th>Valor Default</th><th>Tipo</th><th>Descripción</th> </tr>
</thead> <tbody>
<tr> <td><span class="codigo">type</span></td><td>No</td><td><span class="codigo">input</span></td><td><span class="codigo">String</span></td><td>Indica la forma en la que se mostrará e elemento. Los valores posibles son "<span class="codigo">input</span>", "<span class="codigo">button</span>" e "<span class="codigo">image</span>"</td> </tr>
<tr> <td><span class="codigo">value</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>El texto que mostrará el botón.</td> </tr>
<tr> <td colspan="6"><span class="codigo">action, method, src</span></td> </tr>
</tbody> </table><br />
<br />
Veamos un ejemplo en el que colocaremos esta etiqueta como un botón.<br />
<br />
Si colocamos la siguiente etiqueta:<br />
<br />
<pre><code>
<s:reset type="button" value="Limpiar" />
</code></pre><br />
<br />
Obtendremos el siguiente código:<br />
<br />
<pre><code>
<button type="reset" value="Limpiar">Limpiar</button>
</code></pre><br />
<br />
Que se ve así en el navegador:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgUUorS4RKYCatlmZZVffCLdzt0cVDvVXXdNMYotrK5q9OGEwKSAANFMY6xqsEkiQPnFJ_lTw2s0VUlpZgxd1_nwVpEDLkwiS6mXPV3dfc0YkXZcm7Ec-eMRSnS08MvgCnOWs1GkcxY3vKT/s1600/S7_65.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgUUorS4RKYCatlmZZVffCLdzt0cVDvVXXdNMYotrK5q9OGEwKSAANFMY6xqsEkiQPnFJ_lTw2s0VUlpZgxd1_nwVpEDLkwiS6mXPV3dfc0YkXZcm7Ec-eMRSnS08MvgCnOWs1GkcxY3vKT/s400/S7_65.png" /></a></div><br />
<br />
Si tuviéramos varios campos de formulario, con información, y presionáramos el botón "<span class="codigo">Limpiar</span>", veríamos cómo estos campos regresan a sus valores iniciales. Por ahora sólo confíen en mi <span class="codigo">^_^</span>.<br />
<br />
<h3 class="subtitulo"><a href="http://www.blogger.com/null" id="submit"><span class="codigo">submit</span></a></h3>Esta etiqueta genera un botón que permite enviar el formulario al servidor para ser evaluado.<br />
<br />
Al igual que con la etiqueta anterior, este componente puede mostrarse de distintas formas:<br />
<br />
<ul><li><span class="codigo">input</span>: muestra la etiqueta como <span class="codigo"><input type="submit"...></span></li>
<li><span class="codigo">image</span>: muestra la etiqueta como <span class="codigo"><input type="image"...></span></li>
<li><span class="codigo">button</span>: muestra la etiqueta como <span class="codigo"><button type="submit"...></span></li>
</ul><br />
<br />
Los atributos de esta etiqueta se muestran en la siguiente tabla:<br />
<br />
<br />
<br />
<table><thead>
<tr> <th>Nombre</th><th>Obligatorio</th><th>Valor Default</th><th>Tipo</th><th>Descripción</th> </tr>
</thead> <tbody>
<tr> <td><span class="codigo">type</span></td><td>No</td><td><span class="codigo">input</span></td><td><span class="codigo">String</span></td><td>Indica la forma en la que se mostrará e elemento. Los valores posibles son "<span class="codigo">input</span>", "<span class="codigo">button</span>" e "<span class="codigo">image</span>"</td> </tr>
<tr> <td><span class="codigo">value</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>El texto que mostrará el botón.</td> </tr>
<tr> <td colspan="6"><span class="codigo">action, method, src</span></td></tr>
</tbody> </table><br />
<br />
El ejemplo en este caso también será muy sencillo. Si colocamos la etiqueta de esta forma:<br />
<br />
<pre><code>
<s:submit type="button" value="Enviar" />
</code></pre><br />
<br />
Generará el siguiente código:<br />
<br />
<pre><code>
<button type="submit" id="formulario_0" value="Enviar">Enviar</button>
</code></pre><br />
<br />
Que se ve así en el navegador:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgS8CV8c4bv0IXSlZH_dov03j_Gb0Cmo7YLP4349uVKs0ZFr1kVbUnzwP1W-WwxgXV_csSIfbOcmlvuM7IvvacTU4gfLw8aV2r6FJL5mma9aqV_g4bIyc8gR8-hKtFYs0tcxJ91wljceXWa/s1600/S7_66.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgS8CV8c4bv0IXSlZH_dov03j_Gb0Cmo7YLP4349uVKs0ZFr1kVbUnzwP1W-WwxgXV_csSIfbOcmlvuM7IvvacTU4gfLw8aV2r6FJL5mma9aqV_g4bIyc8gR8-hKtFYs0tcxJ91wljceXWa/s400/S7_66.png" /></a></div><br />
<br />
Esta etiqueta la usaremos en cada uno de los formularios que usemos en nuestras aplicaciones (siempre y cuando queramos que la información sea enviada a algún lado).<br />
<br />
<h3 class="subtitulo"><a href="http://www.blogger.com/null" id="token"><span class="codigo">token</span></a></h3>Esta etiqueta, además de ser la última de esta categoría, evita que un formulario sea enviado dos veces (por eso de los usuarios que siempre dan doble clic sobre cada botón o que piensan que entre más clics hagan en el botón de enviar, más rápido se atenderá su petición<span class="codigo">^_^!</span>).<br />
<br />
Esta etiqueta coloca un elemento oculto que contiene un tóken único. El servidor, al recibir este valor, verifica si ya ha sido enviado o no. En caso de que ya haya sido enviado, simplemente ignora las peticiones subsecuentes.<br />
<br />
Esta etiqueta no tiene atributos.<br />
<br />
Su forma de uso es muy simple, sólo la colocamos en un formulario, de la siguiente forma:<br />
<br />
<pre><code>
<s:token />
</code></pre><br />
<br />
Y con esto obtenemos, más o menos, el siguiente código:<br />
<br />
<pre><code>
<input type="hidden" name="token" value="Q6BZKAOPZ2WI2VBNHHXCXBNK7F1XS1I2" />
</code></pre><br />
<br />
Este tóken varía y es el que nos asegura que el formulario no haya sido enviado más de una vez.<br />
<br />
Con esto terminamos la categoría de etiquetas de formulario y podemos pasar a la última categoría del tutorial.<br />
<br />
<h2 class="titulo"><a href="http://www.blogger.com/null" id="noformulario">Etiquetas UI de No Formulario</a></h2>Estas etiquetas se usan básicamente para mostrar información al usuario sobre lo ocurrido en un <span class="codigo">Action</span>. Hay básicamente dos tipos de mensajes, unos que llamaremos mensajes informativos, o simplemente mensajes; y los que llamaremos mensajes de error.<br />
<br />
Existen cinco etiquetas en esta categoría, aunque sólo tres nos son de utilidad:<br />
<br />
<ul><li><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#actionmessage"><span class="codigo">actionmessage</span></a></li>
<li><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#actionerror"><span class="codigo">actionerror</span></a></li>
<li><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#fielderror"><span class="codigo">fielderror</span></a></li>
<li><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#component"><span class="codigo">component</span></a></li>
<li><a href="http://www.javatutoriales.com/2013/11/struts-2-parte-7-tags.html#div"><span class="codigo">div</span></a></li>
</ul><br />
<br />
En el tutorial sólo hablaremos de las tres primeras.<br />
<br />
Para ver el funcionamiento de las mismas, necesitamos crear una clase <span class="codigo">Action</span> que se encargará de establecer los mensajes.<br />
<br />
Crearemos, en el paquete "<span class="codigo">actions</span>", una nueva clase llamada "<span class="codigo">MensajesAction</span>". Esta clase extenderá de "<span class="codigo">ActionSupport</span>". Esta clase sobre-escribirá el método "<span class="codigo">execute</span>" y por ahora sólo regresará el valor "<span class="codigo">SUCCESS</span>":<br />
<br />
<pre><code>
public class Mensajes<span class="codigo">Action</span> extends <span class="codigo">Action</span>Support {
@Override
public String execute() throws Exception {
return SUCCESS;
}
}
</code></pre><br />
<br />
Decoraremos esta clase con alguna anotaciones de <span class="codigo">Struts 2</span>, para indicar que se trata de un <span class="codigo">Action</span> que se encontrará en el namespace "<span class="codigo">/</span>", que tendrá el nombre "<span class="codigo">mensajes</span>" y que en el caso de un resultado exitoso el flujo de la aplicación ira a la página "<span class="codigo">mensajes.jsp</span>":<br />
<br />
<pre><code>
@Namespace(value = "/")
@Action(value = "mensajes", results = {
@Result(name = "success", location = "/mensajes.jsp")})
public class Mensajes<span class="codigo">Action</span> extends <span class="codigo">Action</span>Support {
@Override
public String execute() throws Exception {
return SUCCESS;
}
}
</code></pre><br />
<br />
También crearemos la página "<span class="codigo">mensajes.jsp</span>" que será donde colocaremos las etiquetas para ver su funcionamiento:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiXeVwaIKVbbXjrQ0tbtdOgzSDGHSt4dyMs5lxt1LczlumLP5inXXQe17DvCLfDcyBpXtqSbUyMlJ10ODLLFOPlRjsjBtgaMq5eW4xVtn_wcMo0qFo9GHp1CQR4_CPpMSG_AeSO5VgUwMEU/s1600/S7_67.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiXeVwaIKVbbXjrQ0tbtdOgzSDGHSt4dyMs5lxt1LczlumLP5inXXQe17DvCLfDcyBpXtqSbUyMlJ10ODLLFOPlRjsjBtgaMq5eW4xVtn_wcMo0qFo9GHp1CQR4_CPpMSG_AeSO5VgUwMEU/s400/S7_67.png" /></a></div><br />
<br />
En esta página indicamos que usaremos las etiquetas de <span class="codigo">Struts 2</span> usando la directiva "<span class="codigo">@taglib</span>":<br />
<br />
<pre><code>
<%@taglib uri="/struts-tags" prefix="s" %>
</code></pre><br />
<br />
<div class="nota">*Nota: Las etiquetas de esta categoría comparten los mismos atributos generales que las de la categoría anterior, así que tampoco los indicaremos.</div>Ahora si, comencemos a ver el funcionamiento de las etiquetas.<br />
<br />
<h3 class="subtitulo"><a href="http://www.blogger.com/null" id="actionmessage"><span class="codigo">actionmessage</span></a></h3>Esta etiqueta muestra un mensaje proveniente de un <span class="codigo">Action</span>.<br />
<br />
Como se imaginarán, por el texto anterior, el mensaje es establecido desde una clase <span class="codigo">Action</span> y este se muestra en la página del resultado de la ejecución del mismo.<br />
<br />
Esta etiqueta no tiene atributos.<br />
<br />
Para el ejemplo, iremos primero a la clase "<span class="codigo">MensajesAction</span>". En su método <span class="codigo">execute</span> pondremos un mensaje que será fijo (aunque este debería ser generado dinámicamente dependiendo de la ejecución de la aplicación). Es importante decir que este mensaje debe ser para informar cualquier cosa que no sea un error del <span class="codigo">Action</span> (para esto usaremos otra etiqueta). Puede ser un mensaje informativo, una pregunta, o cualquier otra cosa que necesiten avisar al usuario.<br />
<br />
Para colocar estos textos, la clase <span class="codigo">ActionSupport</span> proporciona unos métodos de utilidad para que esto sea fácil y sencillo; el método "<span class="codigo">add<span class="codigo">Action</span>Message</span>", el cual, como su nombre lo indica, añade un nuevo mensaje a los ya existentes, o sea que podemos tener más de un mensaje a la vez.<br />
<br />
En mi caso simplemente pondré un texto indicando que ese es un texto informativo (recuerden que este código debe estar dentro del método "<span class="codigo">execute</span>").<br />
<br />
<pre><code>
addActionMessage("Hola, este es un texto informativo");
</code></pre><br />
<br />
Ahora vayamos a la página "<span class="codigo">mensajes.jsp</span>". Es esta página agregamos la etiqueta de la siguiente forma:<br />
<br />
<pre><code>
<s:actionmessage />
</code></pre><br />
<br />
Eso es todo.<br />
<br />
Ahora, si ejecutamos nuevamente nuestra aplicación y entramos a la siguiente dirección:<br />
<br />
<pre><code>
<a href="http://localhost:8080/tags/mensajes">http://localhost:8080/tags/mensajes</a>
</code></pre><br />
<br />
Debemos ver el siguiente mensaje:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj5bRbmIbp4ud5M6Yc7RKjzyOlshu0LhFTVHrhjKdSrlEXWrFFAuC-oi4LTslTInhZ6ZllXappFbWqAvAEuAcnbTOHgMvCUHnATJnjIleC4dNaR2_5hJju24qG5q-XvWS4EEOL3vfI4rbFC/s1600/S7_68.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj5bRbmIbp4ud5M6Yc7RKjzyOlshu0LhFTVHrhjKdSrlEXWrFFAuC-oi4LTslTInhZ6ZllXappFbWqAvAEuAcnbTOHgMvCUHnATJnjIleC4dNaR2_5hJju24qG5q-XvWS4EEOL3vfI4rbFC/s400/S7_68.png" /></a></div><br />
<br />
Podemos ver que junto a este mensaje aparece una viñeta. Eso quiere decir que estos mensajes se muestran en una lista. Para comprobar esto agregaremos un segundo mensaje:<br />
<br />
<pre><code>
addActionMessage("Este es otro texto informativo");
</code></pre><br />
<br />
Con lo que veremos aparecer un segundo mensaje en la lista:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEixNbU9HrMMwWV6ish8g4ZCZywmS4UCAvWN24AT3dZxKAIWxx24Gon9kZNblZd76OW47Su-FPSvymmc4Z-8tSrxy58hBQWWBoKv40BraxOhP5MMlZd2YtOACTU8ErBidrqtQYzx_ll7hLpJ/s1600/S7_69.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEixNbU9HrMMwWV6ish8g4ZCZywmS4UCAvWN24AT3dZxKAIWxx24Gon9kZNblZd76OW47Su-FPSvymmc4Z-8tSrxy58hBQWWBoKv40BraxOhP5MMlZd2YtOACTU8ErBidrqtQYzx_ll7hLpJ/s400/S7_69.png" /></a></div><br />
<br />
Si vemos el código fuente que se genera, podremos comprobar que estos mensajes se colocan en una lista. También veremos que esta lista tiene la clase de <span class="codigo">CSS</span> "<span class="codigo">ActionMessage</span>", la cual podemos usar para personalizar la forma en la que se muestran los mensajes.<br />
<br />
<pre><code>
<ul class="ActionMessage">
<li><span>Hola, este es un texto informativo</span></li>
<li><span>Este es otro texto informativo</span></li>
</ul>
</code></pre><br />
<br />
Ahora veremos la siguiente etiqueta de esta categoría.<br />
<br />
<h3 class="subtitulo"><a href="http://www.blogger.com/null" id="actionerror"><span class="codigo">actionerror</span></a></h3>Esta etiqueta, de forma similar a la anterior, se utiliza para mostrar mensajes al usuario. Estos mensajes deben indicar que ocurrió algún error en la aplicación, siempre y cuando este error no esté relacionado con algún campo de un formulario, ya que para este caso existe una etiqueta especial, como puede ser un error al llamar a otro servicio, o al procesar los datos, etc.<br />
<br />
Esta etiqueta no tiene atributos.<br />
<br />
Para este ejemplo, nuevamente regresaremos a la clase "<span class="codigo">MensajesAction</span>". Aquí utilizaremos nuevamente un método de utilidad de la clase base "<span class="codigo">ActionSupport</span>" para agregar estos mensajes, el método "<span class="codigo">add<span class="codigo">Action</span>Error</span>". Con este método agregaremos un mensaje de error de la siguiente forma:<br />
<br />
<pre><code>
addActionError("Ha ocurrido un error al procesar su solicitud");
</code></pre><br />
<br />
Ahora, en la página "<span class="codigo">mensajes.jsp</span>" pondremos la etiqueta "<span class="codigo">s:actionerror</span>" de la siguiente forma:<br />
<br />
<pre><code>
<s:actionerror />
</code></pre><br />
<br />
Si ejecutamos nuevamente la aplicación veremos aparecer el siguiente mensaje:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiJ-_74N6LCBZdNUYuybr_9FVU44qSILxITpzGKzeHEi3XYLh-YvFMlBNGxhuoqDX-Xazu4b06oMiPr9dWvMeJM2oYDT1k7RI29xgJd3yoqxY6-rnGWxArjVNhP3OWoFMZvfGqElja1e2n8/s1600/S7_70.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiJ-_74N6LCBZdNUYuybr_9FVU44qSILxITpzGKzeHEi3XYLh-YvFMlBNGxhuoqDX-Xazu4b06oMiPr9dWvMeJM2oYDT1k7RI29xgJd3yoqxY6-rnGWxArjVNhP3OWoFMZvfGqElja1e2n8/s400/S7_70.png" /></a></div><br />
<br />
Al igual que con la etiqueta anterior, esta también se encuentra en una lista. Esta lista tiene la clase <span class="codigo">CSS</span> "<span class="codigo">errorMessage</span>".<br />
<br />
Si vemos el código fuente generado por la etiqueta anterior podremos ver más o menos lo siguiente:<br />
<br />
<pre><code>
<ul class="errorMessage">
<li><span>Ha ocurrido un error al procesar su solicitud</span></li>
</ul>
</code></pre><br />
<br />
Ahora veremos la última etiqueta de esta categoría, y de este tutorial.<br />
<br />
<h3 class="subtitulo"><a href="http://www.blogger.com/null" id="fielderror"><span class="codigo">fielderror</span></a></h3>Esta etiqueta muestra algún error relacionado con un campo de un formulario. <br />
<br />
Esta etiqueta sólo tiene un atributo, que se muestra en la siguiente tabla:<br />
<br />
<table><thead>
<tr> <th>Nombre</th><th>Obligatorio</th><th>Valor Default</th><th>Tipo</th><th>Descripción</th> </tr>
</thead> <tbody>
<tr> <td><span class="codigo">fieldname</span></td><td>No</td><td> </td><td><span class="codigo">String</span></td><td>Indica el nombre del campo que se mostrará (en casa de que sólo se quieran mostrar los mensajes de un campo).</td> </tr>
</tbody> </table><br />
<br />
Si colocamos esta etiqueta sin ningún atributo o cuerpo, mostrará los mensajes relacionados con todos los campos de los formularios de la página. También podemos indicar que muestre los mensajes relacionados con un solo campo, usando el atributo "<span class="codigo">fieldname</span>", o pasándolos como parámetros en el cuerpo de la etiqueta.<br />
<br />
Para este ejemplo debemos crear un formulario en la página "<span class="codigo">mensajes.jsp</span>". En él colocaremos 3 campos, un "<span class="codigo">nombre</span>", un "<span class="codigo">correo</span>", y un "<span class="codigo">password</span>" (con sus correspondientes etiquetas). Indicaremos que el <span class="codigo">Action</span> que procesará este formulario será el <span class="codigo">Action</span> llamado "<span class="codigo">mensajes</span>", de la siguiente forma:<br />
<br />
<pre><code>
<s:form action="mensajes">
<s:label value="Nombre: " for="nombre" />
<s:textfield name="nombre" id="nombre" />
<s:label value="Password: " for="password" />
<s:password name="password" id="password" />
<s:label value="Correo:" for="correo" />
<s:textfield name="correo" id="correo" />
<s:submit value="Enviar" />
</s:form>
</code></pre><br />
<br />
En la clase "<span class="codigo">MensajesAction</span>", agregamos tres <span class="codigo">String</span>s, una para cada uno de los campos anteriores, junto con sus correspondientes <span class="codigo">setters</span> y <span class="codigo">getters</span>:<br />
<br />
<pre><code>
private String nombre;
private String password;
private String correo;
public String getNombre() {
return nombre;
}
public void setNombre(String nombre) {
this.nombre = nombre;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getCorreo() {
return correo;
}
public void setCorreo(String correo) {
this.correo = correo;
}
</code></pre><br />
<br />
Ahora, si queremos que exista la posibilidad de que haya errores al establecer los valores de los campos del formulario, debemos colocar algunas validaciones.<br />
<br />
Del <a href="http://www.javatutoriales.com/2011/10/struts-2-parte-3-trabajo-con.html">tutorial de trabajo con formularios</a>, sabemos que la forma más sencilla de agregar estas validaciones es usando anotaciones, y que estas deben ser colocadas en los <span class="codigo">setters</span> de los atributos.<br />
<br />
Validaremos que los tres campos sean proporcionados y, adicionalmente, que el campo "<span class="codigo">correo</span>" tenga el formato de un correo electrónico:<br />
<br />
<pre><code>
@Required<span class="codigo">String</span>Validator(message = "El nombre es un campo obligatorio")
public void setNombre(String nombre) {
this.nombre = nombre;
}
@Required<span class="codigo">String</span>Validator (message = "El password es un campo obligatorio")
public void setPassword(String password) {
this.password = password;
}
@Required<span class="codigo">String</span>Validator(message = "El correo electronico es un campo obligatorio")
@EmailValidator(message = "El campo no contiene un correo electronico valido")
public void setCorreo(String correo) {
this.correo = correo;
}
</code></pre><br />
<br />
También debemos agregar un <span class="codigo">@Result</span> más a la configuración del <span class="codigo">Action</span>, indicando que, en caso de algún error en la entrada de algún campo, debemos regresar a la página "<span class="codigo">/mensajes.jsp</span>":<br />
<br />
<pre><code>
@Result(name = "input", location = "/mensajes.jsp")
</code></pre><br />
<br />
La configuración completa del <span class="codigo">Action</span> queda de la siguiente forma:<br />
<br />
<pre><code>
@Namespace(value = "/")
@Action(value = "mensajes", results = {
@Result(name = "success", location = "/mensajes.jsp"),
@Result(name = "input", location = "/mensajes.jsp")
})
public class Mensajes<span class="codigo">Action</span> extends <span class="codigo">Action</span>Support {
</code></pre><br />
<br />
Ahora regresemos a "<span class="codigo">mensajes.jsp</span>". Aquí pondremos, antes del formulario, la etiqueta "<span class="codigo">s:fielderror</span>":<br />
<br />
<pre><code>
<s:fielderror />
</code></pre><br />
<br />
Ahora, ejecutamos nuevamente nuestra aplicación, y entramos a la siguiente dirección:<br />
<br />
<pre><code>
<a href="http://localhost:8080/tags/mensajes.jsp">http://localhost:8080/tags/mensajes.jsp</a>
</code></pre><br />
<br />
Con lo que deberemos ver el formulario correspondiente:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhIuaXA1ZcWUpV32ufQytJ5X2mwKW5eCQiwmPSlxRtqlhPcGMZ31oZndeNEzXoruvKKe_EjVCS7IMhZu7Bib3sDfE-vF5kcKb3i728Uq2wYVHzMH8CgY-_T9e7IAbYvBju8Zx5YbZ4j2Leh/s1600/S7_71.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhIuaXA1ZcWUpV32ufQytJ5X2mwKW5eCQiwmPSlxRtqlhPcGMZ31oZndeNEzXoruvKKe_EjVCS7IMhZu7Bib3sDfE-vF5kcKb3i728Uq2wYVHzMH8CgY-_T9e7IAbYvBju8Zx5YbZ4j2Leh/s400/S7_71.png" /></a></div><br />
<br />
Si presionamos el botón "<span class="codigo">Enviar</span>", sin colocar ningún valor en ninguno de los campos, debemos ver la siguiente salida.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhvQYocuFi8Hea81ok6FWuoKXWRa5YRUDk15LC8Qv9y0qXs9NrnsWRc-VkVjCxuej7Z932ZOtVNBsGHQbnFDpQJtl0GocVnn5zdQMV6dd0WSQjvRyMrFDVUL7zYAvwbcEohT7KxhLMnFgCc/s1600/S7_72.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhvQYocuFi8Hea81ok6FWuoKXWRa5YRUDk15LC8Qv9y0qXs9NrnsWRc-VkVjCxuej7Z932ZOtVNBsGHQbnFDpQJtl0GocVnn5zdQMV6dd0WSQjvRyMrFDVUL7zYAvwbcEohT7KxhLMnFgCc/s400/S7_72.png" /></a></div><br />
<br />
Podemos ver que en la parte superior de formulario se colca una lista con los mensajes de error de cada uno de los campos. Esta lista tiene la clase <span class="codigo">CSS</span> "<span class="codigo">errorMessage</span>" (la misma que la etiqueta anterior).<br />
<br />
El código generado por la etiqueta anterior es el siguiente:<br />
<br />
<pre><code>
<ul class="errorMessage">
<li><span>El password es un campo obligatorio</span></li>
<li><span>El nombre es un campo obligatorio</span></li>
<li><span>El correo electronico es un campo obligatorio</span></li>
</ul>
</code></pre><br />
<br />
¿Qué pasaría si en vez de querer mostrar todos los errores juntos, quisiéramos mostrarlos de forma individual, o en grupos propios, o junto al campo correspondiente (pensando además que un campo pudiera generar más de un error)?<br />
<br />
Para esos casos, la etiqueta "<span class="codigo">errorMessage</span>" nos permite indicar el nombre del campo del cual deseamos mostrar el error.<br />
<br />
Tenemos dos formas de hacer esto. La primera es mostrar el error correspondiente a un solo campo, usando el atributo "<span class="codigo">fieldName</span>" de la etiqueta. En él colocamos el nombre del campo como aparezca en la etiqueta del formulario. Por ejemplo, para el campo "<span class="codigo">nombre</span>", colocamos la etiqueta de la siguiente forma:<br />
<br />
<pre><code>
<s:fielderror fieldName="nombre" />
</code></pre><br />
<br />
Para el campo "<span class="codigo">password</span>", de la siguiente:<br />
<br />
<pre><code>
<s:fielderror fieldName="password" />
</code></pre><br />
<br />
Etc.<br />
<br />
Modifiquemos el formulario anterior para incluir estos mensajes.<br />
<br />
<div class="nota">*Nota: En una aplicación web real, la maquetación deberíamos hacerla mediante <span class="codigo">CSS</span>s para que el acomodo de los elementos quede de una forma más estilizada. Sin embargo, para la caridad del ejemplo, sólo colocaremos los componentes esenciales.</div><pre><code>
<s:fielderror />
<s:form action="mensajes">
<s:label value="Nombre: " for="nombre" />
<s:textfield name="nombre" id="nombre" />
<s:fielderror fieldName="nombre" />
<br />
<s:label value="Password: " for="password" />
<s:password name="password" id="password" />
<s:fielderror fieldName="password" />
<br />
<s:label value="Correo:" for="correo" />
<s:textfield name="correo" id="correo" />
<s:fielderror fieldName="correo" />
<br />
<s:submit value="Enviar" />
</s:form>
</code></pre><br />
<br />
Si presionamos nuevamente el botón "<span class="codigo">Enviar</span>" del formulario, sin colocar ningún valor en los campos de texto, veremos la siguiente salida:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg8wI1kcZat8FrdqTIu_KYbqNhQXmudGc4gHDtt77xfXjUFKtMaLN9_-gs3GA9t9V_sUqia7-I_WpSMboxoyJ6ehrro2xm8liaRaJJt3PVmyFZCrj6oaakJ7_ebsIvd_BCpiCAYzRPas1cb/s1600/S7_73.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg8wI1kcZat8FrdqTIu_KYbqNhQXmudGc4gHDtt77xfXjUFKtMaLN9_-gs3GA9t9V_sUqia7-I_WpSMboxoyJ6ehrro2xm8liaRaJJt3PVmyFZCrj6oaakJ7_ebsIvd_BCpiCAYzRPas1cb/s400/S7_73.png" /></a></div><br />
<br />
Que genera el siguiente código:<br />
<br />
<pre><code>
<form id="mensajes" name="mensajes" action="/tags/mensajes.action" method="post">
<label for="nombre">Nombre: </label>
<input type="text" name="nombre" value="" id="nombre"/>
<ul class="errorMessage">
<li><span>El nombre es un campo obligatorio</span></li>
</ul>
<br />
<label for="password">Password: </label>
<input type="password" name="password" id="password"/>
<ul class="errorMessage" >
<li><span>El password es un campo obligatorio</span></li>
</ul>
<br />
<label for="correo">Correo:</label>
<input type="text" name="correo" value="" id="correo"/>
<ul class="errorMessage">
<li><span>El correo electronico es un campo obligatorio</span></li>
</ul>
<br />
<input type="submit" id="mensajes_0" value="Enviar"/>
</form>
</code></pre><br />
<br />
Podemos ver que ahora se coloca una lista para cada uno de los elementos indicados, esto para el caso que dijimos anteriormente: que un campo genere más de un error.<br />
<br />
Veamos el último ejemplo, en el que colocamos los errores de varios campos en un solo bloque. Para hacer esto, podemos pasar como parámetros, en el cuerpo de la etiqueta "<span class="codigo">s:fielderror</span>", los nombres de los campos de los cuales queremos mostrar los errores. <br />
<br />
Supongamos que queremos mostrar en un bloque los errores de los campos "<span class="codigo">nombre</span>" y "<span class="codigo">correo</span>", y en otro los del campo "<span class="codigo">password</span>". Para este caso debemos colocar dos etiquetas, la primera queda de la siguiente forma: <br />
<br />
<pre><code>
<s:fielderror>
<s:param>nombre</s:param>
<s:param>correo</s:param>
</s:fielderror>
</code></pre><br />
<br />
Y la segunda puede quedar así (aunque para este caso particular también pudimos haber usado el atributo "<span class="codigo">fieldName</span>"):<br />
<br />
<pre><code>
<s:fielderror>
<s:param>password</s:param>
</s:fielderror>
</code></pre><br />
<br />
Para que sea más claro a la hora de ver estos dos grupos de errores en la pantalla, colocaremos cada uno dentro de un "<span class="codigo">fieldset</span>":<br />
<br />
<pre><code>
<fieldset>
<legend>Nombre y correo</legend>
<s:fielderror>
<s:param>nombre</s:param>
<s:param>correo</s:param>
</s:fielderror>
</fieldset>
<fieldset>
<legend>Password</legend>
<s:fielderror>
<s:param>password</s:param>
</s:fielderror>
</fieldset>
</code></pre><br />
<br />
Si presionamos nuevamente el botón "<span class="codigo">Enviar</span>" del formulario, sin colocar valores en los campos de texto, veremos la siguiente salida:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhy203Mofttm76t3G2TONU8hNkuuXLrJXSXIB-hq2PVQX41kPpUYwKd35piiLVdMbtEwIVN06LyW6GwhzIgqIIhjoXx1gMsDdhToP6e5K6rIBKn5n6LdGGelDE-8kwysHDWN54Tg-bxQCHQ/s1600/S7_74.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhy203Mofttm76t3G2TONU8hNkuuXLrJXSXIB-hq2PVQX41kPpUYwKd35piiLVdMbtEwIVN06LyW6GwhzIgqIIhjoXx1gMsDdhToP6e5K6rIBKn5n6LdGGelDE-8kwysHDWN54Tg-bxQCHQ/s400/S7_74.png" /></a></div><br />
<br />
En donde podemos ver los dos grupos anteriores de errores.<br />
<br />
La salida anterior genera el siguiente código:<br />
<br />
<pre><code>
<fieldset>
<legend>Nombre y correo</legend>
<ul class="errorMessage">
<li><span>El nombre es un campo obligatorio</span></li>
<li><span>El correo electronico es un campo obligatorio</span></li>
</ul>
</fieldset>
<fieldset>
<legend>Password</legend>
<ul class="errorMessage">
<li><span>El password es un campo obligatorio</span></li>
</ul>
</fieldset>
</code></pre><br />
<br />
Donde podemos ver que el ejemplo funciona correctamente.<br />
<br />
Existen otras dos etiquetas en este grupo, las cuales dijimos que no veríamos ejemplos. Para los curiosos sólo las mencionaremos:<br />
<br />
<h3 class="subtitulo"><a href="http://www.blogger.com/null" id="component"><span class="codigo">component</span></a></h3>Muestra un componente de UI propio usando el template especificado. Este template puede ser creado usando <span class="codigo">JSP</span>, <span class="codigo">Freemarker</span> o <span class="codigo">Velocity</span>.<br />
<br />
<h3 class="subtitulo"><a href="http://www.blogger.com/null" id="div"><span class="codigo">div</span></a></h3>Crea una etiqueta "<span class="codigo">div</span>" de <span class="codigo">HTML</span>. Sólo tiene una utilidad cuando se usa el tema "<span class="codigo">ajax</span>".<br />
<br />
Con esto terminamos con las etiquetas de esta categoría y con el tutorial.<br />
<br />
Habíamos dicho que existe una quinta categoría de etiquetas, las etiquetas "<span class="codigo">AJAX</span>", pero que estas hacen uso del framework <span class="codigo">Dojo</span> y que tiene problemas para integrarse con nuevas versiones del framework, o con otros frameworks, y que por lo tanto en las nuevas versiones de <span class="codigo">Struts 2</span> estas se proporcionan como un plugin adicional.<br />
<br />
De forma personal, si van a hacer uso de Ajax en una aplicación <span class="codigo">Struts 2</span>, les recomiendo el uso del <a href="http://code.google.com/p/struts2-jquery/">plugin de jQuery</a><br />
<br />
Con esto terminamos este largo tutorial, que espero que les sea de utilidad.<br />
<br />
Si tienen alguna duda, sugerencia, comentario o aclaración, pueden dejarla en la sección de comentarios o enviar un correo a <a href="mailto:programadorjavablog@gmail.com">programadorjavablog@gmail.com</a> (pueden agregarme al chat de gmail). También pueden seguir JavaTutoriales en las siguientes redes sociales:<br />
<ul><li><a href="http://www.facebook.com/JavaTutoriales"><span class="codigo">Facebook</span></a></li>
<li><a href="https://plus.google.com/u/0/114078943018704761597"><span class="codigo">Google+</span></a></li>
<li><a href="https://twitter.com/#!/JavaTutoriales"><span class="codigo">Twitter</span></a></li>
</ul><br />
Saludos y gracias.<br />
<br />
<span class="negritas">Descarga los archivos de este tutorial desde aquí:</span><br />
<br />
<ul><li><a href="https://sites.google.com/site/javatutoriales/struts2/Struts2Tags.zip?attredirects=0&d=1"><span class="ligaArchivo">Etiquetas</span></a></li>
</ul><br />
<br />
<span class="negritas">Entradas Relacionadas:</span><br />
<br />
<ul><li><a href="http://www.javatutoriales.com/2011/06/struts-2-parte-1-configuracion.html">Parte 1: Configuración</a></li>
<li><a href="http://www.javatutoriales.com/2011/06/struts-2-parte-2-ognl.html">Parte 2: OGNL</a></li>
<li><a href="http://www.javatutoriales.com/2011/10/struts-2-parte-3-trabajo-con.html">Parte 3: Trabajo con Formularios</a></li>
<li><a href="http://www.javatutoriales.com/2011/12/struts-2-parte-4-scopes-de-objetos-web.html">Parte 4: Scopes de Objetos Web</a></li>
<li><a href="http://www.javatutoriales.com/2012/04/struts-2-parte-5-tipos-de-results.html">Parte 5: Tipos de Results</a></li>
<li><a href="http://www.javatutoriales.com/2012/06/struts-2-parte-6-interceptores.html">Parte 6: Interceptores</a></li>
</ul>Alexhttp://www.blogger.com/profile/06974037481671868076noreply@blogger.comtag:blogger.com,1999:blog-2012232069289957092.post-72278353563144433542012-06-26T14:50:00.000-07:002012-06-26T14:50:05.861-07:00Struts 2 - Parte 6: Interceptores<div style="text-align: justify;">Al desarrollar una aplicación, es necesario desarrollar algunas funcionalidades de utilidad que nos hagan más fácil el desarrollo de los procesos de negocio en los cuales estamos interesados. Algunos ejemplos de estas funcionalidades pueden ser el verificar que el usuario haya iniciado una sesión en el sistema, o que tenga permisos para ejecutar la operación que está solicitando, convertir los parámetros de un tipo a otro (de <span class="codigo">String</span> a <span class="codigo">Date</span>, o a <span class="codigo">int</span>), verificar que se hayan proporcionado los datos que son obligatorios, agregar alguna información a la sesión del usuario, etc.<br /><br />
La mayoría de estas funcionalidades pueden ser creadas de una forma genérica y usadas en varias partes de nuestra aplicación o en varias aplicaciones.<br /><br />
<span class="codigo">Struts 2</span> integra un mecanismo que nos permite abstraer estas funcionalidades de una manera sencilla, aplicarlas de forma transparente a las acciones que la necesiten (que pueden ser desde una hasta todas las acciones de la aplicación) y configurarlas a través del uso de parámetros.<br /><br />
En este tutorial aprenderemos cómo configurar los interceptores que vienen integrados con <span class="codigo">Struts 2</span>, además de crear nuestros propios interceptores, agregarlos a un <span class="codigo">Action</span>, y agregarlos a la pila que se aplica por default a los <span class="codigo">Action</span>s de nuestra aplicación.<br /><br />
<a name='more'></a>Recordando un poco de la teoría que vimos en <a href="http://www.javatutoriales.com/2011/06/struts-2-parte-1-configuracion.html">el primer tutorial</a>: los interceptores son clases que siguen el patrón <span class="codigo">interceptor</span>. Estos permiten que se implementen funcionalidades "<span class="codigo">cruzadas</span>" o comunes para todos los <span class="codigo">Action</span>s, pero que se ejecuten fuera del <span class="codigo">Action</span> (por ejemplo validaciones de datos, conversiones de tipos, población de datos, etc.).<br /><br />
Los interceptores realizan tareas antes y después de la ejecución de un <span class="codigo">Action</span> y también pueden evitar que un <span class="codigo">Action</span> se ejecute (por ejemplo si estamos haciendo alguna validación que no se ha cumplido). Sirven para ejecutar algún proceso particular que se quiere aplicar a un conjunto de <span class="codigo">Action</span>s. De hecho muchas de las características con que cuenta <span class="codigo">Struts 2</span> son proporcionadas por los interceptores.<br /><br />
Si alguna funcionalidad que necesitamos no se encuentra en los interceptores de <span class="codigo">Struts 2</span>, podemos crear nuestro propio interceptor y agregarlo a la cadena o pila que se ejecuta por default. <br /><br />
De la misma forma, podemos modificar la pila de interceptores de <span class="codigo">Struts 2</span>, por ejemplo para quitar un interceptor o modificar su orden de ejecución.<br /><br />
Cada interceptor proporciona una característica distinta al <span class="codigo">Action</span>. Para sacar la mayor ventaja posible de los interceptores, un <span class="codigo">Action</span> permite que se aplique más de un interceptor. Para lograr esto <span class="codigo">Struts 2</span> permite crear pilas o stacks de interceptores y aplicarlas a los <span class="codigo">Action</span>s. Cada interceptor es aplicado en el orden en el que aparece en el stack. También <span class="negritas">podemos formar pilas de interceptores en base a otras pilas</span>. Una de estas pilas de interceptores es aplicada por default a todos los <span class="codigo">Action</span>s de la aplicación (aunque si queremos también podemos aplicar una pila particular a un <span class="codigo">Action</span>).<br /><br />
En <a href="http://www.javatutoriales.com/2011/06/struts-2-parte-1-configuracion.html">el primer tutorial</a> pueden ver una tabla con algunos de los interceptores más usados en <span class="codigo">Struts 2</span>, y de las pilas de interceptores que vienen pre-configuradas.<br /><br />
Para decirlo de otra forma, los interceptores en <span class="codigo">Struts 2</span> son los equivalentes a los <a href="http://www.oracle.com/technetwork/java/filters-137243.html">filtros en la especificación de <span class="codigo">Servlets</span></a>.<br /><br />
Comencemos a ver cómo funcionan los interceptores en <span class="codigo">Struts 2</span> de la forma en la que nos gusta a los programadores, con código <span class="codigo">^^</span>.<br /><br />
Creamos un nuevo proyecto web en <span class="codigo">NetBeans</span>, vamos al Menú "<span class="codigo">File->New Project...</span>". En la ventana que se abre seleccionamos la categoría "<span class="codigo">Java Web</span>" y en el tipo de proyecto "<span class="codigo">Web Application</span>". Le damos una ubicación y un nombre al proyecto, en mi caso será "<span class="codigo">Struts2Interceptores</span>". Presionamos el botón "<span class="codigo">Next</span>". En la siguiente pantalla deberemos configurar el servidor de aplicaciones que usaremos. Yo usaré la versión 7 de <span class="codigo">Tomcat</span>. Como versión de <span class="codigo">Java EE</span> usaré la 5 (pueden usar también la 6 si quieren, solo que en ese caso <span class="codigo">NetBeans</span> no genera el archivo "<span class="codigo">web.xml</span>" por default, y tendrán que crearlo a mano o usando el wizard correspondiente). De la misma forma, si quieren pueden modificar el context path de la aplicación. Presionamos el botón "<span class="codigo">Finish</span>", con lo que veremos aparecer la página "<span class="codigo">index.jsp</span>" en nuestro editor:<br /><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhvLrmhiHFoh0zPVHCvfEbnBFboCFfYtzyzBaYKMepeFuyhPRzbQPHNIlETV_JL6HDILVIDmpR6jkY2zhwVXokk15zvqN_Lln9FAALMVIbZaKP8zdKGlpEV6yVjlRcBOTMC8h0Li37HAIdN/s1600/S6_1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="256" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhvLrmhiHFoh0zPVHCvfEbnBFboCFfYtzyzBaYKMepeFuyhPRzbQPHNIlETV_JL6HDILVIDmpR6jkY2zhwVXokk15zvqN_Lln9FAALMVIbZaKP8zdKGlpEV6yVjlRcBOTMC8h0Li37HAIdN/s400/S6_1.png" width="400" /></a></div><br /><br />
Ya que nuestro proyecto está creado, agregamos la biblioteca "<span class="codigo">Struts2</span>" que creamos en <a href="http://www.javatutoriales.com/2011/06/struts-2-parte-1-configuracion.html">el primer tutorial de la serie</a>. Para esto hacemos clic derecho en el nodo "<span class="codigo">Libraries</span>" del panel de proyectos. En el menú que aparece seleccionamos la opción "<span class="codigo">Add Library...</span>". En la ventana que aparece seleccionamos la biblioteca "<span class="codigo">Struts2</span>" y presionamos "<span class="codigo">Add Library</span>". Con esto ya tendremos los jars de <span class="codigo">Struts 2</span> en nuestro proyecto.<br /><br />
A continuación configuramos el filtro "struts2" en el deployment descriptor. Abrimos el archivo "web.xml" y colocamos el siguiente contenido, como se explicó en el primer tutorial de la serie:<br /><br />
<pre><code>
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</code></pre><br /><br />
Ahora crearemos un nuevo archivo llamado "<span class="codigo">struts.xml</span>" en el paquete default de código fuente de nuestra aplicación:<br /><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhNGzmBBT9oCnkhz0-mJLdD84gIeXBHex0bPY1O5XGtDi2CaGez2C6KIF5LvmsIwTNdDrmhzu_rf3ed331xXrGxzwcE2114TuXOk9ztYyXpWnhYOjdBhKoHzj3r5LX5ZXyXmq5DKa3Msrpt/s1600/S6_2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhNGzmBBT9oCnkhz0-mJLdD84gIeXBHex0bPY1O5XGtDi2CaGez2C6KIF5LvmsIwTNdDrmhzu_rf3ed331xXrGxzwcE2114TuXOk9ztYyXpWnhYOjdBhKoHzj3r5LX5ZXyXmq5DKa3Msrpt/s1600/S6_2.png" /></a></div><br /><br />
Este archivo contendrá la configuración de <span class="codigo">Struts 2</span> de nuestra aplicación. Colocaremos la configuración inicial, con un par de constantes para indicar que nos encontramos en modo de desarrollo y que cualquier cambio que hagamos en el archivo de configuración debe recargarse de inmediato, y un paquete inicial:<br /><br />
<pre><code>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<constant name="struts.devMode" value="true" />
<constant name="struts.configuration.xml.reload" value="true" />
<package name="struts-interceptores" extends="struts-default">
</package>
</struts>
</code></pre><br /><br />
Creamos un nuevo paquete en el nodo "<span class="codigo">Source Packages</span>" llamado "<span class="codigo">com.javatutoriales.interceptores.actions</span>" que será donde colocaremos las clases <span class="codigo">Action</span> de nuestra aplicación.<br /><br />
Antes de comenzar con los ejemplos, dejaré la lista de los interceptores de dos de las pilas de interceptores que vienen configuradas en <span class="codigo">Struts 2</span>. La primera es la pila básica ("<span class="codigo">basicStack</span>"), que contiene, como pueden imaginar, los interceptores mínimos necesarios para que una aplicación pueda proporcionar las funcionalidades básicas. Los interceptores de esta pila son:<br /><br />
<ul><li><span class="codigo">exception</span></li>
<li><span class="codigo">servletConfig</span></li>
<li><span class="codigo">prepare</span></li>
<li><span class="codigo">checkbox</span></li>
<li><span class="codigo">params</span></li>
<li><span class="codigo">conversionError</span></li>
</ul><br /><br />
<br />
La segunda pila importante es la pila que se aplica por default a todos los <span class="codigo">Action</span>s se nuestra aplicación ("<span class="codigo">defaultStack</span>"). Esta pila aplicará todos los interceptores, en el orden en que se encuentran:<br /><br />
<ul><li><span class="codigo">exception</span></li>
<li><span class="codigo">alias</span></li>
<li><span class="codigo">servletConfig</span></li>
<li><span class="codigo">prepare</span></li>
<li><span class="codigo">i18n</span></li>
<li><span class="codigo">chain</span></li>
<li><span class="codigo">debugging</span></li>
<li><span class="codigo">profiling</span></li>
<li><span class="codigo">scopedModelDriven</span></li>
<li><span class="codigo">modelDriven</span></li>
<li><span class="codigo">fileUpload</span></li>
<li><span class="codigo">checkbox</span></li>
<li><span class="codigo">staticParams</span></li>
<li><span class="codigo">conversionError</span></li>
<li><span class="codigo">validation</span></li>
<li><span class="codigo">workflow</span></li>
</ul><br /><br />
Es importante tener en cuenta cuáles y en qué orden se ejecutan estos interceptores para comprender algunos de los ejemplos que haremos.<br /><br />
Ahora sí, ya con esta información comenzaremos con el primer ejemplo del tutorial:<br /><br />
<br />
<h2 class="titulo">Configurar parámetros de Interceptores</h2>Iniciaremos modificando el comportamiento de uno de los interceptores que ya vienen en la pila por default. Ya hemos tratado un poco con este interceptor en <a href="http://www.javatutoriales.com/2011/12/struts-2-parte-4-scopes-de-objetos-web.html">el cuarto tutorial de la serie</a>: el interceptor "<span class="codigo">fileUpload</span>" (que si revisan la lista anterior se aplica sólo en la "<span class="codigo">defaultStack</span>").<br /><br />
Este interceptor permite que se envíen a través de un formulario, además de la información en texto plano normal, archivos de cualquier tipo.<br /><br />
Veamos cómo funciona por default este interceptor. Crearemos un nuevo directorio llamado "<span class="codigo">parametros</span>" dentro de las páginas web:<br /><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjzKy0u0ksjmjCA8e9cYVOQsB20qRS-a3D-37Cq_QwTq1uxe0SWpKlSEt0G2m6EcMG0_QsSxVUbEPU117AIGXSJH5oitekhSC-Ngh2Ld1Ra7lV1IsfEi7A8tmXtVmizEP1d_mizFXCGkwxR/s1600/S6_3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjzKy0u0ksjmjCA8e9cYVOQsB20qRS-a3D-37Cq_QwTq1uxe0SWpKlSEt0G2m6EcMG0_QsSxVUbEPU117AIGXSJH5oitekhSC-Ngh2Ld1Ra7lV1IsfEi7A8tmXtVmizEP1d_mizFXCGkwxR/s1600/S6_3.png" /></a></div><br /><br />
Dentro de este directorio creamos dos <span class="codigo">JSP</span>s, la primera se llamará "<span class="codigo">index.jsp</span>" y la segunda "<span class="codigo">resultado.jsp</span>". "<span class="codigo">index.jsp</span>" contendrá un formulario con un único campo de tipo "<span class="codigo">file</span>" para subir un archivo. Este formulario será procesado por una <span class="codigo">Action</span> llamado "<span class="codigo">carga-archivo</span>".<br /><br />
Recuerden que cuando se realizará la carga de un archivo en una página web, el formulario debe estar codificado con el tipo "<span class="codigo">multipart/form-data</span>".<br /><br />
También recuerden que para hacer uso de las etiquetas de <span class="codigo">Struts 2</span> es necesario indicar en la página que se usará el siguiente <span class="codigo">taglib</span>:<br /><br />
<pre><code>
<%@taglib prefix="s" uri="/struts-tags" %>
</code></pre><br /><br />
La declaración del formulario queda de la siguiente forma:<br /><br />
<pre><code>
<s:form action="carga-archivo" enctype="multipart/form-data">
<s:file name="archivo" label="Archivo" />
<s:submit />
</s:form>
</code></pre><br /><br />
Ya teniendo listo el formulario vamos a la página "<span class="codigo">resultado.jsp</span>". En esta página sólo pondremos un mensaje que indicará cuál es el <span class="codigo">content-type</span> del archivo que hemos cargado. Para esto usamos la etiqueta "<span class="codigo"><s:property></span>" de la siguiente forma:<br /><br />
<pre><code>
El archivo es de tipo <s:property value="archivoContentType" />
</code></pre><br /><br />
Ahora crearemos el <span class="codigo">Action</span> que procesará este formulario. Dentro del paquete "<span class="codigo">com.javatutoriales.interceptores.actions</span>" creamos una nueva clase llamada "<span class="codigo">CargaArchivo</span>", esta clase debe extender de "<span class="codigo">ActionSupport</span>":<br /><br />
<pre><code>
public class CargaArchivo extends ActionSupport
{
}
</code></pre><br /><br />
Esta será una clase sencilla. Sólo contendrá dos atributos, uno de tipo "<span class="codigo">File</span>" para almacenar el archivo que sea cargado desde la página, y otro de tipo "<span class="codigo">String</span>" para mantener el <span class="codigo">content-type</span> del archivo que subamos (para más detalles pueden revisar el cuarto tutorial de la serie):<br /><br />
<pre><code>
private File archivo;
private String archivoContentType;
</code></pre><br /><br />
Para el ejemplo regresaremos el valor del <span class="codigo">content-type</span>, por lo tanto colocaremos su <span class="codigo">getter</span>, y como que el framework establezca los valores de los atributos anteriores necesitamos sus <span class="codigo">setters</span>, colocamos los siguientes métodos en nuestra clase:<br /><br />
<pre><code>
public void setArchivo(File archivo)
{
this.archivo = archivo;
}
public String getArchivoContentType()
{
return archivoContentType;
}
public void setArchivoContentType(String archivoContentType)
{
this.archivoContentType = archivoContentType;
}
</code></pre><br /><br />
Ahora sobre-escribimos el método "<span class="codigo">execute</span>" de la clase. En este caso lo único que hará será regresar el valor "<span class="codigo">SUCCESS</span>" para que nos envíe a la página del resultado "<span class="codigo">success</span>":<br /><br />
<pre><code>
@Override
public String execute() throws Exception
{
return SUCCESS;
}
</code></pre><br /><br />
La clase "<span class="codigo">CargaArchivo</span>" completa queda de la siguiente forma:<br /><br />
<pre><code>
public class CargaArchivo extends ActionSupport
{
private File archivo;
private String archivoContentType;
@Override
public String execute() throws Exception
{
return SUCCESS;
}
public void setArchivo(File archivo)
{
this.archivo = archivo;
}
public String getArchivoContentType()
{
return archivoContentType;
}
public void setArchivoContentType(String archivoContentType)
{
this.archivoContentType = archivoContentType;
}
}
</code></pre><br /><br />
Eso es todo, como pueden ver es una clase muy sencilla que lo único que hace es mantener el archivo recibido a través del formulario, y regresar el <span class="codigo">content-type</span> del archivo.<br /><br />
Ahora haremos la declaración de este. Vamos al archivo "<span class="codigo">struts.xml</span>" y dentro del paquete "<span class="codigo">struts-interceptores</span>" que creamos el inicio del tutorial declaramos un nuevo "<span class="codigo">action</span>" llamado "<span class="codigo">carga-archivo</span>" y que será implementado por la clase "<span class="codigo">com.javatutoriales.interceptores.actions.CargaArchivo</span>":<br /><br />
<pre><code>
<package name="struts-interceptores" extends="struts-default">
<action name="carga-archivo" class="com.javatutoriales.interceptores.actions.CargaArchivo">
</action>
</package>
</code></pre><br /><br />
Este "<span class="codigo">action</span>" tendrá dos <span class="codigo">result</span>s, el primero, "<span class="codigo">success</span>" enviará a la página "<span class="codigo">resultado.jsp</span>", y el segundo será un resultado de tipo "<span class="codigo">input</span>" que nos regresará nuevamente a nuestro formulario:<br /><br />
<pre><code>
<action name="carga-archivo" class="com.javatutoriales.interceptores.actions.CargaArchivo">
<result>/parametros/resultado.jsp</result>
<result name="input">/parametros/index.jsp</result>
</action>
</code></pre><br /><br />
Ya está lista la primer parte del ejemplo. Ahora ejecutamos nuestra aplicación y entramos a la siguiente dirección:<br /><br />
<pre><code>
<a href="http://localhost:8080/interceptores/parametros/index.jsp">http://localhost:8080/interceptores/parametros/index.jsp</a>
</code></pre><br /><br />
Con lo que deberemos ver nuestro formulario:<br /><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgkbH9gX8BAp-MyA2Aryd5XfXl9kYVfi9CTWG1BV80t42bUHvgevLUtusZiQjMYk19H7kvz1QftZvGDnPQpXotL3HShvAA5n8wWg_2JsFwfr_p6yHcqHFB1SLXEImrbr8rzzgm4pk-IAz76/s1600/S6_4.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="216" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgkbH9gX8BAp-MyA2Aryd5XfXl9kYVfi9CTWG1BV80t42bUHvgevLUtusZiQjMYk19H7kvz1QftZvGDnPQpXotL3HShvAA5n8wWg_2JsFwfr_p6yHcqHFB1SLXEImrbr8rzzgm4pk-IAz76/s640/S6_4.png" width="640" /></a></div><br /><br />
Seleccionamos un archivo de cualquier tipo, en este caso yo seleccionaré un archivo <span class="codigo">.pdf</span>, y presionamos el botón para enviar el formulario. Al hacer esto debemos ver la siguiente salida:<br /><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZiudzo3gttmLxvNA3kV20jkGoc9m9MPFjd6DG9I6GVO8TqBKJ-0Vq7QqhoiH7uvHN52jjcBl4oRrbBdj0mDkR9eEKdk40jFeiBmtlLf3h8_DBe6vXu_9IrGRJelpq6VU2wBCYe8HhDv5m/s1600/S6_5.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="160" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZiudzo3gttmLxvNA3kV20jkGoc9m9MPFjd6DG9I6GVO8TqBKJ-0Vq7QqhoiH7uvHN52jjcBl4oRrbBdj0mDkR9eEKdk40jFeiBmtlLf3h8_DBe6vXu_9IrGRJelpq6VU2wBCYe8HhDv5m/s640/S6_5.png" width="640" /></a></div><br /><br />
Como podemos ver, el <span class="codigo">content-type</span> del archivo es "<span class="codigo">application/pdf</span>". Si ahora repetimos la operación seleccionando una imagen de tipo <span class="codigo">jpg</span>, obtendremos la siguiente salida:<br /><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhgkUbS7UQxpa0EFiFXuOxgk15oSJM07Ls2YwKEYWR_MXggShUtoiGx11tEoDYZG5Mv_NucFhS_6jOVMoReaXIHdNo0IBi9veO7NnMtRz0YWBuvtc34qflHrziHZO0YjvTeNZN_tsBJVNQ5/s1600/S6_6.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="160" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhgkUbS7UQxpa0EFiFXuOxgk15oSJM07Ls2YwKEYWR_MXggShUtoiGx11tEoDYZG5Mv_NucFhS_6jOVMoReaXIHdNo0IBi9veO7NnMtRz0YWBuvtc34qflHrziHZO0YjvTeNZN_tsBJVNQ5/s640/S6_6.png" width="640" /></a></div><br /><br />
Podemos ver que ahora el archivo es de tipo "<span class="codigo">image/jpeg</span>".<br /><br />
Podríamos estar todo el día subiendo archivos de distintos tipos para ver qué aparece en pantalla... pero eso evitaría que viéramos la parte del tutorial en la que tenemos que colocar los parámetros para configurar el interceptor <span class="codigo">^^</span>.<br /><br />
<span class="codigo">Struts 2</span> nos permite indicar de forma explícita qué interceptores utilizará cada <span class="codigo">Action</span> de nuestra aplicación. Para esto podemos utilizar el elemento "<span class="codigo"><interceptor-ref></span>" que se coloca dentro de "<span class="codigo"><action></span>".<br /><br />
"<span class="codigo"><interceptor-ref></span>" tiene solamente un parámetro, "<span class="codigo">name</span>", en el que se indica el nombre del interceptor que queremos configurar. En este caso, si miran la segunda tabla, podrán ver que el nombre del interceptor para la carga de archivos es "<span class="codigo">fileUpload</span>":<br /><br />
<pre><code>
<action name="carga-archivo" class="com.javatutoriales.interceptores.actions.CargaArchivo">
<interceptor-ref name="fileUpload">
</interceptor-ref>
<result>/parametros/resultado.jsp</result>
<result name="input">/parametros/index.jsp</result>
</action>
</code></pre><br /><br />
Ahora que hemos indicado cuál interceptor vamos a parametrizar, el siguiente paso es realizar dicha parametrización. Los parámetros los colocamos como <span class="negritas">pares nombre-valor</span> utilizando el elemento "<span class="codigo"><param></span>", dentro del interceptor que queremos parametrizar. El nombre lo indicamos con el atributo "<span class="codigo">name</span>" del elemento anterior, y el valor lo indicamos como valor del elemento (o sea entre las dos etiquetas). Por ejemplo, para establecer el valor de un parámetro llamado "<span class="codigo">nombre</span>" con el valor de "<span class="codigo">Alex</span>", lo haríamos de la siguiente forma:<br /><br />
<pre><code>
<param name="allowedTypes">image/png</param>
</code></pre><br /><br />
Si vamos a la documentación de la clase "<span class="codigo"><a href="http://struts.apache.org/2.1.6/struts2-core/apidocs/org/apache/struts2/interceptor/FileUploadInterceptor.html">org.apache.struts2.interceptor.FileUploadInterceptor</a></span>", que es la que implementa el interceptor "<span class="codigo">fileUpload</span>", podremos ver que puede recibir dos parámetros: "<span class="codigo">maximumSize</span>" y "<span class="codigo">allowedTypes</span>". El primero ya lo vimos en <a href="http://www.javatutoriales.com/2011/12/struts-2-parte-4-scopes-de-objetos-web.html">el cuarto tutorial de la serie</a>, así que ahora trabajaremos con el segundo.<br /><br />
Con las pruebas que hicimos anteriormente pudimos notar que podemos subir cualquier tipo de archivo al servidor, pero ¿qué pasaría si solo quisiéramos subir archivos de imágenes de tipo "<span class="codigo">png</span>" y "<span class="codigo">jpg</span>"? Para estos casos usamos el segundo parámetro, en el que debemos indicar los <span class="codigo">content-types</span> de los archivos que serán aceptados por este <span class="codigo">Action</span>. En este caso los <span class="codigo">content-types</span> son: "<span class="codigo">image/png</span>" y "<span class="codigo">image/jpg</span>".<br /> <br />
Cuando tenemos más de un valor para este parámetro, los separamos usando una coma, por lo que el valor del parámetro queda de la siguiente forma:<br /><br />
<pre><code>
<param name="allowedTypes">image/png,image/jpeg</param>
</code></pre><br /><br />
Y la configuración completa del <span class="codigo">Action</span> así:<br /><br />
<pre><code>
<action name="carga-archivo" class="com.javatutoriales.interceptores.actions.CargaArchivo">
<interceptor-ref name="fileUpload">
<param name="allowedTypes">image/png,image/jpeg</param>
</interceptor-ref>
<result>/parametros/resultado.jsp</result>
<result name="input">/parametros/index.jsp</result>
</action>
</code></pre><br /><br />
Si ahora ejecutamos nuestra aplicación y entramos a la siguiente dirección:<br /><br />
<pre><code>
<a href="http://localhost:8080/interceptores/parametros/index.jsp">http://localhost:8080/interceptores/parametros/index.jsp</a>
</code></pre><br /><br />
Veremos nuevamente nuestro formulario. Seleccionamos cualquier archivo que no sea una imagen "<span class="codigo">png</span>" o "<span class="codigo">jpg</span>", en mi caso será nuevamente un archivo <span class="codigo">PDF</span>, y presionamos el botón enviar. Es ese momento veremos... nada <span class="codigo">:|</span><br /><br />
Así es, no hay mensaje de error, ni ha aparecido el <span class="codigo">content-type</span> del archivo, nada ¿Por qué ha pasado esto?...me alegra que pregunten <span class="codigo">^_^</span>.<br /><br />
En realidad si ha aparecido un mensaje de error, aunque un poco más discreto. Su vamos a la consola del <span class="codigo">Tomcat</span> veremos un mensaje de error que dice algo así:<br /><br />
<pre><code>
ADVERTENCIA: Content-Type not allowed: archivo "Ing de Sw Java tutoriales.pdf" "upload_5010ddf2_137aed166ed__8000_00000002.tmp" application/pdf
</code></pre><br /><br />
Anteriormente dije que "podemos configurar los interceptores que se aplicarán a un <span class="codigo">Action</span>, directamente dentro del elemento <span class="codigo"><action></span>". Esto quiere <span class="negritas">sólo se aplicarán los interceptores que indiquemos</span>. En el ejemplo anterior, lo que ocurre es que <span class="negritas">sólo se aplica el interceptor "<span class="codigo">fileUpload</span>" al <span class="codigo">Action</span>, y no el resto de la cadena que teníamos por default</span>. O sea, no se aplican los interceptores que se encargan de enviar los parámetros al <span class="codigo">Action</span>, ni los que se encargan de validar los datos, ni los que se encargan de mostrar los errores; sólo "<span class="codigo">fileUpload</span>".<br /><br />
Entonces ¿cómo hacemos para que el resto de los interceptores normales también se apliquen? Simplemente indicando que, además del interceptor "<span class="codigo">fileUpload</span>", se deben aplicar el resto de los interceptores del "<span class="codigo">defaultStack</span>" y, esto es importante, se deben aplicar <span class="negritas">después de "<span class="codigo">fileUpload</span>"</span>. De esta forma <span class="codigo">Struts 2</span> marcará que este interceptor ya se ha aplicado y no volverá a hacerlo. Si colocan la parametrización del interceptor "<span class="codigo">fileUpload</span>" después del "<span class="codigo">defaultStack</span>", no verán ningún cambio con el comportamiento normal, así que lo repetiré, <span class="negritas">coloquen siempre el "<span class="codigo">defaultStack</span>" al final de los interceptores que hayan paremetrizado o agregado</span>.<br /><br />
Por lo que la declaración, completa y correcta, del <span class="codigo">Action</span> queda de la siguiente forma:<br /><br />
<pre><code>
<action name="carga-archivo" class="com.javatutoriales.interceptores.actions.CargaArchivo">
<interceptor-ref name="fileUpload">
<param name="allowedTypes">image/png,image/jpg</param>
</interceptor-ref>
<interceptor-ref name="defaultStack"/>
<result>/parametros/resultado.jsp</result>
<result name="input">/parametros/index.jsp</result>
</action>
</code></pre><br /><br />
Si volvemos a ejecutar nuestra aplicación y entramos al formulario, al seleccionar un archivo que no sea una imagen "<span class="codigo">jpg</span>" o "<span class="codigo">png</span>" obtendremos el siguiente error:<br /><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjnw1tsE5CCkoijtKge5o_oFRpJ3_L091e22Sw3mdEanvkkqgUW4c3qBjT5M26d9CDycmfnxIXeNp00HqKvEXaJMzzmdTvH6ZCP4M7UFZvPvx9jB-dItsLctG0VjPONsRb6eD1ivoPTouJp/s1600/S6_7.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="166" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjnw1tsE5CCkoijtKge5o_oFRpJ3_L091e22Sw3mdEanvkkqgUW4c3qBjT5M26d9CDycmfnxIXeNp00HqKvEXaJMzzmdTvH6ZCP4M7UFZvPvx9jB-dItsLctG0VjPONsRb6eD1ivoPTouJp/s640/S6_7.png" width="640" /></a></div><br /><br />
Si seleccionamos una imagen debemos obtener el siguiente resultado:<br /><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEizgkMyUkbuYXaaLPopWrylIpfxGl11cR2eVhp-aKfK2iTBACANxzE3kwNnw84yKm6_23VOWIJJTNjYdPKYV5pQOT-J1IcyxXV3cJZqE51_0Y-LLp3HMjm_gSJMrc-Q5V9rcNYVWc7Pzd9r/s1600/S6_8.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="372" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEizgkMyUkbuYXaaLPopWrylIpfxGl11cR2eVhp-aKfK2iTBACANxzE3kwNnw84yKm6_23VOWIJJTNjYdPKYV5pQOT-J1IcyxXV3cJZqE51_0Y-LLp3HMjm_gSJMrc-Q5V9rcNYVWc7Pzd9r/s640/S6_8.png" width="640" /></a></div><br /><br />
Podemos ver que los parámetros se han aplicado correctamente al interceptor.<br /><br />
Existe una segunda forma de establecer los parámetros de los interceptores, la cual es útil cuando vamos a configurar muchos interceptores, y esta es usando como nombre del parámetro el nombre del interceptor, seguido de un punto y el nombre del parámetro que queremos configurar. Esto se hace dentro de la declaración del stack, por lo que la declaración anterior también puede quedar de la siguiente forma:<br /><br />
<pre><code>
<interceptor-ref name="defaultStack">
<param name="fileUpload.allowedTypes">image/png,image/jpg</param>
</interceptor-ref>
</code></pre><br /><br />
Las dos dan los mismos resultados. Ahora que ya sabemos cómo configurar nuestros interceptores, veremos cómo agregar un interceptor que no se encuentra en la pila por default de <span class="codigo">Struts 2</span>.<br /><br />
<br />
<h2 class="titulo">Uso de Interceptores que no están en el defaultStack</h2><span class="codigo">Struts 2</span> permite agregar a la ejecución de un <span class="codigo">Action</span> interceptores que no estén en declarados en ninguna pila (y de igual forma configurarlos).En este ejemplo veremos cómo agregar dos interceptores muy útiles, <span class="codigo">logger</span> y <span class="codigo">timer</span>. Para no agregar un nuevo <span class="codigo"><action></span>, agregaremos este par de interceptores al que ya tenemos declarado.<br /><br />
Como su nombre lo indica, <span class="codigo">logger</span> registra el inicio y el final de la ejecución de un <span class="codigo">Action</span>. Agregarlo a su ejecución consiste solamente en dos cosas:<br /><br />
<ol><li>1. Conocer el nombre del interceptor (en este caso "<span class="codigo">logger</span>")</li>
<li>2. Indicar en qué punto se debe comenzar a registrar la información.</li>
</ol><br /><br />
El primer punto es sencillo, ya sabemos que el nombre del interceptor es "<span class="codigo">logger</span>". El segundo punto necesita una pequeña explicación. El lugar en el que declaremos el interceptor es importante, ya que <span class="negritas">su funcionamiento comenzará en el punto en el que lo coloquemos</span>. Esto quiere decir que si colocamos el interceptor antes del <span class="codigo">Action</span> (o, como veremos, antes de los <span class="codigo">results</span>), este registrará sólo la ejecución del <span class="codigo">Action</span>. Si lo colocamos antes de la pila de interceptores, se registrará la ejecución de cada uno de los interceptores que conforman la pila además del <span class="codigo">Action</span>. Por lo tanto, si colocamos la declaración de esta forma:<br /><br />
<pre><code>
<action name="carga-archivo" class="com.javatutoriales.interceptores.actions.CargaArchivo">
<interceptor-ref name="fileUpload">
<param name="allowedTypes">image/png,image/jpeg</param>
</interceptor-ref>
<interceptor-ref name="defaultStack"/>
<interceptor-ref name="logger" />
<result>/parametros/resultado.jsp</result>
<result name="input">/parametros/index.jsp</result>
</action>
</code></pre><br /><br />
Se registrará sólo la ejecución del <span class="codigo">Action</span>. Probémoslo ejecutando la aplicación. Al subir un archivo deberán de ver una salida parecida a esta en su consola:<br /><br />
<pre><code>
4/06/2012 07:47:26 PM com.opensymphony.xwork2.util.logging.jdk.JdkLogger info
INFO: Starting execution stack for action carga-archivo
4/06/2012 07:47:26 PM com.opensymphony.xwork2.util.logging.jdk.JdkLogger info
INFO: Finishing execution stack for action carga-archivo
</code></pre><br /><br />
La salida deberá cambiar dependiendo de si el archivo que suben es válido o no, pero noten que prácticamente los únicos mensajes que aparecen son los de la ejecución del <span class="codigo">Action</span> "<span class="codigo">carga-archivo</span>".<br /><br />
Si por el contrario, declaramos el interceptor de la siguiente forma:<br /><br />
<pre><code>
<action name="carga-archivo" class="com.javatutoriales.interceptores.actions.CargaArchivo">
<interceptor-ref name="logger" />
<interceptor-ref name="fileUpload">
<param name="allowedTypes">image/png,image/jpeg</param>
</interceptor-ref>
<interceptor-ref name="defaultStack"/>
<result>/parametros/resultado.jsp</result>
<result name="input">/parametros/index.jsp</result>
</action>
</code></pre><br /><br />
Obtendremos la siguiente salida:<br /><br />
<pre><code>
4/06/2012 07:58:11 PM com.opensymphony.xwork2.util.logging.jdk.JdkLogger info
INFO: Starting execution stack for action carga-archivo
4/06/2012 07:58:11 PM com.opensymphony.xwork2.util.logging.jdk.JdkLogger info
INFO: Finishing execution stack for action carga-archivo
</code></pre><br /><br />
(Si, se que se ve igual, pero eso es por el <span class="codigo">Action</span> tan simple que estamos ejecutando)<br /><br />
Ahora agregaremos el interceptor "<span class="codigo">timer</span>". Este interceptor se encarga de tomar el tiempo de ejecución de un <span class="codigo">Action</span> y mostrarlo en la consola. Agregar el interceptor "<span class="codigo">timer</span>" a nuestra aplicación es igual de sencillo. <br /><br />
<span class="nota">Nota: Comentaré el interceptor "<span class="codigo">logger</span>" para que la salida de la consola quede más limpia.</span><br /><br />
Podemos aplicar la misma lógica para este interceptor "<span class="codigo">timer</span>": si lo colocamos antes del <span class="codigo">Action</span> (de los <span class="codigo">results</span>) sólo tomará el tiempo de ejecución del <span class="codigo">Action</span>, si lo colocamos antes de los interceptores tomará el tiempo de ejecución del <span class="codigo">Action</span> y del resto de los interceptores.<br /><br />
Por lo tanto, si declaramos el interceptor de esta forma:<br /><br />
<pre><code>
<action name="carga-archivo" class="com.javatutoriales.interceptores.actions.CargaArchivo">
<interceptor-ref name="fileUpload">
<param name="allowedTypes">image/png,image/jpeg</param>
</interceptor-ref>
<interceptor-ref name="defaultStack"/>
<interceptor-ref name="timer" />
<result>/parametros/resultado.jsp</result>
<result name="input">/parametros/index.jsp</result>
</action>
</code></pre><br /><br />
Veremos más o menos la siguiente salida:<br /><br />
<pre><code>
INFO: Executed action [carga-archivo!execute] took 16 ms.
</code></pre><br /><br />
Y si lo declaramos de esta forma:<br /><br />
<pre><code>
<action name="carga-archivo" class="com.javatutoriales.interceptores.actions.CargaArchivo">
<interceptor-ref name="timer" />
<interceptor-ref name="fileUpload">
<param name="allowedTypes">image/png,image/jpeg</param>
</interceptor-ref>
<interceptor-ref name="defaultStack"/>
<result>/parametros/resultado.jsp</result>
<result name="input">/parametros/index.jsp</result>
</action>
</code></pre><br /><br />
Veremos más o menos la siguiente salida:<br /><br />
<pre><code>
INFO: Executed action [carga-archivo!execute] took 24 ms.
</code></pre><br /><br />
Como podemos ver, agregar un <span class="codigo">Action</span> que no esté en la pila por default es, en realidad, muy sencillo.<br /><br />
Ahora, imaginen esta situación, ¿qué pasaría si quisiéramos aplicar uno de estos interceptores, digamos "<span class="codigo">timer</span>" a todos los <span class="codigo">Action</span>s de nuestra aplicación? Con lo que hemos visto hasta ahora, tendríamos que agregar la declaración del interceptor "<span class="codigo">timer</span>" (y la correspondiente declaración del "<span class="codigo">defaultStack</span>" en cada uno de los <span class="codigo">Action</span>s de la aplicación. Esto, como podrán imaginarse, no es muy práctico, así que ahora veremos cómo anexar interceptores a la pila por default.<br /><br />
<br />
<h2 class="titulo">Agregando Interceptores a un Stack</h2>Para evitar el tener que repetir declaraciones de interceptores que usaremos de manera frecuente en nuestros <span class="codigo">Action</span>s, <span class="codigo">Struts 2</span> permite que agreguemos los interceptores que queramos en una pila de interceptores, que puede estar formado por otras pilas, y luego declarar esta pila como la pila por default para los <span class="codigo">Action</span>s de un paquete (un package de struts, no de Java) y de todos los paquetes que hereden de este.<br /><br />
Para todo trabajo relacionado con interceptores el archivo "<span class="codigo">struts.xml</span>" tiene una sección especial dentro del elemento "<span class="codigo"><interceptors></span>". Dentro de este elemento podemos declarar interceptores nuevos (lo cual veremos cómo hacer en la siguiente parte del tutorial), o crear nuevas pilas. Para esto último usamos los elementos "<span class="codigo"><interceptor-stack></span>" y "<span class="codigo"><interceptor-ref></span>", que usamos hace un momento.<br /><br />
Para declarar una nueva pila le asignamos un nombre, que debe ser único dentro del paquete, usando el atributo "<span class="codigo">name</span>" del elemento "<span class="codigo"><interceptor-stack></span>". El nombre que le daré a la nueva pila será "<span class="codigo">defaultTimerStack</span>":<br /><br />
<pre><code>
<interceptors>
<interceptor-stack name="defaultTimerStack">
</interceptor-stack>
</interceptors>
</code></pre><br /><br />
El siguiente paso es indicar qué interceptores conformarán esta pila. Para esto podemos indicar interceptores individuales (como "<span class="codigo">timer</span>" o "<span class="codigo">logger</span>") o indicar otras pilas de interceptores (como "<span class="codigo">basicStack</span>" o "<span class="codigo">defaultStack</span>"). Para ambos casos se utiliza el elemento "<span class="codigo"><interceptor-ref></span>", y se indica en su elemento "<span class="codigo">name</span>" el nombre del interceptor o de la pila del interceptores al que hacemos referencia. Por ejemplo, para que la nueva pila sea una copia de la pila "<span class="codigo">defaultStack</span>", colocamos la configuración de la siguiente forma:<br /><br />
<pre><code>
<interceptors>
<interceptor-stack name="defaultTimerStack">
<interceptor-ref name="defaultStack" />
</interceptor-stack>
</interceptors>
</code></pre><br /><br />
Así, si aplicamos esta pila a un <span class="codigo">Action</span> particular, como el <span class="codigo">Action</span> "<span class="codigo">carga-archivo</span>" que tenemos, debemos agregarla como en el ejemplo de la sección anterior, usando el elemento "<span class="codigo"><interceptor-ref></span>" dentro del elemento "<span class="codigo"><action></span>":<br /><br />
<pre><code>
<action name="carga-archivo" class="com.javatutoriales.interceptores.actions.CargaArchivo">
<interceptor-ref name="defaultTimerStack" />
<result>/parametros/resultado.jsp</result>
<result name="input">/parametros/index.jsplt;/result>
</action>
</code></pre><br /><br />
De esta forma se aplicará la pila por default a nuestro <span class="codigo">Action</span>. Si ejecutan la aplicación, esta deberá comportarse de forma normal (subiendo cualquier tipo de archivo y sin mostrar ninguna salida en la consola.<br /><br />
El hacer esto nos permite que modifiquemos los interceptores de la pila (junto con sus atributos) y que se refleje el cambio en todos los <span class="codigo">Action</span>s que utilicen esta pila. Por ejemplo, agreguemos a esta pila la parametrización del interceptor "<span class="codigo">fileUpload</span>" para que sólo acepte imágenes de tipo "<span class="codigo">png</span>":<br /><br />
<pre><code>
<interceptors>
<interceptor-stack name="defaultTimerStack">
<interceptor-ref name="fileUpload">
<param name="allowedTypes">image/png</param>
</interceptor-ref>
<interceptor-ref name="defaultStack" />
</interceptor-stack>
>/interceptors>
</code></pre><br /><br />
La definición de nuestro "<span class="codigo"><action></span>" permanece sin cambios.<br /><br />
Si ejecutamos nuevamente la aplicación, esta deberá aceptar sólo la carga de archivos cuyo <span class="codigo">content-type</span> sea "<span class="codigo">image/png</span>".<br /><br />
Podemos agregar también los dos interceptores anteriores, "<span class="codigo">logger</span>" y "<span class="codigo">timer</span>", recordando que las reglas anteriores (los interceptores se ejecutan en el orden en el que están declarados) continúan aplicando para las pilas propias:<br /><br />
<pre><code>
<interceptors>
<interceptor-stack name="defaultTimerStack">
<interceptor-ref name="fileUpload">
<param name="allowedTypes">image/png</param>
</interceptor-ref>
<interceptor-ref name="defaultStack" />
<interceptor-ref name="timer" />
<interceptor-ref name="logger" />
</interceptor-stack>
</interceptors>
</code></pre><br /><br />
Si ahora ejecutamos nuestra aplicación, y cargamos una imagen válida, debemos ver la siguiente salida en la consola:<br /><br />
<pre><code>
9/06/2012 07:40:37 PM com.opensymphony.xwork2.util.logging.jdk.JdkLogger info
INFO: Starting execution stack for action carga-archivo
9/06/2012 07:40:37 PM com.opensymphony.xwork2.util.logging.jdk.JdkLogger info
INFO: Finishing execution stack for action carga-archivo
9/06/2012 07:40:37 PM com.opensymphony.xwork2.util.logging.jdk.JdkLogger info
INFO: Executed action [carga-archivo!execute] took 71 ms.
</code></pre><br /><br />
Podemos ver que la nueva pila de interceptores se aplica con la configuración adecuada a nuestro action.<br /><br />
Esto funciona bien si tenemos unos cuantos <span class="codigo">Action</span>s en nuestra aplicación, o si la pila debe aplicarse sólo a unos cuantos <span class="codigo">Action</span>s. Pero si esta debe aplicarse a todos o la mayor parte de los <span class="codigo">Action</span>s, puede ser muy tedioso el tener que declarar dentro de cada uno que se desea aplicar ese stack. Para evitar esto, <span class="codigo">Struts 2</span> proporciona una forma sencilla de decir que queremos que una pila sea aplicada por default a todos los <span class="codigo">Action</span>s de nuestra aplicación.<br /><br />
<br />
<h2 class="titulo">Asignando una Pila de Interceptores por default para Nuestra Aplicación</h2>Esta parte será realmente corta. Una vez que ya hemos creado y configurado una pila propia podemos indicar cuál de las pilas de nuestro paquete será la pila que debe usarse por default usando el elemento "<span class="codigo"><default-interceptor-ref></span>", en cuyo atributo "<span class="codigo">name</span>" indicaremos cuál pila de interceptores deberá aplicarse por default a todos los <span class="codigo">Action</span>s de la aplicación. En nuestro caso queremos que por default se aplique nuestra nueva pila de interceptores "<span class="codigo">defaultTimerStack</span>":<br /><br />
<pre><code>
<default-interceptor-ref name="defaultTimerStack" />
</code></pre><br /><br />
Y... eso es todo. Como nuestra pila se aplica ahora por default, podemos dejar la declaración de nuestro action libre de esta información:<br /><br />
<pre><code>
<action name="carga-archivo" class="com.javatutoriales.interceptores.actions.CargaArchivo">
<result>/parametros/resultado.jsp</result>
<result name="input">/parametros/index.jsp</result>
</action>
</code></pre><br /><br />
Si volvemos a ejecutar nuestra aplicación, al subir una imagen válida debemos ver la siguiente salida en consola:<br /><br />
<pre><code>
9/06/2012 08:22:11 PM com.opensymphony.xwork2.util.logging.jdk.JdkLogger info
INFO: Starting execution stack for action carga-archivo
9/06/2012 08:22:11 PM com.opensymphony.xwork2.util.logging.jdk.JdkLogger info
INFO: Finishing execution stack for action carga-archivo
9/06/2012 08:22:11 PM com.opensymphony.xwork2.util.logging.jdk.JdkLogger info
INFO: Executed action [carga-archivo!execute] took 70 ms.
</code></pre><br /><br />
Podemos ver, la pila se aplicó correctamente al único <span class="codigo">Action</span> de la aplicación.<br /><br />
Ahora que conocemos los detalles del aspecto de configuración de los interceptores, veremos la última parte del tutorial, y la que seguramente la mayoría están esperando:<br /><br />
<br />
<h2 class="titulo">Creación de interceptores propios</h2>Un interceptor es, como habíamos dicho, un componente que permite pre-procesar una petición y post-procesar una respuesta. O sea que podemos manipular una petición antes de que esta llegue al <span class="codigo">Action</span>, y procesar una respuesta una vez que la ejecución del <span class="codigo">Action</span> ha terminado pero antes de que sea regresada al usuario.<br /><br />
Tener la posibilidad de crear interceptores propios nos permite agregar bloques de lógica a nuestra aplicación de una forma más ordenada y reutilizable.<br /><br />
Para crear un interceptor debemos, como casi siempre en Java, implementar una interface. En este caso la interface a implementar es "<span class="codigo">com.opensymphony.xwork2.interceptor.Interceptor</span>". Esta interface tiene tres métodos, y se ve de la siguiente forma:<br /><br />
<pre><code>
public interface Interceptor
{
void destroy();
void init();
String intercept(ActionInvocation invocation);
}
</code></pre><br /><br />
De estos tres métodos el que realmente nos interesa es "<span class="codigo">intercept</span>", ya que es aquí en donde se implementa la lógica del interceptor. "<span class="codigo">init</span>" se utiliza para inicializar los valores (conexiones a base de datos, valores iniciales, etc.) que usará el interceptor. "<span class="codigo">destroy</span>" es su contraparte y se usa para liberar los recursos que hayan sido apartados en "<span class="codigo">init</span>".<br /><br />
El argumento que recibe "<span class="codigo">intercept</span>" , un objeto de tipo "<span class="codigo">ActionInvocation</span>", representa el estado de ejecución del <span class="codigo">Action</span> y nos permitirá, entre otras cosas, obtener una referencia al <span class="codigo">Action</span> que será ejecutado y modificar el resultado que será mostrado al usuario. Es gracias a este objeto que podemos agregar valores al <span class="codigo">Action</span>, evitar que este se invoque, o permitir que continúe el flujo normal de la aplicación.<br /><br />
Para los casos en los que no usaremos los métodos "<span class="codigo">init</span>" y "<span class="codigo">destroy</span>", y no queramos proporcionar implementaciones vacías, también podemos extender de la clase "<span class="codigo">com.opensymphony.xwork2.interceptor.AbstractInterceptor</span>"<br /><br />
Hagamos un primer ejemplo sencillo para asimilar estos conceptos. Nuestro primer interceptor indicará qué <span class="codigo">Action</span> se está invocando junto con la fecha y la hora de dicha invocación; de paso también haremos que este nos salude <span class="codigo">^_^</span>.<br /><br />
Lo primero que haremos es crear un nuevo paquete llamado "<span class="codigo">interceptores</span>" ( o sea que le nombre completo del paquete será "<span class="codigo">com.javatutoriales.interceptores.interceptores</span>"). Dentro de este paquete creamos una clase llamada "<span class="codigo">InterceptorSaludo</span>" que implemente la interface "<span class="codigo">com.opensymphony.xwork2.interceptor.Interceptor</span>":<br /><br />
<pre><code>
public class InterceptorSaludo implements Interceptor
{
}
</code></pre><br /><br />
Debemos implementar los tres métodos (vistos anteriormente) de esta interface, por el momento permanecerán vacios:<br /><br />
<pre><code>
public class InterceptorSaludo implements Interceptor
{
@Override
public void destroy()
{
}
@Override
public void init()
{
}
@Override
public String intercept(ActionInvocation actionInvocation) throws Exception
{
}
}
</code></pre><br /><br />
En este caso no haremos nada en los métodos "<span class="codigo">init</span>" y "<span class="codigo">destroy</span>", sólo implementaremos el método "<span class="codigo">intercept</span>". Iniciemos con la parte fácil, el saludo, para lo que usaremos el tradicional "<span class="codigo">System.out.println</span>":<br /><br />
<pre><code>
@Override
public String intercept(ActionInvocation actionInvocation) throws Exception
{
System.out.println("Hola desarrollador");
}
</code></pre><br /><br />
Ahora, para obtener el nombre del <span class="codigo">Action</span> que se está ejecutando usamos el objeto "<span class="codigo">ActionContext</span>" que hemos usado en los tutoriales anteriores. Este objeto tiene un método "<span class="codigo">get</span>" que permite obtener algunos datos referentes al <span class="codigo">Action</span> en ejecución; debemos indicar qué valor queremos obtener usando algunas de las constantes que tiene el objeto "<span class="codigo">ActionContext</span>", en particular a nosotros nos interesa "<span class="codigo">ACTION_NAME</span>". Recordemos que este objeto es un "<span class="codigo">Singleton</span>" por lo que debemos obtener esta instancia usando su método "<span class="codigo">getContext</span>":<br /><br />
<pre><code>
String actionName = (String)ActionContext.getContext().get(ActionContext.ACTION_NAME);
</code></pre><br /><br />
Debemos castear el valor anterior a un String porque "<span class="codigo">get</span>" regresa un "<span class="codigo">Object</span>".<br />
Para obtener la fecha y hora actual usamos un objeto tipo "<span class="codigo">Date</span>" y para que se vea más presentable usaremos un objeto de tipo "<span class="codigo">DateFormat</span>". Como no es el tema del tutorial, no explicaré lo que se está haciendo y sólo pondré el código:<br /><br />
<pre><code>
String tiempoActual = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM).format(new Date());
</code></pre><br /><br />
Finalmente, imprimimos la información anterior en consola:<br /><br />
<pre><code>
System.out.println("Ejecutando " + actionName + " a las " + tiempoActual);
</code></pre><br /><br />
Ya que hemos obtenido e impreso la información que nos interesaba, el siguiente paso es indicar que queremos que se continúe con la ejecución de la pila de interceptores (o dicho de otra forma, que no queremos que se interrumpa). Para esto, es necesario explicar dos cosas.<br /><br />
Lo primero que hay que saber es que el método "<span class="codigo">intercept</span>" regresa un "<span class="codigo">String</span>", este "<span class="codigo">String</span>" es el nombre del <span class="codigo">result</span> que será mostrado al usuario.<br /><br />
Lo segundo que hay que saber es que no podemos regresar cualquier valor que queramos (bueno, en realidad sí, pero debemos hacerlo por una razón muy especial, veremos esto un poco más adelante) sino que debemos regresar el mismo valor que el <span class="codigo">Action</span> dentro de su ejecución. ¿Y cómo obtenemos este valor?<br /><br />
Para obtener el nombre del <span class="codigo">result</span> que regresa el método "<span class="codigo">execute</span>" del <span class="codigo">Action</span> debemos indicar que el interceptor se ha procesado correctamente y que se debe continuar con la ejecución normal de la aplicación (ya sea ejecutar el siguiente interceptor en la pila o ejecutar el <span class="codigo">Action</span> en caso de que este sea el último interceptor), para lo cual debemos invocar el método "<span class="codigo">invoque</span>" del "<span class="codigo">ActionInvocation</span>" que el método recibe como parámetro. Esté método regresa un <span class="codigo">String</span> que es (así es, adivinaron) el nombre del <span class="codigo">result</span>, por lo que lo único que debemos hacer es almacenar el valor que regresa este método para posteriormente volverlo a regresar o, como en mi caso, regresar directamente este valor:<br /><br />
<pre><code>
return actionInvocation.invoke();
</code></pre>Por lo que nuestro método "<span class="codigo">intecept</span>" completo queda de la siguiente forma:<br /><br />
<pre><code>
@Override
public String intercept(ActionInvocation actionInvocation) throws Exception
{
System.out.println("Hola desarrollador");
String actionName = (String)ActionContext.getContext().get(ActionContext.ACTION_NAME);
String tiempoActual = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM).format(new Date());
System.out.println("Ejecutando " + actionName + " a las " + tiempoActual);
return actionInvocation.invoke();
}
</code></pre><br /><br />
Si queremos realizar un proceso después de que el <span class="codigo">Action</span> se haya terminado de ejecutar, debemos almacenar el valor regresado por la llamada a "<span class="codigo">invoke</span>", y realizar el post-procesamiento después de este punto. Por ejemplo, si queremos enviar un mensaje cuando el <span class="codigo">Action</span> ha terminado de ejecutarse debemos dejar el método "<span class="codigo">intercept</span>" de la siguiente forma:<br /><br />
<pre><code>
@Override
public String intercept(ActionInvocation actionInvocation) throws Exception
{
System.out.println("Hola desarrollador");
String actionName = (String)ActionContext.getContext().get(ActionContext.ACTION_NAME);
String tiempoActual = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM).format(new Date());
System.out.println("Ejecutando " + actionName + " a las " + tiempoActual);
String resultado = actionInvocation.invoke();
System.out.println("Gracias, regresa pronto :)");
return resultado;
}
</code></pre><br /><br />
Y el interceptor completo de esta otra:<br /><br />
<pre><code>
public class InterceptorSaludo implements Interceptor
{
@Override
public void destroy()
{
}
@Override
public void init()
{
}
@Override
public String intercept(ActionInvocation actionInvocation) throws Exception
{
System.out.println("Hola desarrollador");
String actionName = (String)ActionContext.getContext().get(ActionContext.ACTION_NAME);
String tiempoActual = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM).format(new Date());
System.out.println("Ejecutando " + actionName + " a las " + tiempoActual);
String resultado = actionInvocation.invoke();
System.out.println("Gracias, regresa pronto :)");
return resultado;
}
}
</code></pre><br /><br />
Ahora que ya tenemos lista la clase que implementa el interceptor, debemos configurarlo. Para esto vamos nuevamente a nuestro archivo "<span class="codigo">struts.xml</span>". Si recuerdan, anteriormente dijimos que todo lo que tiene que ver con configuración de los interceptores se hace dentro de la sección "<span class="codigo"><interceptors></span>" de este archivo. <br /><br />
Para declarar un nuevo interceptor usamos el elemento..."<span class="codigo"><interceptor></span>". Este elemento tiene dos atributos: "<span class="codigo">name</span>", en el que se indica cuál será el nombre que se use para hacer referencia a este interceptor, y "<span class="codigo">class</span>", en el que se indica cuál clase implementa la funcionalidad de este interceptor. <br /><br />
En nuestro ejemplo, llamaremos al interceptor "<span class="codigo">saludo</span>", y la clase que lo implementa es "<span class="codigo">com.javatutoriales.interceptores.interceptores.InterceptorSaludo</span>". La declaración completa del interceptor, dentro de la sección "<span class="codigo"><interceptors></span>", queda de la siguiente forma:<br /><br />
<pre><code>
<interceptor name="saludo" class="com.javatutoriales.interceptores.interceptores.InterceptorSaludo" />
</code></pre><br /><br />
Ahora lo único que debemos hacer es agregar este interceptor a la pila "<span class="codigo">defaultTimerStack</span>" que creamos anteriormente. Recuerden que el interceptor se ejecutará en la posición en la que lo coloquen dentro de la pila; para este ejemplo pueden colocarlo en cualquier posición que deseen, en mí caso será el segundo interceptor que se ejecute:<br /><br />
<pre><code>
<interceptor-stack name="defaultTimerStack">
<interceptor-ref name="fileUpload">
<param name="allowedTypes">image/png</param>
</interceptor-ref>
<interceptor-ref name="saludo" />
<interceptor-ref name="defaultStack" />
<interceptor-ref name="timer" />
<interceptor-ref name="logger" />
</interceptor-stack>
</code></pre><br /><br />
Ahora ejecutamos nuestra aplicación, y al ejecutar nuestro <span class="codigo">Action</span> debemos ver la siguiente salida en la consola:<br /><br />
<pre><code>
Hola desarrollador
Ejecutando carga-archivo a las 23/06/2012 11:46:33 PM
Gracias, regresa pronto :)
</code></pre><br /><br />
Esto quiere decir que el interceptor se ejecutó de forma correcta ^_^<br /><br />
Para terminar con este tutorial, veremos un pequeño ejemplo que combina todos los elementos que hemos visto:<br /><br />
<br />
<h2 class="titulo">Creación de un Interceptor de Verificación de Sesión Válida</h2>Una de las cosas más comunes y útiles que podemos, y queremos, hacer con interceptores propios es verificar que un usuario pueda ejecutar los <span class="codigo">Action</span>s de nuestra aplicación, pero solamente si es un usuario válido, o sea, si existe una sesión creada para este usuario dentro del sistema.<br /><br />
Para hacer esto debemos pensar básicamente en dos cosas:<br /><br />
<ul><li>¿Cuáles <span class="codigo">Action</span>s dentro de la aplicación deben estar protegidos?</li>
<li>¿Cuáles <span class="codigo">Action</span>s deben ser públicos?</li>
<li>¿A dónde debe dirigirse al usuario en caso de que este no se haya logueado dentro del sistema aún?</li>
</ul>Para responder a estas preguntas diremos que normalmente queremos que casi todos los <span class="codigo">Action</span>s de la aplicación estén protegidos....¿casi? Si, casi todos, debemos dejar al menos un <span class="codigo">Action</span> sin proteger (público): el <span class="codigo">Action</span> para el login de la aplicación. Claro, podemos dejar públicos tantos <span class="codigo">Action</span>s como queramos, pero necesitamos al menos un punto de entrada a la aplicación para que el usuario pueda loguearse.<br /><br />
Si el usuario intenta ejecutar un <span class="codigo">Action</span> que esté protegido, y aún no se ha logueado, podemos enviarlo a una página de error o directamente al formulario de logueo. Nosotros haremos esto último.<br /><br />
Ahora vamos al código del ejemplo. Lo primero que haremos es crear cuatro páginas: "<span class="codigo">menu.jsp</span>", "<span class="codigo">login.jsp</span>" , "<span class="codigo">publica.jsp</span>", y "<span class="codigo">protegida.jsp</span>", en la raíz de las páginas web:<br /><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi1M4MFzLUK25Jze4hbnM0JAFOTfAXM1p0_tKQV_oLyhFzPWU2qEbCuC355ZeAnFxJzTRyHBqVyG346Syp04nIls2xqXrKuRfPCPpEYC2hXt2mRl4U8-HhBXpN-pt4vBn2f0cF8KRVh5Hgi/s1600/S6_9.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi1M4MFzLUK25Jze4hbnM0JAFOTfAXM1p0_tKQV_oLyhFzPWU2qEbCuC355ZeAnFxJzTRyHBqVyG346Syp04nIls2xqXrKuRfPCPpEYC2hXt2mRl4U8-HhBXpN-pt4vBn2f0cF8KRVh5Hgi/s1600/S6_9.png" /></a></div><br /><br />
"<span class="codigo">publica.jsp</span>" y "<span class="codigo">protegida.jsp</span>" serán el resultado de la ejecución de un par de <span class="codigo">Action</span>s, "<span class="codigo">login.jsp</span>" será un formulario para que el usuario pueda ingresar su usuario y contraseña de acceso al sitio; finalmente "<span class="codigo">menú.jsp</span>" será una página que mantendrá dos ligas, cada una a uno de los <span class="codigo">Action</span>s de nuestro interés.<br /><br />
Las dos primeras páginas serán muy sencillas y sólo contendrán un mensaje indicando si es un recuso protegido o público. El contenido de "<span class="codigo">publica.jsp</span>" es el siguiente:<br /><br />
<pre><code>
<h1>Esta página puede ser vista por cualquier persona</h1>
</code></pre><br /><br />
El contenido de "<span class="codigo">protegida.jsp</span>" es el siguiente:<br /><br />
<pre><code>
<h1>Esta página sólo puede ser vista por los usuarios autorizados</h1>
</code></pre><br /><br />
En "<span class="codigo">menú.jsp</span>" tendremos las ligas a dos <span class="codigo">Action</span>s que crearemos en un momento, cada a una a uno de los recursos que queremos proteger o mantener públicos:<br /><br />
<pre><code>
<ul>
<li><s:a action="protegido">Action Protegido</s:a></li>
<li><s:a action="publico">Action Público</s:a></li>
</ul>
</code></pre><br /><br />
No olviden agregar el taglib de <span class="codigo">Struts 2</span> a la página anterior, ya que estamos haciendo uso de sus etiquetas.<br /><br />
El contenido de "<span class="codigo">login.jsp</span>" es un poco más interesante, un formulario que permite ingresar dos campos, un nombre de usuario y una contraseña; además del botón que permite enviar estos datos. También agregaremos un elemento que nos mostrará los mensajes de error en caso de que algo malo ocurra:<br /><br />
<pre><code>
<s:actionerror />
<s:form action="login">
<s:textfield name="username" label="Username" />
<s:password name="password" label="Password" />
<s:submit value="Ingresar" />
</s:form>
</code></pre>El siguiente paso es crear el <span class="codigo">Action</span> para que el usuario pueda iniciar una sesión en el sistema. Para indicar que el usuario se ha logueado en el sistema, colocaremos un atributo preestablecido en la sesión, usando una de las técnicas que aprendimos en la cuarta parte de la serie.<br /><br />
<span class="nota">*Nota: Normalmente crearíamos un objeto "<span class="codigo">Usuario</span>" con algunas propiedades del cliente que va a loguearse, y haríamos todo el manejo de los datos de la sesión utilizando este objeto. Además haríamos una verificación de los permisos del usuario para saber a qué recursos tiene acceso (autorización). En este tutorial sólo agregaremos una cadena como indicador se la sesión para poder centrarnos en el objetivo del tutorial (los interceptores).</span><br /><br />
Creamos una nueva clase llamada "<span class="codigo">LoginAction</span>" en el paquete "<span class="codigo">actions</span>". Esta clase debe extender de "<span class="codigo">ActionSupport</span>":<br /><br />
<pre><code>
public class LoginAction extends ActionSupport
{
}
</code></pre><br /><br />
Colocaremos un par de atributos, con sus correspondientes <span class="codigo">setter</span>s, para almacenar el nombre del usuario y su contraseña:<br /><br />
<pre><code>
public class LoginAction extends ActionSupport
{
private String username;
private String password;
public void setPassword(String password)
{
this.password = password;
}
public void setUsername(String username)
{
this.username = username;
}
}
</code></pre><br /><br />
Para terminar con este <span class="codigo">Action</span>, sobre-escribiremos su método "<span class="codigo">execute</span>" para agregar al usuario a la sesión (en un sistema real debemos verificar a este usuario contra algún almacén de datos como una base de datos o un directorio ldap, pero aquí no lo haremos por... por lo mismo que dice la nota anterior <span class="codigo">^_^</span>):<br /><br />
<pre><code>
@Override
public String execute() throws Exception
{
HttpServletRequest request = ServletActionContext.getRequest();
HttpSession sesion = request.getSession();
sesion.setAttribute("usuario", username);
return SUCCESS;
}
</code></pre><br /><br />
Nuestra clase "<span class="codigo">LoginAction</span>" completa queda de la siguiente forma:<br /><br />
<pre><code>
public class LoginAction extends ActionSupport
{
private String username;
private String password;
@Override
public String execute() throws Exception
{
HttpServletRequest request = ServletActionContext.getRequest();
HttpSession sesion = request.getSession();
sesion.setAttribute("usuario", username);
return SUCCESS;
}
public void setPassword(String password)
{
this.password = password;
}
public void setUsername(String username)
{
this.username = username;
}
}
</code></pre><br /><br />
Ahora necesitamos configurar estos elementos en el archivo "<span class="codigo">struts.xml</span>". Lo primero que haremos es configurar los dos <span class="codigo">Action</span>s con sus <span class="codigo">result</span>s correspondientes:<br /><br />
<pre><code>
<action name="publico">
<result>/publica.jsp</result>
</action>
<action name="protegido">
<result>/protegida.jsp</result>
</action>
<action name="login" class="com.javatutoriales.interceptores.actions.LoginAction">
<result>/menu.jsp</result>
<result name="input">/login.jsp</result>
</action>
</code></pre><br /><br />
Podemos ver que en el caso de "<span class="codigo">LoginAction</span>", cuando el usuario ingrese este será enviado de nuevo al menú, y si algo sale mal será enviado nuevamente el formulario de login.<br /><br />
Como el <span class="codigo">result</span> para el caso del login será usado para todos los <span class="codigo">Action</span>s de la aplicación, debemos colocarlo de forma global (recuerden que este elemento se configura después de los interceptores pero antes de los <span class="codigo">Action</span>s, dentro del archivo de configuración):<br /><br />
<pre><code>
<global-results>
<result name="login">/login.jsp</result>
</global-results>
</code></pre><br /><br />
Si ahora ejecutamos la aplicación, y entramos a la siguiente dirección:<br /><br />
<pre><code>
<a href="http://localhost:8080/interceptores/menu.jsp">http://localhost:8080/interceptores/menu.jsp</a>
</code></pre><br /><br />
Debemos ver nuestro menú:<br /><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjzDbZa6N03srP1snUHXICxqm4aEeKapoRHCeZTRPIK3-xp-DYJOuBbbzy_FdLHJBjYduC2Lke5xPaK-Xg9WFv8toC_XU5mpAMvAHk2uiytu-4wTMIGBnTvEOCXMd8rAgcEaJ_405fZFtHg/s1600/S6_10.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="176" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjzDbZa6N03srP1snUHXICxqm4aEeKapoRHCeZTRPIK3-xp-DYJOuBbbzy_FdLHJBjYduC2Lke5xPaK-Xg9WFv8toC_XU5mpAMvAHk2uiytu-4wTMIGBnTvEOCXMd8rAgcEaJ_405fZFtHg/s640/S6_10.png" width="640" /></a></div><br /><br />
Cuando entremos a cada una de las ligas, debemos ver las páginas correspondientes:<br /><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEidfRgkSmfSCUZ95jRsDN7wdQU1CCjLsegxW2NVsHYy2mJfb6qGBr80a6snhitG9f2UCjlzY_OCKYIu1pjw7Qh9GDO5FxqUdcJwuqbumMwDK1ySodlzmbayqybjo7xj7jumtaAMa1DR98VB/s1600/S6_11.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="277" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEidfRgkSmfSCUZ95jRsDN7wdQU1CCjLsegxW2NVsHYy2mJfb6qGBr80a6snhitG9f2UCjlzY_OCKYIu1pjw7Qh9GDO5FxqUdcJwuqbumMwDK1ySodlzmbayqybjo7xj7jumtaAMa1DR98VB/s400/S6_11.png" width="400" /></a></div><br /><br />
Podemos ver que hasta aquí la aplicación sigue funcionando de la misma forma (no se ha restringido el acceso a nada). Ahora crearemos el interceptor que nos permitirá proteger nuestras páginas.<br /><br />
Primero debemos crear una clase llama "<span class="codigo">InterceptorAcceso</span>" en el paquete "<span class="codigo">interceptores</span>" de la aplicación. En esta ocasión, esta clase extenderá de "<span class="codigo">AbstractInterceptor</span>" y sólo sobre-escribirá su método "<span class="codigo">intercept</span>":<br /><br />
<pre><code>
public class InterceptorAcceso extends AbstractInterceptor
{
@Override
public String intercept(ActionInvocation ai) throws Exception
{
}
}
</code></pre><br /><br />
Por ahora sólo usaremos el método "<span class="codigo">intercept</span>", en el cual verificaremos que exista la propiedad llamada "<span class="codigo">usuario</span>" en la sesión, la cual se establece en el <span class="codigo">Action</span> que se encarga de realizar el logueo del usuario. Si no encuentra este objeto entonces supondremos que el usuario aún no se ha logueado y lo enviaremos al formulario de ingreso al sistema:<br /><br />
<pre><code>
@Override
public String intercept(ActionInvocation actionInvocation) throws Exception
{
String result = Action.LOGIN;
if (actionInvocation.getInvocationContext().getSession().containsKey("usuario"))
{
result = actionInvocation.invoke();
}
return result;
}
</code></pre><br /><br />
Primero asumimos que el usuario no se ha logueado, así que usamos la constante "<span class="codigo">LOGIN</span>" de la interface "<span class="codigo">Action</span>" como valor inicial de la variable que regresaremos en el interceptor. Verificamos, usando el mapa de sesión que obtenemos a través del objeto "<span class="codigo">ActionInvovation</span>", si el existe un usuario en sesión. Si el usuario existe, entonces ejecutamos el <span class="codigo">Action</span> y regresamos el valor que obtengamos de la ejecución de su método "<span class="codigo">execute</span>", en caso contrario este será enviado al formulario de login.<br /><br />
Ya teniendo nuestro interceptor podemos configurarlo en el archivo "<span class="codigo">struts.xml</span>" de la forma en la que lo hicimos hace un momento. El nombre del interceptor será "<span class="codigo">sesionValida</span>":<br /><br />
<pre><code>
<interceptor name="sesionValida" class="com.javatutoriales.interceptores.interceptores.InterceptorAcceso" />
</code></pre><br /><br />
Y colocamos el interceptor en la pila que se ejecuta por default (junto con el resto de los interceptores que hemos configurado en el tutorial) para que sea aplicado a todos los <span class="codigo">Action</span>s de la aplicación. Podemos colocar el interceptor a cualquier altura que queramos de la pila:<br /><br />
<pre><code>
<interceptor-stack name="defaultTimerStack">
<interceptor-ref name="sesionValida" />
<interceptor-ref name="fileUpload">
<param name="allowedTypes">image/png</param>
</interceptor-ref>
<interceptor-ref name="saludo" />
<interceptor-ref name="defaultStack" />
<interceptor-ref name="timer" />
<interceptor-ref name="logger" />
</interceptor-stack>
</code></pre><br /><br />
Ahora podemos ejecutar nuestra aplicación y entrar a la siguiente dirección:<br /><br />
<pre><code>
<a href="http://localhost:8080/interceptores/menu.jsp">http://localhost:8080/interceptores/menu.jsp</a>
</code></pre><br /><br />
Con lo que veremos el menú de hace un momento. Cuando demos clic en el enlace para ir al <span class="codigo">Action</span> protegido, la petición pasará primero por el interceptor "<span class="codigo">sesionValida</span>", este interceptor verá que el usuario aún no ha iniciado sesión y lo enviará el formulario de acceso:<br /><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgLO2h0O-FxQ4JdbURTE8o_awbByz3xMVtSLE8LdEqYr5BMP0h2M_VNvxeSIbzaSWyYhA3DoANyFUvY0z-YquE-numIVlOLDe6SBdi_5qoUgtzCjC5MurG5audp4j5HiRnEG28zg6tW-XYS/s1600/S6_12.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="111" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgLO2h0O-FxQ4JdbURTE8o_awbByz3xMVtSLE8LdEqYr5BMP0h2M_VNvxeSIbzaSWyYhA3DoANyFUvY0z-YquE-numIVlOLDe6SBdi_5qoUgtzCjC5MurG5audp4j5HiRnEG28zg6tW-XYS/s400/S6_12.png" width="400" /></a></div><br /><br />
Con lo que podemos comprobar que la verificación está funcionando correctamente.<br /><br />
Si ahora regresamos y hacemos clic en el enlace para ir al <span class="codigo">Action</span> público podremos comprobar que...<br /><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgLO2h0O-FxQ4JdbURTE8o_awbByz3xMVtSLE8LdEqYr5BMP0h2M_VNvxeSIbzaSWyYhA3DoANyFUvY0z-YquE-numIVlOLDe6SBdi_5qoUgtzCjC5MurG5audp4j5HiRnEG28zg6tW-XYS/s1600/S6_12.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="111" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgLO2h0O-FxQ4JdbURTE8o_awbByz3xMVtSLE8LdEqYr5BMP0h2M_VNvxeSIbzaSWyYhA3DoANyFUvY0z-YquE-numIVlOLDe6SBdi_5qoUgtzCjC5MurG5audp4j5HiRnEG28zg6tW-XYS/s400/S6_12.png" width="400" /></a></div><br /><br />
También nos envía al formulario de acceso <span class="codigo">°_°</span>. Bueno, parece que algo está pasando. No importa. Si ahora ingresamos nuestro nombre de usuario y contraseña, y hacemos clic en el botón "<span class="codigo">Ingresar</span>" (no importa los datos que coloquen, recuerden que no se están validando), deberemos ser enviados a...<br /><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgLO2h0O-FxQ4JdbURTE8o_awbByz3xMVtSLE8LdEqYr5BMP0h2M_VNvxeSIbzaSWyYhA3DoANyFUvY0z-YquE-numIVlOLDe6SBdi_5qoUgtzCjC5MurG5audp4j5HiRnEG28zg6tW-XYS/s1600/S6_12.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="111" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgLO2h0O-FxQ4JdbURTE8o_awbByz3xMVtSLE8LdEqYr5BMP0h2M_VNvxeSIbzaSWyYhA3DoANyFUvY0z-YquE-numIVlOLDe6SBdi_5qoUgtzCjC5MurG5audp4j5HiRnEG28zg6tW-XYS/s400/S6_12.png" width="400" /></a></div><br /><br />
Bueno, una vez más estamos siendo enviados al formulario de acceso, aun cuando ya hemos ingresado nuestros datos para entrar al sistema. ¿Qué es lo que está pasando?... me alegra que pregunten <span class="codigo">^_^</span>.<br /><br />
Lo que ocurre es que, como debe de ser, todas las peticiones (incluyendo la petición de acceso al sitio) están pasando por el interceptor "<span class="codigo">sesionValida</span>", el cual verifica que la sesión exista. Como en realidad la petición nunca llega al <span class="codigo">Action</span> "<span class="codigo">LoginAction</span>", nunca iniciamos una sesión en el sistema (el filtro no tiene forma de saber que esta es la finalidad de este <span class="codigo">Action</span>), y por lo tanto nunca podemos pasar a través del interceptor (de hecho si revisan el <span class="codigo">Action</span> de la carga de imágenes, este también ha dejado de funcionar <span class="codigo">^_^</span>).<br /><br />
¿Qué podemos para que esto no ocurra? Bueno, un paso muy importante, como acabamos de ver, es indicarle a nuestro interceptor cuáles <span class="codigo">Action</span>s <span class="negritas">no debe filtrar</span>, o dicho de otra forma: cuáles <span class="codigo">Action</span>s deben poder ser accesibles aún sin una sesión de usuario válida. Para hacer eso le pasaremos a nuestro interceptor, como parámetros, la lista de los <span class="codigo">Action</span>s que no debe filtrar.<br /><br />
Regresamos a nuestra clase "<span class="codigo">InterceptorAcceso</span>" y agregamos un atributo, de tipo "<span class="codigo">String</span>", llamado "<span class="codigo">actionsPublicos</span>", con su correspondiente <span class="codigo">setter</span>:<br /><br />
<pre><code>
private String actionsPublicos;
public void setActionsPublicos(String actionsPublicos)
{
this.actionsPublicos = actionsPublicos;
}
</code></pre><br /><br />
Este atributo recibirá una lista separada por comas con los nombres de los <span class="codigo">Action</span>s que no serán filtrados. Como este parámetro es una cadena, la transformaremos en una lista para usar los métodos de la interface "<span class="codigo">List</span>" para saber si el <span class="codigo">Action</span> que se esté ejecutando es protegido o no. Esta transformación la haremos en el método "<span class="codigo">init</span>", por lo que lo sobre-escribimos de la siguiente forma:<br /><br />
<pre><code>
private List<String> actionsSinFiltrar = new ArrayList<String>();
@Override
public void init()
{
actionsSinFiltrar = Arrays.asList(actionsPublicos.split(","));
}
</code></pre><br /><br />
Finalmente, modificamos la condición del método "<span class="codigo">execute</span>", para verificar si el nombre del <span class="codigo">Action</span> que se está ejecutando actualmente está en la lista de los <span class="codigo">Action</span>s que no deben ser filtrados:<br /><br />
<pre><code>
String actionActual = (String)ActionContext.getContext().get(ActionContext.ACTION_NAME);
if (actionInvocation.getInvocationContext().getSession().containsKey("usuario") || actionsSinFiltrar.contains(actionActual))
{
result = actionInvocation.invoke();
}
</code></pre><br /><br />
Nuestra clase "<span class="codigo">InterceptorAcceso</span>" completa queda de la siguiente forma:<br /><br />
<pre><code>
public class InterceptorAcceso extends AbstractInterceptor
{
private String actionsPublicos;
private List<String> actionsSinFiltrar = new ArrayList<String>();
@Override
public void init()
{
actionsSinFiltrar = Arrays.asList(actionsPublicos.split(","));
}
@Override
public String intercept(ActionInvocation actionInvocation) throws Exception
{
String result = Action.LOGIN;
String actionActual = (String)ActionContext.getContext().get(ActionContext.ACTION_NAME);
if (actionInvocation.getInvocationContext().getSession().containsKey("usuario") || actionsSinFiltrar.contains(actionActual))
{
result = actionInvocation.invoke();
}
return result;
}
public void setActionsPublicos(String actionsPublicos)
{
this.actionsPublicos = actionsPublicos;
}
}
</code></pre><br /><br />
Ahora, para terminar, regresamos al archivo "<span class="codigo">struts.xml</span>" e indicamos cuáles son los actions que queremos dejar públicos, pasándolos como el parámetro "<span class="codigo">actionsPublicos</span>" del interceptor "<span class="codigo">sesionValida</span>":<br /><br />
<pre><code>
<interceptor-ref name="sesionValida">
<param name="actionsPublicos">login,publico</param>
</interceptor-ref>
</code></pre><br /><br />
Y esto es todo (lo prometo <span class="codigo">^_^</span>). Ahora, volvemos a ejecutar nuestra aplicación. Ingresamos a la siguiente dirección:<br /><br />
<pre><code>
<a href="http://localhost:8080/interceptores/menu.jsp">http://localhost:8080/interceptores/menu.jsp</a>
</code></pre><br /><br />
Cuando ingresemos en la liga "<span class="codigo">Action Público</span>" debemos ver el siguiente mensaje:<br /><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhKTIrNMP8unW-DabvzonyZswUgXMKigkYH2jay9lMjrNS_RUdEfk1xxCfaKlgBCLbOG2LMMRSWIreMfIrIaE-uk81VSoC6R-huXj-W7MzHi1tnD6PJto8NbJfLoOreiHM34gL2h0V_EiSS/s1600/S6_13.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="111" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhKTIrNMP8unW-DabvzonyZswUgXMKigkYH2jay9lMjrNS_RUdEfk1xxCfaKlgBCLbOG2LMMRSWIreMfIrIaE-uk81VSoC6R-huXj-W7MzHi1tnD6PJto8NbJfLoOreiHM34gL2h0V_EiSS/s400/S6_13.png" width="400" /></a></div><br /><br />
Hasta ahora vamos bien. Ahora, cuando entremos a la liga "<span class="codigo">Action Protegido</span>" debemos ver el formulario de login:<br /><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiu2W02djNv4oZ7WH-Mfs_sllSvzMF7W5RU2zt00r2ljA7A7KpxwRE9CRYYf1kj_JyZmeo5kOLLEAZKv5EwSM4JN656lJMh8tubqrsg_ImiMfBocKfm7sxdZ0dYJWhw7Fn_sO97vsowIACQ/s1600/S6_14.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="111" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiu2W02djNv4oZ7WH-Mfs_sllSvzMF7W5RU2zt00r2ljA7A7KpxwRE9CRYYf1kj_JyZmeo5kOLLEAZKv5EwSM4JN656lJMh8tubqrsg_ImiMfBocKfm7sxdZ0dYJWhw7Fn_sO97vsowIACQ/s400/S6_14.png" width="400" /></a></div><br /><br />
Cuando ingresemos un usuario y una contraseña debemos ser reenviados al menú:<br /><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjK12fwCrXW3Mvl6PK9UKqNGTKfF7mHRgInHEevYkQp1_sXrdHTTtf3ggAjaHUhf-QmvVAg2ItY1tQT8u8j94es-6_CMa3hSAcczvZG-L6GP6tfHe9rm6cydQvsROIstDvj1d8_TcPzl3eF/s1600/S6_15.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="111" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjK12fwCrXW3Mvl6PK9UKqNGTKfF7mHRgInHEevYkQp1_sXrdHTTtf3ggAjaHUhf-QmvVAg2ItY1tQT8u8j94es-6_CMa3hSAcczvZG-L6GP6tfHe9rm6cydQvsROIstDvj1d8_TcPzl3eF/s400/S6_15.png" width="400" /></a></div><br /><br />
Van dos de dos <span class="codigo">^_^!</span>. Si ahora volvemos a entrar a la liga "<span class="codigo">Action Protegido</span>" debemos ver finalmente el mensaje que hemos estado esperando:<br /><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj-1PhxmAYds_UDuCHFferaF3rb2n2nAJ3-E2yUXsNNAbG1mppL24SPqhWkjsJgyFNdgV31WMARGVgofkE1QOi6ws_9JcvqKm44M5SYYX5sqLXL9glNUVbA4udovi3Zy4xEappVUwyUasgU/s1600/S6_16.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="111" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj-1PhxmAYds_UDuCHFferaF3rb2n2nAJ3-E2yUXsNNAbG1mppL24SPqhWkjsJgyFNdgV31WMARGVgofkE1QOi6ws_9JcvqKm44M5SYYX5sqLXL9glNUVbA4udovi3Zy4xEappVUwyUasgU/s400/S6_16.png" width="400" /></a></div><br /><br />
Con esto comprobamos que el interceptor está funcionando de forma adecuada (también el <span class="codigo">Action</span> para la carga de las imágenes está funcionando correctamente bajo este esquema) <span class="codigo">^_^</span>.<br /><br />
Quiero que noten una cosa: con esto hemos protegido los <span class="codigo">Action</span>s, sin embargo las páginas <span class="codigo">JSP</span>s aún siguen siendo accesibles de forma libre. Para evitar esto existen varias alternativas, de hecho algunos frameworks para plantillas como <span class="codigo"><a href="http://tiles.apache.org/">tiles</a></span> o <span class="codigo"><a href="http://wiki.sitemesh.org/display/sitemesh/Home">sitemesh</a></span> son muy útiles para estos casos. Pero si no usamos un framework de plantillas aún podemos proteger nuestras <span class="codigo">JSP</span>s de varias formas, sólo las listaré y no entraré en detalles <span class="codigo">^_^</span>:<br /><br />
<ul><li>Hacer que el filtro de <span class="codigo">Struts 2</span> también procese las peticiones que lleguen con extensión "<span class="codigo">.jsp</span>" (recuerden que por default procesa las peticiones que llegan con "<span class="codigo">.action</span>" o que llegan sin extensión) e indicar en el filtro, además de los <span class="codigo">Action</span>s públicos, las páginas que también serán accesibles libremente.</li>
<li>Colocar los <span class="codigo">JSP</span>s dentro del directorio <span class="codigo">WEB-INF</span>, el cual no es regresado por el servidor. De esta forma ninguna página puede ser accedida por el navegador, esto significa que para ver las <span class="codigo">JSP</span>s que deben ser públicas deberemos colocar "<span class="codigo"><actions></span>" vacios (sin atributo "<span class="codigo">class</span>") que en su <span class="codigo">result</span> "<span class="codigo">success</span>" regresen esta página. También se pueden dejar las <span class="codigo">JSP</span>s públicas fuera del directorio <span class="codigo">WEB-INF</span> y las protegidas dentro.</li>
<li>Colocar una validación en cada una de las <span class="codigo">JSP</span>s (por medio de tags) para que ellas deciden si deben mostrarse o no. También pueden ser colocadas en una página que sirva como encabezado a todas las páginas del sitio, e incluirlas usando la directiva apropiada, así sólo debemos colocar en un lugar este código.</li>
</ul>Cada una de estas alternativas tiene ventajas y desventajas, así que elijan la que más se ajuste a sus necesidades.<br /><br />
Con esto hemos terminado con este tutorial, ahora saben todo lo referente a Interceptores en <span class="codigo">Struts 2 :)</span>. Este iba a ser el último tutorial de la serie, sin embargo algunas personas han pedido ampliarlo para tocar un tema más... y como me gusta mucho hacer estos tutoriales, hablaremos de un segundo tema, así que en el siguiente tutorial aprenderemos cómo usar todas (si, leyeron bien: <span class="negritas">todas</span>) las etiquetas de <span class="codigo">Struts 2</span>. <br /><br />
No olviden dejar sus dudas comentarios y sugerencias en la sección de comentarios, o enviar un correo a: <a href="mailto:programadorjavablog@gmail.com">programadorjavablog@gmail.com</a> (pueden agregarme al chat de gmail). También pueden seguir JavaTutoriales en las siguientes redes sociales:<br /><br />
<ul><li><a href="http://www.facebook.com/JavaTutoriales"><span class="codigo">Facebook</span></a></li>
<li><a href="https://plus.google.com/u/0/114078943018704761597"><span class="codigo">Google+</span></a></li>
<li><a href="https://twitter.com/#!/JavaTutoriales"><span class="codigo">Twitter</span></a></li>
</ul><br /><br />
<span class="negritas">Descarga los archivos de este tutorial desde aquí:</span><br /><br />
<ul><li><a href="https://sites.google.com/site/javatutoriales/struts2/Struts2Interceptores.zip"><span class="ligaArchivo">Interceptores</span></a></li>
</ul><br /><br />
<span class="negritas">Entradas Relacionadas:</span><br /><br />
<ul><li><a href="http://www.javatutoriales.com/2011/06/struts-2-parte-1-configuracion.html">Parte 1: Configuración</a></li>
<li><a href="http://www.javatutoriales.com/2011/06/struts-2-parte-2-ognl.html">Parte 2: OGNL</a></li>
<li><a href="http://www.javatutoriales.com/2011/10/struts-2-parte-3-trabajo-con.html">Parte 3: Trabajo con Formularios</a></li>
<li><a href="http://www.javatutoriales.com/2011/12/struts-2-parte-4-scopes-de-objetos-web.html">Parte 4: Scopes de Objetos Web</a></li>
<li><a href="http://www.javatutoriales.com/2012/04/struts-2-parte-5-tipos-de-results.html">Parte 5: Tipos de Results</a></li>
</ul></div>Alexhttp://www.blogger.com/profile/06974037481671868076noreply@blogger.comtag:blogger.com,1999:blog-2012232069289957092.post-87319866491178867392012-04-07T10:05:00.001-07:002012-06-29T07:40:44.910-07:00Struts 2 - Parte 5: Tipos de Results<div style="text-align: justify;">Cada vez que un <span class="codigo">Action</span> termina su ejecución, se muestra un resultado al usuario. Estos resultados pueden ser de muchos tipos y tener muchos significados. El tipo más común de resultado es mostrar al usuario una nueva página web cierta información, pero ¿si quisiéramos hacer otra cosa? ¿Qué ocurre si queremos regresar un archivo como texto plano? ¿Y cuando queremos regresar un archivo binario? ¿Y su necesitamos redirigir la petición a otro <span class="codigo">Action</span> o enviar al usuario a otra página?<br />
<br />
Los casos anteriores también son muy comunes cuando desarrollamos una aplicación web.<br />
<br />
<span class="codigo">Struts 2</span> ofrece una gran variedad de tipos de <span class="codigo">Results</span> para manejar estas y otras situaciones y que aprenderemos cómo usar en este tutorial.<br />
<br />
<a name='more'></a>Primero lo primero, aunque los hemos estado usando a lo largo de esta serie de tutoriales, nunca hemos definido de una forma clara qué es un <span class="codigo">Result</span>. Cuando el método "<span class="codigo">execute</span>" de un <span class="codigo">Action</span> termina de ejecutarse este siempre regresa un objeto de tipo <span class="codigo">String</span>. El valor de ese <span class="codigo">String</span> se usa para seleccionar un elemento de tipo "<span class="codigo"><result></span>", si trabajamos con archivos de configuración en <span class="codigo">XML</span>, o "<span class="codigo">@Result</span>", si trabajamos con anotaciones. Dentro del elemento se indica qué tipo de <span class="codigo">Result</span> se quiere regresar al usuario (enviar un recurso, redirigirlo a otro <span class="codigo">Action</span>, un archivo de texto plano, etc.), el recurso que se quiere regresar, algunos parámetros adicionales que requiera el <span class="codigo">Result</span> y, lo más importante, <span class="negritas">el nombre</span> que tendrá este <span class="codigo">Result</span>, en donde este nombre es precisamente lo que se indica con la cadena que es regresada de la ejecución del método "<span class="codigo">execute</span>".<br />
<br />
Cada result tiene un nombre que <span class="negritas">debe ser único</span> para un <span class="codigo">Action</span> y es en base a este nombre que <span class="codigo">Struts 2</span> sabrá qué es lo que debe mostrarle al usuario. La interface "<span class="codigo">Action</span>", que es implementada por la clase "<span class="codigo">ActionSupport</span>", que hemos estado utilizando como base para nuestros <span class="codigo">Actions</span>, proporciona un conjunto de constantes que contienen los nombres de los results más comunes para que podamos usarlos dentro de nuestros <span class="codigo">Actions</span>. El identificador de la constante es el mismo que el valor que contiene (usado las respectivas convenciones) para que sea fácil identificar cuándo debemos usar cada uno:<br />
<br />
<pre><code>
String SUCCESS = "success";
String NONE = "none";
String ERROR = "error";
String INPUT = "input";
String LOGIN = "login";
</code></pre><br />
<br />
Claro que no es obligatorio que regresemos alguna de estas contantes en el método "<span class="codigo">execute</span>", podemos usar cualquier cadena que queramos, siempre y cuando exista un "<span class="codigo"><result></span>" con ese nombre para nuestro <span class="codigo">Action</span>. Por ejemplo en caso de algún valor incorrecto introducido por el usuario podríamos regresar la cadena "<span class="codigo">fail</span>" <span class="codigo">^^</span>; o en caso de que dependiendo del perfil que tenga el usuario que ingresa a la aplicación podríamos redirigirlo a una página apropiada para ese perfil regresando el nombre del perfil, por ejemplo "<span class="codigo">operador</span>", "<span class="codigo">administrador</span>", "<span class="codigo">auditor</span>", etc.<br />
<br />
Hasta ahora solo hemos utilizado los valores "<span class="codigo">SUCCESS</span>" e "<span class="codigo">INPUT</span>" (cuando trabajamos con formularios) en nuestros results, pero el valor de cada una de las constantes anteriores representa una situación que puede ocurrir durante la ejecución de nuestro <span class="codigo">Action</span>:<br />
<br />
<ul><li>"<span class="codigo">success</span>" - Indica que todo ha salido correctamente (validaciones, y el proceso de lógica en general) durante la ejecución del método, por lo que el usuario puede ver la salida esperada.</li>
<li>"<span class="codigo">error</span>" - Indica que alguna cosa dentro del método ha salido mal. Puede ser que algún cálculo no pueda realizarse, o que algún servicio externo que estemos utilizando no esté disponible en ese momento o haya lanzado alguna excepción, o cualquier otra cosa que se les ocurra que pueda salir mal.</li>
<li>"<span class="codigo">input</span>" - Indica que algún campo proporcionado en un formulario es incorrecto. Puede ser que el valor no sea del tipo esperado, o no cumpla con el formato o rango requerido, o cosas más sofisticadas como que un valor esté repetido (digamos que alguien está tratando de usar un nombre de usuario que ya está siendo usado por otra persona).</li>
<li>"<span class="codigo">login</span>" - Indica que el recurso al que el usuario está intentado acceder solo está disponible para usuarios registrados del sitio y por lo tanto debe loguearse primero. Para poder usar este <span class="codigo">result</span> de forma correcta debemos crear un result global en la configuración del sitio. Veremos cómo hacer esto al final del tutorial.</li>
<li>"<span class="codigo">none</span>" - Este es un <span class="codigo">result</span> de un tipo especial ya que le indica a <span class="codigo">Struts 2</span> que no debe enviar al usuario a ningún lugar (o en términos formales que el procesamiento del resultado sea cancelado). Esto es usado cuando el <span class="codigo">Action</span> maneja el proceso de regresar el resultado al usuario usando, por ejemplo, de forma directa los objetos "<span class="codigo">HttpServletResponse</span>" o "<span class="codigo">ServletOutputStream</span>".</li>
</ul><br />
<br />
Como podrán imaginar un <span class="codigo">Action</span> puede tener más de un <span class="codigo">result</span> y estos pueden ser de distintos tipos, uno para cada situación que necesite manejar (siempre y cuando cada uno tenga un nombre único para ese <span class="codigo">Action</span>).<br />
<br />
<span class="codigo">Struts 2</span> maneja por default <span class="negritas">11 tipos de <span class="codigo">results</span></span>, aunque los plugins (como por ejemplo el de jasperreports) pueden definir sus propios tipos. Los results que por default maneja <span class="codigo">Struts 2</span> son:<br />
<br />
<ul><li>"<span class="codigo">dispatcher</span>" - Este es el result más usado y el default. Envía como resultado una nueva vista, usalmente una <span class="codigo">jsp</span>.</li>
<li>"<span class="codigo">redirect</span>" - Le indica al navegador que debe redirigirse a una nueva página, que puede estar en nuestra misma aplicación o en algún sitio externo, y por lo tanto este creará una nueva petición para ese recurso.</li>
<li>"<span class="codigo">redirectAction</span>" - Redirige la petición a otro <span class="codigo">Action</span> de nuestra aplicación. En este caso se crea una nueva petición hacia el nuevo <span class="codigo">Action</span>.</li>
<li>"<span class="codigo">chain</span>" - Al terminarse la ejecución del <span class="codigo">Action</span> se invoca otro <span class="codigo">Action</span>. En este caso se usa la misma petición para el segundo <span class="codigo">Action</span>, el cual se ejecuta de forma completa, con todo su stack de interceptores y sus <span class="codigo">results</span> (podemos crear cadenas de cuantos <span class="codigo">Actions</span> queramos).</li>
<li>"<span class="codigo">stream</span>" - Permite enviar un archivo binario de vuelta al usuario.</li>
<li>"<span class="codigo">plaintext</span>" - Envía el contenido del recurso que indiquemos como un texto plano. Típicamente se usa cuando necesitamos mostrar una <span class="codigo">JSP</span> o <span class="codigo">HTML</span> sin procesar</li>
<li>"<span class="codigo">httpheader</span>" - Permite establecer el valor de la cabecera <span class="codigo">HTTP</span> de código de estatus que se regresará al cliente. Así por ejemplo podemos usarlo para enviar un error al cliente (estatus <span class="codigo">500</span>), un recurso no encontrado (<span class="codigo">404</span>), un recurso al que no tiene acceso (<span class="codigo">401</span>), o por el que requiere pagar (<span class="codigo">402</span>).</li>
<li>"<span class="codigo">xslt</span>" - Se usa cuando el resultado generará un <span class="codigo">XML</span> será procesado por una hoja de transformación de estilos para generar la vista que se mostrará al cliente.</li>
<li>"<span class="codigo">freemarker</span>" - Para integración con <span class="codigo">FreeMarker</span></li>
<li>"<span class="codigo">velocity</span>" - Para integración con <span class="codigo">Velocity</span></li>
<li>"<span class="codigo">tiles</span>" - Para integración con <span class="codigo">Tiles</span></li>
</ul><br />
<br />
En este tutorial veremos cómo funcionan los <span class="negritas">primeros 7</span> tipos de <span class="codigo">results</span> (desde "<span class="codigo">dispatcher</span>" hasta "<span class="codigo">httpheader</span>").<br />
<br />
Suficiente teoría, comencemos con la práctica. Comencemos creando un nuevo proyecto web en <span class="codigo">NetBeans</span>, vamos al Menú "<span class="codigo">File->New Project...</span>". En la ventana que se abre seleccionamos la categoría "<span class="codigo">Java Web</span>" y en el tipo de proyecto "<span class="codigo">Web Application</span>". Le damos una ubicación y un nombre al proyecto, en mi caso será "<span class="codigo">Struts2Results</span>". Presionamos el botón "<span class="codigo">Next</span>". En la siguiente pantalla deberemos configurar el servidor de aplicaciones que usaremos. Yo usaré la versión 7 de <span class="codigo"><a href="http://tomcat.apache.org/">Tomcat</a></span>. Como versión de <span class="codigo">Java EE</span> usaré la 5 (pueden usar también la 6 si quieren, solo que en ese caso <span class="codigo">NetBeans</span> no genera el archivo "<span class="codigo">web.xml</span>" por default, y tendrán que crearlo a mano o usando el wizard correspondiente). De la misma forma, si quieren pueden modificar el <span class="codigo">context path</span> de la aplicación. Presionamos el botón "<span class="codigo">Finish</span>", con lo que veremos aparecer la página "<span class="codigo">index.jsp</span>" en nuestro editor:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEivbfivebr4pnSx-ZJXQ0BPAyMQ4nkMg7D2qPhQtF3G0pW4y3CsokYq4Lg9KxSrPNB6vF3dpjpJznwPNjvZeOnq8WtSKy9rb1dAOm4QrXXrv04nv0ZtDapx1zKo-wIg4rEuQbGERBtPfGtX/s1600/S5_1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="253" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEivbfivebr4pnSx-ZJXQ0BPAyMQ4nkMg7D2qPhQtF3G0pW4y3CsokYq4Lg9KxSrPNB6vF3dpjpJznwPNjvZeOnq8WtSKy9rb1dAOm4QrXXrv04nv0ZtDapx1zKo-wIg4rEuQbGERBtPfGtX/s400/S5_1.png" width="400" /></a></div><br />
<br />
Una vez creado nuestro proyecto debemos agregar la biblioteca "<span class="codigo">Struts2</span>" que creamos en <a href="http://www.javatutoriales.com/2011/06/struts-2-parte-1-configuracion.html">el primer tutorial de la serie</a>. Para esto hacemos clic derecho en el nodo "<span class="codigo">Libraries</span>" del panel de proyectos. En el menú que aparece seleccionamos la opción "<span class="codigo">Add Library...</span>". En la ventana que aparece seleccionamos la biblioteca "<span class="codigo">Struts2</span>" y presionamos "<span class="codigo">Add Library</span>". Con esto ya tendremos los jars de <span class="codigo">Struts 2</span> en nuestro proyecto.<br />
<br />
A continuación configuramos el filtro "<span class="codigo">struts2</span>" en el <span class="codigo">deployment descriptor</span>. Abrimos el archivo "<span class="codigo">web.xml</span>" y colocamos el siguiente contenido, como se explicó en <a href="http://www.javatutoriales.com/2011/06/struts-2-parte-1-configuracion.html">el primer tutorial de la serie</a>:<br />
<br />
<pre><code>
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</code></pre><br />
<br />
Ahora crearemos un nuevo archivo llamado "<span class="codigo">struts.xml</span>" en el paquete default de código fuente de nuestra aplicación:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjUS2OpQQz2pSoOqm26RoxYgKPX6m2ufU5o5ooIMtKbROWAZX3MJRLwLeiZf-7QUQyZdgBPUVe1FGbaZLYYpExS6lBU56O45-mvesHf4z4Js19sVBiKmd7GqomrauQdnI6D_e6m0nNXwLH7/s1600/S5_2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjUS2OpQQz2pSoOqm26RoxYgKPX6m2ufU5o5ooIMtKbROWAZX3MJRLwLeiZf-7QUQyZdgBPUVe1FGbaZLYYpExS6lBU56O45-mvesHf4z4Js19sVBiKmd7GqomrauQdnI6D_e6m0nNXwLH7/s1600/S5_2.png" /></a></div><br />
<br />
Este archivo contendrá la configuración de <span class="codigo">Struts 2</span> de nuestra aplicación. Colocaremos la configuración inicial, con un par de contantes para indicar que nos encontramos en modo de desarrollo y que cualquier cambio que hagamos en el archivo de configuración debe recargarse de inmediato, y un paquete inicial:<br />
<br />
<pre><code>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<constant name="struts.devMode" value="true" />
<constant name="struts.configuration.xml.reload" value="true" />
<package name="struts-results" extends="struts-default">
<action name="index">
<result>/index.jsp</result>
</action>
</package>
</struts>
</code></pre><br />
<br />
Creamos un nuevo paquete en el nodo "<span class="codigo">Source Packages</span>" llamado "<span class="codigo">com.javatutoriales.results.actions</span>" que será donde colocaremos las clases <span class="codigo">Action</span> de nuestra aplicación.<br />
<br />
Ya con la configuración inicial comencemos viendo el primer tipo de <span class="codigo">result</span>:<br />
<br />
<br />
<h2 class="titulo"><span class="codigo">Result</span> "<span class="codigo">dispatcher</span>"</h2>Lo primero que haremos será crear un directorio llamado "<span class="codigo">dispatcher</span>" en el nodo de "<span class="codigo">Web Pages</span>" del proyecto:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiKDixBe9o7hQ49es0-07tf3yZdRn48pH3-q6rmUmSh8vzPT6lLOCL1A1l_Bisr-7UzfbkQHpHXKTU-EiV-4oZ-_jViaMAiHhDq2nSUuAlI9mQ1koPUKHDvV3YkusrItmmqW4LLPKsUBBek/s1600/S5_3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiKDixBe9o7hQ49es0-07tf3yZdRn48pH3-q6rmUmSh8vzPT6lLOCL1A1l_Bisr-7UzfbkQHpHXKTU-EiV-4oZ-_jViaMAiHhDq2nSUuAlI9mQ1koPUKHDvV3YkusrItmmqW4LLPKsUBBek/s1600/S5_3.png" /></a></div><br />
<br />
En este nuevo directorio crearemos una nueva <span class="codigo">JSP</span> llamada "<span class="codigo">index.jsp</span>" que contendrá un sencillo formulario con solo dos campos, un campo de texto para ingresar nuestro <span class="codigo">nombre</span>, y un combo-box, lista desplegable, select o cómo quiere que llamen, para seleccionar su <span class="codigo">lenguaje</span> de programación favorito <span class="codigo">;)</span>. Este formulario será procesado por un <span class="codigo">Action</span> llamado "<span class="codigo">dispatcher</span>". No olviden que debemos indicar que haremos uso de las etiquetas de <span class="codigo">Struts 2</span> con el taglib correspondiente:<br />
<br />
<pre><code>
<%@taglib prefix="s" uri="/struts-tags" %>
</code></pre><br />
<br />
El formulario queda de la siguiente forma:<br />
<br />
<pre><code>
<s:form action="dispatcher">
<s:textfield name="nombre" label="Nombre" />
<s:select name="lenguaje" label="Lenguaje de Programacion" list="{'Java', 'PHP', '.Net'}" />
<s:submit value="Enviar" />
</s:form>
</code></pre><br />
<br />
Ahora crearemos, en nuestro paquete "<span class="codigo">actions</span>" una nueva clase <span class="codigo">Java</span> que extenderá de "<span class="codigo">ActionSupport</span>", el nombre de esta clase será "<span class="codigo">DispatcherResultAction</span>":<br />
<br />
<pre><code>
public class DispatcherResultAction extends ActionSupport
{
}
</code></pre><br />
<br />
En esta clase colocaremos un par de atributos de tipo <span class="codigo">String</span>, con sus <span class="codigo">setters</span> y <span class="codigo">getters</span>, para recibir los parámetros del formulario que acabamos de crear:<br />
<br />
<pre><code>
private String nombre;
private String lenguaje;
public String getLenguaje()
{
return lenguaje;
}
public void setLenguaje(String lenguaje)
{
this.lenguaje = lenguaje;
}
public String getNombre()
{
return nombre;
}
public void setNombre(String nombre)
{
this.nombre = nombre;
}
</code></pre><br />
<br />
Ahora sobre-escribiremos el método "<span class="codigo">execute</span>" para realizar una simple validación: en el caso de que el lenguaje de programación seleccionado por el usuario no sea <span class="codigo">Java</span>, vamos a enviar al usuario a una página de error <span class="codigo">^_^</span>:<br />
<br />
<pre><code>
@Override
public String execute() throws Exception
{
if(!"Java".equals(lenguaje))
{
return ERROR;
}
return SUCCESS;
}
</code></pre><br />
<br />
Como podemos ver en el código anterior, estamos haciendo uso de la constantes definidas en el interface "<span class="codigo"><span class="codigo">Action</span></span>", la cual es implementada por la clase "<span class="codigo">ActionSupport</span>" para hacer referencia a los nombres de los <span class="codigo">results</span>.<br />
<br />
Ahora crearemos dos nuevas <span class="codigo">JSP</span>s en el directorio "<span class="codigo">dispatcher</span>" una para mostrar el resultado en caso de que todo haya salido bien y una para mostrar el mensaje de error. La primer <span class="codigo">JSP</span> que crearemos se llamará "<span class="codigo">exito.jsp</span>" y como pueden imaginarse es la que mostrará los resultados en caso de que todo sea correcto.<br />
<br />
En esta página primero indicamos que haremos uso de las etiquetas de <span class="codigo">Struts 2</span>, con el mismo taglib que usamos hace un momento, además colocaremos como único contenido el siguiente mensaje:<br />
<br />
<pre><code>
Bienvenido <s:property value="nombre" /> al mundo de la programación Java.
</code></pre><br />
<br />
Donde estamos usando la etiqueta "<span class="codigo"><s:property></span>" para mostrar el nombre del usuario que ha quedado almacenado en la propiedad "<span class="codigo">nombre</span>" de nuestro <span class="codigo">Action</span>, y que a su vez se encuentra en el "<span class="codigo">ValueStack</span>" (pueden encontrar una explicación más clara en <a href="http://www.javatutoriales.com/2011/06/struts-2-parte-2-ognl.html">el segundo tutorial de la serie</a>).<br />
<br />
Ahora vamos a la página "<span class="codigo">error.jsp</span>", en ella también indicaremos que haremos uso de la biblioteca de etiquetas de <span class="codigo">Struts 2</span>, y colocaremos el siguiente mensaje:<br />
<br />
<pre><code>
Lo siento <s:property value="name" />, este es un sitio exclusivo para programadores Java, pide perdón y retírate u_u
</code></pre><br />
<br />
Así de directo <span class="codigo">^_^</span><br />
<br />
Ahora que tenemos nuestras dos páginas vamos a configurar ambas en los <span class="codigo">results</span> para nuestro <span class="codigo">Action</span>, lo haremos con archivos de mapeo en <span class="codigo">XML</span>, para los que trabajen con anotaciones, las opciones son las mismas.<br />
<br />
En el archivo "<span class="codigo">struts.xml</span>" agregamos un elemento "<span class="codigo"><action></span>" que tendrá como nombre "<span class="codigo">dispatcher</span>" y que esté implementado por la clase "<span class="codigo">com.javatutoriales.results.actions. DispatcherResultAction</span>":<br />
<br />
<pre><code>
<action name="dispatcher" class="com.javatutoriales.results.actions.DispatcherResultAction">
</action>
</code></pre><br />
<br />
Dentro de este elemento "<span class="codigo"><action></span>" definiéremos dos elementos "<span class="codigo">result</span>" de tipo "<span class="codigo">dispatcher</span>", uno para cada el resultado "<span class="codigo">success</span>" y otro para el "<span class="codigo">error</span>":<br />
<br />
<pre><code>
<result name="success" type="dispatcher">
</result>
<result name="error" type="dispatcher">
</result>
</code></pre><br />
<br />
Cada uno de los tipos de results pueden recibir varios parámetros. Estos parámetros son indicados usando el elemento "<span class="codigo"><param></span>", para cuál parámetro queremos establecer se usa el atributo "<span class="codigo">name</span>" de este elemento. El valor del parámetro se coloca como contenido de este elemento. Por ejemplo para establecer un parámetro llamado, por ejemplo, "<span class="codigo">tiempo</span>" a "<span class="codigo">15</span>" segundos lo haríamos de la siguiente forma:<br />
<br />
<pre><code>
<param name="tiempo">15</param>
</code></pre><br />
<br />
En el caso de los results de tipo "<span class="codigo">dispatcher</span>" pueden recibir dos parámetros: "<span class="codigo">location</span>" y "<span class="codigo">parse</span>". "<span class="codigo">location</span>" indica dónde se encuentra el recurso que se enviará al usuario cuando se termine la ejecución del <span class="codigo">Action</span> (usualmente una <span class="codigo">JSP</span>). No se preocupen por el otro atributo porque casi no se usa (aún la explicación de lo que hace es un poco complicada ^_^).<br />
<br />
Así que indicaremos, usando el parámetro "<span class="codigo">location</span>" cuáles son las <span class="codigo">JSP</span>s que deben regresar en cada caso:<br />
<br />
<pre><code>
<result name="success" type="dispatcher">
<param name="location">/dispatcher/exito.jsp</param>
</result>
<result name="error" type="dispatcher">
<param name="location">/dispatcher/error.jsp</param>
</result>
</code></pre><br />
<br />
Si ahora ejecutamos nuestra aplicación y entramos en la siguiente dirección:<br />
<br />
<pre><code>
<a href="http://localhost:8080/results/dispatcher/index.jsp">http://localhost:8080/results/dispatcher/index.jsp</a>
</code></pre><br />
<br />
Debemos ver el siguiente formulario:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjlv9cFNztooBJQUgqnP4C2lqRz6iaQ6chgAnD-Q8qvfjs70munxxgzuoxOeo9-AvYq7dZNEwf-Z23qt4SdnNWXE3lt8uS_jnG0hvTCkgzoVH67tbehwTBVQQVNZB_-pgHik6iTKv_k6DCx/s1600/S5_4.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="180" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjlv9cFNztooBJQUgqnP4C2lqRz6iaQ6chgAnD-Q8qvfjs70munxxgzuoxOeo9-AvYq7dZNEwf-Z23qt4SdnNWXE3lt8uS_jnG0hvTCkgzoVH67tbehwTBVQQVNZB_-pgHik6iTKv_k6DCx/s400/S5_4.png" width="400" /></a></div><br />
<br />
Al colocar nuestro nombre, seleccionar "<span class="codigo">Java</span>" como nuestro lenguaje de programación y enviar nuestro formulario, veremos la siguiente salida:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiGOmxzYjn4kfT0DDmfn0nBTEfuFFuS_yov1ryM3oOPeJMrb7CMFJ05mC0NS99mVpDINFjRCtNTc0bXiemMVkMp5Hu9LvQD-1g0MmIidGgxi1zHjT8nGcF_5BsarAM-roMncwQKmzsYdhOg/s1600/S5_5.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="180" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiGOmxzYjn4kfT0DDmfn0nBTEfuFFuS_yov1ryM3oOPeJMrb7CMFJ05mC0NS99mVpDINFjRCtNTc0bXiemMVkMp5Hu9LvQD-1g0MmIidGgxi1zHjT8nGcF_5BsarAM-roMncwQKmzsYdhOg/s400/S5_5.png" width="400" /></a></div><br />
<br />
Si seleccionamos otro lenguaje de programación veremos la siguiente salida:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEia_ww7YCqwKq50evGkEOsri3jhDQ6KAZSe3EhG1hPqh-SOyeqDeypK5yizIFMex0s0fhvtAYegnSnFWJ6lxzqiJKBHQgjs7ztvGqWt7LFKf7_7B1BDz2s0nEV8uJUQJmG1ggIQePQHWXa6/s1600/S5_6.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="165" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEia_ww7YCqwKq50evGkEOsri3jhDQ6KAZSe3EhG1hPqh-SOyeqDeypK5yizIFMex0s0fhvtAYegnSnFWJ6lxzqiJKBHQgjs7ztvGqWt7LFKf7_7B1BDz2s0nEV8uJUQJmG1ggIQePQHWXa6/s400/S5_6.png" width="400" /></a></div><br />
<br />
Como podemos ver, la validación se realiza de forma correcta y nos envía al result adecuado en cada uno de los casos que seleccionamos.<br />
<br />
Ahora regresemos a la configuración de los results para explicar algunas cosas interesantes.<br />
<br />
En <a href="http://www.javatutoriales.com/2011/06/struts-2-parte-1-configuracion.html">el primer tutorial de la serie</a> dijimos que una de las ventajas que nos da <span class="codigo">Struts 2</span> es que nos proporciona muchos valores por default que son bastante útiles. El caso de los <span class="codigo">Action</span>s es un ejemplo perfecto de los valores por default. Si regresamos a la definición básica de los results en <span class="codigo">XML</span> veremos que tenemos lo siguiente:<br />
<br />
<pre><code>
<result name="success" type="dispatcher">
<param name="location">/dispatcher/exito.jsp</param>
</result>
<result name="error" type="dispatcher">
<param name="location">/dispatcher/error.jsp</param>
</result>
</code></pre><br />
<br />
Podemos ver que en realidad la configuración es muy corta y muy sencilla; pero imaginen que en vez de dos results, necesitáramos 10, y lo mismo para cada uno de los <span class="codigo">Action</span>s de nuestra aplicación... ya no suena tan bien ¿cierto?<br />
<br />
Bueno, el enunciado anterior puede ser un poco exagerado, pero sirve para ilustrar el deseo de querer simplificar un poco las cosas.<br />
<br />
Lo primero que hay que saber es que cada uno de los tipos de <span class="codigo">result</span>s tienen siempre un parámetro por default. Si solo vamos a utilizar un parámetro en nuestro <span class="codigo">result</span>, y ese parámetro es el parámetro por default entonces podemos omitir el elemento "<span class="codigo"><param></span>" y colocar directamente el valor dentro del elemento "<span class="codigo"><result></span>". En el caso del <span class="codigo">result</span> de tipo "<span class="codigo">dispatcher</span>" el parámetro por default es "<span class="codigo">location</span>", por lo que podemos simplificar los dos elementos anteriores de la siguiente forma:<br />
<br />
<pre><code>
<result name="success" type="dispatcher">/dispatcher/exito.jsp</result>
<result name="error" type="dispatcher">/dispatcher/error.jsp</result>
</code></pre><br />
<br />
Podemos ver que con esto la configuración ya ha quedado mucho más sencilla.<br />
<br />
Otro valor por default útil que tenemos es el caso del atributo "<span class="codigo">type</span>" del elemento "<span class="codigo"><result></span>". Por default el tipo de <span class="codigo">result</span> es "<span class="codigo">dispatcher</span>", por lo que en las definiciones anteriores podemos omitir este atributo. Esto es útil ya que en nuestras aplicaciones, en la mayoría de los casos nuestros <span class="codigo">result</span>s serán de tipo "<span class="codigo">dispatcher</span>". Por lo tanto las definiciones anteriores quedan de la siguiente forma:<br />
<br />
<pre><code>
<result name="success">/dispatcher/exito.jsp</result>
<result name="error">/dispatcher/error.jsp</result>
</code></pre><br />
<br />
Podemos ver que cada vez queda más corta la declaración de nuestros <span class="codigo">result</span>s ^_^.<br />
<br />
Para terminar eliminaremos un valor más. También el atributo "<span class="codigo">name</span>" tiene un valor por default: "<span class="codigo">success</span>". Esto quiere decir que si vamos a definir el <span class="codigo">result</span> para el caso "<span class="codigo">success</span>", podemos poner un <span class="codigo">result</span> sin nombre (solo uno por <span class="codigo">Action</span>). Si nuestro <span class="codigo">result</span> tiene otro nombre, si debemos colocarlo <span class="negritas">de forma explícita</span>. Con este nuevo cambio la definición anterior queda de la siguiente forma:<br />
<br />
<pre><code>
<result>/dispatcher/exito.jsp</result>
<result name="error">/dispatcher/error.jsp</result>
</code></pre><br />
<br />
Como podemos ver, gracias a los valores por default proporcionados por <span class="codigo">Struts 2</span>, pasamos de esto:<br />
<br />
<pre><code>
<result name="success" type="dispatcher">
<param name="location">/dispatcher/exito.jsp</param>
</result>
<result name="error" type="dispatcher">
<param name="location">/dispatcher/error.jsp</param>
</result>
</code></pre><br />
<br />
A esto:<br />
<br />
<pre><code>
<result>/dispatcher/exito.jsp</result>
<result name="error">/dispatcher/error.jsp</result>
</code></pre><br />
<br />
Con lo que no solo nos ahorramos unas cuantas líneas de código, sino que las definiciones son más fáciles de leer.<br />
<br />
La definición del <span class="codigo">Action</span> queda de la siguiente forma:<br />
<br />
<pre><code>
<action name="dispatcher" class="com.javatutoriales.results.actions.DispatcherResultAction">
<result>/dispatcher/exito.jsp</result>
<result name="error">/dispatcher/error.jsp</result>
</action>
</code></pre><br />
<br />
Si volvemos a ejecutar nuestra aplicación debemos ver exactamente la misma salida. Podemos ver con esto que los valores por default proporcionados en todos los elementos de <span class="codigo">Struts 2</span> pueden ahorrarnos muchas líneas de código. En cada uno de los <span class="codigo">result</span>s les diré cuál es el parámetro por default.<br />
<br />
Una última cosa importante que hay que aprender desde el inicio acerca de los <span class="codigo">result</span>s es que algunos parámetros (por lo regular los más importantes) se pueden establecer de forma dinámica usando una expresión en <span class="codigo">OGNL</span>.<br />
<br />
Vemos un ejemplo para ver cómo podemos indicar cuál será la ubicación a la que debe enviarse al usuario en caso de un error. Para esto debemos indicar en el archivo "<span class="codigo">struts.xml</span>" que este parámetro será recibido usando una expresión, un parámetro llamado "<span class="codigo">ubicacion</span>":<br />
<br />
<pre><code>
<result name="error">${ubicacion}</result>
</code></pre><br />
<br />
Ahora modificaremos un poco nuestra clase "<span class="codigo">DispatcherResultAction</span>". Primero tendremos que agregar un <span class="codigo">getter</span> para una propiedad llamada "<span class="codigo">ubicacion</span>". <span class="codigo">Struts 2</span> usará este <span class="codigo">getter</span> cuando busque el valor para la expresión "<span class="codigo">${ubicacion}</span>". Este <span class="codigo">getter</span> debe regresar un <span class="codigo">String</span> que indicará, en este caso, la ubicación a la que se enviará al usuario. El valor que regrese el método puede ser una cadena estática o generada de forma dinámica. En este caso regresaremos la ubicación de manera estática:<br />
<br />
<pre><code>
public String getUbicacion()
{
return "/dispatcher/error.jsp";
}
</code></pre><br />
<br />
Al ejecutar nuevamente nuestra aplicación y seleccionemos un lenguaje que no sea <span class="codigo">Java</span>, deberemos volver a ver nuestra página de error normal.<br />
<br />
Casi todos los parámetros de los <span class="codigo">result</span>s puedes ser establecidos de forma dinámica usando expresiones (con excepción de los que indican si los parámetros pueden aceptar expresiones ^^, los parámetros llamados "<span class="codigo">parse</span>"), esto nos da una gran ventaja para que nuestra aplicación sea realmente dinámica.<br />
<br />
Ahora veamos el siguiente tipo de <span class="codigo">result</span>:<br />
<br />
<br />
<h2 class="titulo"><span class="codigo">Result</span> "<span class="codigo">redirect</span>"</h2>Este <span class="codigo">result</span> le indica al navegador que debe dirigirse hacia otro recurso, de nuestra aplicación o algún lugar externo a nuestro sitio. Esto termina con la ejecución de la petición actual del cliente. Muy útil cuando queremos enviar al usuario a otro sitio como Google o algún otro servicio de nuestra empresa.<br />
<br />
<br />
Este <span class="codigo">result</span> tiene tres parámetros:<br />
<ul><li><span class="codigo">location</span> (default): indica la ubicación a donde irá el resultado (puede ser una página dentro de nuestro sitio o afuera de él).</li>
<li><span class="codigo">parse</span>: indica si el parámetro "<span class="codigo">location</span>" (el anterior) puede ser obtenido a través de una expresión en <span class="codigo">OGNL</span> , logrando que la ubicación se pueda establecer de forma dinámica. Por default es verdadero.</li>
<li><span class="codigo">anchor</span>: se puede especificar un ancla para el resultado (un ancla es un lugar en una página html al que se le ha dado un nombre especial, único en el documento y que nos permite enviar al navegador del usuario a ese punto específico). Esta parámetro es opcional. Sobre este parámetro, aunque en la documentación oficial no lo dice, parece que sólo funciona cuando redireccionamos a otra parte de nuestro mismo sitio.</li>
</ul><br />
<br />
Para ver un ejemplo de este <span class="codigo">result</span> en acción primero agregaremos un directorio llamado "<span class="codigo">redirect</span>" en el nodo "<span class="codigo">Web Pages</span>":<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEitTGoYORkP9dliXc_y2CpSJKhxXF6uyMm22GItWvPC9eer7FLYjWwJi_qde1bKQFnmFLlE_0X12udksISVktAa6-A3_oFH_-uE7XkX93hZYpiQ1YxHa9AW8GJ3J14zVD9sK1FI_-rNjTgM/s1600/S5_7.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEitTGoYORkP9dliXc_y2CpSJKhxXF6uyMm22GItWvPC9eer7FLYjWwJi_qde1bKQFnmFLlE_0X12udksISVktAa6-A3_oFH_-uE7XkX93hZYpiQ1YxHa9AW8GJ3J14zVD9sK1FI_-rNjTgM/s1600/S5_7.png" /></a></div><br />
<br />
Dentro de este directorio crearemos una nueva <span class="codigo">JSP</span> llamada "<span class="codigo">index</span>". Como en este caso lo único que interesa es llamar al <span class="codigo">Action</span> que regresará el <span class="codigo">result</span> de tipo "<span class="codigo">redirect</span>", que crearemos en un momento. Lo único que colocaremos en esta página será una liga, usando la etiqueta "<span class="codigo"><s:a></span>" para invocar directamente un <span class="codigo">Action</span> llamado "<span class="codigo">redirect</span>":<br />
<br />
<pre><code>
<s:a action="redirect">Redireccionar</s:a>
</code></pre><br />
<br />
Ahora crearemos, en el paquete "<span class="codigo">actions</span>" una nueva clase llamada "<span class="codigo">RedirectResultAction</span>" que extienda de "<span class="codigo">ActionSupport</span>":<br />
<br />
<pre><code>
public class RedirectResultAction extends ActionSupport
{
}
</code></pre><br />
<br />
Como en este caso no recibimos parámetros o algo por el estilo, lo único que necesitaremos hacer es sobre-escribir el método "<span class="codigo">excute</span>" del <span class="codigo">Action</span> para que regrese el valor "<span class="codigo">SUCCESS</span>":<br />
<br />
<pre><code>
@Override
public String execute() throws Exception
{
return SUCCESS;
}
</code></pre><br />
<br />
Lo dejaremos solamente así ya que no nos interesa, en este caso, realizar ningún tipo de procesamiento para la entrada.<br />
<br />
La clase "<span class="codigo">RedirectResultAction</span>" completa queda de la siguiente forma:<br />
<br />
<pre><code>
public class RedirectResultAction extends ActionSupport
{
@Override
public String execute() throws Exception
{
return SUCCESS;
}
}
</code></pre><br />
<br />
Ahora, en el archivo "<span class="codigo">struts.xml</span>" agregaremos la siguiente definición para nuestro <span class="codigo">Action</span>:<br />
<br />
<pre><code>
<action name="redirect" class="com.javatutoriales.results.actions.RedirectResultAction">
</action>
</code></pre><br />
<br />
Agregaremos un único <span class="codigo">result</span>, que será de tipo "<span class="codigo">redirect</span>". Como el parámetro por default para este <span class="codigo">result</span> es "<span class="codigo">location</span>" (que indica a dónde será redirigido el usuario), podemos indicarlo como valor del elemento "<span class="codigo"><result></span>"; en este caso redirigiremos al usuario a la primer página que creamos, el "<span class="codigo">index.jsp</span>" del directorio "<span class="codigo">dispatcher</span>":<br />
<br />
<pre><code>
<result type="redirect">/dispatcher/index.jsp</result>
</code></pre><br />
<br />
Recuerden que cuando no colocamos un nombre al <span class="codigo">result</span> por default el result se llama "<span class="codigo">success</span>". La configuración de nuestro <span class="codigo">Action</span> queda de la siguiente forma:<br />
<br />
<pre><code>
<action name="redirect" class="com.javatutoriales.results.actions.RedirectResultAction">
<result type="redirect">/dispatcher/index.jsp</result>
</action>
</code></pre><br />
<br />
Nuestro ejemplo ya está listo, así que ahora ejecutamos nuestra aplicación y entramos en la siguiente dirección:<br />
<br />
<pre><code>
<a href="http://localhost:8080/results/redirect/index.jsp">http://localhost:8080/results/redirect/index.jsp</a>
</code></pre><br />
<br />
Deberemos ver la siguiente página:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEigFyJBJ3RjWdp097QREty7p242HyHykssp4uM8CI0VmVYTyzr-Xu15rUNWy31D6s8I25LGuu2ZNwdRNoCqJvNpBX4-NXkPKAyERAV44D2Df70coicFsYuoGfAQSTKT3I02FIYP3tQ5fCSS/s1600/S5_8.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="180" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEigFyJBJ3RjWdp097QREty7p242HyHykssp4uM8CI0VmVYTyzr-Xu15rUNWy31D6s8I25LGuu2ZNwdRNoCqJvNpBX4-NXkPKAyERAV44D2Df70coicFsYuoGfAQSTKT3I02FIYP3tQ5fCSS/s400/S5_8.png" width="400" /></a></div>br /><br />
En donde lo único que hay es la liga que nos enviará al <span class="codigo">Action</span> "<span class="codigo">redirec</span>". Al hacer clic en esa liga seremos enviados al <span class="codigo">Action</span> correspondiente, el cual nos enviará a su vez a la página "<span class="codigo">/dispatcher/index.jsp</span>", por lo que terminaremos viendo el primer formulario:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgUFSSj-kc35lXYRcJiBJc7s25hGdNvD2i2uVms1AglKf9OMZtP8gxOlqPUoemhiEC8ANcviPvIZL4p_ulAYkHIsM8qoi2ezXaBhzt4Klla4pl_0aIDU5Z9Ygzx0eOI-QLce3Flthd5kxHv/s1600/S5_9.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="180" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgUFSSj-kc35lXYRcJiBJc7s25hGdNvD2i2uVms1AglKf9OMZtP8gxOlqPUoemhiEC8ANcviPvIZL4p_ulAYkHIsM8qoi2ezXaBhzt4Klla4pl_0aIDU5Z9Ygzx0eOI-QLce3Flthd5kxHv/s400/S5_9.png" width="400" /></a></div><br />
<br />
Ahora veremos cómo redireccionar a una página que no se encuentre en nuestro sitio. Para esto crearemos una nueva clase, en el paquete "<span class="codigo">actions</span>", llamada "<span class="codigo">RedirectExternoResultAction</span>" que, como podrán imaginarse, extenderá de "<span class="codigo">ActionSupport</span>":<br />
<br />
<pre><code>
public class RedirectExternoResultAction extends ActionSupport
{
}
</code></pre><br />
<br />
Como esta clase tampoco hará nada especial, solo sobre-escribiremos su método "<span class="codigo">execute</span>" para que regresa un valor de "<span class="codigo">success</span>", usando la constante apropiada:<br />
<br />
<pre><code>
public class RedirectExternoResultAction extends ActionSupport
{
@Override
public String execute() throws Exception
{
return SUCCESS;
}
}
</code></pre><br />
<br />
Ahora iremos al archivo de configuración "<span class="codigo">struts.xml</span>" y declaramos un nuevo elemento "<span class="codigo"><action></span>", cuyo nombre sea "<span class="codigo">redirect-externo</span>" y la clase que lo implemente sea nuestra clase "<span class="codigo">RedirectExternoResultAction</span>":<br />
<br />
<pre><code>
<action name="redirect-externo" class="com.javatutoriales.results.actions.RedirectExternoResultAction">
</action>
</code></pre><br />
<br />
A continuación agregaremos un elemento "<span class="codigo"><result></span>", de tipo "<span class="codigo">redirect</span>", en cuyo valor colocaremos cualquier página de un sitio externo, yo elegiré una al azar que será "<span class="codigo"><a href="http://www.javatutoriales.com/2011/10/struts-2-parte-3-trabajo-con.html">http://www.javatutoriales.com/2011/10/struts-2-parte-3-trabajo-con.html</a></span>", así que el result quedará definido de la siguiente forma:<br />
<br />
<pre><code>
<result type="redirect">http://www.javatutoriales.com/2011/10/struts-2-parte-3-trabajo-con.html</result>
</code></pre><br />
<br />
Y el mapeo de nuestro <span class="codigo">Action</span> de la siguiente forma:<br />
<br />
<pre><code>
<action name="redirect-externo" class="com.javatutoriales.results.actions.RedirectExternoResultAction">
<result type="redirect">http://www.javatutoriales.com/2011/10/struts-2-parte-3-trabajo-con.html</result>
</action>
</code></pre><br />
<br />
Agregaremos una liga a este <span class="codigo">Action</span> en la página "<span class="codigo">index.jsp</span>" del directorio "<span class="codigo">redirect</span>" para poder invocar a este <span class="codigo">Action</span>:<br />
<br />
<pre><code>
<s:a action="redirect-externo">Redireccionar Externo</s:a>
</code></pre><br />
<br />
Ahora ejecutamos la aplicación y entramos en la siguiente dirección:<br />
<br />
<pre><code>
<a href="http://localhost:8080/results/redirect/index.jsp">http://localhost:8080/results/redirect/index.jsp</a>
</code></pre><br />
<br />
En la que veremos los dos enlaces a los <span class="codigo">Action</span>s de redirección:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi2bZHJiqLMhk2vZOjMVoym4CwDP1Kx8O4xY-JOivQyKJTAxvCvMEyVFa_gO2Raxu3UeXtoYL1Gb9NiELmobtIVYMDsOalmzTsy_Dv9wVZYn6ojUfnd8nKeebjxMsgU6ZvcUM8ymSRV4pd2/s1600/S5_10.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="250" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi2bZHJiqLMhk2vZOjMVoym4CwDP1Kx8O4xY-JOivQyKJTAxvCvMEyVFa_gO2Raxu3UeXtoYL1Gb9NiELmobtIVYMDsOalmzTsy_Dv9wVZYn6ojUfnd8nKeebjxMsgU6ZvcUM8ymSRV4pd2/s640/S5_10.png" width="640" /></a></div><br />
<br />
Hacemos clic en el enlace que acabamos de agregar, y en ese momento deberemos ser re-dirigidos a la página que hayamos indicado:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgt4hkVr3U-yMSeQZNfoqxnR7dRnooWreH9k8R9vW67c8ZZANU3DJlNGBK3gMWpze-hRSdz5OXz62aRXmdFb4oOB4Oi-t2SXYx80TcgiiPTr7ghggVyWid39Apek3WFLkFK5oAcrvAlXLIJ/s1600/S5_11.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="207" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgt4hkVr3U-yMSeQZNfoqxnR7dRnooWreH9k8R9vW67c8ZZANU3DJlNGBK3gMWpze-hRSdz5OXz62aRXmdFb4oOB4Oi-t2SXYx80TcgiiPTr7ghggVyWid39Apek3WFLkFK5oAcrvAlXLIJ/s640/S5_11.png" width="640" /></a></div><br />
<br />
Si están viendo la página que indicaron en el elemento "<span class="codigo"><result></span>" quiere decir que todo ha funcionado correctamente ^_^.<br />
<br />
Ahora veremos cómo funciona el siguiente tipo de <span class="codigo">result</span>: "<span class="codigo">redirectAction</span>"<br />
<br />
<br />
<h2 class="titulo"><span class="codigo">Result</span> "<span class="codigo">redirectAction</span>"</h2>Este tipo de <span class="codigo">result</span> lo podemos ver como una forma especializada del <span class="codigo">result</span> anterior, en donde el navegador del usuario es redirigido y ejecuta el <span class="codigo">Action</span> que le especifiquemos. Nos permite especificar qué parámetros queremos enviar a este <span class="codigo">Action</span>, desde el archivo de configuración, los cuales pueden ser parámetros estáticos o dinámicos (en realidad podemos hacer esto con todos los <span class="codigo">result</span>s, pero este es el que me parece más claro para mostrar esta característica).<br />
<br />
Este <span class="codigo">result</span> tiene tres parámetros:<br />
<br />
<ul><li><span class="codigo">actionName</span> (default): El nombre del <span class="codigo">Action</span> al que seremos redirigidos.</li>
<li><span class="codigo">namespace</span>: El namespace al que pertenece el <span class="codigo">Action</span>. Si no lo indicamos, se usa por default el namespace actual.</li>
<li><span class="codigo">supressEmptyParameters</span>: Evita que los parámetros que estén vacios sean enviados al siguiente <span class="codigo">Action</span>. Su valor por default es "<span class="codigo">false</span>", o sea que los parámetros vacios si serán enviados.</li>
</ul><br />
<br />
Hagamos un ejemplo. Primero creamos un nuevo directorio, en las páginas web del proyecto, llamado "<span class="codigo">redirectAction</span>":<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgyTlgEhPqxXiYFD65DPBylb9_Lip0IcyBWJeV1dEGa_JMbmA6YaQjFKNZQmUhM5Y6LmTM5dmGEiOFO-h4JDXTP4MGimah9D4KNRD4Ast6YEMjVvKbyMH-WfrhESYP1xTKprbWPImr8qvnX/s1600/S5_12.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgyTlgEhPqxXiYFD65DPBylb9_Lip0IcyBWJeV1dEGa_JMbmA6YaQjFKNZQmUhM5Y6LmTM5dmGEiOFO-h4JDXTP4MGimah9D4KNRD4Ast6YEMjVvKbyMH-WfrhESYP1xTKprbWPImr8qvnX/s1600/S5_12.png" /></a></div><br />
<br />
Dentro de este directorio creamos una página llamada "<span class="codigo">index.jsp</span>". Por el momento dejaremos esta página tal como está y comenzaremos a crear nuestro <span class="codigo">Action</span>.<br />
<br />
Agregamos una nueva clase llamada "<span class="codigo">RedirectActionAction</span>" dentro del paquete "<span class="codigo">actions</span>". Esta clase debe exender de "<span class="codigo">ActionSupport</span>":<br />
<br />
<pre><code>
public class RedirectActionAction extends ActionSupport
{
}
</code></pre><br />
<br />
Al igual que en el ejemplo anterior, esta clase será muy simple: no tendrá atributos y sólo sobre-escribiremos su método "<span class="codigo">execute</span>" para regresar el valor de la constante "<span class="codigo">SUCCESS</span>":<br />
<br />
<pre><code>
public class RedirectActionAction extends ActionSupport
{
@Override
public String execute() throws Exception
{
return SUCCESS;
}
}
</code></pre><br />
<br />
Ahora que nuestro <span class="codigo">Action</span> está listo, vamos al archivo de configuración "<span class="codigo">struts.xml</span>" y hacemos su declaración:<br />
<br />
<pre><code>
<action name="redirect-action" class="com.javatutoriales.results.actions.RedirectActionAction">
</action>
</code></pre><br />
<br />
Lo siguiente es declarar el <span class="codigo">result</span> al que iremos en el caso de la respuesta "<span class="codigo">success</span>". El <span class="codigo">result</span> será del tipo "<span class="codigo">redirectAction</span>" y nos redirigirá al <span class="codigo">Action</span> llamado "<span class="codigo">dispatcher</span>" (el primero que creamos en el tutorial), recordemos que el parámetro por default de este <span class="codigo">result</span> es el nombre del <span class="codigo">Action</span>:<br />
<br />
<pre><code>
<result type="redirectAction">dispatcher</result>
</code></pre><br />
<br />
La declaración de este <span class="codigo">Action</span> queda de la siguiente forma:<br />
<br />
<pre><code>
<action name="redirect-action" class="com.javatutoriales.results.actions.RedirectActionAction">
<result type="redirectAction">dispatcher</result>
</action>
</code></pre><br />
<br />
Regresemos a la página "<span class="codigo">index.jsp</span>" que creamos hace unos momentos, y agreguemos un enlace para el <span class="codigo">Action</span> "<span class="codigo">redirect-action</span>" (no olviden indicar que haremos uso de las etiquetas de <span class="codigo">Struts 2</span> con la directiva taglib correspondiente):<br />
<br />
<pre><code>
<s:a action="redirect-action">Redireccionar</s:a>
</code></pre><br />
<br />
Ahora ejecutamos nuestra aplicación y entramos a la siguiente dirección:<br />
<br />
<pre><code>
<a href="http://localhost:8080/results/redirectAction/index.jsp">http://localhost:8080/results/redirectAction/index.jsp</a>
</code></pre><br />
<br />
En donde únicamente debemos ver la liga que colocamos hace un momento. Al hacer clic sobre ella seremos redirigidos al <span class="codigo">Action</span> llamado "<span class="codigo">dispatcher</span>":<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhNgIXMXB92C6QYyHmWqfmRjDZOkOs4SkH4jfX5cTKeKzH4eQIMmmlGn1dAUCS53cdFZ0v5Cgqrc3iBOkUFZU_0GmJB8cNG_kUqwzNRIFN6Hb7-1JbrU2S8p8_jyJ-voOejxEnuu3myolh2/s1600/S5_13.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="182" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhNgIXMXB92C6QYyHmWqfmRjDZOkOs4SkH4jfX5cTKeKzH4eQIMmmlGn1dAUCS53cdFZ0v5Cgqrc3iBOkUFZU_0GmJB8cNG_kUqwzNRIFN6Hb7-1JbrU2S8p8_jyJ-voOejxEnuu3myolh2/s400/S5_13.png" width="400" /></a></div><br />
<br />
Recordemos que el <span class="codigo">Action</span> anterior recibe dos parámetros, el nombre del usuario que envía el formulario y el lenguaje de programación que este usa. Por el resultado anterior podemos ver que no se están recibiendo ninguno de los dos parámetros. Ahora resolveremos esto pasando ambos parámetros, uno de forma estática y el otro de forma dinámica.<br />
<br />
Como ahora pasaremos más parámetros al <span class="codigo">result</span>, ya no podemos seguir indicando el nombre del <span class="codigo">Action</span> como el parámetro por default, y debemos hacerlo de forma explícita:<br />
<br />
<pre><code>
<result type="redirectAction">
<param name="actionName">dispatcher</param>
</result>
</code></pre><br />
<br />
Para pasar parámetros a los <span class="codigo">Action</span>s, los definimos dentro del <span class="codigo">result</span> que los invoca, y es tan simple como definirlos, usando su nombre, dentro del elemento "<span class="codigo"><param></span>"; el valor del parámetro lo colocaremos dentro de este elemento. Por ejemplo, para definir el parámetro "<span class="codigo">nombre</span>", con un valor de "<span class="codigo">Alex</span>", lo hacemos de la siguiente forma:<br />
<br />
<pre><code>
<param name="nombre">Alex</param>
</code></pre><br />
<br />
El parámetro anterior está definido de forma estática, pero también podemos definirlos de forma dinámica. Los parámetros dinámicos necesitan tener dos partes, una declaración en el <span class="codigo">result</span>, y un método <span class="codigo">getter</span> en el <span class="codigo">Action</span> del cual se obtendrá el valor. Definamos el parámetro "<span class="codigo">lenguaje</span>".<br />
<br />
Primero declararemos en el <span class="codigo">result</span> un nuevo parámetro cuyo nombre será <span class="codigo">lenguaje</span>:<br />
<br />
<pre><code>
<param name="lenguaje"></param>
</code></pre><br />
<br />
Queremos que el valor de este parámetro sea establecido de forma dinámica, por lo que lo debemos indicar, entre los elementos "<span class="codigo">${"</span> y "<span class="codigo">}</span>", qué atributo del <span class="codigo">Action</span> que se debe llamar para obtener este valor. El valor se obtendrá del <span class="codigo">getter</span> de este atributo, que debe seguir la convención de nombres de los <span class="codigo">JavaBeans</span>; o sea que si queremos obtener el valor de un atributo llamado "<span class="codigo">lenguajeProgramacion</span>", nuestro <span class="codigo">Action</span> debe tener un método "<span class="codigo">public String getLenguajeProgramacion()</span>". Por lo tanto, la definición de parámetro queda de la siguiente forma:<br />
<br />
<pre><code>
<param name="lenguaje">${lenguajeProgramacion}</param>
</code></pre><br />
<br />
También debemos modificar la clase "<span class="codigo">RedirectActionAction</span>", agregándole el <span class="codigo">getter</span> necesario. En este caso el regresaremos un valor constante, pero este parámetro podría ser obtenido de forma dinámica, conectándose a una base de datos, de la sesión del usuario, o de alguna otra forma. Regresaremos el valor "<span class="codigo">Java</span>" como nuestro lenguaje de programación favorito <span class="codigo">^_^</span>:<br />
<br />
<pre><code>
public String getLenguajeProgramacion()
{
return "Java";
}
</code></pre><br />
<br />
Ahora, cuando volvamos a ejecutar nuestra aplicación debemos ver la siguiente salida:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEghqe_1tjX9jypXSkPqESUR2pghNcW_JQwFQ1kz0dW_zsD3fWpRLkYk9gwtZsiPDPphxvu1RPy0QD6zUmB2CzT4yF_jPMA4xWAJRZadHCOhS6jnGOv-bvc5fUTGJNr-Sb4oN6bipGw9AsBM/s1600/S5_14.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="145" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEghqe_1tjX9jypXSkPqESUR2pghNcW_JQwFQ1kz0dW_zsD3fWpRLkYk9gwtZsiPDPphxvu1RPy0QD6zUmB2CzT4yF_jPMA4xWAJRZadHCOhS6jnGOv-bvc5fUTGJNr-Sb4oN6bipGw9AsBM/s400/S5_14.png" width="400" /></a></div><br />
<br />
Esto nos indica que el ejemplo ha funcionado correctamente <span class="codigo">^_^</span>. Ahora veremos cómo funciona el siguiente tipo de <span class="codigo">result</span>:<br />
<br />
<br />
<h2 class="titulo"><span class="codigo">Result</span> "<span class="codigo">chain</span>"</h2>Este <span class="codigo">result</span> permite que se invoque otro <span class="codigo">Action</span> completo, incluyendo todos sus interceptores y su <span class="codigo">result</span>.<br />
<br />
Aunque puede sonar como el <span class="codigo">result</span> anterior, no lo es, ya que en el caso anterior el <span class="codigo">result</span> le indicaba al navegador que debía redirigirse hacia otro <span class="codigo">Action</span>, en este caso todos los <span class="codigo">Action</span>s de la cadena se ejecutan en el servidor, y este nos regresa el resultado del último <span class="codigo">Action</span>. Por la explicación anterior podemos entender además que es posible colocar más de un <span class="codigo">Action</span> en la cadena de invocación (para esto el segundo <span class="codigo">Action</span> debe regresar a un <span class="codigo">result</span> que también sea de tipo "<span class="codigo">chain</span>").<br />
<br />
Este <span class="codigo">Action</span> tiene cuatro parámetros:<br />
<br />
<ul><li><span class="codigo">actionName</span> (default) - El nombre del <span class="codigo">Action</span> que será encadenado.</li>
<li><span class="codigo">namespace</span> - Indica en cuál namespace se encuentra el <span class="codigo">Action</span></li>
<li><span class="codigo">method</span> - Si queremos invocar un método en el <span class="codigo">Action</span> que no sea el método "<span class="codigo">execute</span>" lo indicamos con este parámetro.</li>
<li><span class="codigo">skipActions</span> - Una lista, separada por comas, de los nombres de los <span class="codigo">Action</span>s que pueden ser encadenados a este.</li>
</ul><br />
<br />
Comencemos con el ejemplo, en el cual encadenaremos todos los <span class="codigo">Action</span>s que hemos creado hasta el momento <span class="codigo">^_^</span>.<br />
<br />
Primero crearemos un nuevo directorio, en la parte de "<span class="codigo">Web Pages</span>" del proyecto, llamado "<span class="codigo">chain</span>", dentro de este creamos una nueva página llamada "<span class="codigo">index.jsp</span>". En esta página nuevamente sólo pondremos una liga que nos enviará al <span class="codigo">Action</span> que definiremos en un momento y que iniciará con la cadena de llamadas. El nombre de este <span class="codigo">Action</span> será "<span class="codigo">chain</span>", así que colocamos la liga de la siguiente forma (recuerden indicar con la directiva taglib correspondiente que haremos uso de las etiquetas de <span class="codigo">Struts 2</span>):<br />
<br />
<pre><code>
<s:a action="chain">Encadenar</s:a><br />
</code></pre><br />
<br />
Como en esta ocasión haremos uso de varios <span class="codigo">Action</span>s "<span class="codigo">vacios</span>" no crearemos ninguna clase, esto ayudará para mostrar cómo se va llamando uno después de otro... y para aprovechar a mostrar otra cosa interesante de <span class="codigo">Struts 2</span>.<br />
<br />
Vamos al archivo de configuración "<span class="codigo">struts.xml</span>" y declaramos un nuevo <span class="codigo">action</span>, llamado "<span class="codigo">chain</span>", sólo que en esta ocasión no declararemos ninguna clase para este <span class="codigo">action</span>, de la siguiente forma:<br />
<br />
<pre><code>
<action name="chain">
</action>
</code></pre><br />
<br />
Cuando <span class="negritas">no</span> colocamos un valor en el atributo "<span class="codigo">class</span>" del elemento "<span class="codigo"><action></span>" este por default usa la clase "<span class="codigo">ActionSupport</span>" (aunque podemos cambiar esto en los archivos de configuración de <span class="codigo">Struts 2</span>), de la cual han extendido todos nuestros <span class="codigo">Action</span>s hasta el momento. <span class="codigo">ActionSupport</span> tiene un método "<span class="codigo">execute</span>" que regresa el valor de "<span class="codigo">success</span>", y un método "<span class="codigo">input</span>" que regresa el valor de "<span class="codigo">input</span>". Eso quiere decir que el elemento "<span class="codigo"><action></span>" que acabamos de declarar, siempre irá a su <span class="codigo">result</span> llamado "<span class="codigo">success</span>".<br />
<br />
Ahora colocaremos el <span class="codigo">result</span> "<span class="codigo">success</span>", el cual recuerden que es el nombre por default de los <span class="codigo">result</span>s, indicando que este será de tipo "<span class="codigo">chain</span>" y que al terminar ejecutará un <span class="codigo">action</span> llamado "<span class="codigo">chain2</span>":<br />
<br />
<pre><code>
<action name="chain">
<result type="chain">chain2</result>
</action>
</code></pre><br />
<br />
Declararemos otro <span class="codigo">action</span> llamado "<span class="codigo">chain2</span>", que será muy parecido al anterior, y en su <span class="codigo">result</span>, también de tipo "<span class="codigo">chain</span>", encadenará la llamada a un <span class="codigo">action</span> llamado "<span class="codigo">chain3</span>":<br />
<br />
<pre><code>
<action name="chain2">
<result type="chain">chain3</result>
</action>
</code></pre><br />
<br />
Para terminar, declaramos un <span class="codigo">action</span> llamado "<span class="codigo">chain3</span>", que será muy parecido a los dos anteriores, con la diferencia de que este nos enviará ahora a un <span class="codigo">Action</span> que si realiza una acción, yo encadenaré el <span class="codigo">Action</span> "<span class="codigo">redirect-externo</span>" pero pueden usar cualquiera que ustedes prefieran:<br />
<br />
<pre><code>
<action name="chain3">
<result type="chain">redirect-externo</result>
</action>
</code></pre><br />
<br />
La invocación de los <span class="codigo">Action</span>s será más o menos la siguiente:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjOqyDSbgQzDricmX4ILw5VcsuDCmRyVlqQ3Tn_dvq5O4P-xgKcG803J_883fOe-kXN3tobHCaIPlQetW43FxVo_esVonna-w_OWS3rvRgHJt2WW7QGsCN_uTW1UXzm3Z5-J0ZSLeGwM8fc/s1600/S5_15.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="204" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjOqyDSbgQzDricmX4ILw5VcsuDCmRyVlqQ3Tn_dvq5O4P-xgKcG803J_883fOe-kXN3tobHCaIPlQetW43FxVo_esVonna-w_OWS3rvRgHJt2WW7QGsCN_uTW1UXzm3Z5-J0ZSLeGwM8fc/s640/S5_15.png" width="640" /></a></div><br />
<br />
<ol><li><span class="negritas">1-</span> Primero el <span class="codigo">action</span> "<span class="codigo">chain</span>" ejecuta su método "<span class="codigo">execute</span>" que regresa "<span class="codigo">succes</span>". Como este <span class="codigo">result</span> es de tipo "<span class="codigo">chain</span>", se manda llamar el siguiente <span class="codigo">action</span> de la cadena, que en este caso es "<span class="codigo">chain2</span>".</li>
<li><span class="negritas">2-</span> El <span class="codigo">action</span> "<span class="codigo">chain2</span>" ejecuta su método "<span class="codigo">execute</span>", este regresa el valor "<span class="codigo">success</span>", el cual es un <span class="codigo">result</span> de tipo "<span class="codigo">chain</span>", por lo que se ejecuta el siguiente action de la cadena: "<span class="codigo">chain3</span>".</li>
<li><span class="negritas">3-</span> "<span class="codigo">chain3</span>" ejecuta su método "<span class="codigo">execute</span>" el cual regresa el valor "<span class="codigo">success</span>", este envía a un <span class="codigo">result</span> de tipo "<span class="codigo">chain</span>" el cual hará una llamada al <span class="codigo">action</span> llamado "<span class="codigo">redirect-externo</span>".</li>
<li><span class="negritas">4-</span> el <span class="codigo">action</span> llamado "<span class="codigo">redirect-externo</span>" ejecuta su método "<span class="codigo">execute</span>", al terminar de ejecutarse este regresa el valor "<span class="codigo">success</span>", el cual nos envía a un <span class="codigo">result</span> de tipo "<span class="codigo">redirect</span>" que nos envía a la página de <span class="codigo">www.javatutoriales.com</span></li>
<li><span class="negritas">5-</span> Nuestro navegador nos muestra la entrada correspondiente del blog <span class="codigo">^_^</span>.</li>
</ol><br />
<br />
En cada uno de los casos se ejecutarán todos los <span class="codigo">actions</span>, del lado del servidor, y cuando se llegué a un <span class="codigo">result</span> que no sea de tipo "<span class="codigo">chain</span>" este se ejecutará de forma normal, en este caso será redirigir el navegador a la página de <span class="codigo">Java Tutoriales :)</span>.<br />
<br />
Ejecutamos nuestra aplicación y entramos a la siguiente dirección:<br />
<br />
<pre><code>
<a href="http://localhost:8080/results/chain/index.jsp">http://localhost:8080/results/chain/index.jsp</a>
</code></pre><br />
<br />
Veremos el enlace que creaos hace un momento. Al hacer clic sobre él, se ejecutarán nuestros cuatro <span class="codigo">Action</span>s, y posteriormente veremos la página correspondiente del blog.<br />
<br />
Como podemos imaginarnos, este tipo de <span class="codigo">result</span> es muy útil cuando necesitamos ejecutar dos o más <span class="codigo">Action</span>s juntos, y el primero no necesita un resultado visual (como un proceso de login y una carga de permisos del usuario).<br />
<br />
Ahora que sabemos cómo funciona este <span class="codigo">result</span>, pasemos al siguiente:<br />
<br />
<br />
<h2 class="titulo"><span class="codigo">Result</span> "<span class="codigo">stream</span>"</h2>Ya conocemos un poco acerca de este <span class="codigo">result</span>, ya que lo vimos en <a href="http://www.javatutoriales.com/2011/12/struts-2-parte-4-scopes-de-objetos-web.html">el tutorial número 4</a>, cuando trabajamos con formularios. Por lo cual ahora solo hablaremos de los pequeños detalles.<br />
<br />
Este <span class="codigo">result</span> nos permite regresar un flujo de bytes al navegador del usuario para que este los interprete de alguna manera. Por ejemplo, podemos enviar un flujo de bytes que representen un archivo <span class="codigo">PDF</span>, y si el navegador del cliente tiene el plugin adecuado lo podrá visualizar. Por lo regular, si el navegador no sabe como mostrar la información que le estamos enviando, le preguntará al usuario si quiere guardar el archivo o abrirlo con alguna aplicación particular.<br />
<br />
Este result tiene los siguientes parámetros<br />
<br />
<ul><li><span class="codigo">contentType</span>: El <span class="codigo">mime-type</span> (el tipo del contenido) que es enviado al usuario. Por default su valor es "<span class="codigo">text/plain</span>".</li>
<li><span class="codigo">contentLength</span>: El número de bytes contenidos en el flujo que enviaremos al usuario. Como vimos en <a href="http://www.javatutoriales.com/2011/12/struts-2-parte-4-scopes-de-objetos-web.html">el tutorial anterior</a>, este es usado para que el navegador pueda mostrar al usuario, de forma correcta, una barra de progreso de la descarga.</li>
<li><span class="codigo">contentDisposition</span>: Este parámetro sirve para establecer el valor de la cabecera "<span class="codigo">content-disposition</span>", que el navegador usa para establecer el nombre del archivo que recibirá. El valor por default es "<span class="codigo">inline</span>".</li>
<li><span class="codigo">inputName</span>: El nombre del atributo de nuestro <span class="codigo">Action</span> que contiene el flujo de bytes. El atributo debe ser de tipo "<span class="codigo">InputStream</span>". Por default se busca un atributo de nombre "<span class="codigo">inputStream</span>".</li>
<li><span class="codigo">bufferSize</span>: El tamaño del buffer que se usa para copiar los valores de la entrada al flujo de salida del navegador. Por default es "<span class="codigo">1024</span>".</li>
<li><span class="codigo">allowCaching</span>: Indica si el navegador debe o no mantener en caché los datos que le hemos enviado. Esto quiere decir que solamente descargará el archivo una vez, y posteriormente lo regresará del caché. Esto es útil si el contenido que le enviaremos siempre será el mismo (digamos una imagen estática); pero si el contenido que le enviaremos cambiará de manera constante debemos indicarle que no queremos que lo guarde en caché, de esta forma siempre lo descargará del servidor. Su valor por default es "<span class="codigo">true</span>".</li>
<li><span class="codigo">contentCharSet</span>: El juego de caracteres que se usan en el contenido (este parámetro es útil si regresamos texto plano en algún formato como <span class="codigo">XML</span>).</li>
</ul><br />
<br />
Como podemos ver, este <span class="codigo">result</span> no tienen ningún parámetro por default, por lo que siempre tendremos que usar el elemento "<span class="codigo"><param></span>" para establecer sus valores.<br />
<br />
Haremos un pequeño ejemplo en el que regresaremos al usuario una imagen de forma dinámica. Lo primero que necesitamos es una imagen, yo usaré la siguiente:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhRS4_e_emZGptr0Cd2Qncxh8ouU1U776GgoWSBzhWXLb7hulgkDZwZ6H4rLtU0hW8un_q50HPt3wVOLw19SUeGy1EQf9PjDW9TMtd7cRUmFC0mjKmx_V3NPSM8pC93LEn9leZvndTHIsQ4/s1600/S5_16.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhRS4_e_emZGptr0Cd2Qncxh8ouU1U776GgoWSBzhWXLb7hulgkDZwZ6H4rLtU0hW8un_q50HPt3wVOLw19SUeGy1EQf9PjDW9TMtd7cRUmFC0mjKmx_V3NPSM8pC93LEn9leZvndTHIsQ4/s1600/S5_16.png" /></a></div><br />
<br />
Colocaremos la imagen anterior en cualquier ubicación de nuestro disco duro, por facilidad yo la pondré en "<span class="codigo">C:\imagenes</span>":<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjLePuwGXUZwdM9B9Suul7I7Ai3JiKl6qaFAX6eky85aqXj4C6EcTenba-B2jQDZaH-r1ganMqfNdAwSHYQFQPq9gs8jikwob1T7D_uQu3Yc-qtH_TFesoVioax-ElpVjcbHGCR-hmZvl2G/s1600/S5_17.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="290" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjLePuwGXUZwdM9B9Suul7I7Ai3JiKl6qaFAX6eky85aqXj4C6EcTenba-B2jQDZaH-r1ganMqfNdAwSHYQFQPq9gs8jikwob1T7D_uQu3Yc-qtH_TFesoVioax-ElpVjcbHGCR-hmZvl2G/s400/S5_17.png" width="400" /></a></div><br />
<br />
Ahora que tenemos la imagen, creamos un nuevo directorio, en la zona de páginas web de nuestro proyecto, llamado "<span class="codigo">stream</span>". Dentro creamos una página llamada "<span class="codigo">index.jsp</span>". Por el momento dejaremos la página como está.<br />
<br />
Creamos en el paquete "<span class="codigo">actions</span>" una nueva clase llamada "<span class="codigo">StreamResultAction</span>", esta clase debe extender de "<span class="codigo">ActionSupport</span>":<br />
<br />
<pre><code>
public class StreamResultAction extends ActionSupport
{
}
</code></pre><br />
<br />
Esta clase se encargará de crear un <span class="codigo">InputStream</span> para leer la imagen, indicar el tamaño de la misma, y decir cuál será el nombre de esta imagen. Lo primero que debemos hacer es colocar unos cuantos atributos para mantener los valores de los datos anteriores. Colocamos en esta clase tres atributos, con sus correspondientes <span class="codigo">getters</span>, de la siguiente forma:<br />
<br />
<pre><code>
private int bytesArchivo;
private InputStream streamImagen;
private String nombreImagen;
public int getBytesArchivo()
{
return bytesArchivo;
}
public String getNombreImagen()
{
return nombreImagen;
}
public InputStream getStreamImagen()
{
return streamImagen;
}
</code></pre><br />
<br />
Los <span class="codigo">getters</span> son necesarios ya que es por medio de estos que el <span class="codigo">result</span> puede leer los valores del <span class="codigo">Action</span>.<br />
<br />
Lo siguiente que debemos hacer es sobre-escribir el método "<span class="codigo">execute</span>" para poder establecer el valor de los atributos anteriores. Dentro del método lo primero que haremos es crear un nuevo objeto de tipo "<span class="codigo">File</span>" que apunte al archivo de la imagen, de esta manera podremos obtener el tamaño de la misma con el método "<span class="codigo">length</span>" y su nombre usando el método "<span class="codigo">getName()</span>":<br />
<br />
<pre><code>
@Override
public String execute() throws Exception
{
File archivoImagen = new File("/imagenes/imagen.png");
bytesArchivo = (int)archivoImagen.length();
nombreImagen = archivoImagen.getName();
return SUCCESS;
}
</code></pre><br />
<br />
Usando el <span class="codigo">File</span> anterior, creamos un <span class="codigo">InputStream</span>, un objeto de tipo "<span class="codigo">FileInputStream</span>" que será el flujo de bytes de la imagen:<br />
<br />
<pre><code>
streamImagen = new FileInputStream(archivoImagen);
</code></pre><br />
<br />
Y esto es todo lo que debemos hacer. La clase "<span class="codigo">StreamResultAction</span>" completa queda de la siguiente forma:<br />
<br />
<pre><code>
public class StreamResultAction extends ActionSupport
{
private int bytesArchivo;
private InputStream streamImagen;
private String nombreImagen;
@Override
public String execute() throws Exception
{
File archivoImagen = new File("/imagenes/imagen.png");
bytesArchivo = (int)archivoImagen.length();
nombreImagen = archivoImagen.getName();
streamImagen = new FileInputStream(archivoImagen);
return SUCCESS;
}
public int getBytesArchivo()
{
return bytesArchivo;
}
public String getNombreImagen()
{
return nombreImagen;
}
public InputStream getStreamImagen()
{
return streamImagen;
}
}
</code></pre><br />
<br />
Lo siguiente que debemos hacer es declarar este <span class="codigo">Action</span> en el archivo "<span class="codigo">struts.xml</span>", el nombre que le daremos será "<span class="codigo">stream</span>":<br />
<br />
<pre><code>
<action name="stream" class="com.javatutoriales.results.actions.StreamResultAction">
</action>
</code></pre><br />
<br />
Ahora declararemos el <span class="codigo">result</span> para el caso "<span class="codigo">success</span>", este <span class="codigo">result</span> será de tipo "<span class="codigo">stream</span>":<br />
<br />
<pre><code>
<result type="stream">
</result>
</code></pre><br />
<br />
Lo siguiente es declarar cada uno de los parámetros necesarios. En este ejemplo, algunos parámetros, como el "<span class="codigo">contentType</span>" serán estáticos y otros como el "<span class="codigo">contentLength</span>" serán dinámicos:<br />
<br />
<pre><code>
<result type="stream">
<param name="contentType">image/png</param>
<param name="contentLength">${bytesArchivo}</param>
<param name="inputName">streamImagen</param>
<param name="contentDisposition">attachment;filename="${nombreImagen}"</param>
</result>
</code></pre><br />
<br />
La declaración del <span class="codigo">Action</span> queda de la siguiente forma:<br />
<br />
<pre><code>
<action name="stream" class="com.javatutoriales.results.actions.StreamResultAction">
<result type="stream">
<param name="contentType">image/png</param>
<param name="contentLength">${bytesArchivo}</param>
<param name="inputName">streamImagen</param>
<param name="contentDisposition">attachment;filename="${nombreImagen}"</param>
</result>
</action>
</code></pre><br />
<br />
Regresemos a la página <span class="codigo">index.jsp</span> que creamos hace un momento. En esta colaremos una etiqueta "<span class="codigo"><img /></span>" indicando que la imagen será devuelta por nuestro <span class="codigo">Action</span>, para eso sólo debemos declarar en su atributo "<span class="codigo">src</span>" con el valor del nombre de nuestro <span class="codigo">Action</span>, o sea de la siguiente forma:<br />
<br />
<pre><code>
<img src="stream.action" alt="imagen" />
</code></pre><br />
<br />
Ahora podemos ejecutar nuestra aplicación. Entramos en la siguiente dirección:<br />
<br />
<pre><code>
<a href="http://localhost:8080/results/stream/index.jsp">http://localhost:8080/results/stream/index.jsp</a>
</code></pre><br />
<br />
Y con eso deberemos ver la página con la imagen que cargamos:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiyv09SvGDLsEPL620XzuR2cFHq1th0BAxYR5qRm8q3k92AOXz-Oxa5f5fT8LSAy64y9vi7fRqwzCHhsaaFAVgAmXIq0ClrNjX5ofSTK157Jw1qhT9juyWbS7NrePoFJz2gSfAOmV_1_Prq/s1600/S5_18.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="224" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiyv09SvGDLsEPL620XzuR2cFHq1th0BAxYR5qRm8q3k92AOXz-Oxa5f5fT8LSAy64y9vi7fRqwzCHhsaaFAVgAmXIq0ClrNjX5ofSTK157Jw1qhT9juyWbS7NrePoFJz2gSfAOmV_1_Prq/s400/S5_18.png" width="400" /></a></div><br />
<br />
Podemos ver con esto que el ejemplo ha funcionado de forma correcta, o sea el flujo de bytes se envío al navegador, indicando el tipo de contenido del mismo flujo, y este lo interpretó de forma correcta para mostrarlo al usuario.<br />
<br />
Veamos ahora el penúltimo tipo de result de este tutorial:<br />
<br />
<br />
<h2 class="titulo"><span class="codigo">Result</span> "<span class="codigo">plaintext</span>"</h2>Este tipo de <span class="codigo">result</span> envía el contenido de un archivo como texto plano al navegador. Con este <span class="codigo">result</span> podemos, por ejemplo enviar el contenido de una <span class="codigo">JSP</span>, sin que esta sea procesada, o cualquier otro archivo que pueda ser abierto con un editor de texto plano.<br />
<br />
Este result tiene dos parámetros:<br />
<br />
<ul><li><span class="codigo">location</span> (default): la ubicación del archivo que será mostrada como texto plano. Esta ubicación será relativa a la raíz del proyecto web.</li>
<li><span class="codigo">chatSet</span>: El conjunto de caracteres con el que será mostrado el archivo.</li>
</ul><br />
<br />
Veamos el ejemplo correspondiente. Lo primero que haremos es crear un nuevo directorio, en la sección de páginas web, llamado "<span class="codigo">plaintext</span>". Dentro de este directorio crearemos dos páginas, la primera será el típico "<span class="codigo">index.jsp</span>" el cual dejaremos por el momento, y la otra será una página llamada "<span class="codigo">pagina.html</span>" (así es, <span class="codigo">html</span>). <br />
<br />
Esta página tendrá un simple mensaje que dirá: "<span class="codigo">soy tan sólo una página HTML</span>". La página (en <span class="codigo">html5</span>) es la siguiente:<br />
<br />
<pre><code>
<!DOCTYPE html>
<html>
<head>
<title>Página HTML - http://javatutoriales.com/</title>
<meta charset="UTF-8" />
</head>
<body>
Soy tan sólo una <strong>página HTML</strong>.
</body>
</html>
</code></pre><br />
<br />
Si ejecutamos la aplicación y entramos a la siguiente dirección:<br />
<br />
<pre><code>
<a href="http://localhost:8080/results/plaintext/pagina.html">http://localhost:8080/results/plaintext/pagina.html</a>
</code></pre><br />
<br />
Debemos ver el siguiente contenido:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjLnotT0QUBFsY_7vjUV30A7-87dv3xadSAuJJtIc5P8GU4rzn8cQzdmadtsnTDRK8E1popcw2R3Y53ONDljOFgaRnO1JqjLGhHEJeBYhSRD8gfJGNot34GgeLbL2nz-KStaNEYKAXnAG1X/s1600/S5_19.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="223" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjLnotT0QUBFsY_7vjUV30A7-87dv3xadSAuJJtIc5P8GU4rzn8cQzdmadtsnTDRK8E1popcw2R3Y53ONDljOFgaRnO1JqjLGhHEJeBYhSRD8gfJGNot34GgeLbL2nz-KStaNEYKAXnAG1X/s400/S5_19.png" width="400" /></a></div><br />
<br />
Con eso comprobamos que el navegador pueda ejecutar correctamente esta página.<br />
<br />
Ahora crearemos en el paquete "<span class="codigo">actions</span>" una nueva clase llamada "<span class="codigo">PlainTextResultAction</span>", la cual extenderá de "<span class="codigo">ActionSupport</span>" y lo único que tendrá será un método "<span class="codigo">execute</span>" sobrecargado que regresará el valor de la constante "<span class="codigo">SUCCESS</span>":<br />
<br />
<pre><code>
public class PlainTextResultAction extends ActionSupport
{
@Override
public String execute() throws Exception
{
return SUCCESS;
}
}
</code></pre><br />
<br />
A continuación pasemos a la declaración de este <span class="codigo">Action</span> en el archivo "<span class="codigo">struts.xml</span>". El nombre de este <span class="codigo">Action</span> será "<span class="codigo">plaintext</span>":<br />
<br />
<pre><code>
<action name="plaintext" class="com.javatutoriales.results.actions.PlainTextResultAction">
</action>
</code></pre><br />
<br />
Declaramos el <span class="codigo">result</span> para este <span class="codigo">Action</span>. El tipo del <span class="codigo">result</span> será "<span class="codigo">plainText</span>". Como en el juego de caracteres de nuestra página (<span class="codigo">UTF-8</span>) es distinta a la del proyecto (<span class="codigo">ISO-8859-1</span>), debemos indicar el valor del mismo en el parámetro "<span class="codigo">charSet</span>". Además colocaremos el valor del parámetro "<span class="codigo">location</span>" como la página <span class="codigo">HTML</span> que creamos hace un momento (recuerden que este valor debe ser relativo a la raíz de las páginas web):<br />
<br />
<pre><code>
<action name="plaintext" class="com.javatutoriales.results.actions.PlainTextResultAction">
<result type="plainText">
<param name="location">/plaintext/pagina.html</param>
<param name="charSet">UTF-8</param>
</result>
</action>
</code></pre><br />
<br />
Ahora que tenemos esta declaración, lo último que haremos es regresar a la página "<span class="codigo">index.jsp</span>" que creamos hace un momento y colocaremos un enlace al <span class="codigo">Action</span> que acabamos de declarar, de esta forma (recuerden indicar que haremos uso de las etiquetas de <span class="codigo">Struts 2</span> con el <span class="codigo">taglib</span> correspondiente):<br />
<br />
<pre><code>
<s:a action="plaintext">Ver página</s:a>
</code></pre><br />
<br />
Entramos a la siguiente dirección:<br />
<br />
<pre><code>
<a href="http://localhost:8080/results/plaintext/index.jsp">http://localhost:8080/results/plaintext/index.jsp</a>
</code></pre><br />
<br />
Con lo que deberemos ver el enlace que acabamos de declarar. Al hacer clic sobre el nos enviará al <span class="codigo">Action</span> correspondiente, con lo cual deberemos ver en el contenido de la página <span class="codigo">HTML</span> que creamos (en texto plano, sin ser procesada por el navegador):<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgeI4zdt_Rc97wcmHVe1FuuYNXUTQSVpV-BX5djdw-TovOJPUZcr3eW5Nlsm9C6fF-ID6wKrcA7SjK2zijdPORPj1MXwcHL1WfIFBPJCh3QuNzJMWfFmX5QP85bd_8jqz6bj70WiNPwz2O8/s1600/S5_20.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="359" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgeI4zdt_Rc97wcmHVe1FuuYNXUTQSVpV-BX5djdw-TovOJPUZcr3eW5Nlsm9C6fF-ID6wKrcA7SjK2zijdPORPj1MXwcHL1WfIFBPJCh3QuNzJMWfFmX5QP85bd_8jqz6bj70WiNPwz2O8/s640/S5_20.png" width="640" /></a></div><br />
<br />
Si revisan el código fuente de la página verán que es exactamente el mismo que creamos, lo que ocurre es que este <span class="codigo">result</span> cambia el <span class="codigo">content-type</span> para que pueda ser mostrado como texto.<br />
<br />
Ahora veremos el último <span class="codigo">result</span> de este tutorial:<br />
<br />
<br />
<h2 class="titulo"><span class="codigo">Result</span> "<span class="codigo">httpheader</span>"</h2>Este <span class="codigo">result</span> permite establecer el valor de las cabeceras de la respuesta que se le envía al cliente. Con este <span class="codigo">result</span> podemos, por ejemplo enviar estatus de error al cliente, indicando errores o falta de permisos para ejecutar ciertas acciones.<br />
<br />
Este <span class="codigo">result</span> puede recibir los siguientes parámetros:<br />
<br />
<ul><li><span class="codigo">status</span>: El estatus de la respuesta http.</li>
<li><span class="codigo">parse</span>: Indica si los parámetros "<span class="codigo">headers</span>" deben ser parseados como expresiones <span class="codigo">OGNL</span> (por default es true)</li>
<li><span class="codigo">headers</span>: Los valores de las cabeceras que queramos establecer. Estos se establecer colocando el nombre de la cabecera que queremos establecer precedida por un punto, como por ejemplo "<span class="codigo">headers.a</span>", "<span class="codigo">headers.b</span>", etc.</li>
<li><span class="codigo">error</span>: El código de error <span class="codigo">HTTP</span> que será enviado como respuesta.</li>
<li><span class="codigo">errorMessage</span>: El mensaje de error que será mostrado en la respuesta.</li>
</ul>La mayor utilidad de este tipo <span class="codigo">result</span> es, como habrán imaginado por los parámetros, es para enviar estatus de error al usuario (ya que los mensajes de éxito muestran el contenido correspondiente del sitio).<br />
<br />
Para los que tienen dudas de cuáles son los estatus que se pueden tener, pueden consultar la siguiente entrada de Wikipedia:<br />
<br />
<pre><code>
<a href="http://en.wikipedia.org/wiki/List_of_HTTP_status_codes">http://en.wikipedia.org/wiki/List_of_HTTP_status_codes</a>
</code></pre><br />
<br />
También esta es la lista de los campos de las cabeceras que se pueden establecer para las peticiones y respuestas, en Wikipedia:<br />
<br />
<pre><code>
<a href="http://en.wikipedia.org/wiki/List_of_HTTP_header_fields">http://en.wikipedia.org/wiki/List_of_HTTP_header_fields</a>
</code></pre><br />
<br />
Como una nota adicional, cada uno de los códigos de estatus debe ser usado con alguna o algunas cabeceras adicionales. <br />
<br />
Veamos algunos ejemplos del uso de este <span class="codigo">result</span>. Lo primero que hacemos es crear un nuevo directorio, en la sección de páginas web, llamado "<span class="codigo">headers</span>". Dentro de este directorio crearemos una página llamada "<span class="codigo">index.jsp</span>" en la que colocaremos las ligas de los <span class="codigo">action</span>s que vayamos creando, por lo que indicaremos que haremos uso de las etiquetas de <span class="codigo">Struts 2</span> con la directiva <span class="codigo">taglib</span> correspondiente:<br />
<br />
<pre><code>
<%@taglib prefix="s" uri="/struts-tags" %>
</code></pre><br />
<br />
En este caso no crearemos ninguna clase <span class="codigo">Action</span>, ya que solamente necesitamos su declaración, con su correspondiente "<span class="codigo"><result></span>" dentro del archivo "<span class="codigo">struts.xml</span>". <br />
<br />
El primer <span class="codigo">result</span> que crearemos será para el caso que el usuario busque un recurso (una página, una imagen, un archivo, etc.) que haya sido movido de nuestro servidor a algún otro lugar. Si revisamos la lista de códigos de estatus, vemos que existe uno para este caso (vaya coincidencia ^^!), el código "<span class="codigo">301 - Moved Permanently</span>". Si buscamos información sobre este código, veremos que, según Wikipedia (http://en.wikipedia.org/wiki/HTTP_301) este código debe de usarse junto con la cabecera "<span class="codigo">location</span>".<br />
<br />
Vamos al archivo "<span class="codigo">struts.xml</span>" y declaramos un nuevo action llamado "<span class="codigo">httpstatus301</span>", el cual no será implementado por ninguna clase:<br />
<br />
<pre><code>
<action name="httpstatus301">
</action>
</code></pre><br />
<br />
A continuación declaremos un "<span class="codigo"><result></span>" para el caso "<span class="codigo">success</span>", el tipo de este <span class="codigo">result</span> será "<span class="codigo">httpheader</span>":<br />
<br />
<pre><code>
<action name="httpstatus301">
<result type="httpheader">
</result>
</action>
</code></pre><br />
<br />
Ahora viene lo interesante; "<span class="codigo">301</span>" no es un código de error, sino que indica que la petición se realizó correctamente pero el recurso se movió a otra ubicación, la cual por cierto conocemos y queremos que el usuario sea redirigido de forma automática a esa nueva ubicación. Como <span class="codigo">301</span> no es un código de error, lo indicamos usado el parámetro "<span class="codigo">status</span>":<br />
<br />
<pre><code>
<result type="httpheader">
<param name="status">301</param>
</result>
</code></pre><br />
<br />
Y como vimos hace un momento, cuando usamos el estatus <span class="codigo">301</span>, también debemos indicar la cabecera "<span class="codigo">Location</span>" con la nueva ubicación del recurso. Supongamos que en este caso la ubicación nueva del recurso que estamos buscando es: "<span class="codigo">http://www.javatutoriales.com/</span>", por lo que lo indicamos de la siguiente forma:<br />
<br />
<pre><code>
<result type="httpheader">
<param name="status">301</param>
<param name="headers.Location">http://www.javatutoriales.com/</param>
</result>
</code></pre><br />
<br />
La declaración completa de nuestro <span class="codigo">action</span> queda de la siguiente forma:<br />
<br />
<pre><code>
<action name="httpstatus301">
<result type="httpheader">
<param name="status">301</param>
<param name="headers.Location">http://www.javatutoriales.com/</param>
</result>
</action>
</code></pre><br />
<br />
Ahora regresaremos a la página "<span class="codigo">index.jsp</span>" y agregaremos un enlace al action que acabamos de declarar, de la siguiente forma:<br />
<br />
<pre><code>
<s:a action="httpstatus301">301 - Moved Permanently</s:a>
</code></pre><br />
<br />
Ejecutamos nuestra aplicación y entramos a la siguiente dirección:<br />
<br />
<pre><code>
<a href="http://localhost:8080/results/headers/index.jsp">http://localhost:8080/results/headers/index.jsp</a>
</code></pre><br />
<br />
Con lo que debemos ver el enlace que creamos hace un momento. Cuando hagamos clic en el debemos ser redirigidos a otra página:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgOb-XgVBKL9Aly6w2SMdBHhKp6GPEutwMc9jq4BpH9t5uSmT2I18NGI1Sf19d1lhC46UFZey7yHNSxiNDRIRvhMXudcSn9bebXPwcTcG1nLDHzHzRZ6NcbOo-YL9ranTWTdVWV_t2m3Yog/s1600/S5_21.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="213" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgOb-XgVBKL9Aly6w2SMdBHhKp6GPEutwMc9jq4BpH9t5uSmT2I18NGI1Sf19d1lhC46UFZey7yHNSxiNDRIRvhMXudcSn9bebXPwcTcG1nLDHzHzRZ6NcbOo-YL9ranTWTdVWV_t2m3Yog/s400/S5_21.png" width="400" /></a></div><br />
<br />
¿Qué fue lo que pasó? Para responder a esta pregunta haremos uso de una extensión de firefox llamada "<span class="codigo">live http headers</span>" que nos permite analizar los encabezados de las peticiones y las respuestas que realizamos.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgkI3NyKCW4Df1ILDeY6QNJklvvnZH6ozP3hqttugO2KUbS7k8Dihvl2BkjH8tO3KU8iD4VkXFmf4bw__uu86abI7IipIJIkzwvpyG8GfI0-AUve6ayNeUKuCKYo_Ej3sWwFeyDfLa_MPR1/s1600/S5_22.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="629" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgkI3NyKCW4Df1ILDeY6QNJklvvnZH6ozP3hqttugO2KUbS7k8Dihvl2BkjH8tO3KU8iD4VkXFmf4bw__uu86abI7IipIJIkzwvpyG8GfI0-AUve6ayNeUKuCKYo_Ej3sWwFeyDfLa_MPR1/s640/S5_22.png" width="640" /></a></div><br />
<br />
Podemos ver que en primer lugar (paso 1) el navegador hace una petición a la página "<span class="codigo">http://localhost:8080/results/headers/index.jsp</span>" (la parte que dice "<span class="codigo">jsessionid</span>" es la cookie por la cual se manejan las peticiones en los servidores java), el servidor envía la respuesta (paso 2) con el código "<span class="codigo">301 Movido permanentemente</span>" y envía además la cabecera "<span class="codigo">Location</span>" indicando que el recurso ahora se encuentra en "<span class="codigo">http://www.javatutoriales.com</span>". El navegador hace una nueva petición a esta ubicación (paso 3) y se continúa con el proceso normal.<br />
<br />
Podemos ver que la redirección ha funcionado correctamente.<br />
<br />
Veamos ahora un ejemplo de un código de error. En <span class="codigo">Http</span> existen dos tipos de errores: los errores generados por el cliente (cuando, por ejemplo, la petición no se entiende o cuando excede de un cierto tamaño) y los errores generados por el servidor (cuando ocurre algún error inesperado que no permite que la petición se procese).<br />
<br />
Los errores de cliente tienen un código a partir del <span class="codigo">400</span> y los del servidor a partir del <span class="codigo">500</span>. Veamos primero un error generado por el cliente.<br />
<br />
El status <span class="codigo">400</span> indica que la petición no puede ser completada debido a que la sintaxis en la que se recibió no es correcta.<br />
<br />
Declaramos un nuevo elemento "<span class="codigo"><action></span>" en el archivo "<span class="codigo">struts.xml</span>" que se llamará "<span class="codigo">httpstatus400</span>" y no será implementado por ninguna clase:<br />
<br />
<pre><code>
<action name="httpstatus400">
</action>
</code></pre><br />
<br />
Dentro de este action declaramos el <span class="codigo">result</span> para el caso "<span class="codigo">success</span>", que será del tipo "<span class="codigo">httpheader</span>".<br />
<br />
<pre><code>
<result type="httpheader">
</result>
</code></pre><br />
<br />
Como en este caso "<span class="codigo">400</span>" si se trata de un error, debemos indicarlo usando el parámetro "<span class="codigo">error</span>" en vez de "<span class="codigo">status</span>" como la vez anterior:<br />
<br />
<pre><code>
<result type="httpheader">
<param name="error">400</param>
</result>
</code></pre><br />
<br />
Para terminar, cuando indicamos un error, en vez de proporcionar una cabecera se indica un mensaje de error para que el usuario tenga una idea de lo que ha salido mal, esto lo hacemos con el parámetro "<span class="codigo">errorMessage</span>":<br />
<br />
<pre><code>
<result type="httpheader">
<param name="error">400</param>
<param name="errorMessage">Los datos proporcionados no pueden ser entendidos, tal vez ha escrito algo mal o su navegador no puede colocarlos en la sintaxis adecuada, favor de utilizar otro navegador.</param>
</result>
</code></pre><br />
<br />
La declaración completa del <span class="codigo">action</span> queda de la siguiente forma:<br />
<br />
<pre><code>
<action name="httpstatus400">
<result type="httpheader">
<param name="error">400</param>
<param name="errorMessage">Los datos proporcionados no pueden ser entendidos, tal vez ha escrito algo mal o su navegador no puede colocarlos en la sintaxis adecuada, favor de utilizar otro navegador.</param>
</result>
</action>
</code></pre><br />
<br />
Ahora regresamos a la página "<span class="codigo">index.jsp</span>" y agregamos un enlace más, para este nuevo <span class="codigo">Action</span>:<br />
<br />
<pre><code>
<s:a action="httpstatus400">404 - Bad Request</s:a>
</code></pre><br />
<br />
Ejecutamos nuesta aplicación y entramos en la siguinte dirección:<br />
<br />
<pre><code>
<a href="http://localhost:8080/results/headers/index.jsp">http://localhost:8080/results/headers/index.jsp</a>
</code></pre><br />
<br />
En donde deberemos ver enlace que acabamos de crear. Al hacer clic sobre él, deberemos ver la siguiente salida:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZHq1cf2LXOJ5OUm3q7ZP-U4d4QdyjLzyAC2i1SdzPZbA96rc4Izj9aCcvzoUEXtIDRiYqr-TX8tdGcmiSy7ClJTZy8-gJ1kseF1QSfj9tAioR_ZeCMBIgK_LMdMtT0330d07oHdk6_qLs/s1600/S5_23.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="335" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZHq1cf2LXOJ5OUm3q7ZP-U4d4QdyjLzyAC2i1SdzPZbA96rc4Izj9aCcvzoUEXtIDRiYqr-TX8tdGcmiSy7ClJTZy8-gJ1kseF1QSfj9tAioR_ZeCMBIgK_LMdMtT0330d07oHdk6_qLs/s640/S5_23.png" width="640" /></a></div><br />
<br />
En donde podemos ver que el navegador nos muestra una ventana de error, con el mensaje que hemos indicado. Así el usuario sabrá que ha hecho algo mal y así podrá corregirlo.<br />
<br />
Veamos el último ejemplo, en este crearemos otra respuesta de error, sólo que ahora será un error del servidor, en el cual usaremos el statis "<span class="codigo">501</span>" para indicar que la funcionalidad que está solicitada no ha sido implementada aún.<br />
<br />
En el archivo "<span class="codigo">struts.xml</span>" declaramos nuevamente un elemento "<span class="codigo"><action></span>" que se llamará "<span class="codigo">httpstatus501</span>" y no será implementado por ninguna clase:<br />
<br />
<pre><code>
<action name="httpstatus501">
</action>
</code></pre><br />
<br />
Lo siguiente que haremos es declarar un elemento "<span class="codigo"><result></span>" para el caso "<span class="codigo">success</span>" que será de tipo "<span class="codigo">httpheader</span>":<br />
<br />
<pre><code>
<result type="httpheader">
</result>
</code></pre><br />
<br />
Como el estatus "<span class="codigo">501</span>" se trata de un error, lo indicaremos usando el parámetro "<span class="codigo">error</span>" y daremos un mensaje con la descripción del error usando el parámetro "<span class="codigo">errorMessage</span>":<br />
<br />
<pre><code>
<result type="httpheader">
<param name="error">501</param>
<param name="errorMessage">Estimado usuario, la funcionalidad que está tratando de usar aún no ha sido implementada. Le pedimos tenga paciencia y regrese más tarde ^^!</param>
</result>
</code></pre><br />
<br />
La declaración completa del <span class="codigo">Action</span> queda de la siguiente forma:<br />
<br />
<pre><code>
<action name="httpstatus501">
<result type="httpheader">
<param name="error">501</param>
<param name="errorMessage">Estimado usuario, la funcionalidad que está tratando de usar aún no ha sido implementada. Le pedimos tenga paciencia y regrese más tarde ^^!</param>
</result>
</action>
</code></pre><br />
<br />
Ahora regresamos nuevamente a la página "<span class="codigo">index.jsp</span>" y agregamos un enlace al action que acabamos de declarar:<br />
<br />
<pre><code>
<s:a action="httpstatus501">501 - Not Implemented</s:a>
</code></pre><br />
<br />
Ejecutamos nuevamente nuestra aplicación y entramos en la siguiente dirección:<br />
<br />
<pre><code>
<a href="http://localhost:8080/results/headers/index.jsp">http://localhost:8080/results/headers/index.jsp</a>
</code></pre><br />
<br />
Hacemos clic en el enlace que acabamos de colocar y deberemos ver el siguiente error:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhPJ9APJuUbT0bPhMcSv3sVyD4RKL4dInFN9yXJuMKfTdCdyxqlt9dZ8vC-w6adyvVzqVquekQknJiz4IncPZfBVDusYIHGOKC8G281kjTJ7O9sgtAa6mCYHmlQCYBGJHF3eMdpJbjtOO8a/s1600/S5_24.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="334" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhPJ9APJuUbT0bPhMcSv3sVyD4RKL4dInFN9yXJuMKfTdCdyxqlt9dZ8vC-w6adyvVzqVquekQknJiz4IncPZfBVDusYIHGOKC8G281kjTJ7O9sgtAa6mCYHmlQCYBGJHF3eMdpJbjtOO8a/s640/S5_24.png" width="640" /></a></div><br />
<br />
Lo cual nos indica que el ejemplo ha funcionado correctamente <span class="codigo">^_^</span>.<br />
<br />
Para terminar con este tutorial, veremos una funcionalidad que nos permite ejecutar un proceso después de que el <span class="codigo">Action</span> se ha ejecutado, pero antes de regresar el <span class="codigo">result</span>:<br />
<br />
<br />
<h2 class="titulo"><span class="codigo">PreResultListener</span></h2>Como dijimos hace un momento: este mecanismo permite realizar algún procesamiento después de que se ha ejecutado nuestro <span class="codigo">Action</span> (o interceptor, pero eso lo veremos en el siguiente tutorial) y antes de que se ejecute el <span class="codigo">result</span>. Esto nos permite que modifiquemos algunas cosas, como por ejemplo, podemos cambiar el <span class="codigo">result</span> al que seremos dirigidos (en vez de ir al <span class="codigo">result</span> "<span class="codigo">input</span>" iremos a "<span class="codigo">success</span>") o modificar los parámetros del <span class="codigo">Action</span> antes de que el <span class="codigo">result</span> se ejecute.<br />
<br />
<span class="codigo">Struts 2</span> proporciona la interface "<span class="codigo">com.opensymphony.xwork2.interceptor.PreResultListener</span>" para este fin. Esta interface puede ser agregada tanto a un <span class="codigo">Action</span> como a un Interceptor, aunque la única forma de agregar este <span class="codigo">PreResultListener</span> es en código dentro del método "<span class="codigo">execute</span>", esto quiere decir que no hay alguna forma de hacerlo en configuración por lo que, si queremos quitar este listener deberemos ir a nuestro código, eliminarlo y volver a compilarlo.<br />
<br />
Veamos cómo funciona este listener con un ejemplo. Primero creamos un nuevo directorio llamado "<span class="codigo">preresult</span>" en la sección de páginas web. Dentro de esta crearemos tres páginas, la primera se llamara "<span class="codigo">index.jsp</span>", la segunda "<span class="codigo">exito.jsp</span>", y la tercera "<span class="codigo">error.jsp</span>". En la primer página colocaremos un formulario que será muy parecido al del primer ejemplo que vimos. En el formulario preguntaremos el nombre del usuario y su lenguaje de programación favorito, si el lenguaje es "<span class="codigo">Java</span>" lo enviaremos a la página "<span class="codigo">exito.jsp</span>", en caso contrario lo enviaremos a la página "<span class="codigo">error.jsp</span>". El formulario queda de la siguiente forma:<br />
<br />
<pre><code>
<s:form action="presult">
<s:textfield name="nombre" label="Nombre" />
<s:select name="lenguaje" label="Lenguaje de Programacion" list="{'Java', 'PHP', '.Net'}" />
<s:submit value="Enviar" />
</s:form>
</code></pre><br />
<br />
No olviden indicar que haremos uso de las etiquetas de <span class="codigo">Struts 2</span> usando la directiva "<span class="codigo">taglib</span>" correspondiente.<br />
<br />
Antes de ver el <span class="codigo">Action</span> crearemos el contenido de las páginas "<span class="codigo">exito.jsp</span>" y "<span class="codigo">error.jsp</span>". El contenido de "<span class="codigo">exito.jsp</span>" será el siguiente:<br />
<br />
<pre><code>
Pasa por favor mi estimado <s:property value="nombre" />, has entrado al mundo de la programación Java.
</code></pre><br />
<br />
Si, sólo el contenido anterior. Y el de "<span class="codigo">error.jsp</span>" será el siguiente:<br />
<br />
<pre><code>
Perdón <s:property value="nombre" />, este es un sitio exclusivo para programadores Java, pero siéntate al fondo a ver si aprendes algo...
</code></pre><br />
<br />
Ahora crearemos un nuevo <span class="codigo">Action</span>, en el paquete "<span class="codigo">actions</span>", llamado "<span class="codigo">PreResultListenerAction</span>" que extenderá de "<span class="codigo">ActionSupport</span>":<br />
<br />
<pre><code>
public class PreResultListenerAction extends ActionSupport
{
}
</code></pre><br />
<br />
A esta clase le colocaremos dos atributos de tipo <span class="codigo">String</span>: "<span class="codigo">nombre</span>" y "<span class="codigo">lenguaje</span>", con sus correspondientes <span class="codigo">getters</span> y <span class="codigo">setters</span>, estos atributos contendrán los valores que son recibidos a través del formulario:<br />
<br />
<pre><code>
private String nombre;
private String lenguaje;
public String getLenguaje()
{
return lenguaje;
}
public void setLenguaje(String lenguaje)
{
this.lenguaje = lenguaje;
}
public String getNombre()
{
return nombre;
}
public void setNombre(String nombre)
{
this.nombre = nombre;
}
</code></pre><br />
<br />
Ahora sobre-escribiremos el método "<span class="codigo">execute</span>" para, en base al lenguaje seleccionado, decidir a cuál página enviar al usuario. Si el lenguaje es "<span class="codigo">Java</span>" lo enviaremos el <span class="codigo">result</span> "<span class="codigo">SUCCESS</span>", en caso contrario lo enviaremos al <span class="codigo">result</span> "<span class="codigo">ERROR</span>":<br />
<br />
<pre><code>
@Override
public String execute() throws Exception
{
if (!"Java".equals(lenguaje))
{
return ERROR;
}
return SUCCESS;
}
</code></pre><br />
<br />
Ahora que tenemos todos los elementos, vamos al archivo "<span class="codigo">struts.xml</span>" y declaramos un nuevo elemento "<span class="codigo"><action></span>" llamado "<span class="codigo">presult</span>" que será implementado por la clase que acabamos de crear:<br />
<br />
<pre><code>
<action name="presult" class="com.javatutoriales.results.actions.PreResultListenerAction">
</action>
</code></pre><br />
<br />
Agregaremos dos <span class="codigo">results</span>, uno llamado "<span class="codigo">succes</span>" que nos enviará a la página "<span class="codigo">/preresult/exito.jsp</span>", y otro llamado "<span class="codigo">error</span>" que nos enviara a la página "<span class="codigo">/preresult/error.jsp</span>":<br />
<br />
<pre><code>
<action name="presult" class="com.javatutoriales.results.actions.PreResultListenerAction">
<result>/preresult/exito.jsp</result>
<result name="error">/preresult/error.jsp</result>
</action>
</code></pre><br />
<br />
Como ya somos expertos en <span class="codigo">results</span>, ¿de qué tipo son los dos <span class="codigo">results</span> anteriores <span class="codigo">;)</span>?<br />
<br />
Ejecutamos nuestra aplicación y entramos a la siguiente dirección:<br />
<br />
<pre><code>
<a href="http://localhost:8080/results/preresult/index.jsp">http://localhost:8080/results/preresult/index.jsp</a>
</code></pre><br />
<br />
Con lo que debemos ver el formulario que creamos hace un momento:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjRP32VW2Aa3lYoHuwXaVOb6ACE0fU499sbq6XsSjPKmDPBl5QsWmbb7s-DwS0a5SnbMlEcKVF6iIcqyrWPfYqZuQliBCh8lTcdyKz2knJR0YiYdGQX8h2T7U85x12XR77iRviMg152STy7/s1600/S5_25.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="156" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjRP32VW2Aa3lYoHuwXaVOb6ACE0fU499sbq6XsSjPKmDPBl5QsWmbb7s-DwS0a5SnbMlEcKVF6iIcqyrWPfYqZuQliBCh8lTcdyKz2knJR0YiYdGQX8h2T7U85x12XR77iRviMg152STy7/s400/S5_25.png" width="400" /></a></div><br />
<br />
Colocamos nuestro nombre, y si en el lenguaje de programación colocamos "<span class="codigo">Java</span>" debemos ser enviados a la página "<span class="codigo">exito.jsp</span>":<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgvOmqJdlidPb7qkulTYwKB6aeF5Jbu5ZEz-bB95IyLFpOMLpINnDIQ4BIgcq7KsIWRxvgTdAmiFsginrVBXB31UY0yI8NDBAIa3-cwjADwYdDpzs6sQNYky7_K2xzWWF543u0r3dMLmffS/s1600/S5_26.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="156" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgvOmqJdlidPb7qkulTYwKB6aeF5Jbu5ZEz-bB95IyLFpOMLpINnDIQ4BIgcq7KsIWRxvgTdAmiFsginrVBXB31UY0yI8NDBAIa3-cwjADwYdDpzs6sQNYky7_K2xzWWF543u0r3dMLmffS/s400/S5_26.png" width="400" /></a></div><br />
<br />
De lo contrario iremos a la página "<span class="codigo">error.jsp</span>":<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg32BcehFdKJcGnZX3Oc-2BxFo0Qnvt3wnlNKZudHjzB6S9ZjU0WtGargEcmIF1Za71KQTjINXerLM18bE-G42oTEIsnaQJ3hz6d3Ls5jyJAuZat7jH2gx1oZtsyU6uNtSq_EH_hvaizDY6/s1600/S5_27.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="249" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg32BcehFdKJcGnZX3Oc-2BxFo0Qnvt3wnlNKZudHjzB6S9ZjU0WtGargEcmIF1Za71KQTjINXerLM18bE-G42oTEIsnaQJ3hz6d3Ls5jyJAuZat7jH2gx1oZtsyU6uNtSq_EH_hvaizDY6/s640/S5_27.png" width="640" /></a></div><br />
<br />
Con esto podemos ver que todo está configurado y funcionado de forma correcta.... ya sé lo que se están preguntado: "¿.... y dónde está el <span class="codigo">PreResultListener</span>? <span class="codigo">:S</span>". Bueno, esto sólo era para ver el comportamiento normal de la aplicación. Ahora agregaremos el listener:<br />
<br />
Cada <span class="codigo">Action</span>, como habíamos visto en el tutorial anterior, tiene una forma de obtener el contexto en el cual se ejecuta (el "<span class="codigo">ActionContext</span>"). Este contexto nos permite obtener un objeto de tipo "<span class="codigo">ActionInvocation</span>" que representa... bueno la invocación del <span class="codigo">Action</span> ^^!. Con este objeto podemos, entre otras cosas, agregar un "<span class="codigo">PreResultListener</span>" a la invocación de este <span class="codigo">Action</span>.<br />
<br />
La obtención de estos dos objetos debe hacerse dentro del método "<span class="codigo">execute</span>":<br />
<br />
<pre><code>
@Override
public String execute() throws Exception
{
ActionInvocation actionInvocation = ActionContext.getContext().getActionInvocation();
}
</code></pre><br />
<br />
"<span class="codigo">PreResultListener</span>" es una interface que tiene sólo un método:<br />
<br />
<pre><code>
void beforeResult(ActionInvocation invocation, String resultCode);
</code></pre><br />
<br />
El cual será invocado cuando la ejecución del <span class="codigo">Action</span> termine, pero antes de que se comience con el procesamiento de <span class="codigo">result</span>.<br />
<br />
Normalmente esta interface se implementa de dos formas: como una clase anidada dentro del <span class="codigo">Action</span>, o como una clase interna anónima dentro del método "<span class="codigo">execute</span>", al momento de agregar el listener. La desición de cuál de las dos usar es, como siempre, una cuestión de diseño y dependerá más de cuántas cosas haremos dentro de método "<span class="codigo">beforeResult</span>". Si haremos muchas cosas lo mejor podría ser crear una clase anidada que se encargue de realizar todas las tareas, si se harán pocas (como es nuestro caso) lo mejor es usar una clase anónima.<br />
<br />
La clase "<span class="codigo">ActionInvocation</span>" tiene un método llamado "<span class="codigo">addResultListener</span>" que es el que usamos para agregar nuestro listener:<br />
<br />
<pre><code>
actionInvocation.addPreResultListener(new PreResultListener()
{
public void beforeResult(ActionInvocation ai, String resultCode)
{
}
});
</code></pre><br />
<br />
Dentro del método "<span class="codigo">beforeResult</span>" colocaremos la lógica que se ejecutará antes de comenzar con el procesamiento del <span class="codigo">result</span>. En nuestro caso haremos un cambio sencillo, no importa qué datos introduzca el usuario, siempre lo regresaremos al <span class="codigo">result</span> "<span class="codigo">success</span>".<br />
<br />
El método "<span class="codigo">beforeResult</span>" recibe el nombre del <span class="codigo">result</span> al que actualmente irá la petición, a través del parámetro "<span class="codigo">resultCode</span>", de esta forma sabremos a dónde se supone que debe ir el usuario. En nuestro caso no importa a cuál <span class="codigo">result</span> vaya a ser enviado originalmente, de todas formas lo reenviaremos al <span class="codigo">result</span> "<span class="codigo">success</span>". Para cambiar el <span class="codigo">result</span> al que lo enviaremos establecemos usamos el método "<span class="codigo">setResultCode</span>" del objeto "<span class="codigo">ActionInvocation</span>" que recibimos como parámetro; a este método le pasamos como argumento el nombre del nuevo <span class="codigo">result</span>, en nuestro caso usaremos la constante "<span class="codigo">SUCCESS</span>", de la siguiente forma:<br />
<br />
<pre><code>
public void beforeResult(ActionInvocation ai, String resultCode)
{
ai.setResultCode(SUCCESS);
}
</code></pre><br />
<br />
Probemos que efectivamente seamos enviados siempre a la página de éxito; ejecutamos nuestra aplicación y entramos a la siguiente dirección:<br />
<br />
<pre><code>
<a href="http://localhost:8080/results/preresult/index.jsp">http://localhost:8080/results/preresult/index.jsp</a>
</code></pre><br />
<br />
Colocamos los datos en nuestro formulario, y no importa qué lenguaje de programación seleccionemos, siempre seremos enviados a la página del resultado de éxito:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi0ib010CvCcXTY3JcrvyVrpo3QPyPEWbkt0crcVJbzibmfb5CqFIRtNx5oV-pTznBWuNYVJmqQLvxDs10tDHpyN83rQH4W5QyQQZX3tDgReInRpKYzU9TzzH3csTzN8vvg7kxtHJsf2Ckh/s1600/S5_28.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="354" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi0ib010CvCcXTY3JcrvyVrpo3QPyPEWbkt0crcVJbzibmfb5CqFIRtNx5oV-pTznBWuNYVJmqQLvxDs10tDHpyN83rQH4W5QyQQZX3tDgReInRpKYzU9TzzH3csTzN8vvg7kxtHJsf2Ckh/s640/S5_28.png" width="640" /></a></div><br />
<br />
Hagamos un cambio más. Al momento de saludar al usuario podremos hacerlo de una forma aún más amable. En vez de saludarlo sólo por su nombre, lo saludaremos diciéndole "<span class="codigo">colega</span>", para lo cual modificaremos el nombre del usuario, agregando siempre la palabra "<span class="codigo">colega</span>" antes del nombre que nos proporciona:<br />
<br />
<pre><code>
public void beforeResult(ActionInvocation ai, String resultCode)
{
ai.setResultCode(SUCCESS);
nombre = " colega " + nombre;
}
</code></pre><br />
<br />
Y listo, ahora podemos volver a ejecutar nuestra aplicación, entrar a la siguiente dirección y proporcionar nuestros datos:<br />
<br />
<pre><code>
<a href="http://localhost:8080/results/preresult/index.jsp">http://localhost:8080/results/preresult/index.jsp</a>
</code></pre><br />
<br />
Al enviar nuestros datos, debemos ser saludados como nos lo merecemos <span class="codigo">^_^</span>:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjTZejRC1XuG5PWUd2YIqlXvu8ad2F9qHoKK_ZcFq83TsLCvQXkHODNnOD_QamArvEU3J2xryHe8aiHrg_LFq3HWgii-PmxPTM_kb_O740HMXCwavwf4MTa8MQgGJFI2z4E79SZXjtkkWCJ/s1600/S5_29.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="156" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjTZejRC1XuG5PWUd2YIqlXvu8ad2F9qHoKK_ZcFq83TsLCvQXkHODNnOD_QamArvEU3J2xryHe8aiHrg_LFq3HWgii-PmxPTM_kb_O740HMXCwavwf4MTa8MQgGJFI2z4E79SZXjtkkWCJ/s640/S5_29.png" width="640" /></a></div><br />
<br />
Como vimos, usando un "<span class="codigo">PreResultListener</span>" es muy fácil modificar el comportamiento de nuestros <span class="codigo">results</span> sin tener que hacer modificaciones en la definición de los mismos. <br />
<br />
Ahora veamos un último y pequeño tema extra acerca de los <span class="codigo">results</span>:<br />
<br />
<br />
<h2 class="titulo"><span class="codigo">Results</span> Globales</h2>Hasta ahora, todos los <span class="codigo">results</span> que hemos visto son declarados dentro de un <span class="codigo">Action</span>, pero ¿qué pasa si necesitamos que más de un <span class="codigo">Action</span> tenga acceso al mismo <span class="codigo">result</span>? Imaginemos el caso de una página para errores globales cuando algo dentro de nuestra aplicación sale mal, y no tenemos idea de por qué (aunque sé que esto nunca pasa <span class="codigo">^^</span>).<br />
<br />
El caso más común es cuando necesitamos una página de "<span class="codigo">login</span>" a la cual enviar a un usuario si está tratando de ejecutar un <span class="codigo">Action</span> que sólo pueden usar los usuarios registrados en el sitio y este no ha iniciado una sesión. <br />
<br />
Los <span class="codigo">results globales</span> funcionan de la misma forma que el resto de los que hemos visto, y declararlos es igual de sencillo que el resto, sólo que en este caso no están dentro de un elemento "<span class="codigo"><action></span>", sino dentro del elemento "<span class="codigo"><global-results></span>" que debe estar siempre dentro de un paquete. Para declarar varios resultados globales lo hacemos de la siguiente forma:<br />
<br />
<pre><code>
<global-results>
<result name="login">/login.jsp</result>
<result name="error">/error.jsp</result>
</global-results>
</code></pre><br />
<br />
Una pregunta que deben estarse haciendo en este momento es: ¿y qué pasa si tengo declarado un <span class="codigo">result</span> con el nombre de "<span class="codigo">error</span>" en mi "<span class="codigo"><action></span>" y otro con el mismo nombre en los <span class="codigo">results</span> globales? Buena pregunta; en ese caso el <span class="codigo">result</span> declarado dentro del <span class="codigo">action</span> tiene mayor precedencia. Cuando indicamos que debemos ir a un <span class="codigo">result</span> (digamos "<span class="codigo">login</span>"), <span class="codigo">Struts 2</span> siempre lo buscará dentro de la declaración del <span class="codigo">action</span> en ejecución, si no lo encuentra irá a buscarlo a los resultados globales, y en caso de que este tampoco sea encontrado ahí, arrojará un error.<br />
<br />
Esto es todo lo que respecta a este tutorial, espero que les sea de utilidad. No olviden dejar sus dudas comentarios y sugerencias en la sección de comentarios o al correo <a href="mailto:programadorjavablog@gmail.com">programadorjavablog@gmail.com</a> (también recuerden hacerse fans de JavaTutoriales en <a href="www.facebook.com/JavaTutoriales">Facebook</a>, <a href="https://plus.google.com/u/0/114078943018704761597">Google+</a>, y <a href="https://twitter.com/#!/JavaTutoriales">Twitter</a> <span class="codigo">^_^</span>). <br />
<br />
Saludos y gracias.<br />
<br />
<span class="negritas">Descarga los archivos de este tutorial desde aquí:</span><br />
<br />
<ul><li><a href="https://sites.google.com/site/javatutoriales/struts2/Struts2Results.zip?attredirects=0&d=1"><span class="ligaArchivo">Tipos de Results</span></a></li>
</ul><br />
<br />
<span class="negritas">Entradas Relacionadas:</span><br />
<br />
<ul><li><a href="http://www.javatutoriales.com/2011/06/struts-2-parte-1-configuracion.html">Parte 1: Configuración</a></li>
<li><a href="http://www.javatutoriales.com/2011/06/struts-2-parte-2-ognl.html">Parte 2: OGNL</a></li>
<li><a href="http://www.javatutoriales.com/2011/10/struts-2-parte-3-trabajo-con.html">Parte 3: Trabajo con Formularios</a></li>
<li><a href="http://www.javatutoriales.com/2011/12/struts-2-parte-4-scopes-de-objetos-web.html">Parte 4: Scopes de Objetos Web</a></li>
<li><a href="http://www.javatutoriales.com/2012/06/struts-2-parte-6-interceptores.html">Parte 6: Interceptores</a></li>
</ul></div>Alexhttp://www.blogger.com/profile/06974037481671868076noreply@blogger.comtag:blogger.com,1999:blog-2012232069289957092.post-34130056038124088212011-12-19T21:54:00.000-08:002012-06-29T07:40:09.480-07:00Struts 2 - Parte 4: Scopes de Objetos Web<div style="text-align: justify;">Cuando estamos desarrollando una aplicación web debemos almacenar información que será procesada de distinta manera. Dependiendo de cuál sea el propósito de esta información querremos que tenga un tiempo de vida más corto o más largo, alguna información deberá permanecer disponible durante todo el momento que viva nuestra aplicación, mientras que otra solo nos interesará que viva durante una petición. Además habrá información que pertenecerá a cada usuario que acceda a la aplicación y que deberá estar disponible sólo para el usuario correspondiente.<br />
<br />
Estos tiempos de vida son llamados <span class="codigo">scopes</span>, y en las aplicaciones web tenemos un cierto número de ellos. Es importante conocer estos <span class="codigo">scopes</span> y ver qué tipo de información es conveniente colocar en cada uno de ellos. A la información que colocamos en los distintos <span class="codigo">scopes</span> les llamamos <span class="negritas">atributos</span>.<br />
<br />
También algunas veces es necesario tener un acceso directamente a los objetos del API de <span class="codigo">Servlets</span>, como el "<span class="codigo">HttpServletRequest</span>", o el "<span class="codigo">ServletContext</span>", o a los parámetros de la petición,<br />
<br />
<span class="codigo">Struts 2</span> nos proporciona una forma simple y elegante, además de diversa, para manejar todas estas cosas y en este tutorial aprenderemos estas maneras <span class="codigo">^_^</span>.<br />
<br />
<a name='more'></a>Las aplicaciones web con Java tienen básicamente tres scopes o tiempos de vida:<br />
<br />
<ul><li><span class="codigo">Application</span>: Es el scope más largo ya que abarca <span class="negritas">el tiempo de vida completo de la apicación</span>; esto es, los datos vivirán mientras la aplicación esté activa.</li>
<li><span class="codigo">Session</span>: Este scope nos permite tener datos que vivirán a lo largo de <span class="negritas">múltiples peticiones HTTP para un mismo usuario</span>, mientras el usuario esté dentro de la aplicación. Cada usuario verá únicamente sus datos y no habrá forma de que vea los de los demás.</li>
<li><span class="codigo">Request</span>: Este es el scope más pequeño, los datos asociados con la petición únicamente estarán disponibles <span class="negritas">mientras se realiza dicha petición</span>.</li>
</ul><br />
<br />
Existen algunos otros scopes, pero estos son los más y importantes, además no todos los frameworks proporcionan acceso a los demás scopes.<br />
<br />
La información o atributos que se puede colocar dentro de estos scopes son <span class="negritas">pares nombre valor</span>, en donde <span class="negritas">el nombre debe ser una cadena</span> y <span class="negritas">el valor puede ser cualquier objeto</span> que nosotros queramos.<br />
<br />
<span class="codigo">Struts 2</span> nos proporciona tres formas para colocar y leer los atributos que se encuentren en estos scopes:<br />
<br />
<ul><li>Implementación de interfaces <span class="codigo">Aware</span></li>
<li>Uso del objeto "<span class="codigo">ActionContext</span>"</li>
<li>Uso del objeto "<span class="codigo">ServletActionContext</span>"</li>
</ul><br />
<br />
Las tres son igual de sencillas y nos permiten obtener más o menos los mismos resultados.<br />
<br />
Además podemos usar dos de los métodos anteriores, las interfaces <span class="codigo">Aware</span> y el objeto "<span class="codigo">ServletActionContext</span>", para obtener acceso directo a los objetos "<span class="codigo">HttpServletRequest</span>" y "<span class="codigo">ServletContext</span>"<br />
<br />
Lo primero que haremos es crear un nuevo proyecto web en NetBeans. Vamos al menú "<span class="codigo">File -> New Project...</span>". En la ventana que aparece seleccionamos la categoría "<span class="codigo">Java Web</span>" y en el tipo de proyecto "<span class="codigo">Web Application</span>". Presionamos el botón "<span class="codigo">Next ></span>" y le damos un nombre y una ubicación a nuestro proyecto; presionamos nuevamente el botón "<span class="codigo">Next ></span>" y en este punto se nos preguntará el servidor que queremos usar. En nuestro caso usaremos el servidor "<span class="codigo">Tomcat 7.0</span>", con la versión 5 de <span class="codigo">JEE</span> y presionamos el botón "<span class="codigo">Finish</span>".<br />
<br />
Una vez que tengamos nuestro proyecto debemos recordar agregar la biblioteca "<span class="codigo">Struts2</span>" (o "<span class="codigo">Struts2Anotaciones</span>" si van a hacer uso de anotaciones, como es mi caso <span class="codigo">^_^</span>), que creamos en <a href="http://www.javatutoriales.com/2011/06/struts-2-parte-1-configuracion.html">el primer tutorial de la serie de <span class="codigo">Struts 2</span></a>. Hacemos clic derecho sobre el nodo "<span class="codigo">Libraries</span>" del proyecto. En el menú que aparece seleccionamos la opción "<span class="codigo">Add Library...</span>". En la ventana que aparece seleccionamos la biblioteca "<span class="codigo">Struts2</span>" o "<span class="codigo">Struts2Anotaciones</span>" y presionamos "<span class="codigo">Add Library</span>". Con esto ya tendremos los jars de <span class="codigo">Struts 2</span> en nuestro proyecto.<br />
<br />
Ahora configuramos el filtro "<span class="codigo">struts2</span>" en el deployment descriptor. Abrimos el archivo "<span class="codigo">web.xml</span>" y colocamos el siguiente contenido, como se explicó en <a href="http://www.javatutoriales.com/2011/06/struts-2-parte-1-configuracion.html">el primer tutorial de la serie</a>:<br />
<br />
<pre><code>
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</code></pre><br />
<br />
Comenzaremos viendo cómo poder agregar y leer datos en los scopes anteriores, y cómo mostrarlos en nuestras JSPs.<br />
<br />
<br />
<h2 class="titulo">Manejo de Scopes</h2><h3 class="subtitulo">Manejo de Scopes usando interfaces Aware</h3><span class="codigo">Struts 2</span> proporciona un conjunto de interfaces llamadas interfaces <span class="codigo">Aware</span> (bueno, creo que solo yo las llamo así <span class="codigo">^_^</span>) las cuales permite que nuestros <span class="codigo">Actions</span> reciban cierta información, al momento de inicializarlos. La mayoría de estas interfaces proporcionan un <span class="codigo">Map</span> con los pares nombre valor de los atributos de cada uno de los scopes. <br />
<br />
<span class="codigo">Struts 2</span> contiene las siguientes interfaces <span class="codigo">Aware</span> para obtener y leer atributos de los scopes:<br />
<br />
<ul><li><span class="codigo">ApplicationAware</span></li>
<li><span class="codigo">SessionAware</span></li>
<li><span class="codigo">RequestAware</span></li>
</ul><br />
<br />
Además de un par de interfaces para recibir el "<span class="codigo">HttpServletRequest</span>" y el "<span class="codigo">ServletContext</span>", pero hablaremos de ellas en su momento.<br />
<br />
El uso de estas interfaces es muy intuitivo, creamos un <span class="codigo">Action</span> que implemente la interface que nos interese. Cada una de estas interfaces proporciona un método <span class="codigo">setter</span> que permite inyectar el mapa con los atributos del scope correspondiente.<br />
<br />
Los métodos de estas interfaces se muestran a continuación:<br />
<br />
<pre><code>
public interface RequestAware
{
public void setRequest(Map<String, Object> map);
}
public interface SessionAware
{
public void setSession(Map<String, Object> map);
}
public interface ApplicationAware
{
public void setApplication(Map<String, Object> map);
}
</code></pre><br />
<br />
La forma de los tres métodos es prácticamente la misma: no regresan nada y reciben como único parámetro un <span class="codigo">Map</span> donde el nombre del atributo es un <span class="codigo">String</span> y el valor es un <span class="codigo">Object</span>.<br />
<br />
Como la forma de trabajo con estas tres interfaces es también el mismo, hagamos un ejemplo de todo en uno.<br />
<br />
Lo primero que haremos es crear una nueva <span class="codigo">JSP</span> llamada "<span class="codigo">datosScopesInterfaces.jsp</span>":<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjV1KXNFGGcaQeJCLCW4IT88NWcIxzJ0V_NSn7Dg6xAOt412owRGUWPqovh6WtjfNLgloYMvGZlcWiGM_GyndcI07S8QFESiEy7EpBBwXwvT3pTNB8aph8lFM76ppxPh8bdd3S5rUlfeTZo/s1600/S4_1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="311" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjV1KXNFGGcaQeJCLCW4IT88NWcIxzJ0V_NSn7Dg6xAOt412owRGUWPqovh6WtjfNLgloYMvGZlcWiGM_GyndcI07S8QFESiEy7EpBBwXwvT3pTNB8aph8lFM76ppxPh8bdd3S5rUlfeTZo/s400/S4_1.png" width="400" /></a></div><br />
<br />
Esta página contendrá un formulario que nos permitirá establecer el valor de algunos datos que colocaremos posteriormente en cada uno de los scopes. Como usaremos las etiquetas de <span class="codigo">Struts 2</span> para generar este formulario, lo primero que debemos hacer es indicar, con la directiva "<span class="codigo">taglib</span>", que usaremos la bibliotecas de etiquetas de <span class="codigo">Struts 2</span>:<br />
<br />
<pre><code>
<%@taglib prefix="s" uri="/struts-tags" %>
</code></pre><br />
<br />
Ahora crearemos un formulario con unos cuantos campos de texto, para colocar los valores que enviaremos a nuestro <span class="codigo">Action</span> para ser procesados. Cada uno de estos valores tendrá un nombre que indica en cuál scope quedará. Este formulario se enviará a un <span class="codigo">Action</span> llamado "<span class="codigo">scopesInterfaces</span>" que crearemos un poco más adelante:<br />
<br />
<pre><code>
<s:form action="scopesInterfaces">
<s:textfield name="datoSesion" label="Sesion" />
<s:textfield name="datoRequest" label="Request" />
<s:textfield name="datosAplicacion" label="Aplicacion" />
<s:submit />
</s:form>
</code></pre><br />
<br />
Ahora crearemos un nuevo paquete, llamado "<span class="codigo">com.javatutoriales.actions</span>", para colocar nuestros <span class="codigo">Actions</span>. Dentro de este paquete crearemos una clase llamada "<span class="codigo">ScopesInterfacesAction</span>", esta clase deberá extender de "<span class="codigo">ActionSupport</span>":<br />
<br />
<pre><code>
public class ScopesInterfacesAction extends ActionSupport
{
}
</code></pre><br />
<br />
Agregaremos un atributo de tipo <span class="codigo">String</span>, con sus correspondientes <span class="codigo">setters</span>, para cada uno de los campos del formulario:<br />
<br />
<pre><code>
public class ScopesInterfacesAction extends ActionSupport
{
private String datoSesion;
private String datoRequest;
private String datosAplicacion;
public void setDatoRequest(String datoRequest)
{
this.datoRequest = datoRequest;
}
public void setDatoSesion(String datoSesion)
{
this.datoSesion = datoSesion;
}
public void setDatosAplicacion(String datosAplicacion)
{
this.datosAplicacion = datosAplicacion;
}
}
</code></pre><br />
<br />
Sólo hemos colocado los <span class="codigo">setters</span> porque estableceremos estos valores en los scopes correspondientes y, por lo tanto, los leeremos posteriormente de una manera diferente <span class="codigo">^_^</span>.<br />
<br />
Ahora el tema que nos interesa, las interfaces. Como toda buena interface de Java, para implementar las interfaces <span class="codigo">Aware</span> lo primero que debemos hacer es declarar que nuestra clase implementará estas interfaces; implementaremos las tres interfaces de una sola vez:<br />
<br />
<pre><code>
public class ScopesInterfacesAction extends ActionSupport implements RequestAware, SessionAware, ApplicationAware
{
}
</code></pre><br />
<br />
Como mencioné antes: estas interfaces tienen solo un método cada una (el método que mencioné anteriormente). Normalmente colocamos un atributo, de tipo "<span class="codigo">Map<String, Object></span>" para almacenar el argumento que es establecido por el framework y poder usarlo posteriormente en el método "<span class="codigo">execute</span>" de nuestro <span class="codigo">Action</span>; después todo lo que hay que hacer es implementar cada uno de los métodos de estas interfaces de la siguiente forma:<br />
<br />
<pre><code>
private Map<String, Object> sesion;
private Map<String, Object> application;
private Map<String, Object> request;
public void setRequest(Map<String, Object> map)
{
this.request = map;
}
public void setApplication(Map<String, Object> map)
{
this.application = map;
}
public void setSession(Map<String, Object> map)
{
this.sesion = map;
}
</code></pre><br />
<br />
En el método "<span class="codigo">execute</span>" lo único que haremos es agregar el valor recibido desde el formulario al scope correspondiente, usando el "<span class="codigo">Map</span>" apropiado:<br />
<br />
<pre><code>
@Override
public String execute() throws Exception
{
application.put("datoAplicacion", datosAplicacion);
sesion.put("datoSesion", datoSesion);
request.put("datoRequest", datoRequest);
return SUCCESS;
}
</code></pre><br />
<br />
Así de simple <span class="codigo">^^</span>.<br />
<br />
Para terminar este <span class="codigo">Action</span> debemos agregar las anotaciones que vimos en <a href="http://www.javatutoriales.com/2011/06/struts-2-parte-1-configuracion.html">el primer tutorial de la serie</a>, y que a estas alturas ya conocemos de memoria. El nombre de nuestro <span class="codigo">Action</span> será "<span class="codigo">scopesInterfaces</span>" y el resultado será enviado a la página "<span class="codigo">resultado.jsp</span>":<br />
<br />
<pre><code>
@Namespace(value = "")
@Action(value = "scopesInterfaces", results ={@Result(name = "success", location = "/resultado.jsp")})
public class ScopesInterfacesAction extends ActionSupport implements RequestAware, SessionAware, ApplicationAware
{
}
</code></pre><br />
<br />
La clase "<span class="codigo">ScopesInterfacesAction</span>" completa queda de la siguiente forma:<br />
<br />
<pre><code>
@Namespace(value = "")
@Action(value = "scopesInterfaces", results ={@Result(name = "success", location = "/resultado.jsp")})
public class ScopesInterfacesAction extends ActionSupport implements RequestAware, SessionAware, ApplicationAware
{
private String datoSesion;
private String datoRequest;
private String datosAplicacion;
private Map<String, Object> sesion;
private Map<String, Object> application;
private Map<String, Object> request;
@Override
public String execute() throws Exception
{
application.put("datoAplicacion", datosAplicacion);
sesion.put("datoSesion", datoSesion);
request.put("datoRequest", datoRequest);
return SUCCESS;
}
public void setRequest(Map<String, Object> map)
{
this.request = map;
}
public void setApplication(Map<String, Object> map)
{
this.application = map;
}
public void setSession(Map<String, Object> map)
{
this.sesion = map;
}
public void setDatoRequest(String datoRequest)
{
this.datoRequest = datoRequest;
}
public void setDatoSesion(String datoSesion)
{
this.datoSesion = datoSesion;
}
public void setDatosAplicacion(String datosAplicacion)
{
this.datosAplicacion = datosAplicacion;
}
}
</code></pre><br />
<br />
Ahora crearemos la página "<span class="codigo">resultado.jsp</span>", en la raíz de las páginas web, para comprobar que los datos se han establecido correctamente. En esta página primero indicaremos que haremos uso de la biblioteca de etiquetas de <span class="codigo">Struts 2</span>, como lo hicimos anteriormente. Además usaremos la etiqueta "<span class="codigo"><s:property></span>" para mostrar el valor en cada uno de los scopes. Para obtener el valor usaremos <span class="codigo">OGNL</span> con el cual, como recordarán del segundo tutorial de la serie, tiene una sintaxis especial para los objetos que se encuentran en los scopes anteriores. Si no recuerdan la sintaxis pueden regresar a ver <a href="http://www.javatutoriales.com/2011/06/struts-2-parte-2-ognl.html">el tutorial correspondiente</a>... o continuar leyendo este.<br />
<br />
Recuerden que <span class="codigo">Struts 2</span> coloca en el "<span class="codigo">ActionContext</span>" algunos objetos extra además del objeto raíz, para acceder a estos objetos necesitamos usar un operador especial, el operador "<span class="codigo">#</span>", junto con el nombre del objeto al que queremos acceder. En este caso <span class="codigo">Struts 2</span> coloca 3 objetos especiales que hacen referencia cada uno a uno a uno de los distintos scopes:<br />
<br />
<ul><li><span class="codigo">application</span></li>
<li><span class="codigo">session</span></li>
<li><span class="codigo">request</span></li>
</ul><br />
<br />
Y como habíamos visto, es posible obtener el valor de los atributos colocados en estos scopes usando el operador punto ("<span class="codigo">.</span>") o colocando el nombre del parámetro entre corchetes ("<span class="codigo">[</span>" y "<span class="codigo">]</span>"). <br />
<br />
En este caso, para mostrar el valor almacenado en el <span class="codigo">request</span> debemos hacer:<br />
<br />
<pre><code>
<s:property value="#request.datoRequest" />
</code></pre><br />
<br />
Para el resto de los parámetros se hace algo similar. Al final la página queda de la siguiente forma:<br />
<br />
<pre><code>
Request: <s:property value="#request.datoRequest" /><br />
Sesión: <s:property value="#session.datoSesion" /><br />
Aplicacion: <s:property value="#application.datoAplicacion" /><br />
</code></pre><br />
<br />
Cuando ejecutemos la aplicación y entremos a la siguiente dirección:<br />
<br />
<pre><code>
<a href="http://localhost:8080/scopes/datosScopesInterfaces.jsp">http://localhost:8080/scopes/datosScopesInterfaces.jsp</a>
</code></pre><br />
<br />
Debemos ver el formulario que creamos anteriormente. Si colocamos los siguientes datos:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhnGhosv8OaB-TmeEkkStMuoAQHyeWlff8ciB6XkcbgO3z78iHhU6TV2EEA2wauaSWmTqdf9DQaM0PFIhWFx0G1yVM3suSzhPCLZXQpFkXr5JqMttJoZsTSjOhVuAqnJA-7iZGqyS5N3Q2q/s1600/S4_2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="283" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhnGhosv8OaB-TmeEkkStMuoAQHyeWlff8ciB6XkcbgO3z78iHhU6TV2EEA2wauaSWmTqdf9DQaM0PFIhWFx0G1yVM3suSzhPCLZXQpFkXr5JqMttJoZsTSjOhVuAqnJA-7iZGqyS5N3Q2q/s640/S4_2.png" width="640" /></a></div><br />
<br />
Y presionamos el botón de enviar, veremos la siguiente salida:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjqfneg5r233cbDQhsvtWyqcoikFgAltozzMObLO2oXxrQMoZm6AXVpUa_QmIQUV6D6r6PH8Jz9ES6xUvxTYJ_X6oO3ffQ_UsTh6HLLtVrDzd8AK7bcQjzjgQbaUn4PE9MOMDX8ZRc3QTRP/s1600/S4_3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="283" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjqfneg5r233cbDQhsvtWyqcoikFgAltozzMObLO2oXxrQMoZm6AXVpUa_QmIQUV6D6r6PH8Jz9ES6xUvxTYJ_X6oO3ffQ_UsTh6HLLtVrDzd8AK7bcQjzjgQbaUn4PE9MOMDX8ZRc3QTRP/s640/S4_3.png" width="640" /></a></div><br />
<br />
Ahora, ¿cómo podemos comprobar que cada dato está efectivamente en los scopes indicados? Bueno para esto hay varias formas. Primero, para comprobar el atributo del <span class="codigo">request</span> podemos entrar directamente a la página del resultado:<br />
<br />
<pre><code>
<a href="http://localhost:8080/scopes/resultado.jsp">http://localhost:8080/scopes/resultado.jsp</a>
</code></pre><br />
<br />
Con esto estamos creando una nueva petición, por lo que el atributo que teníamos anteriormente debería desaparecer:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgriKQ8zxyaKOOGeNN5EAeOEBgf3u9QGcHvv8KpnLu4cBqCeIVTzVfgezEALxtLGXfNb4zhhZwHDQgW8NS1fOLVuUSuHY_5QyoHc-hYbAKOWnJrlra0aYmsD7nqca1xEOm8xEYjw2e7jAh5/s1600/S4_4.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="284" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgriKQ8zxyaKOOGeNN5EAeOEBgf3u9QGcHvv8KpnLu4cBqCeIVTzVfgezEALxtLGXfNb4zhhZwHDQgW8NS1fOLVuUSuHY_5QyoHc-hYbAKOWnJrlra0aYmsD7nqca1xEOm8xEYjw2e7jAh5/s640/S4_4.png" width="640" /></a></div><br />
<br />
Para el dato de la <span class="codigo">sesión</span>, podemos esperar a que termine nuestra sesión de forma automática (después de 30 minutos), podemos usar otro navegador para entrar a la misma dirección, o simplemente podemos terminar las sesiones activas de nuestro navegador. Con esto los atributos de la sesión deben desaparecer: <br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjLLkIK-Y_QlbinbyS7olPc0E5rbdcJSj-oNSUMYdeHfi0x1gqBUwvaos2nHTvJM0zMMWGKvOJAj0i0AzmzQxk7pArhBF6l0-b17JuWK6API2qGy1sukpFxUOqJrnFIklxqgJWAOay_UNe0/s1600/S4_5.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="516" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjLLkIK-Y_QlbinbyS7olPc0E5rbdcJSj-oNSUMYdeHfi0x1gqBUwvaos2nHTvJM0zMMWGKvOJAj0i0AzmzQxk7pArhBF6l0-b17JuWK6API2qGy1sukpFxUOqJrnFIklxqgJWAOay_UNe0/s640/S4_5.png" width="640" /></a></div><br />
<br />
Finalmente, y como podemos ver por la imagen anterior del explorer, los atributos del scope aplicación permanecerán hasta que detengamos nuestro servidor (con lo cual, por cierto, ya no podremos entrar al sitio <span class="codigo">^_^</span>).<br />
<br />
Como podemos ver, esta forma de establecer (y leer) atributos de los distintos scopes es bastante sencilla pero, como dijimos al inicio del tutorial: no es la única forma. Ahora veremos la segunda forma de hacer esto: usado el objeto "<span class="codigo">ActionContext</span>"<br />
<br />
<br />
<h2 class="subtitulo">Manejo de Atributos de Scope usando ActionContext</h2>La segunda forma que tenemos de manejar los atributos de los scopes es a través de un objeto especial de <span class="codigo">Struts 2</span> llamado "<span class="codigo">ActionContext</span>". Este objeto es el contexto en al cual el <span class="codigo">Action</span> se ejecuta. Cada contexto es básicamente un contenedor para objetos que un <span class="codigo">Action</span> necesita para su ejecución, como la sesión, sus parámetros, etc.<br />
<br />
Dentro de los objetos que se encuentran dentro del "<span class="codigo">ActionContext</span>" están justamente los mapas que contienen los atributos de <span class="codigo">sesion</span> y <span class="codigo">aplicación</span> (así es, no se pueden establecer los atributos de <span class="codigo">request</span> usando este objeto), que son los que nos interesan para esta pare del tutorial.<br />
<br />
Obtener una referencia al "<span class="codigo">ActionContext</span>" es en realidad muy sencillo ya que, aunque este objeto no es un singleton como tal, lo obtenemos como si fuera uno; veremos esto dentro de un momento, primero crearemos una nueva página que contendrá un formulario como el anterior.<br />
<br />
Creamos una nueva página llamada "<span class="codigo">datosScopesActionContext.jsp</span>". Esta página contendrá un formulario parecido al que creamos anteriormente, con la diferencia de que los datos de este serán procesados por un <span class="codigo">Action</span> distinto:<br />
<br />
<pre><code>
<s:form action="scopesActionContext">
<s:textfield name="datoSesion" label="Sesion" />
<s:textfield name="datosAplicacion" label="Aplicacion" />
<s:submit />
</s:form>
</code></pre><br />
<br />
Ahora crearemos, en el paquete "<span class="codigo">actions</span>", una nueva clase llamada "<span class="codigo">ScopesActionContextAction</span>" que extienda de "<span class="codigo">ActionSupport</span>":<br />
<br />
<pre><code>
public class ScopesActionContextAction extends ActionSupport
{
}
</code></pre><br />
<br />
Esta clase tendrá dos atributos de tipo <span class="codigo">String</span>, uno para el dato que irá en sesión y otro para el dato que irá en el scope aplicación, con sus correspondientes <span class="codigo">setters</span>:<br />
<br />
<pre><code>
public class ScopesActionContextAction extends ActionSupport
{
private String datoSesion;
private String datosAplicacion;
public void setDatoSesion(String datoSesion)
{
this.datoSesion = datoSesion;
}
public void setDatosAplicacion(String datosAplicacion)
{
this.datosAplicacion = datosAplicacion;
}
}
</code></pre><br />
<br />
Lo último que esta clase necesita es sobre-escribir su método "<span class="codigo">execute</span>" para, haciendo uso del "<span class="codigo">ActionContext</span>", establecer los valores en los scopes adecuados.<br />
<br />
Para obtener una instancia del "<span class="codigo">ActionContext</span>" se usa el método estático "<span class="codigo">getContext()</span>" que nos regresa la instancia de "<span class="codigo">ActionContext</span>" adecuada para el <span class="codigo">Action</span> que estamos ejecutando:<br />
<br />
<pre><code>
ActionContext contexto = ActionContext.getContext();
</code></pre><br />
<br />
Y una vez teniendo una referencia al "<span class="codigo">ApplicationContext</span>" lo único que debemos hacer es usar el método "<span class="codigo">getApplication()</span>" para obtener un mapa con los atributos del scope <span class="codigo">application</span> y "<span class="codigo">getSession()</span>" para obtener un mapa con los atributos del scope <span class="codigo">session</span>:<br />
<br />
<pre><code>
Map<String, Object> application = contexto.getApplication();
Map<String, Object> sesion = contexto.getSession();
</code></pre><br />
<br />
El resto es solo colocar los atributos que recibidos del formulario en los mapas correspondientes:<br />
<br />
<pre><code>
application.put("datoAplicacion", datosAplicacion);
sesion.put("datoSesion", datoSesion);
</code></pre><br />
<br />
El método "<span class="codigo">execute</span>" queda de la siguiente forma:<br />
<br />
<pre><code>
@Override
public String execute() throws Exception
{
ActionContext contexto = ActionContext.getContext();
Map<String, Object> application = contexto.getApplication();
Map<String, Object> sesion = contexto.getSession();
application.put("datoAplicacion", datosAplicacion);
sesion.put("datoSesion", datoSesion);
return SUCCESS;
}
</code></pre><br />
<br />
Lo último que falta es anotar nuestro <span class="codigo">Action</span> para indicar que responderá al nombre de "<span class="codigo">scopesActionContext</span>" y que regresará a la página "<span class="codigo">/resultado.jsp</span>" (la cual creamos para el ejemplo anterior así que ahora la reutilizaremos):<br />
<br />
<pre><code>
@Namespace(value = "/")
@Action(value = "scopesActionContext", results =
{
@Result(name = "success", location = "/resultado.jsp")
})
public class ScopesActionContextAction extends ActionSupport
{
}
</code></pre><br />
<br />
La clase "<span class="codigo">ScopesActionContextAction</span>" completa queda de la siguiente forma:<br />
<br />
<pre><code>
@Namespace(value = "/")
@Action(value = "scopesActionContext", results ={@Result(name = "success", location = "/resultado.jsp")})
public class ScopesActionContextAction extends ActionSupport
{
private String datoSesion;
private String datosAplicacion;
@Override
public String execute() throws Exception
{
ActionContext contexto = ActionContext.getContext();
Map<String, Object> application = contexto.getApplication();
Map<String, Object> sesion = contexto.getSession();
application.put("datoAplicacion", datosAplicacion);
sesion.put("datoSesion", datoSesion);
return SUCCESS;
}
public void setDatoSesion(String datoSesion)
{
this.datoSesion = datoSesion;
}
public void setDatosAplicacion(String datosAplicacion)
{
this.datosAplicacion = datosAplicacion;
}
}
</code></pre><br />
<br />
Ahora que ya tenemos todo listo ejecutamos la aplicación y al entrar a la siguiente dirección:<br />
<br />
<pre><code>
<a href="http://localhost:8080/scopes/datosScopesActionContext.jsp">http://localhost:8080/scopes/datosScopesActionContext.jsp</a>
</code></pre><br />
<br />
Debemos ver el siguiente formulario, muy parecido al anterior:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhWkKu-S9YVu0_G1nLyqWwpXROTtt5w1czBw913ILUKTbFmxDZJwf4BArafxAYnJF3nBdA3Y6KJtkXTPpo1fuvyGfq89XmVPiMiy5iiAIuozDcZ0SA9Bqa_HbSmUmFoUHgZuQ8ZUfzkE6Bs/s1600/S4_6.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="270" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhWkKu-S9YVu0_G1nLyqWwpXROTtt5w1czBw913ILUKTbFmxDZJwf4BArafxAYnJF3nBdA3Y6KJtkXTPpo1fuvyGfq89XmVPiMiy5iiAIuozDcZ0SA9Bqa_HbSmUmFoUHgZuQ8ZUfzkE6Bs/s640/S4_6.png" width="640" /></a></div><br />
<br />
Cuando ingresemos algunos valores y demos clic en el botón de enviar, veremos un resultado como el siguiente:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjYW9q-6-rRz_VL0L2TaTo3HPgWK6ztMWuIqPRv4IpjZjguv0ytgU0SBbdQIEs3DrzEQtsE3HxadJDvadGTHVqOGPfk2H6V_h36Zg2Bpa_4ImcfiRSm64dx7VZ0urxvCL8GT-CgYQOwMeEN/s1600/S4_7.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="284" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjYW9q-6-rRz_VL0L2TaTo3HPgWK6ztMWuIqPRv4IpjZjguv0ytgU0SBbdQIEs3DrzEQtsE3HxadJDvadGTHVqOGPfk2H6V_h36Zg2Bpa_4ImcfiRSm64dx7VZ0urxvCL8GT-CgYQOwMeEN/s640/S4_7.png" width="640" /></a></div><br />
<br />
El dato del <span class="codigo">request</span> queda vacio ya que no lo hemos establecido en esta ocasión.<br />
<br />
Podemos ver que esta segunda forma de establecer los datos es también muy simple de utilizar y da buenos resultados. <br />
<br />
Veremos la tercer y última forma de manejar los atributos de los scopes <span class="codigo">request</span>, <span class="codigo">session</span> y <span class="codigo">application</span>. Debo decir que esta tercer forma no es recomendable ya que rompe con el esquema de trabajo de <span class="codigo">Struts 2</span>, el cual oculta detalles de la especificación de <span class="codigo">Servlets</span>, pero eso lo veremos en un momento.<br />
<br />
<br />
<h3 class="subtitulo">Manejo de Atributos de Scope usando ServletActionContext</h3>Con esta última forma tendremos acceso directo a los objetos "<span class="codigo">HttpServletRequest</span>" y "<span class="codigo">ServletContext</span>", de la especificación de <span class="codigo">Servlet</span>s; haciendo uso de otro objeto especial de <span class="codigo">Struts 2</span>: "<span class="codigo">ServletActionContext</span>".<br />
<br />
Este objeto especial "<span class="codigo">ServletActionContext</span>" contiene una serie de métodos estáticos que nos dan acceso a otros objetos de utilidad (entre ellos "<span class="codigo">ActionContext</span>"). Los métodos que en este momento nos interesan son: "<span class="codigo">getRequest</span>", con el que podemos obtener el objeto "<span class="codigo">HttpServletRequest</span>" (desde el cual tenemos además acceso a la sesión) asociado con la petición que el <span class="codigo">Action</span> está sirviendo, y "<span class="codigo">getServletContext</span>", que nos regresa el objeto "<span class="codigo">ServletContext</span>" que representa el contexto de la aplicación web.<br />
<br />
El uso de estos dos métodos es muy directo y lo veremos en un momento, pero primero crearemos una nueva página <span class="codigo">JSP</span> que contendrá el formulario, como los anteriores, que nos permitirá introducir los datos que se agregarán como atributos de cada uno de los scopes. Esta página se llamará "<span class="codigo">datosScopesServletActionContext.jsp</span>" y contendrá un formulario como el primero que creamos con la diferencia de los datos de este formulario serán procesados por otro <span class="codigo">Action</span>:<br />
<br />
<pre><code>
<s:form action="scopesServletActionContext">
<s:textfield name="datoSesion" label="Sesion" />
<s:textfield name="datoRequest" label="Request" />
<s:textfield name="datosAplicacion" label="Aplicacion" />
<s:submit />
</s:form>
</code></pre><br />
<br />
Ahora creamos, en el paquete "<span class="codigo">actions</span>", una nueva clase llamada "<span class="codigo">ScopesServletActionContextAction</span>" que extienda de "<span class="codigo">ActionSupport</span>":<br />
<br />
<pre><code>
public class ScopesServletActionContextAction extends ActionSupport
{
}
</code></pre><br />
<br />
Esta clase tendrá tres atributos de tipo <span class="codigo">String</span>, con sus correspondientes <span class="codigo">setters</span>, uno para cada valor que recibimos del formulario:<br />
<br />
<pre><code>
public class ScopesServletActionContextAction extends ActionSupport
{
private String datoSesion;
private String datoRequest;
private String datosAplicacion;
public void setDatoRequest(String datoRequest)
{
this.datoRequest = datoRequest;
}
public void setDatoSesion(String datoSesion)
{
this.datoSesion = datoSesion;
}
public void setDatosAplicacion(String datosAplicacion)
{
this.datosAplicacion = datosAplicacion;
}
}
</code></pre><br />
<br />
Ahora sobre-escribiremos el método "<span class="codigo">execute</span>" para, haciendo uso de los métodos estáticos de la clase "<span class="codigo">ServletActionContext</span>", obtener referencias al "<span class="codigo">HttpServletRequest</span>" y al "<span class="codigo">ServletContext</span>". Como había dicho "<span class="codigo">ServletActionContext</span>" nos proporciona métodos estáticos que nos regresan estas referencias de forma directa, por lo que lo único que tenemos que hacer es invocarlos:<br />
<br />
<pre><code>
HttpServletRequest request = ServletActionContext.getRequest();
ServletContext context = ServletActionContext.getServletContext();
</code></pre><br />
<br />
La sesión (el objeto "<span class="codigo">HttpSession</span>") se obtiene usando el método "<span class="codigo">getSession()</span>" del objeto "<span class="codigo">request</span>":<br />
<br />
<pre><code>
HttpSession sesion = request.getSession();
</code></pre><br />
<br />
Y ya con estas referencias podemos obtener los atributos de los scopes correspondientes como lo hacemos cuando trabajamos directamente con <span class="codigo">Servlet</span>s: usando el método "<span class="codigo">setAttribute</span>" de cada uno de los objetos:<br />
<br />
<pre><code>
request.setAttribute("datoRequest", datoRequest);
sesion.setAttribute("datoSesion", datoSesion);
context.setAttribute("datoAplicacion", datosAplicacion);
</code></pre><br />
<br />
Y eso es todo lo que hay que hacer. El método "<span class="codigo">execute</span>" queda de la siguiente forma:<br />
<br />
<pre><code>
@Override
public String execute() throws Exception
{
HttpServletRequest request = ServletActionContext.getRequest();
ServletContext context = ServletActionContext.getServletContext();
HttpSession sesion = request.getSession();
request.setAttribute("datoRequest", datoRequest);
sesion.setAttribute("datoSesion", datoSesion);
context.setAttribute("datoAplicacion", datosAplicacion);
return SUCCESS;
}
</code></pre><br />
<br />
Lo único que resta hacer es colocar las anotaciones para indicar que esta clase será un <span class="codigo">Action</span>. Al igual que en los casos anteriores, reutilizaremos la página "<span class="codigo">resultados.jsp</span>". Como ya conocemos estas anotaciones de memoria, solo mostraré como queda finalmente la clase "<span class="codigo">ScopesServletActionContextAction</span>"<br />
<br />
<pre><code>
@Namespace(value = "")
@Action(value = "scopesServletActionContext", results ={@Result(name = "success", location = "/resultado.jsp")})
public class ScopesServletActionContextAction extends ActionSupport
{
private String datoSesion;
private String datoRequest;
private String datosAplicacion;
@Override
public String execute() throws Exception
{
HttpServletRequest request = ServletActionContext.getRequest();
ServletContext context = ServletActionContext.getServletContext();
HttpSession sesion = request.getSession();
request.setAttribute("datoRequest", datoRequest);
sesion.setAttribute("datoSesion", datoSesion);
context.setAttribute("datoAplicacion", datosAplicacion);
return SUCCESS;
}
public void setDatoRequest(String datoRequest)
{
this.datoRequest = datoRequest;
}
public void setDatoSesion(String datoSesion)
{
this.datoSesion = datoSesion;
}
public void setDatosAplicacion(String datosAplicacion)
{
this.datosAplicacion = datosAplicacion;
}
}
</code></pre><br />
<br />
Cuando ejecutemos nuestra aplicación y entremos a la siguiente dirección:<br />
<br />
<pre><code>
<a href="http://localhost:8080/scopes/datosScopesServletActionContext.jsp">http://localhost:8080/scopes/datosScopesServletActionContext.jsp</a>
</code></pre><br />
<br />
Debemos ver el mismo formulario que en el primer ejemplo:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj-7ustqXSjOYfJvkFSlrRxxp1VhUYFsKSLW4EUKiIyQ7-fB2U5sjS4xf0Us5ttYghdqXdfP06vS9vyDVehCTSHWkVQn0a58LYY2KBRVvZgRJ5ghJyvaDnJ15FCDM96fzEuA1rBACCym8TK/s1600/S4_8.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="258" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj-7ustqXSjOYfJvkFSlrRxxp1VhUYFsKSLW4EUKiIyQ7-fB2U5sjS4xf0Us5ttYghdqXdfP06vS9vyDVehCTSHWkVQn0a58LYY2KBRVvZgRJ5ghJyvaDnJ15FCDM96fzEuA1rBACCym8TK/s640/S4_8.png" width="640" /></a></div><br />
<br />
Al rellenar los campos con algunos datos y enviar el formulario debemos ver el siguiente resultado:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgPn_lY5wOsJnPwkMup1unrJ8e67qpZjtma7j5t_VabGlDi887X2phanaJPoZXyQ4QDXvLEhax2vtM1Z_TU3NufFDaVt6zDYWKS_-IUIkqRzNOPyzdD0pH-lPYoDEclYEBvhMnFH_RoqdcR/s1600/S4_9.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="258" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgPn_lY5wOsJnPwkMup1unrJ8e67qpZjtma7j5t_VabGlDi887X2phanaJPoZXyQ4QDXvLEhax2vtM1Z_TU3NufFDaVt6zDYWKS_-IUIkqRzNOPyzdD0pH-lPYoDEclYEBvhMnFH_RoqdcR/s640/S4_9.png" width="640" /></a></div><br />
<br />
Como podemos ver esta tercer forma abarca en realidad los dos temas del tutorial, establecer valores en los scopes de la aplicación web y obtener los objetos "<span class="codigo">HttpServletRequest</span>" y "<span class="codigo">ServletContext</span>", así que a continuación veremos la segunda forma de obtener estos objetos:<br />
<br />
<br />
<h2 class="titulo">Obtención de Objetos "<span class="codigo">HttpServletRequest</span>" y "<span class="codigo">ServletContext</span>"</h2><br />
<h3 class="subtitulo">Obtención de Objetos de Servlet usando interfaces Aware</h3>En la primer parte del tutorial vimos cómo trabajan las interfaces <span class="codigo">Aware</span>, y en el ejemplo anterior vimos cómo tener acceso directo a los objetos de la especificación de <span class="codigo">Servlet</span>s. Así que en este último ejemplo veremos una forma de obtener estos objetos, usando las interfaces <span class="codigo">Aware</span>.<br />
<br />
Para no perder la costumbre de este tutorial, lo primero que haremos es crear una nueva <span class="codigo">JSP</span> llamada "<span class="codigo">datosServletsInterfaces.jsp</span>" que contendrá el formulario que también ya debemos conocer de memoria ^_^, en donde, nuevamente, lo único que cambiará es el valor del atributo "<span class="codigo">action</span>":<br />
<br />
<pre><code>
<s:form action="datosInterfaces">
<s:textfield name="datoSesion" label="Sesion" />
<s:textfield name="datoRequest" label="Request" />
<s:textfield name="datosAplicacion" label="Aplicacion" />
<s:submit />
</s:form>
</code></pre><br />
<br />
Ahora crearemos una nueva clase llamada "<span class="codigo">ObjetosServletAction</span>", en el paquete "<span class="codigo">actions</span>". Esta clase deberá extender "<span class="codigo">ActionSupport</span>":<br />
<br />
<pre><code>
public class ObjetosServletAction extends ActionSupport
{
}
</code></pre><br />
<br />
<span class="codigo">Struts 2</span> permite obtener una referencia al objeto "<span class="codigo">HttpServletRequest</span>", que representa la petición actual que está siendo atendida por el <span class="codigo">Action</span>, y al objeto "<span class="codigo">ServletContext</span>", que representa el contexto de la aplicación web, implementando las interfaces "<span class="codigo">ServletRequestAware</span>" y "<span class="codigo">ServletContextAware</span>" respectivamente.<br />
<br />
Ambas interfaces son igual de sencillas que sus contrapartes para los atributos de los scopes. La interface "<span class="codigo">ServletContextAware</span>" luce de la siguiente forma:<br />
<br />
<pre><code>
interface ServletContextAware
{
public void setServletContext(ServletContext sc);
}
</code></pre><br />
<br />
Y la interface "<span class="codigo">ServletRequestAware</span>" se ve así:<br />
<br />
<pre><code>
interface ServletRequestAware
{
public void setServletRequest(HttpServletRequest hsr);
}
</code></pre><br />
<br />
Como podemos ver, ambas interfaces tienen tan solo un método que debemos implementar, y que reciben el objeto adecuado perteneciente a la especificación de <span class="codigo">Servlet</span>s (en vez de algún objeto propio de <span class="codigo">Struts 2</span> o algún objeto genérico, como estamos acostumbrados al trabajar con este framework), por lo que al implementar estos métodos ya tenemos una referencia directa a estos objetos sin tener que hacer ningún otro proceso, conversión, o invocación.<br />
<br />
Junto con la implementación de estas interfaces proporcionaremos un par de atributos, uno de tipo "<span class="codigo">ServletContext</span>" y uno de tipo "<span class="codigo">HttpServletRequest</span>", para almacenar los valores recibidos con la implementación.<br />
<br />
Así que lo primero que hacemos entonces es declarar que nuestra clase "<span class="codigo">ObjetosServletAction</span>" implementará las interfaces "<span class="codigo">ServletContextAware</span>" y "<span class="codigo">ServletRequestAware</span>":<br />
<br />
<pre><code>
public class ObjetosServletAction extends ActionSupport implements ServletContextAware, ServletRequestAware
{
}
</code></pre><br />
<br />
Ahora colocaremos los atributos (sin <span class="codigo">getters</span> ni <span class="codigo">setters</span>) para almacenar las referencias recibidas por los métodos de las interfaces anteriores:<br />
<br />
<pre><code>
private ServletContext application;
private HttpServletRequest request;
</code></pre><br />
<br />
Lo siguiente es proporcionar la implementación de las interfaces, en los cuales almacenaremos las referencias recibidas por estas, en los atributos que hemos colocado en nuestra clase:<br />
<br />
<pre><code>
public void setServletContext(ServletContext sc)
{
this.application = sc;
}
public void setServletRequest(HttpServletRequest hsr)
{
this.request = hsr;
}
</code></pre><br />
<br />
Ahora que ya tenemos las referencias que nos interesaban agregaremos los tres atributos, junto con sus <span class="codigo">setters</span>, que nos permitirán recibir los parámetros provenientes del formulario:<br />
<br />
<pre><code>
private String datoSesion;
private String datoRequest;
private String datosAplicacion;
public void setDatoRequest(String datoRequest)
{
this.datoRequest = datoRequest;
}
public void setDatoSesion(String datoSesion)
{
this.datoSesion = datoSesion;
}
public void setDatosAplicacion(String datosAplicacion)
{
this.datosAplicacion = datosAplicacion;
}
</code></pre><br />
<br />
El siguiente paso es sobre-escribir el método "<span class="codigo">execute</span>" para establecer los valores de los atributos en cada uno de los scopes. Primero obtendremos el objeto "<span class="codigo">HttpSession</span>" usando el "<span class="codigo">HttpServletRequest</span>", como en el ejemplo anterior, para posteriormente poder establecer los atributos en los scopes usando los objetos apropiados:<br />
<br />
<pre><code>
@Override
public String execute() throws Exception
{
HttpSession sesion = request.getSession();
application.setAttribute("datoAplicacion", datosAplicacion);
sesion.setAttribute("datoSesion", datoSesion);
request.setAttribute("datoRequest", datoRequest);
return SUCCESS;
}
</code></pre><br />
<br />
Como ven, la lógica del método es tan sencilla como en todo el tutorial.<br />
<br />
El último paso es anotar esta clase para indicarle a <span class="codigo">Struts 2</span> que se trata de un <span class="codigo">Action</span>. Una vez más reutilizaremos la página "<span class="codigo">resultado.jsp</span>" para mostrar los resultados de la ejecución del <span class="codigo">Action</span>:<br />
<br />
<pre><code>
@Namespace(value = "")
@Action(value = "datosInterfaces", results ={@Result(name = "success", location = "/resultado.jsp")})
public class ObjetosServletAction extends ActionSupport implements ServletRequestAware, ServletContextAware
{
}
</code></pre><br />
<br />
Al final la clase "<span class="codigo">ObjetosServletAction</span>" queda de la siguiente forma:<br />
<br />
<pre><code>
@Namespace(value = "")
@Action(value = "datosInterfaces", results ={@Result(name = "success", location = "/resultado.jsp")})
public class ObjetosServletAction extends ActionSupport implements ServletRequestAware, ServletContextAware
{
private ServletContext application;
private HttpServletRequest request;
private String datoSesion;
private String datoRequest;
private String datosAplicacion;
@Override
public String execute() throws Exception
{
HttpSession sesion = request.getSession();
application.setAttribute("datoAplicacion", datosAplicacion);
sesion.setAttribute("datoSesion", datoSesion);
request.setAttribute("datoRequest", datoRequest);
return SUCCESS;
}
public void setServletContext(ServletContext sc)
{
this.application = sc;
}
public void setServletRequest(HttpServletRequest hsr)
{
this.request = hsr;
}
public void setDatoRequest(String datoRequest)
{
this.datoRequest = datoRequest;
}
public void setDatoSesion(String datoSesion)
{
this.datoSesion = datoSesion;
}
public void setDatosAplicacion(String datosAplicacion)
{
this.datosAplicacion = datosAplicacion;
}
}
</code></pre><br />
<br />
Cuando ejecutemos la aplicación y entremos a la siguiente dirección:<br />
<br />
<pre><code>
<a href="http://localhost:8080/scopes/datosServletsInterfaces.jsp">http://localhost:8080/scopes/datosServletsInterfaces.jsp</a>
</code></pre><br />
<br />
Debemos ver el formulario al que ahora ya debemos estar acostumbrados <span class="codigo">^_^!</span>:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgA9wg62MILW9Rb_FHmg7rPy6ewHxs8pyUTPGEiSlnKReOncBWFnQRS8pWUrydTV-u4-eEWqMhushodG7fE4OGPWvl4100oyOGi7FEvbrGeqw60kkaohnLpP8D8IkqMmd1eOw9sLFo0_0Sg/s1600/S4_10.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="270" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgA9wg62MILW9Rb_FHmg7rPy6ewHxs8pyUTPGEiSlnKReOncBWFnQRS8pWUrydTV-u4-eEWqMhushodG7fE4OGPWvl4100oyOGi7FEvbrGeqw60kkaohnLpP8D8IkqMmd1eOw9sLFo0_0Sg/s640/S4_10.png" width="640" /></a></div><br />
<br />
Y, nuevamente, al ingresar algunos datos y presionar el botón enviar debemos ver la siguiente salida:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgqM5TyU7sHEnZDqndxowP-6jGI8iaQN-ShHe3E43bjhPszHL9R0iszD9EZDonRxBt5QexZL4eSHagwl3g15HuO8SL8dlnOGy4HSlRF_xEHnZzYRIfB0eWDrz2ODADRmuIaFC6oq7NSNITc/s1600/S4_11.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="270" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgqM5TyU7sHEnZDqndxowP-6jGI8iaQN-ShHe3E43bjhPszHL9R0iszD9EZDonRxBt5QexZL4eSHagwl3g15HuO8SL8dlnOGy4HSlRF_xEHnZzYRIfB0eWDrz2ODADRmuIaFC6oq7NSNITc/s640/S4_11.png" width="640" /></a></div><br />
<br />
Como podemos ver, una vez más todo ha funcionado correctamente.<br />
<br />
Algunos se preguntarán ¿para qué queremos tener acceso a estos objetos si <span class="codigo">Struts 2</span> se encarga de manejar todos los aspectos relacionados con <span class="codigo">Servlet</span>s por nosotros? Bueno me alegra que hagan esa pregunta <span class="codigo">^_^</span>.<br />
<br />
En realidad esto tiene varios usos, los dos más utilizados que se me ocurren en este momento es el poder obtener la ubicación absoluta de un recurso (un archivo o algún otro elemento) que se encuentra en nuestra aplicación y que por lo tanto no sabemos en qué directorio del disco duro está. Para esto se necesita el método "<span class="codigo">getRealPath</span>" del objeto "<span class="codigo">ServletContext</span>" de la siguiente forma:<br />
<br />
<pre><code>
application.getRealPath("/recurso.ext");
</code></pre><br />
<br />
El segundo y más común uso es para invalidar una sesión. <span class="codigo">Struts 2</span> no nos da directamente una manera para invalidar la sesión de un usuario de nuestra aplicación web, así que para esto es necesario tener objeto al objeto "<span class="codigo">HttpSession</span>", y para esto al objeto "<span class="codigo">HttpServletRequest</span>":<br />
<br />
<pre><code>
request.getSession().invalidate();
</code></pre><br />
<br />
Así que, como podemos ver, algunas veces es útil tener acceso a los objetos de la específicación de <span class="codigo">Servlet</span>s cuando el framework que estamos usando no nos pueda, por la razón que sea, darnos todas las facilidades que necesitemos para hacer algunas cosas específicas.<br />
<br />
Esto es todo para este tutorial, espero que les sea de utilidad. En el siguiente tutorial de la serie de <span class="codigo">Struts 2</span>, veremos los tipos de <span class="codigo">results</span> que vienen incluidos con <span class="codigo">Struts 2</span> y para que pueden servirnos.<br />
<br />
No olviden dejar cualquier duda, comentario, o sugerencia que puedan tener.<br />
<br />
Saludos.<br />
<br />
<span class="negritas">Descarga los archivos de este tutorial desde aquí:</span><br />
<br />
<ul><li><a href="https://sites.google.com/site/javatutoriales/struts2/ScopesDeObjetosWeb.zip?attredirects=0&d=1"><span class="ligaArchivo">ScopesDeObjetosWeb</span></a></li>
</ul><br />
<br />
<span class="negritas">Entradas Relacionadas:</span><br />
<br />
<ul><li><a href="http://www.javatutoriales.com/2011/06/struts-2-parte-1-configuracion.html">Parte 1: Configuración</a></li>
<li><a href="http://www.javatutoriales.com/2011/06/struts-2-parte-2-ognl.html">Parte 2: OGNL</a></li>
<li><a href="http://www.javatutoriales.com/2011/10/struts-2-parte-3-trabajo-con.html">Parte 3: Trabajo con Formularios</a></li>
<li><a href="http://www.javatutoriales.com/2012/04/struts-2-parte-5-tipos-de-results.html">Parte 5: Tipos de Results</a></li>
<li><a href="http://www.javatutoriales.com/2012/06/struts-2-parte-6-interceptores.html">Parte 6: Interceptores</a></li>
</ul></div>Alexhttp://www.blogger.com/profile/06974037481671868076noreply@blogger.comtag:blogger.com,1999:blog-2012232069289957092.post-16533179820615340852011-11-05T20:29:00.000-07:002012-06-29T07:52:26.919-07:00Sun Certified Java Programmer 6, CX-310-065 - Parte 2: Orientación a Objetos<div style="text-align: justify;">En este segundo post para la certificación de Java 6 hablaremos sobre conceptos de la orientación a objetos, que abarca temas como la herencia, el polimorfismo, la cohesión, bajo acoplamiento (loose coupling), etc.<br />
<br />
<a name='more'></a>Empezaremos hablando sobre la encapsulación:<br />
<br />
<h2 class="titulo">Encapsulación</h2><br />
La encapsulación es un mecanismo que nos permite que, aunque nuestras clases utilicen muchas variables y métodos para su correcto funcionamiento, no todas sean visibles desde el exterior. O sea que solo exponemos lo que los clientes necesitan para poder hacer uso de los objetos de nuestra clase.<br />
<br />
Para entender mejor este concepto imaginemos el siguiente escenario:<br />
<br />
Tenemos la siguiente clase:<br />
<br />
<pre><code>
public class Examen
{
public String pregunta1;
public static void main(String... args)
{
Examen examen = new Examen();
examen.pregunta1 = "¿Que es Java?"; // Legal pero no recomendable
}
}
</code></pre><br />
<br />
El ejemplo anterior compilará y se ejecutará de forma correcta. Sin embargo <span class="negritas">no es recomendable que los atributos de la clase (las variables de instancia) estén expuestas de forma que los clientes puedan leer y escribir sus valores directamente</span>. De esta forma cualquiera podría colocar el valor que quisiera en la variable "<span class="codigo">pregunta1</span>", aún valores que nuestra aplicación no puede o no está preparada para manejar.<br />
<br />
Ahora hagamos una pregunta: ¿Cómo poder cambiar la clase cuando alguien cambia el valor de "<span class="codigo">pregunta1</span>" por un valor incorrecto? La única forma es volver a la clase e implementar un método de sólo escritura (un método setter: "<span class="codigo">setPregunta1(String pregunta1)</span>") y ocultar la variable "<span class="codigo">pregunta1</span>" estableciéndola como privada, pero de esta manera al no establecerla desde un principio con los métodos "<span class="codigo">set</span>" o "<span class="codigo">get</span>" todas las personas que han utilizado este código anteriormente y de la manera en la que estaba implementada se encontraran perdidas.<br />
<br />
La capacidad de realizar cambios en el código de aplicación sin romper el código de otras personas que utilizan este código es un beneficio clave de la encapsulación. <span class="negritas">Ocultando los detalles de la implementación a través de métodos de acceso nos brinda la ventaja de poder rehacer el código dentro de los métodos sin forzar a las demás personas a cambios, ya que ellas usan dichos métodos de acceso</span>.<br />
<br />
Con todo esto obtenemos las dos promesas/beneficios de la programación orientada a objetos: Flexibilidad y Mantenimiento, pero como vemos estos dos beneficios no llegan solos, nosotros tenemos que implementar nuestro código de manera que brinde y soporte estos beneficios.<br />
<br />
Estas son algunas recomendaciones para lograr la encapsulación:<br />
<br />
<ul><li>Mantener las variables de instancia protegidas (mayormente con el modificador de acceso private).</li>
<li>Mantener publicos los métodos de acceso a las variables de instancia (modificador de acceso public), así forzamos a llamar a las variables de instancia y a la implementación del mismo a través de estos métodos en lugar de ir directamente por las variables de instancia.</li>
<li>Para los métodos de acceso se recomienda usar las reglas de las convenciones de nombres de <span class="codigo">JavaBean</span>: <span class="codigo">set<nombrePropiedad></span> y <span class="codigo">get<nombrePropiedad></span>, de las cuales se habló en el post anterior.</li>
</ul><br />
A continuación mostramos un ejemplo más práctico de lo que nos estamos refiriendo:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgNUCAsI2JAm-bkL76lENVy8umNsA7v21MgyAGp2XXb4I32iXOggomFnrMIM2-bNAv9jYM_Yz-JZTmW34RM1CI6MbDtAEyG2XILHq4QQxhlH6H3WotFVGNVLLwTTs-LstaaFy_X9b0CC1fk/s1600/C2_1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgNUCAsI2JAm-bkL76lENVy8umNsA7v21MgyAGp2XXb4I32iXOggomFnrMIM2-bNAv9jYM_Yz-JZTmW34RM1CI6MbDtAEyG2XILHq4QQxhlH6H3WotFVGNVLLwTTs-LstaaFy_X9b0CC1fk/s1600/C2_1.png" /></a></div><br />
<br />
La clase "<span class="codigo">A</span>" no puede acceder a las variables de instancia de la Clase "<span class="codigo">B</span>" sin antes ir a los métodos de acceso ("<span class="codigo">set</span>" y "<span class="codigo">get</span>") de dichas variables, las variables de instancia son marcadas privadas y los métodos de acceso son públicos.<br />
<br />
Debemos tener presente que el código de estos métodos no solo se utiliza para devolver o establecer los valores de las variables de instancia, también se puede establecer lógica o implementación de muchas más cosas o reglas que queramos definir, por ejemplo:<br />
<br />
<pre><code>
public void setTamanio(int tamanio)
{
this.tamanio = tamanio * 0.10;
this.color = "negro";
}
</code></pre><br />
<br />
<br />
El encapsulamiento es uno de los mecanismos que nos proporciona la orientación a objetos para poder darle una funcionalidad rica a nuestras clases sin que las personas que las utilicen sepan los detalles exactos de cómo está implementada dicha funcionalidad.<br />
<br />
Sin embargo, este no es el único mecanismo proporcionado por la orientación a objetos. A continuación veremos otro de ellos. La herencia.<br />
<br />
<br />
<h2 class="titulo">Herencia, ES–UN, TIENE–UN (IS–A, HAS-A)</h2><br />
En Java, la herencia se encuentra en todos. Es seguro decir que en Java casi nada se puede hacer sin hacer uso de la herencia. Para dar un ejemplo a continuación usaremos del operador "<span class="codigo">instanceof</span>" (por ahora no ahondaremos mucho en la explicación del uso de este operador ya que se tocará más adelante con mayor detalle, sólo cabe recordar ahora que este operador devuelve un "<span class="codigo">true</span>" si la variable puesta al principio es del tipo de la variable con la que se está comparando):<br />
<br />
<pre><code>
public class Test
{
public static void main(String... args)
{
Test t1 = new Test();
Test t2 = new Test();
if(!t1.equals(t2))
{
System.out.println("<span class="codigo">No son iguales</span>")
}
if(t1 instanceof Object)
{
System.out.println("<span class="codigo">t1 es un objeto</span>");
}
}
}
</code></pre><br />
<br />
¿De dónde saca "<span class="codigo">t1</span>" el método "<span class="codigo">equals</span>"? Si no hemos implementado ningún método dentro de la clase con ese nombre, ¿o sí? Por otro lado se pregunta si "<span class="codigo">t1</span>" es una instancia de la clase "<span class="codigo">Object</span>" y de ser así, la condición <span class="codigo">if</span> será exitosa.<br />
<br />
¿Cómo puede ser "<span class="codigo">t1</span>" del tipo "<span class="codigo">Object</span>" si solo lo declaramos que sea del tipo de la clase "<span class="codigo">Test</span>"? Esto ocurre porque todas las clases en java (las que ya están escritas, las que escribimos y las que escribiremos) son una subclase de la clase "<span class="codigo">Object</span>" (excepto por supuesto la clase "<span class="codigo">Object</span>" misma) y siempre tendrán métodos como "<span class="codigo">equals</span>", "<span class="codigo">clone</span>", "<span class="codigo">notify</span>", "<span class="codigo">wait</span>" y otros más. Siempre que creamos una clase, esta hereda todos los métodos de la clase "<span class="codigo">Object</span>".<br />
<br />
El método "<span class="codigo">equals</span>" por ejemplo: Los creadores de Java asumieron correctamente que nosotros comúnmente desearíamos comparar instancias de las clases para comprobar la igualdad; si la clase "<span class="codigo">Object</span>" no tuviera un método "<span class="codigo">equals</span>" tendríamos que implementar nosotros mismos un método para este propósito.<br />
<br />
También debemos recordar que las 2 razones más importantes para el uso de la herencia son:<br />
<br />
<ul><li>Reutilización de código</li>
<li>Uso del polimorfismo</li>
</ul><br />
Empecemos con la reutilización. Un enfoque de diseño común es crear una versión de una clase bastante genérica y después crear subclases muy especializadas que hereden de esta, por ejemplo:<br />
<br />
<pre><code>
public class Animal
{
public void muevete()
{
System.out.println("Me estoy moviendo");
}
}
public class Perro extends Animal
{
public void ladra()
{
System.out.println("Estoy ladrando");
}
}
public class PruebaAnimal
{
public static void main(String... args)
{
Perro perro = new Perro();
perro.muevete();
perro.ladra();
}
}
</code></pre><br />
<br />
La salida del código anterior seria:<br />
<br />
<pre><code>
Me estoy moviendo
Estoy ladrando
</code></pre><br />
<br />
Como podemos ver, la clase "<span class="codigo">Perro</span>" está heredando el método "<span class="codigo">muevete</span>" de la clase "<span class="codigo">Animal</span>" y que también tiene su propio método agregado, en este caso es el método "<span class="codigo">ladra</span>". Aquí se está haciendo uso de la reusabilidad al utilizar un método genérico de una clase padre que en este caso es el método "<span class="codigo">muevete</span>", con esto podemos crear diferentes tipos de animales y todos van a poder utilizar el método "<span class="codigo">muevete</span>" sin necesidad de implementarlo ellos mismos.<br />
<br />
El segundo objetivo de la herencia es poder acceder a las clases polimórficamente. Imaginemos este escenario: digamos que tenemos una clase llamada "<span class="codigo">Entrenador</span>" que quiere recorrer todos los diferentes tipos de animal e invocar al método "<span class="codigo">muévete</span>" en cada uno de ellos, al momento de escribir la clase "<span class="codigo">Entrenador</span>" no sabemos cuántas clases de "<span class="codigo">Animal</span>" podría haber y seguro no vamos a querer cambiar el código sólo porque a alguien se le ocurrió crear un nuevo tipo de <span class="codigo">Animal</span>.<br />
<br />
Lo bonito del polimorfismo es que podemos tratar cualquier tipo de "<span class="codigo">Animal</span>" como un "<span class="codigo">Animal</span>", en otras palabras podemos decir lo siguiente: No me importa qué tipo de <span class="codigo">Animal</span> se pueda crear, siempre y cuando extienda (herede) de <span class="codigo">Animal</span> todos van a poder moverse (método "<span class="codigo">muevete</span>").<br />
<br />
Ahora miremos esto con un ejemplo:<br />
<br />
<pre><code>
public class Animal
{
public void muevete()
{
System.out.println("Me estoy moviendo");
}
}
public class Perro extends Animal
{
public void ladra()
{
System.out.println("Estoy ladrando");
}
}
public class Gato extends Animal
{
public void ronronea()
{
System.out.println("Estoy ronroneando");
}
}
</code></pre><br />
<br />
Ahora imaginemos una clase llamada "<span class="codigo">Entrenador</span>" que tiene un método que tiene como argumento un "<span class="codigo">Animal</span>", esto significa que puede tomar cualquier tipo de "<span class="codigo">Animal</span>", cualquier tipo de animal puede ser pasado a un método con un argumento del tipo "<span class="codigo">Animal</span>", ejemplo:<br />
<br />
<pre><code>
public class Entrenador
{
public static void main(String... args)
{
Gato gato = new Gato();
Perro perro = new Perro();
mueveteAnimal(gato);
mueveteAnimal(perro);
}
public static void mueveteAnimal(Animal animal)
{
animal.muevete();
}
}
</code></pre><br />
<br />
La salida del código anterior es:<br />
<br />
<pre><code>
Me estoy moviendo
Me estoy moviendo
</code></pre><br />
<br />
<br />
El método "<span class="codigo">mueveteAnimal</span>" está declarando un "<span class="codigo">Animal</span>" como argumento pero se le puede pasar cualquier sub-clase o sub-tipo de esta clase "<span class="codigo">Animal</span>", este método podría invocar a cualquier método dentro de la clase "<span class="codigo">Animal</span>". Lo que si debemos de tener en cuenta es que sólo podemos llamar a los métodos declarados por "<span class="codigo">Animal</span>", los métodos declarados dentro de las subclases de "<span class="codigo">Animal</span>" son dependiente del tipo declarado, esto significa que no podríamos llamar al método "<span class="codigo">ladra</span>" incluso si el "<span class="codigo">Animal</span>" que se está pasando es del tipo "<span class="codigo">Perro</span>".<br />
<br />
<h2 class="subtitulo">Relaciones IS – A, HAS– A</h2><br />
<h3 class="subtitulo">IS – A</h3><br />
En Orientación a objetos el concepto de "<span class="codigo">IS-A</span>" (es-un) esta basado en la herencia de una clase ("<span class="codigo">extends</span>") o en la implementación de una interface ("<span class="codigo">implements</span>"). <span class="codigo">IS-A</span> es una forma de decir "Esta cosa es del tipo de esta cosa" (o estos dos tipos pueden ser equivalentes), por ejemplo un "<span class="codigo">Perro</span>" es del tipo "<span class="codigo">Animal</span>", en OO nosotros podemos decir: "<span class="codigo">Perro IS-A Animal</span>", "<span class="codigo">Lechuga IS-A Vegetal</span>", en java podemos expresar esta relación <span class="codigo">IS-A</span> por medio de las palabras reservadas "<span class="codigo">extends</span>" (para la herencia de clases) y de "<span class="codigo">implements</span>" (para la implementación de interfaces), veamos un ejemplo:<br />
<pre><code>
public class Carro
{
//cualquier código aquí
}
public class Toyota extends Carro
{
/*Toyota está heredando de carro, no olvidemos que Toyota
hereda los miembros de Carro incluido métodos y variables*/
}
</code></pre><br />
<br />
"<span class="codigo">Carro</span>" también es un vehículo así que se podría implementar un árbol de herencia de la siguiente manera:<br />
<br />
<pre><code>
public class Vehiculo{...}
public class Carro extends Vehiculo{...}
public class Toyota extends Carro{...}
</code></pre><br />
<br />
En términos de OO podríamos decir lo siguiente:<br />
<br />
<ul><li><span class="codigo">Vehículo</span> es la súper clase de <span class="codigo">Carro</span></li>
<li><span class="codigo">Carro</span> es la subclase de <span class="codigo">Vehículo</span></li>
<li><span class="codigo">Carro</span> es la súper clase de <span class="codigo">Toyota</span></li>
<li><span class="codigo">Toyota</span> es la subclase de <span class="codigo">Carro</span></li>
<li><span class="codigo">Toyota</span> hereda de <span class="codigo">Carro</span> y de <span class="codigo">Vehiculo</span></li>
<li><span class="codigo">Toyota</span> deriva de <span class="codigo">Carro</span></li>
<li><span class="codigo">Carro</span> deriva de <span class="codigo">Vehículo</span></li>
<li><span class="codigo">Toyota</span> es subtipo de <span class="codigo">Carro</span> y <span class="codigo">Vehiculo</span></li>
</ul>Retornando a la relación <span class="codigo">IS-A</span>, lo siguiente es correcto:<br />
<br />
<span class="codigo">Toyota extends Carro</span> significa <span class="codigo">Toyota IS-A Carro</span><br />
<br />
<span class="codigo">Carro extends Vehiculo</span> significa <span class="codigo">Carro IS-A Vehiculo</span><br />
<br />
Ahora recordemos que al principio usamos el operador <span class="codigo">instanceof</span>, bueno, si la expresión "<span class="codigo">Toyota instanceof Carro</span>" es verdadera, entonces es lo mismo que decir "<span class="codigo">Toyota IS-A Carro</span>", la expresión "<span class="codigo">Toyota instanceof Vehiculo</span>" también es verdadera aunque explícitamente no dice esto ya que "<span class="codigo">Toyota</span>" extiende de "<span class="codigo">Carro</span>", pero por otro lado "<span class="codigo">Carro</span>" si extiende de "<span class="codigo">Vehiculo</span>" a esto se le llama herencia indirecta ya que una clase puede ser hijo, nieto, bisnieto, etc. de otra clase, una clase puede extender o heredar de otra directa o indirectamente ya que en el árbol de herencia pueden haber clases intermedias.<br />
<br />
<h2 class="subtitulo">HAS–A</h2><br />
La relación <span class="codigo">HAS-A</span> esta basada en el uso en lugar de la herencia, por ejemplo "<span class="codigo">A HAS-A B</span>" si la clase "<span class="codigo">A</span>" tiene una referencia a una instancia de la clase "<span class="codigo">B</span>", por ejemplo:<br />
<br />
<pre><code>
public class Animal{ }
public class Gato extends Animal
{
CajaDeArena miCajaDeArena;
}
</code></pre><br />
<br />
En el código anterior la clase "<span class="codigo">Gato</span>" tiene una referencia una variable de instancia del tipo "<span class="codigo">CajaDeArena</span>", entonces podemos decir "<span class="codigo">Gato HAS-A CajaDeArena</span>", en otra palabras, "<span class="codigo">Gato</span>" tiene una referencia a una "<span class="codigo">CajaDeArena</span>", la clase "<span class="codigo">Gato</span>" puede tener un método llamado "<span class="codigo">llenarCaja(Arena miArena)</span>", de esta manera los usuarios de la clase "<span class="codigo">Gato</span>" no podrán saber nunca que cuando se invoca al método "<span class="codigo">llenarCaja</span>" este método delega toda la responsabilidad a la clase "<span class="codigo">CajaDeArena</span>" llamando a su método "<span class="codigo">llenarCaja</span>", veamos esto con un ejemplo:<br />
<br />
<pre><code>
public class Gato extends Animal
{
private CajaDeArena miCajaDeArena;
public void llenarCaja(Arena miArena)
{
miCajaDeArena.llenarCaja(miArena); /*delegando comportamiento al objeto CajaDeArena */
}
}
public class CajaDeArena
{
public void llenarCaja(Arena miArena)
{
System.out.println("<span class="codigo">Llenando la caja de arena</span>");
}
}
</code></pre><br />
<br />
En OO muchas veces no queremos que la gente se preocupe por cual clase u objeto está haciendo realmente el trabajo, los usuarios de la clase "<span class="codigo">Gato</span>" hacen llamado del método "<span class="codigo">llenarCaja</span>" pero estos no saben si la misma clase hace el trabajo o no, sin embargo a ellos les parece que la propia clase "<span class="codigo">Gato</span>" lo hace, no tienen ni idea de que existe algo como una clase llamada "<span class="codigo">CajaDeArena</span>" que es quien realmente hace el trabajo.<br />
<br />
Toda la explicación anterior nos servirá para entender mejor otro de los conceptos de la programación orientada a objetos, uno de los más útiles si sabemos cómo utilizarlo correctamente: el <span class="codigo">polimorfismo</span>.<br />
<br />
<br />
<h2 class="titulo">Polimorfismo</h2>Cualquier clase que pase la prueba de tener la relación "<span class="codigo">IS-A</span>" puede ser considerada polimórfica, todos los objetos en Java son polimórficos ya que pasan la prueba de la relación IS-A, tanto para su propio tipo como para con la clase "<span class="codigo">Object</span>". Debemos recordar la única forma de a un objeto es a través de una variable de referencia, hay algunas cosas claves que debemos recordar de las variables de referencia:<br />
<br />
<ul><li>Una variable de referencia <span class="negritas">puede ser sólo de un tipo y una vez declarado este tipo nunca podrá cambiar</span> (aunque el objeto al que hace referencia puede cambiar).</li>
<li>Una referencia es una variable, por lo cual su valor puede ser reasignada a otros objetos a menos que esta sea declarada como <span class="codigo">final</span>.</li>
<li>Un tipo de variable de referencia determina los métodos que se pueden invocar en el objeto que esta variable está referenciando.</li>
<li>Una variable de referencia puede referirse a cualquier objeto del mismo tipo que el que está declarando, o puede referirse a cualquier subtipo del tipo declarado.</li>
<li>Una variable de referencia puede ser declarado como un tipo de clase o un tipo de interfaz. Si la variable se declara como un tipo de interfaz, se puede hacer referencia a cualquier objeto de cualquier clase que implementa la interfaz.</li>
</ul><br />
<br />
Anteriormente creamos una clase "<span class="codigo">Animal</span>" que era extendida por dos clases, "<span class="codigo">Perro</span>" y "<span class="codigo">Gato</span>", ahora imaginemos que tenemos una clase llamada "<span class="codigo">Gaviota</span>", después queremos hacer que algunos tipos de <span class="codigo">Animal</span> vuelen o se puedan elevar en el aire y otros no como el caso de "<span class="codigo">Gaviota</span>" que si puede elevar, por el contrario "<span class="codigo">Perro</span>" y "<span class="codigo">Gato</span>" no pueden hacerlo, para esto podríamos crear una clase llamada "<span class="codigo">Elevable</span>" con un método "<span class="codigo">elevar</span>" y hacer que unos tipos de <span class="codigo">Animal</span>es puedan elevarse y otros no, pero también queremos que todos los tipos de <span class="codigo">Animal</span> se muevan que es lo que nos permite el método "<span class="codigo">muevete</span>" de la clase "<span class="codigo">Animal</span>", pero esto no funcionaría ya que Java solo soporta la herencia simple, esto significa que una sub clase sólo puede tener una clase padre, es decir lo siguiente es incorrecto:<br />
<br />
<pre><code>
public class Gaviota extends Animal, Elevable // NO!!
{
//Cualquier codigo aquí
}
</code></pre><br />
<br />
<span class="negritas">Una clase NO puede extender de más que de sólo una clase</span>, ante estos casos la solución sería crearse una interface llamada "<span class="codigo">Elevable</span>" y solo las sub-clases de "<span class="codigo">Animal</span>" que puedan volar implementen esta interfaz, dicha interfaz quedaría de la siguiente manera:<br />
<br />
<pre><code>
public interface Elevable
{
public void volar();
}
public class Animal
{
public void muevete()
{
System.out.println("Me estoymoviendo");
}
}
</code></pre><br />
<br />
A continuación mostramos la clase "<span class="codigo">Gaviota</span>" que implementa esta interfaz:<br />
<br />
<pre><code>
public class Gaviota extends Animal implements Elevable
{
public void volar()
{
System.out.println("¡Estoy volando!");
}
}
</code></pre><br />
<br />
Ahora tenemos a la clase "<span class="codigo">Gaviota</span>" que pasa la prueba de la relación <span class="codigo">IS-A</span> tanto para la clase "<span class="codigo">Animal</span>" como para la interfaz "<span class="codigo">Elevable</span>", esto significa que una "<span class="codigo">Gaviota</span>" puede ser tratada polimórficamente como una de estas cuatro cosas en un momento dado, dependiendo del tipo declarado de la variable de referencia:<br />
<br />
<ul><li>Como un "<span class="codigo">Object</span>" (ya que todas las clases heredan de la clase <span class="codigo">Object</span>).</li>
<li>Como un "<span class="codigo">Animal</span>" (ya que está extendiendo de la clase <span class="codigo">Animal</span>).</li>
<li>Como una "<span class="codigo">Gaviota</span>" (ya que es lo que es).</li>
<li>Como un "<span class="codigo">Elevable</span>" (ya que implementa de la interface <span class="codigo">Elevable</span>).</li>
</ul><br />
<br />
Las siguientes declaraciones son legales:<br />
<br />
<pre><code>
Gaviota gaviota = new Gaviota();
Object object = gaviota;
Animal animal = gaviota;
Elevable elevable = gaviota;
</code></pre><br />
<br />
Aquí solo hay un objeto <span class="codigo">Gaviota</span> pero cuatro diferentes variables de referencia, hagamos una pregunta: ¿Cuál de las cuatro variables de referencia puede llamar al método "<span class="codigo">muevete</span>"? Recuerden que las llamadas a métodos permitidos por el compilador se basan únicamente en el tipo declarado de la referencia, con independencia del tipo de objeto. Así que buscando en los cuatro tipos de referencia de nuevo, "<span class="codigo">Object</span>", "<span class="codigo">Gaviota</span>", "<span class="codigo">Animal</span>" y "<span class="codigo">Elevable</span>" ¿cuál de estos cuatro tipos puede llamar al método "<span class="codigo">muevete()</span>"? La respuesta es la siguiente: El objeto "<span class="codigo">animal</span>" como el objeto "<span class="codigo">gaviota</span>" son conocidos por el compilador para poder llamar o invocar al método "<span class="codigo">muevete</span>".<br />
<br />
¿Qué métodos pueden ser invocados cuando el objeto "<span class="codigo">gaviota</span>" está utilizando la referencia a la inteface "<span class="codigo">Elevable</span>"? Sólo al método "<span class="codigo">volar()</span>".<br />
<br />
Un beneficio es que cualquier clase desde cualquier lugar en el árbol de herencia puede invocar a la interface "<span class="codigo">Elevable</span>", que sucedería si tenemos un método que tiene como argumento declarado con tipo "<span class="codigo">Elevable</span>", podrías pasar una instancia de objeto del tipo "<span class="codigo">gaviota</span>" y cualquier otra instancia de clase que implemente a la interface "<span class="codigo">Elevable</span>", podría usar el parámetro del tipo "<span class="codigo">Elevable</span>" para invocar al método "<span class="codigo">volar()</span>" pero no al método "<span class="codigo">muevete()</span>" o cualquier otro objeto que se sabe que el compilador conocer basado en el tipo de referencia.<br />
<br />
Otra cosa que debemos saber es que si bien el compilador solo conoce al tipo de referencia declarado, la <span class="codigo">JVM</span> en tiempo de ejecución sabe lo que el objeto realmente es, y eso significa que incluso si el objeto "<span class="codigo">gaviota</span>" hace una llamada al método "<span class="codigo">muevete</span>" usando la variable de referencia "<span class="codigo">animal</span>", si el objeto "<span class="codigo">gaviota</span>" sobre-escribe el método "<span class="codigo">muevete</span>", la <span class="codigo">JVM</span> invocara a esa versión del método "<span class="codigo">muevete</span>". La <span class="codigo">JVM</span> mira al objeto real que se encuentra al otro extremo de la referencia, puede ver que se ha sobre escrito el método del tipo de variable de referencia declarado e invoca al método del objeto de la clase actual.<br />
<br />
Siempre se puede hacer referencia a un objeto con un tipo de referencia variable más general (una superclase o interfaz), pero en tiempo de ejecución, las únicas cosas que son seleccionadas dinámicamente basándose en el objeto real (en lugar de tipo de referencia) son métodos de instancia (no los métodos estáticos, no las variables), sólo los métodos de instancia sobre-escritos se invocan de forma dinámica en función del tipo de objeto real.<br />
<br />
Ejemplo:<br />
<br />
<pre><code>
public class Animal
{
public void muevete()
{
System.out.println("Me estoy moviendo");
}
public static void respirar() //Método estático
{
System.out.println("Estoy respirando");
}
}
public class Gaviota extends Animal
{
public void muevete()
{
System.out.println("La gaviota se mueve");
}
public static void respirar() //Método estático
{
System.out.println("La gaviota está respirando");
}
}
public class Prueba
{
public static void main(String... args)
{
Animal a = new Gaviota();
a.muevete();
a.respirar();
}
}
</code></pre><br />
<br />
Salida:<br />
<br />
<pre><code>
La gaviota se mueve
Estoy respirando
</code></pre><br />
<br />
El segundo resultado que vemos se trata de la invocación de un método estático. El comportamiento que vemos pasa porque cuando el compilador ve que se está invocando un método estático, cambia la referencia al objeto por el tipo de la clase, o sea que al final queda de la siguiente manera:<br />
<br />
<pre><code>
Animal a = new Gaviota();
Animal.respirar();
</code></pre><br />
<br />
En el primer caso el método que se invoca es el de "<span class="codigo">Gaviota</span>" y no el de "<span class="codigo">Animal</span>", ya que el método "<span class="codigo">muevete</span>" no es estático sino un método de instancia; por eso dice se dice que sólo los métodos de instancia sobrecargados se invocan de forma dinámica en función del tipo de objeto real.<br />
<br />
<br />
<h2 class="titulo">Sobrecarga y sobre escritura</h2><br />
<h3 class="subtitulo">Sobre escritura de métodos</h3>En cualquier momento que se tenga un método que hereda de una superclase, se tendrá la oportunidad de realizar una sobre-escritura de métodos, a menos que el método este marcado con la palabra reservada <span class="codigo">final</span>. El principal beneficio de la sobre-escritura de métodos es que se puede definir un comportamiento especial para un método de una subclase. En el siguiente ejemplo veremos cómo la clase "<span class="codigo">Gaviota</span>" sobre-escribe el método "<span class="codigo">muevete</span>" de la clase "<span class="codigo">Animal</span>":<br />
<br />
<pre><code>
public class Animal
{
public void muevete()
{
System.out.println("Me estoy moviendo");
}
}
public class Gaviota extends Animal
{
public void muevete()
{
System.out.println("La gaviota se mueve");
}
}
</code></pre><br />
<br />
Para métodos abstractos que se heredan desde una superclase, no se tiene otra opción más que sobre escribir-dichos métodos. Se deben implementar los métodos a menos que la subclase que los herede también este marcada como abstracta. Los métodos abstractos deben ser implementados por una subclase concreta, esto quiere decir que la subclase concreta sobre-escribe el método abstracto de la superclase. Entonces debemos pensar que los métodos abstractos son métodos que forzosamente deben ser sobre-escritos.<br />
<br />
El creador de la clase "<span class="codigo">Animal</span>" podría haber decidido que, a efectos de polimorfismo, todos los subtipos de <span class="codigo">Animal</span> deben implementar el método "<span class="codigo">moverse</span>", de una manera única y especifica. Polimórficamente, cuando alguien tiene una referencia de <span class="codigo">Animal</span> que no refiere a una instancia de <span class="codigo">Animal</span>, sino a una instancia de una subclase de <span class="codigo">Animal</span>, la persona que llama debe ser capaz de invocar al método "<span class="codigo">muevete</span>" en la referencia a "<span class="codigo">Animal</span>", pero el objeto en tiempo de ejecución real ejecutará su propio y especifico método "<span class="codigo">muevete</span>". Marcando el método "<span class="codigo">muevete</span>" como abstracto es la forma que el programador de la clase "<span class="codigo">Animal</span>" dice a todos los desarrolladores de las demás subclases: "<span class="codigo">No tiene ningún sentido para el nuevo subtipo utilizar el método genérico "<span class="codigo">muevete</span>", por lo que tú debes implementar tu propio método "<span class="codigo">muevete</span>"</span>. A continuación mostraremos un ejemplo de clases no abstractas:<br />
<br />
<pre><code>
public class Prueba
{
public static void main(String... args)
{
Animal a = new Animal();
Animal b = new Gaviota(); //Referencia a Animal, pero objeto Gaviota
a.muevete();
b.muevete();
}
}
public class Animal
{
public void muevete()
{
System.out.println("Me estoy moviendo");
}
}
public class Gaviota extends Animal
{
public void muevete()
{
System.out.println("La gaviota se mueve");
}
public void volar()
{
System.out.println("La gaviota vuela");
}
}
</code></pre><br />
<br />
En el código anterior la clase "<span class="codigo">Prueba</span>" utiliza una referencia a "<span class="codigo">Animal</span>" para invocar un método en el objeto "<span class="codigo">Gaviota</span>", hay que recordar que el compilador solo permitirá que se invoquen métodos que se encuentran en la clase "<span class="codigo">Animal</span>", cuando usas una referencia a un "<span class="codigo">Animal</span>". Por lo tanto, el siguiente código no es legal:<br />
<br />
<pre><code>
Animal c = new Gaviota();
c.volar(); // No puedes invocar a "volar()", Animal no tiene un método volar
</code></pre><br />
<br />
Para reiterar: <span class="negritas">el compilar solo mira el tipo de referencia y no el tipo de instancia en tiempo de ejecución</span>. El polimorfismo nos permite tener referencias a súper tipos o a tipos más abstractos (incluyendo las interfaces) para referir a uno de estos subtipos (incluyendo implementación de interfaces).<br />
<br />
El método que sobre-escribe no debe tener un modificador de acceso más restringido que el del método a ser sobre-escrito. Por ejemplo no se puede sobre-escribir un método marcado con el modificador de acceso <span class="codigo">public</span> y cambiarlo a <span class="codigo">protected</span>. Pensemos en los siguiente: si la clase "<span class="codigo">Animal</span>" tiene un método "<span class="codigo">comer()</span>" marcado "<span class="codigo">public</span>" y alguien tiene una referencia de "<span class="codigo">Animal</span>", es decir, una referencia declarada del tipo "<span class="codigo">Animal</span>", se asume que es seguro llamar al método "<span class="codigo">comer()</span>" en la referencia de "<span class="codigo">Animal</span>", independientemente de la instancia actual que la referencia a "<span class="codigo">Animal</span>" está invocando. Si una subclase cambia el modificador de acceso del método sobre-escrito, entonces cuando la <span class="codigo">JVM</span> invoque la verdadera versión del objeto "<span class="codigo">Gaviota</span>" en lugar de la versión del tipo de referencia "<span class="codigo">Animal</span>", el programa perecerá, a continuación un ejemplo:<br />
<br />
<pre><code>
public class Prueba
{
public static void main(String... args)
{
Animal a = new Animal();
Animal b = new Gaviota(); // Referencia a Animal, pero objeto Gaviota
a.muevete();
b.muevete();
}
}
public class Animal
{
public void muevete()
{
System.out.println("Me estoy moviendo");
}
}
public class Gaviota extends Animal
{
private void muevete() //Marcado como privado
{
System.out.println("La gaviota se mueve");
}
}
</code></pre><br />
<br />
Si el código anterior compilara (cosa que no hará) obtendríamos el siguiente error en tiempo de ejecución:<br />
<br />
<pre><code>
Animal b = new Gaviota(); //Referencia a Animal, pero objeto Gaviota
a.muevete(); //Ocurre una crisis en tiempo de ejecución
</code></pre><br />
<br />
La variable "<span class="codigo">b</span>" es del tipo "<span class="codigo">Animal</span>", el cual tiene un método público llamado "<span class="codigo">muevete</span>", pero hay que recordar que en tiempo de ejecución, Java utiliza la invocación de métodos virtuales para seleccionar dinámicamente la versión real del método que se ejecutará, basado en la del tipo del objeto en tiempo de ejecución. Una referencia de "<span class="codigo">Animal</span>" puede referir siempre a una instancia de "<span class="codigo">Gaviota</span>" porque "<span class="codigo">Gaviota</span>" <span class="codigo">IS-A</span> "<span class="codigo">Animal</span>" ("<span class="codigo">Gaviota</span>" ES UN "<span class="codigo">Animal</span>"). Lo que hace posible que una instancia de una superclase referencie a una instancia de la subclase es que la subclase es capaz de hacer todo lo que la superclase puede hacer (ya que recibe el comportamiento de esta por herencia). Esto quiere decir que cualquiera con una referencia a "<span class="codigo">Gaviota</span>" usando una instancia de <span class="codigo">Animal</span> (<span class="codigo">Animal a = new Gaviota();</span>) es libre de llamar a todos los métodos accesibles de "<span class="codigo">Animal</span>", no importa si "<span class="codigo">Gaviota</span>" sobre-escribe los métodos de "<span class="codigo">Animal</span>" o simplemente los hereda. Un método-sobre escrito debe cumplir con el contrato de la superclase. <br />
<br />
Las reglas para sobre escribir un método son las siguientes:<br />
<br />
<ul><li>La lista de argumentos debe ser exactamente igual (del mismo tipo y en el mismo orden) que el método a sobre-escribir. Si esta lista no coincide en realidad lo que se está haciendo es una sobrecarga del método (que puede que no sea nuestra intención).</li>
<li>El tipo de retorno del método sobre escrito debe ser el mismo o un subtipo del declarado en el método de la súper clase.</li>
<li>El modificador de acceso no debe ser más restrictivo que del método a sobre-escribir. Por ejemplo, si en la clase base tenemos un método "<span class="codigo">public</span>" NO podemos sobre-escribirlo poniéndole un modificador "<span class="codigo">protected</span>".</li>
<li>El modificador de acceso puede ser menos restrictivo que el del método a sobre-escribir. Por ejemplo, si en la clase base tenemos un método "<span class="codigo">protected</span>" SI podemos sobre-escribirlo poniéndole un modificador "<span class="codigo">public</span>".</li>
<li>Los métodos de instancia solo pueden ser sobre-escritos si estos son heredados por la subclase. Una subclase dentro del mismo paquete que su superclase puede sobre escribir cualquier método de la súper clase que NO esté marcado como <span class="codigo">private</span> o <span class="codigo">final</span>. Una subclase en diferente paquete puede sobre-escribir solo los métodos no finales marcados <span class="codigo">public</span> o <span class="codigo">protected</span> (los métodos <span class="codigo">protected</span> son heredados por la subclase).</li>
<li>Los métodos sobre-escritos no deben lanzar excepciones marcadas (en las que sean necesarias un <span class="codigo">try</span> <span class="codigo">catch</span>) que sean nuevas o más amplias que aquellas declaradas en el método que sobre-escribe. Por ejemplo un método que declara un "<span class="codigo">FileNotFoundException</span>" no puede ser sobre-escrito por un método que declara un "<span class="codigo">SQLException</span>", "<span class="codigo">Exception</span>", "<span class="codigo">IOException</span>" o cualquier otra excepción a menos que esta sea una subclase de "<span class="codigo">FileNotFoundException</span>".</li>
<li>Los métodos sobre-escritos pueden lanzar excepciones más específicas (excepciones que extiendan de la excepción que se está declarando) o lanzar menos excepciones. Solo porque el método a sobre-escribir puede lanzar excepciones no quiere decir que el método sobre-escrito lanzara estas mismas excepciones, un método sobre-escrito no tiene que declarar una excepción que nunca lanzará. Independientemente de que el método a sobre-escribir las declare.</li>
<li>No se puede sobre-escribir un método marcado con <span class="codigo">final</span>.</li>
<li>No se puede sobre-escribir un método marcado con <span class="codigo">static</span>.</li>
<li>Si un método no puede ser heredado entonces no puede ser sobre-escrito. Hay que recordar que la sobre-escritura implica que se está re-implementando un método que está heredanddo. Por ejemplo el siguiente código no es legal:</li>
</ul><pre><code>
public class Prueba
{
public static void main(String... args)
{
Gaviota a = new Gaviota();
a.muevete(); //No es legal porque Gaviota no hereda muevete(), muevete() es declarado private en Animal
}
}
public class Animal
{
private void muevete()
{
System.out.println("Me estoy moviendo");
}
}
public class Gaviota extends Animal {}
</code></pre><br />
<br />
<br />
<h4 class="subtitulo">Invocando a la versión de la superclase de un método sobre escrito</h4>Tal vez queramos tomar ventaja de una parte del código en la versión de la súper clase de un método y aun así sobre-escribirlo para proveer algún comportamiento especifico adicional. Esto es como decir: "Ejecuta la versión de la superclase de un método, después vuelve y termina con mi código para hacer un comportamiento adicional en el método de la subclase", esto es fácil de hacer utilizando la palabra reservada <span class="codigo">super</span> como en el ejemplo siguiente:<br />
<br />
<pre><code>
public class Prueba
{
public static void main(String... args)
{
Gaviota a = new Gaviota();
a.muevete();
}
}
public class Animal
{
public void muevete()
{
System.out.println("Me estoy moviendo");
}
}
public class Gaviota extends Animal
{
public void muevete()
{
//podemos agregar alguna cosa mas
super.muevete(); //invoca al código de la superclase (Animal)
}
}
</code></pre><br />
<br />
Usar la palabra reservada "<span class="codigo">super</span>" para invocar a un método sobre-escrito sólo se aplica para métodos de instancia, hay que recordar que <span class="negritas">los métodos marcados con <span class="codigo">static</span> no pueden ser sobre escritos</span>.<br />
<br />
<br />
<h3 class="subtitulo">Sobrecarga de métodos</h3>La sobrecarga de métodos permite usar el mismo nombre de un método en una clase, pero con diferentes argumentos y, opcionalmente, un tipo de retorno diferente, el código asume la carga de hacer frente a diferentes tipos de argumentos en lugar de obligar a la persona que llama a hacer las conversiones antes de invocar el método. Las reglas a seguir para la sobrecarga son simples:<br />
<br />
<ul><li>Para sobrecargar métodos se debe cambiar la lista de argumentos.</li>
<li>Para sobrecargar métodos se puede cambiar el tipo de datos de retorno.</li>
<li>Para sobrecargar métodos se puede cambiar el modificador de acceso.</li>
<li>Para sobrecargar métodos se pueden declarar excepciones que sean nuevas o más amplias.</li>
<li>El método puede ser sobrecargado en la misma clase o en una subclase.</li>
<li>Si la clase "<span class="codigo">A</span>" define un método "<span class="codigo">hacerAlgo(int i)</span>", la subclase "<span class="codigo">B</span>" puede definir un método llamado "<span class="codigo">hacerAlgo(String s)</span>", sin sobre-escribir el método "<span class="codigo">hacerAlgo(int i)</span>" de la súper clase que toma como argumento un entero. Dos métodos con el mismo nombre pero en diferentes clases pueden ser consideradas sobrecargados, si la sub-clase hereda una versión del método y luego declara otra versión sobrecargada en la definición de la clase.</li>
</ul><br />
<br />
<br />
<h4 class="subtitulo">Formas legales de sobrecargar un método</h4>El método que queremos sobrecargar es el siguiente:<br />
<br />
<pre><code>
public void unMetodo(String str, int t, double d){}
</code></pre><br />
<br />
Los siguientes métodos son legales para sobrecargar el método anterior:<br />
<br />
<pre><code>
public void unMetodo(String str, int t){}
public int unMetodo(String str, double d){}
public void unMetodo(String str, int t)throws IOException{}
</code></pre><br />
<br />
<br />
<h4 class="subtitulo">Invocando a métodos sobrecargados</h4>Cuando un método es invocado, más de un método con el mismo nombre puede existir para el tipo de objeto que estamos invocando. Por ejemplo la clase "<span class="codigo">Gaviota</span>" puede tener tres métodos con el mismo nombre pero con diferente lista de argumentos, estos serán métodos sobrecargados. Definir cuál de los diferentes métodos se desea invocar dependerá de la lista de argumentos que contenga. Si estamos invocando a un método que tiene un "<span class="codigo">String</span>" como argumento, el método que tiene un "<span class="codigo">String</span>" como argumento será llamado. A continuación veremos un ejemplo:<br />
<br />
<pre><code>
public class UnaClase
{
public int unMetodo(int x, int y)
{
return x + y;
}
public double unMetodo(double x, double y)
{
return x + y;
}
}
public class Prueba
{
public static void main(String... args)
{
UnaClase a = new UnaClase();
int x = 9;
int y = 5;
int resultado = a.unMetodo(x, y); //¿Qué versión de "unMetodo" es invocado?
double otroResultado = a.unMetodo(5.2, 3.6); //¿Qué versión es invocada?
}
}
</code></pre><br />
<br />
En el primer caso se llama a la primera versión del método "<span class="codigo">unMetodo(x,y)</span>" ya que se está pasando a dos enteros y en el segundo caso se llama a la versión del método "<span class="codigo">unMetodo</span>" que recibe dos argumentos del tipo double.<br />
<br />
La invocación de métodos sobrecargados que reciben objetos en lugar de tipos primitivos es un poco más interesante. Si tenemos un método sobrecargado, que en un método toma un objeto del tipo "<span class="codigo">Animal</span>" y otro en el que toma un objeto del tipo "<span class="codigo">Gaviota</span>" (subclase de "<span class="codigo">Animal</span>"). Si pasamos un objeto del tipo <span class="codigo">Gaviota</span> cuando invocamos al método, invocaremos a la versión sobrecargada que toma una "<span class="codigo">Gaviota</span>". O al menos eso parece a primera vista:<br />
<br />
<pre><code>
class Animal{}
class Gaviota extends Animal{}
public class UsaAnimales
{
public void muevete(Animal a)
{
System.out.println("Estas en la versión de Animal");
}
public void muevete(Gaviota g)
{
System.out.println("Estas en la versión de Gaviota");
}
public static void main(String... args)
{
UsaAnimales ua = new UsaAnimales();
Animal a = new Animal();
Gaviora g = new Gaviota():
ua.muevete(a);
ua.muevete(g);
}
}
</code></pre><br />
<br />
La salida es la que esperamos:<br />
<br />
<pre><code>
Estas en la versión de Animal
Estas en la versión de Gaviota
</code></pre><br />
<br />
¿Pero qué sucede si usamos una referencia de "<span class="codigo">Animal</span>" para un objeto "<span class="codigo">Gaviota</span>"?<br />
<br />
<pre><code>
Animal animalReferenciaGaviota = new Gaviota();
ua.muevete(animalReferenciaGaviota);
</code></pre><br />
<br />
¿Cuál de las versiones del método "<span class="codigo">muévete</span>" es invocada?, podríamos pensar que la respuesta es: "El que toma una "<span class="codigo">Gaviota</span>", ya que es un objeto del tipo "<span class="codigo">Gaviota</span>" el que en tiempo de ejecución está siendo pasado al método". Pero no es así como funciona. El código anterior imprime lo siguiente:<br />
<br />
<pre><code>
Estas en la versión de Animal
</code></pre><br />
<br />
El objeto en tiempo de ejecución es una "<span class="codigo">Gaviota</span>" y no un "<span class="codigo">Animal</span>". La elección de cuál método sobrecargado se deberá llamar <span class="negritas">no es decidido dinámicamente en tiempo de ejecución</span>. Sólo hay que recordar que <span class="negritas">el tipo de referencia, no el tipo de objeto, determina qué método sobrecargado es llamado</span>.<br />
<br />
Para resumir lo que hemos visto hasta ahora de sobrecarga y sobre-escritura: cuál versión de un método sobre-escrito es llamado (en otras palabas, desde cuál clase en el árbol de herencia) es decidido en tiempo de ejecución por el tipo de objeto; pero cuál versión del método sobrecargado se llamará está basado en el tipo de referencia del argumento que se pasa en tiempo de compilación. Si invocamos un método pasando una referencia de "<span class="codigo">Animal</span>" para un objeto "<span class="codigo">Gaviota</span>", el compilador solo sabe que está recibiendo un "<span class="codigo">Animal</span>", por lo que elige la versión sobrecargada que toma un "<span class="codigo">Animal</span>". No importa que en tiempo de ejecución realmente se pase una "<span class="codigo">Gaviota</span>".<br />
<br />
<br />
<h3 class="subtitulo">Polimorfismo en métodos sobrecargados y sobre-escritos</h3>¿Cómo trabaja el polimorfismo en métodos sobrecargados? Si pasamos una referencia de "<span class="codigo">Animal</span>", el método que toma un "<span class="codigo">Animal</span>" será llamado, incluso si el objeto actual pasado es una "<span class="codigo">Gaviota</span>". Una vez que la "<span class="codigo">Gaviota</span>" disfrazada de "<span class="codigo">Animal</span>" entra en el método, sin embargo, el objeto "<span class="codigo">Gaviota</span>" sigue siendo una "<span class="codigo">Gaviota</span>" a pesar de ser pasado a un método que recibe un "<span class="codigo">Animal</span>". Entonces es verdad que el polimorfismo no determina que versión del método sobrecargado será llamado. El polimorfismo entra en juego cuando se decide cuál versión de un método sobre-escrito es llamado. Pero algunas veces un método puede ser ambos, sobre-escrito y sobrecargado. Imaginemos lo siguiente:<br />
<br />
<pre><code>
public class Animal
{
public void comer()
{
System.out.println("El Animal genérico está comiendo");
}
}
public class Gaviota extends Animal
{
public void comer()
{
System.out.println("La gaviota está comiendo");
}
public void comer(String s)
{
System.out.println("La gaviota está comiendo esto: " + s);
}
}
</code></pre><br />
<br />
Noten que la clase "<span class="codigo">Gaviota</span>" tiene sobrecargado y sobre-escrito el método "<span class="codigo">comer</span>" (el método sobrecargado es el que recibe un <span class="codigo">String</span> y el sobre-escrito el que no recibe nada).<br />
<br />
La siguiente tabla muestra cuál de las versiones del método "<span class="codigo">comer</span>" será llamado dependiendo de cómo sea invocado:<br />
<br />
<table><thead>
<tr><th>Código de Invocación al método</th><th>Resultado</th></tr>
</thead> <tbody>
<tr><td><span class="codigo">Animal a = new Animal();<br />
a.comer();</span></td><td>"<span class="codigo">el Animal genérico está comiendo</span>"</td></tr>
<tr><td><span class="codigo">Gaviota g = new Gaviota();<br />
g.comer();</span></td><td>"<span class="codigo">La gaviota está comiendo</span>"</td></tr>
<tr><td><span class="codigo">Animal ag = new Gaviota();<br />
ag.comer();</span></td><td>"<span class="codigo">La gaviota está comiendo</span>"<br />
Polimorfismo trabajando: El objeto actual ("<span class="codigo">Gaviota</span>"), no el tipo de referencia ("<span class="codigo">Animal</span>"), es usado para determinar cuál versión del método "<span class="codigo">comer</span>" es llamado.</td></tr>
<tr><td><span class="codigo">Gaviota gp = new Gaviota();<br />
gp.comer("peces")</span></td><td>"<span class="codigo">La gaviota está comiendo esto: peces</span>"<br />
El método sobre cargado "<span class="codigo">comer(String s)</span>" es llamado.</td></tr>
<tr><td><span class="codigo">Animal a2 = new Animal();<br />
a2.comer("manzanas");</span></td><td>¡¡Error de compilación!!<br />
El compilador mira la clase <span class="codigo">Animal</span> y ve que no tiene un método <span class="codigo">comer</span> que reciba un <span class="codigo">String</span>.</td></tr>
<tr><td><span class="codigo">Animal ag2 = new Gaviota();<br />
ag2.comer("peras");</span></td><td>¡¡Error de compilación!!<br />
El compilador ve solo la referencia y sabe que "<span class="codigo">Animal</span>" no tiene un método "<span class="codigo">comer</span>" que reciba un <span class="codigo">String</span>. Al compilador no le importa que el objeto actual pueda ser una "<span class="codigo">Gaviota</span>" en tiempo de ejecución.</td></tr>
</tbody> </table><br />
<br />
<br />
<h3 class="subtitulo">Diferencias entre métodos sobre cargados y sobre escritos:</h3><table><thead>
<tr> <th></th> <th>Método sobrecargado</th> <th>Método sobre-escrito</th> </tr>
</thead> <tbody>
<tr><td>Argumento(s)</td><td>Debe cambiar.</td><td>No debe cambiar.</td></tr>
<tr><td>Tipo de retorno</td><td>Puede cambiar.</td><td>No puede cambiar, excepto para retornos covariantes.</td></tr>
<tr><td>Excepciones</td><td>Puede cambiar.</td><td>Pueden no declarar una excepción del método que sobre-escribe, o declarar una subclase de esta excepción. No debe lanzar nuevas excepciones o más "amplias" que aquellas declaradas en el método que sobre-escribe.</td></tr>
<tr><td>Nivel de Acceso</td><td>Puede cambiar.</td><td>No pueden tener un nivel de acceso más restrictivo, pero si uno igual o menos restrictivo.</td></tr>
<tr><td>Invocación</td><td>El tipo de referencia determina cuál método sobrecargado es seleccionado, basado en el tipo de argumentos declarados. <span class="negritas">Sucede en tiempo de compilación</span>.</td><td>El método real que se invoca es todavía una invocación de método virtual que siempre sucede en tiempo de ejecución, pero el compilador ya sabe la firma del método que se invoca. El tipo de objeto determina cuál método es seleccionado. <span class="negritas">Sucede en tiempo de ejecución</span>.</td></tr>
</tbody> </table><br />
<br />
<br />
<h2 class="titulo">Casting de variables de referencia</h2>Hasta el momento hemos visto como es posible y común el uso de variables de referencia genéricas para referirse a tipos de objetos más específicos, eso es el corazón del polimorfismo, como ejemplo veamos la siguiente línea de código:<br />
<br />
<pre><code>
Animal animal = new Gaviota();
</code></pre><br />
<br />
¿Pero qué sucede si queremos usar una variable de referencia "<span class="codigo">Animal</span>" para invocar un método que solo la clase "<span class="codigo">Perro</span>" tiene? Sabemos que nos estamos refiriendo a un <span class="codigo">Perro</span> y queremos hacer una cosa específica de <span class="codigo">Perro</span>.<br />
<br />
En la siguiente línea de código tendremos un arreglo de <span class="codigo">Animal</span>es y cada vez que encontremos una <span class="codigo">Perro</span> en el arreglo, haremos algo especial de <span class="codigo">Perro</span>. Asumamos por el momento que todo el código escrito a continuación esté correcto, solo que no estamos seguros de la línea de código que invocara al método "<span class="codigo">hacerRuido</span>".<br />
<br />
<pre><code>
public class Animal
{
void hacerRuido()
{
System.out.println("Ruido Generico");
}
}
public class Perro extends Animal
{
void hacerRuido()
{
System.out.println("Ladrando");
}
void hacerseElMuerto()
{
System.out.println("Muriendo");
}
}
public class Prueba
{
public static void main(String... args)
{
Animal[] a = {new Animal(), new Perro(), new Animal()};
for(Animal animal: a)
{
animal.hacerRuido();
if(animal instanceof Perro)
{
animal.hacerseElMuerto(); //Se intenta invocar un metodo de Perro?
}
}
}
}
</code></pre><br />
<br />
Cuando compilemos el código anterior, el compilador nos enviara un mensaje como:<br />
<br />
<pre><code>
Cannot find Symbol
</code></pre><br />
<br />
El compilador nos está diciendo: "Oye, la clase "<span class="codigo">Animal</span>" no tiene un método "<span class="codigo">hacerseElMuerto</span>"".<br />
<br />
Ahora modifiquemos el bloque de la condicional <span class="codigo">if</span>:<br />
<br />
<pre><code>
if(animal instanceof Perro)
{
Perro perro = (Perro) animal; // Casteando la variable de referencia
perro.hacerseElMuerto();
}
</code></pre><br />
<br />
El nuevo y mejorado bloque de código contiene un "<span class="codigo">cast</span>", el cual en algunos casos es llamado <span class="codigo">down casting</span>, porque estamos bajando en el árbol de herencia a una categoría mas especifica. <br />
<br />
Ahora el compilador no nos dará problemas, casteamos la variable "<span class="codigo">animal</span>" a un tipo "<span class="codigo">Perro</span>". El compilador nos está diciendo lo siguiente: "Sabemos que nos estamos refiriendo a un objeto del tipo <span class="codigo">Perro</span>, está bien hacer una nueva variable de referencia de tipo <span class="codigo">Perro</span>, para referirnos a este objeto", en este caso estamos bien, porque antes de intentar el "<span class="codigo">cast</span>" hicimos una prueba de <span class="codigo">instanceof</span> para asegurarnos.<br />
<br />
Es importante saber que el compilador confía en nosotros cuando hacemos un <span class="codigo">down casting</span> aun cuando pudiéramos hacer algo como lo siguiente:<br />
<br />
<pre><code>
public class Animal{}
public class Perro extends Animal{}
public class Prueba
{
public static void main(String... args)
{
Animal animal = new Animal();
Perro perro = (Perro)animal; //Compila pero fallara después
}
}
</code></pre><br />
<br />
Este código compilara correctamente pero cuando intentemos ejecutarlo vamos a obtener una excepción como la siguiente:<br />
<br />
<pre><code>
java.lang.ClassCastException
</code></pre><br />
<br />
¿Por qué no podemos confiar en el compilador para que nos ayude en esto? ¿No puede ver que <span class="codigo">animal</span> es del tipo "<span class="codigo">Animal</span>"? Todo lo que el compilador puede hacer es comprobar que los dos tipos <span class="negritas">pertenezcan al mismo árbol de herencia</span>, por lo que dependiendo de lo que el código puede hacer antes del <span class="codigo">down casting</span>, es posible de que "<span class="codigo">animal</span>" sea del tipo "<span class="codigo">Perro</span>". El compilador puede permitir cosas que posiblemente puedan funcionar en tiempo de ejecución. Sin embargo si el compilador sabe con certeza que el cast no puede funcionar, entonces la compilación fallara. El siguiente bloque de código NO compilará:<br />
<br />
<pre><code>
Animal animal = new Animal();
Perro p = (Perro) animal;
String s = (String) animal; //animal nunca puede ser un String
</code></pre><br />
<br />
En este caso obtendremos un error como el siguiente:<br />
<br />
<pre><code>
inconvertible types
</code></pre><br />
<br />
A diferencia del <span class="codigo">down-casting</span>, el <span class="codigo">up-casting</span> (convertir a un tipo más general en el árbol de herencia) trabaja implícitamente porque cuando estmos haciendo <span class="codigo">up-casting</span> estamos implícitamente restringiendo el numero de métodos que podemos invocar, lo que impide que más adelante podamos invocar a un método más especifico, por ejemplo:<br />
<br />
<pre><code>
class Animal{}
class Perro extends Animal{}
class Prueba
{
public static void main(String … args)
{
Perro p = new Perro();
Animal a1 = p; //upCasting, se puede sin conversión explicita
Animal a2 = (Animal)p; // upCasting, se puede con conversión explicita
}
}
</code></pre><br />
<br />
Ambos <span class="codigo">up-castings</span> anteriores compilaran y se ejecutaran sin excepción, porque un "<span class="codigo">Perro</span>" ES-UN "<span class="codigo">Animal</span>", lo que significa que lo que cualquier "<span class="codigo">Animal</span>" pueda hacer, el "<span class="codigo">Perro</span>" lo hará. Un "<span class="codigo">Perro</span>" puede hacer más, pero en este punto, cualquiera con una referencia de "<span class="codigo">Animal</span>" puede llamar con seguridad a los métodos de "<span class="codigo">Animal</span>" en una instancia de "<span class="codigo">Perro</span>". Los métodos de "<span class="codigo">Animal</span>" pueden haber sido sobre-escritos en la clase "<span class="codigo">Perro</span>", pero todo lo que importa ahora es saber que un "<span class="codigo">Perro</span>" puede hacer todo lo que un "<span class="codigo">Animal</span>" puede hacer. El compilador y la <span class="codigo">JVM</span> saben esto también, entonces el <span class="codigo">up-casting</span> implícito es siempre legar para la asignación de un objeto de un subtipo para una referencia a su súper tipo o interface. Si "<span class="codigo">Perro</span>" implementa la interface "<span class="codigo">Mascota</span>", y "<span class="codigo">Mascota</span>" define el método "<span class="codigo">seAmigable()</span>", un "<span class="codigo">Perro</span>" puede implícitamente hacer casting a una "<span class="codigo">Mascota</span>", pero el único método de "<span class="codigo">Perro</span>" que se podrá invocar es "<span class="codigo">seAmigable()</span>", el cual "<span class="codigo">Perro</span>" fue forzado a implementar porque "<span class="codigo">Perro</span>" implementó a la interface "<span class="codigo">Mascota</span>".<br />
<br />
Una cosa más, si "<span class="codigo">Perro</span>" implementa a la interface "<span class="codigo">Mascota</span>", y si "<span class="codigo">Sabueso</span>" extiende a "<span class="codigo">Perro</span>", pero "<span class="codigo">Sabueso</span>" no declara que este implementando a "<span class="codigo">Mascota</span>", "<span class="codigo">Sabueso</span>" sigue siendo una "<span class="codigo">Mascota</span>", "<span class="codigo">Sabueso</span>" es una "<span class="codigo">Mascota</span>" simplemente porque este está extendiendo a "<span class="codigo">Perro</span>". La clase "<span class="codigo">Sabueso</span>" puede siempre sobre-escribir cualquier método que este heredando desde "<span class="codigo">Perro</span>", incluyendo los métodos que "<span class="codigo">Perro</span>" implementa para cumplir con el contrato de la interfaz.<br />
<br />
Por último, si "<span class="codigo">Sabueso</span>" declara que implementa a "<span class="codigo">Mascota</span>", es solamente para que los que busquen en "<span class="codigo">Sabueso</span>" puedan ver fácilmente que "<span class="codigo">Sabueso</span>" ES-UNA "<span class="codigo">Mascota</span>", sin tener que mirar a la superclase de "<span class="codigo">Sabueso</span>". "<span class="codigo">Sabueso</span>" no tiene la necesidad de implementar el método "<span class="codigo">seAmigable()</span>" si la clase "<span class="codigo">Perro</span>" (súper clase de "<span class="codigo">Sabueso</span>") ya se ha ocupado de eso. En otras palabras, si "<span class="codigo">Sabueso</span>" ES-UN "<span class="codigo">Perro</span>", y "<span class="codigo">Perro</span>" ES-UNA "<span class="codigo">Mascota</span>", entonces "<span class="codigo">Sabueso</span>" ES-UNA "<span class="codigo">Mascota</span>", y ya ha cumplido con los métodos de "<span class="codigo">Mascota</span>" para la aplicación del método "<span class="codigo">seAmigable()</span>", ya que hereda el método "<span class="codigo">seAmigable()</span>". El compilador es suficientemente inteligente para decir: "Sé que <span class="codigo">Sabueso</span> es un <span class="codigo">Perro</span>, pero está bien para que sea más obvio".<br />
<br />
Así que no hay que dejarse engañar por el código que muestra una clase concreta que declara que implementa una interfaz, pero no implementa el método de la interface, antes de poder decir si el código es legal, debemos mirar cuál es la superclase de la clase que esta implementado esta interface. Si alguna clase en el árbol de herencia ya ha implementado métodos concretos y ha declarado que ella (la superclase) implementa la interfaz, entonces la subclase no tiene ninguna obligación de volver a implementar (sobre-escribir) los métodos.<br />
<br />
<br />
<h2 class="titulo">Implementando una interface</h2>Cuando implementamos una interface estamos aceptando el contrato definido por la interfaz. Eso quiere decir que estamos obligados a implementar todos los métodos definidos por la interfaz y que cualquiera que conozca los métodos de la interfaz (no como lo implementa pero si como se llaman y que retornan) puede invocar estos métodos en una clase que implementa la interfaz.<br />
<br />
Por ejemplo si creamos una clase que implemente a la interfaz "<span class="codigo">Runnable</span>" (para que el código pueda ejecutarse en otro un hilo), debemos implementar el método "<span class="codigo">public void run()</span>", de lo contrario el hilo verá que no tenemos implementado el método "<span class="codigo">run</span>" de la interface "<span class="codigo">Runnable</span>" y provocará un error. Afortunadamente, Java impide que esta crisis se produzca mediante la ejecución de un proceso de comprobación, en el compilador, de cualquier clase que pretende implementar una interfaz.<br />
<br />
Si la clase está implementando una interface, deberíamos tener una implementación para cada método de la interface, con unas pocas excepciones que veremos en un momento.<br />
<br />
Por ejemplo, imaginemos que tenemos la interfaz "<span class="codigo">Rebotable</span>", con dos métodos "<span class="codigo">rebotar()</span>" y "<span class="codigo">setFactorRebote</span>", la siguiente clase compilará:<br />
<br />
<pre><code>
public class Pelota implements Rebotable //palabra reservada implements
{
public void rebotar(){}
public void setFactorRebote(int fr){}
}
</code></pre><br />
<br />
El contrato garantiza que una clase tenga todos los métodos de una interface pero no garantiza que tenga una buena o correcta implementación en el cuerpo del método. El compilador nunca se fijará que haya algo entre las llaves del método, nunca dirá que es un método y que debería hacer algo, solo se fija en que tenga los métodos que se describen en la interface, nada más.<br />
<br />
Las clases que implementan una interface deben de seguir las mismas reglas para una clase que extiende a una clase abstracta.<br />
<br />
Las reglas para que una clase <span class="negritas">NO</span> abstracta implemente correctamente una interface son las siguientes:<br />
<br />
<ul><li>Proveer implementación para todos los métodos declarados en la interface.</li>
<li>Seguir todas las reglas para una sobre-escritura legal (overrides).</li>
<li>No declarar excepciones, en la implementación del método, que no estén en el método definido en la interface. Se pueden declarar subclases de las declaradas por el método de la interfaz.</li>
<li>Mantener el mismo nombre del método de la interface y el mismo tipo de dato de retorno (o un subtipo).</li>
</ul><br />
<br />
Una clase que implementa una interface <span class="negritas">puede ser abstracta</span>. Por ejemplo:<br />
<br />
<pre><code>
abstract class Pelota implements Rebotable{}
</code></pre><br />
<br />
¿Notan que algo falta?, pues no estamos implementando los métodos de la interfaz "<span class="codigo">Rebotable</span>", y no tenemos ningún error. Si la clase que implementa una interface es una clase abstracta esta puede simplemente pasar la implementación de los métodos a la primera clase concreta que se implemente. <br />
<br />
Por ejemplo si tenemos la clase "<span class="codigo">PelotaPlaya</span>" y esta extiende de la clase "<span class="codigo">Pelota</span>", entonces la clase "<span class="codigo">PelotaPlaya</span>" debe de implementar todos los métodos de la interfaz "<span class="codigo">Rebotable</span>":<br />
<br />
<pre><code>
public class PelotaPlaya extends Pelota
{
/*A pesar que no se dice en la declaración anterior (no hay implements a Rebotable)
PelotaPlaya tiene que implementar a la interface Rebotable ya que la super clase
abstracta de PelotaPlaya (Pelota) implementa a Rebotable */
public void rebotar(){}
public void setFactorRebote(int fr){}
/*Si la clase Pelota hubiera declarado algun método abstracto, entonces
ese método debería estar implementado aquí también*/
}
</code></pre><br />
<br />
A menos que la implementación sea de una clase abstracta, la implementación debe tener todos los métodos definidos por la interface.<br />
<br />
Hay dos reglas más que debemos saber:<br />
<br />
<ul><li><span class="negritas">1.</span> Una clase puede implementar más de una interface, por ejemplo:</li>
<pre><code>
public class Pelota implements Rebotable, Runnable, Serializable{}
</code></pre>Podemos extender solo a una clase, pero implementar a muchas interfaces. Pero recuerden que la sub-clasificación (extends) define quién y qué es nuestra clase, mientras que la implementación (implements) define una función que puede desempeñar o un sombrero que nuestra clase puede usar, a pesar de lo diferente que podría ser de la otra clase que implemente la misma interface (pero de un árbol de herencia diferente). Por ejemplo: "<span class="codigo">Persona extends SerHumano</span>", pero una "<span class="codigo">Persona</span>" también puede ser (implements) "<span class="codigo">Programador</span>", "<span class="codigo">Empleado</span>", "<span class="codigo">Pariente</span>".
<li><span class="negritas">2.</span> Una interface puede también extender a otra interface, pero nunca implementar nada, por ejemplo:</li>
<pre><code>
public interface Rebotable extends Movible{}
</code></pre></ul>¿Qué significa esto? La primera clase concreta que implemente "<span class="codigo">Rebotable</span>" debe implementar todos los métodos de esta interface, pero también debe implementar todos los métodos de la interface "<span class="codigo">Movible</span>". La sub-interface simplemente está agregando más requerimientos al contrato de la super-interface.<br />
<br />
Una interface puede extender a más de una interface, pero sabemos que cuando hablamos de clases esto es ilegal, por ejemplo:<br />
<br />
<pre><code>
public class Programador extends Empleado, Geek{} //Esto es ilegal!!
</code></pre><br />
<br />
Como mencionamos anteriormente una clase no permite extender a más de una clase en Java, una interface, sin embargo, puede implementar a más de una interface, por ejemplo:<br />
<br />
<pre><code>
public interface Rebotable extends Movible, Inflable //OK
{
void rebotar();
void setFactorRebote(int fr);
}
interface Movible
{
void muevete();
}
interface Inflable
{
void inflate();
}
</code></pre><br />
<br />
En el próximo ejemplo, "<span class="codigo">Pelota</span>" requiere implementar la interface "<span class="codigo">Rebotable</span>", pero también debe implementar todos los métodos de las interfaces que "<span class="codigo">Rebotable</span>" ha extendido, incluyendo cualquier interface que aquellas interfaces extienden, y así sucesivamente hasta llegar a la parte superior de la pila. "<span class="codigo">Pelota</span>" debe tener lo siguiente:<br />
<br />
<pre><code>
public class Pelota implements Rebotable
{
public void rebotar(){} //implementando metodos de la
public void setFactorRebote(int fr){} //interface Rebotable
public void muevete(){} //implementando de Movible
public void inflate(){} //implementando de Inflable
}
</code></pre><br />
<br />
Si la clase "<span class="codigo">Pelota</span>" no implementa cualquiera de los métodos de "<span class="codigo">Rebotable</span>", "<span class="codigo">Movible</span>" o "<span class="codigo">Inflable</span>", el compilador encontrará errores, al menos que "<span class="codigo">Pelota</span>" sea una clase abstracta. En el caso de que "<span class="codigo">Pelota</span>" sea una clase abstracta esta puede implementar todos, algunos o ningún método de las interfaces, puede dejar la implementación a una sub clase concreta de "<span class="codigo">Pelota</span>", por ejemplo:<br />
<br />
<pre><code>
abstract class Pelota implements Rebotable
{
public void rebotar(){} //define comportamiento de la
public void setFactorRebote(int fr){} //interface Rebotable
/*no implementa el resto de los métodos, deja el resto para una sub-clase concreta*/
}
class PelotaFutbol extends Pelota
{
/*implementando métodos que pelota no hizo*/
public void muevete(){} //implementando de Movible
public void inflate(){} //implementando de Inflable
/*PelotaFutbol puede elegir sobre escribir el método rebotar implementado en pelota*/
public void rebotar(){}
}
</code></pre><br />
<br />
Comparando ejemplos de abstracto y concreto de extender e implementar:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjiuzsuLJyFCk-heIHwFrkyywD7HuwQb5mlUdpT7jHyi-4lATACFDottCGoidxMLj1uZYFEHxPudeY0AkqqKO5FgpUye3Lsfxq3bS0urjBmT9bkAJ2JkmLKYXIYmIOba3WXcARgGSpjKvXV/s1600/C2_2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="465" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjiuzsuLJyFCk-heIHwFrkyywD7HuwQb5mlUdpT7jHyi-4lATACFDottCGoidxMLj1uZYFEHxPudeY0AkqqKO5FgpUye3Lsfxq3bS0urjBmT9bkAJ2JkmLKYXIYmIOba3WXcARgGSpjKvXV/s640/C2_2.png" width="640" /></a></div><br />
<br />
Ya que "<span class="codigo">PelotaFutbol</span>" es la primera clase concreta que Implementa "<span class="codigo">Rebotable</span>", esta debe implementar todos los métodos de la interface "<span class="codigo">Rebotable</span>", excepto aquellos definidos en la clase abstracta "<span class="codigo">Pelota</span>". Ya que "<span class="codigo">Pelota</span>" no provee implementación de los métodos de la interface "<span class="codigo">Rebotable</span>", es necesario que "<span class="codigo">PelotaFutbol</span>" implemente todos aquellos métodos.<br />
<br />
<br />
<h2 class="titulo">Tipos Correctos de Retorno</h2>El objetivo de esta sección es cubrir dos aspectos de los tipos de retorno:<br />
<br />
<ul><li><span class="negritas">1.</span> Qué se puede declarar como un tipo de retorno, y </li>
<li><span class="negritas">2.</span> Qué se puede retornar como un valor.</li>
</ul>Qué se puede y no se puede declarar es muy sencillo, pero esto depende si estamos sobre-escribiendo un método, heredado o simplemente declarando un método nuevo (el cual incluye sobrecarga de métodos). Daremos solo una pequeña mirada a las diferencias entre las reglas para los tipos de retorno para métodos sobre cargados y sobre-escritos (overloaded y overriding).<br />
<br />
<h3 class="subtitulo">Declaración de tipos de retorno</h3>Veremos qué está permitido para declarar como un tipo de retorno, lo cual depende primero en que si estamos sobre-escribiendo, sobrecargando o declarando un nuevo método.<br />
<br />
<h3 class="subtitulo">Tipos de retorno en métodos sobre cargados</h3>Recuerden que un método sobrecargado no es más que una forma para la reutilización del mismo nombre de un método pero con diferentes argumentos. Un método sobrecargado es un método completamente diferente de cualquier otro método con el mismo nombre. Si heredamos un método pero lo sobrecargamos este en una subclase, este no está sujeto a las restricciones de sobre-escritura, lo cual implica que podemos declarar cualquier tipo de retorno. Lo que no podemos hacer es cambiar solo el tipo de retorno. Para sobre cargar un método solo debemos cambiar la lista de argumentos, por ejemplo:<br />
<br />
<pre><code>
public class Animal
{
void muevete(){};
}
public class Perro extends Animal
{
String muevete(int numeroPasos)
{
return null;
}
}
</code></pre><br />
<br />
Noten que el método de "<span class="codigo">Animal</span>" tiene un tipo de retorno diferente que el método de "<span class="codigo">Perro</span>". Esto está bien. En el momento que se ha cambiado la lista de argumentos, hemos sobrecargado el método, entonces el tipo de retorno no tiene que coincidir con el del método de la súper clase, pero por ejemplo, lo siguiente no está permitido: <br />
<br />
<pre><code>
public class Animal
{
void muevete(){};
}
public class Perro extends Animal
{
String muevete() //Error, no se puede cambiar solo el tipo de retorno
{
return null;
}
}
</code></pre><br />
<br />
<h3 class="subtitulo">Sobre escritura y tipos de retorno, retornos covariantes</h3>Cuando una clase quiere cambiar la implementación del método de un método heredado (sobre-escrito), la subclase debe definir un método que debe coincidir exactamente con el método heredado. O, a partir de <span class="codigo">Java 5</span>, se le permite cambiar el tipo de retorno en el método sobre-escrito siempre y cuando el nuevo tipo de retorno es un subtipo del tipo de retorno declarado en el método a sobre-escribir (superclase).<br />
<br />
<pre><code>
class Alpha
{
Alpha hacerAlgo(char c)
{
return new Alpha();
}
}
class Beta extends Alpha
{
Beta hacerAlgo(char c) //sobre escritura legal en Java 1.5
{
return new Beta();
}
}
</code></pre><br />
<br />
En <span class="codigo">Java 5</span> este código compilara sin problemas, pero si intentamos compilar este código con <span class="codigo">Java 1.4</span>, mostrará el siguiente error:<br />
<br />
<pre><code>
attempting to use incompatible return type
</code></pre><br />
<br />
Otras reglas aplicadas a la sobre-escritura, incluyen aquellas para modificadores de acceso y declaración de excepciones.<br />
<br />
<h3 class="subtitulo">Retornando un valor</h3>Tenemos que recordar solo <span class="negritas">6 reglas</span> para retornar un valor:<br />
<br />
<ul><li><span class="negritas">1. </span> Podemos retornar <span class="codigo">null</span> a los métodos que tengan como tipo de retorno un objeto, ejemplo:</li>
<pre><code>
public Button hacerAlgo
{
return null;
}
</code></pre><li><span class="negritas">2. </span> Podemos declarar un arreglo como tipo de retorno sin problemas, ejemplo:</li>
<pre><code>
public String[] go()
{
return new String[] {"Alan", "Alex", "Diego", "y Peke", "Henry", "Cesar", "Pedroww"};
}
</code></pre><li><span class="negritas">3. </span> En un método con un tipo de retorno primitivo, podemos retornar cualquier valor o variable que pueda ser implícitamente convertido al tipo de retorno declarado, ejemplo:</li>
<pre><code>
public int go()
{
char c = ‘c’;
return c; //char es compatible con int
}
</code></pre><li><span class="negritas">4. </span>En un método con un tipo de retorno primitivo, podemos retornar cualquier valor o variable que pueda ser explícitamente convertido al tipo de retorno declarado, ejemplo:</li>
<pre><code>
public int go()
{
float f = 32.5f;
return (int) f
}
</code></pre><li><span class="negritas">5. </span>No debemos retornar nada desde un método que tiene como tipo de retorno "<span class="codigo">void</span>", ejemplo:</li>
<pre><code>
public void go()
{
return "Esto es todo"; //Error, no es legal
}
</code></pre><li><span class="negritas">6. </span>En un método con un tipo de retorno con referencia a un objeto, podemos retornar cualquier tipo de objeto que pueda ser implícitamente convertido al tipo de retorno declarado, ejemplo:</li>
<pre><code>
public Animal getAnimal()
{
return new Perro(); //Asumiendo que Perro extiende de Animal
}
public Object getObject()
{
int[] nums = {1, 2, 3};
return nums; //retorna un arreglo de enteros, el cual sigue siendo un //objeto
}
public interface Rebotable{}
public class Pelota implements Rebotable{}
public class TestPelota
{
//Método con una interface como tipo de retorno
public Rebotable getRebotable()
{
return new Pelota(); //Retorna un implementador de la interface
}
}
</code></pre></ul>Y con eso terminamos todo lo concerniente con las reglas para el retorno de tipos de valor. Ahora veremos algunos detalles interesantes sobre constructores e instanciación de objetos.<br />
<br />
<h2 class="titulo">Constructores e Instanciación</h2>Los objetos son construidos. No podemos crear un nuevo objeto sin invocar a un constructor. No podemos crear un nuevo objeto sin invocar al constructor del objeto actual y a los constructores de sus superclases. <br />
<br />
Los constructores son código que se ejecuta cuando usamos la palabra reservada "<span class="codigo">new</span>". También pueden ser bloques de inicialización que se ejecutan al escribir "<span class="codigo">new</span>" (son como constructores pero un poco distintos), pero vamos a cubrir estos (bloques de inicialización), y sus homólogos de la inicialización estática, más adelante. Vamos a ver cómo se codifican los constructores y como trabajan en tiempo de ejecución.<br />
<br />
<h3 class="subtitulo">Constructores Básicos</h3>Todas las clases, incluyendo las clases abstractas, deben tener un constructor, pero solo porque una clase deba de tener un constructor no quiere decir que necesitamos escribirlo explícitamente. Un constructor es algo como esto:<br />
<br />
<pre><code>
public class Rectangulo
{
Rectangulo(){} //Constructor para la clase Rectangulo
}
</code></pre><br />
<br />
Un constructor no retorna ningún tipo de valor. Hay dos cosas que siempre debemos recordar acerca de los constructores: <br />
<br />
<ol><li><span class="codigo">1. </span>No retornan ningún tipo de valor y </li>
<li><span class="codigo">2. </span>Llevan el mismo nombre que la clase.</li>
</ol>Típicamente los constructores son utilizados para inicializar el estado de las variables de instancia, por ejemplo:<br />
<br />
<pre><code>
public class Rectangulo
{
private int ancho;
private int altura;
public Rectangulo (int ancho, int altura)
{
this.ancho = ancho;
this.altura = altura;
}
}
</code></pre><br />
<br />
En el caso anterior, la clase "<span class="codigo">Rectangulo</span>" <span class="negritas">no tienen un constructor sin argumentos</span>, lo que significa que el siguiente código fallará en tiempo de compilación:<br />
<br />
<pre><code>
Rectangulo r = new Rectangulo(); //No compila, no coinciden el constructor
</code></pre><br />
<br />
Pero el siguiente código, compilará correctamente: <br />
<br />
<pre><code>
Rectangulo r = new Rectangulo(4,2); //No hay problema, los argumentos coinciden con el constructor
</code></pre><br />
<br />
Es muy común (y deseable) tener un constructor sin argumentos, independientemente del número de constructores sobrecargados que nuestra clase pueda tener (sí, <span class="negritas">los constructores pueden ser sobrecargados</span>). De vez en cuando se tiene una clase en la que no tiene sentido crear una instancia sin necesidad de suministrar información al constructor. Un "<span class="codigo">java.awt.Color</span>", por ejemplo, no puede ser llamado mediante una llamada a un constructor sin argumentos, porque es como decirle a la JVM: "Hazme un nuevo color, y no me importa qué tipo de color sea este... tu decide", ¿realmente creen que la <span class="codigo">JVM</span> pueda tomar decisiones de ese tipo?<br />
<br />
<h3 class="subtitulo">Encadenamiento de constructores</h3>Sabemos que los constructores son invocados en tiempo de ejecución cuando usamos la palabra reservada "<span class="codigo">new</span>" de la siguiente manera:<br />
<br />
<pre><code>
Perro p = new Perro();
</code></pre><br />
<br />
Pero ¿qué es lo que realmente sucede cuando escribimos "<span class="codigo">new Rectangulo()</span>"?<br />
<br />
Asumamos que "<span class="codigo">Perro</span>" extiende (hereda) de "<span class="codigo">Animal</span>" y "<span class="codigo">Animal</span>" extiende de "<span class="codigo">Object</span>"<br />
<br />
<ol><li><span class="negritas">1. </span>El constructor de "<span class="codigo">Perro</span>" es invocado. Cada constructor invoca al constructor de su superclase con una llamada implícita a "<span class="codigo">super()</span>", a menos que el constructor invoque a un constructor sobrecargado de la misma clase, pero esto lo veremos más adelante.</li>
<li><span class="negritas">2. </span>El constructor de "<span class="codigo">Animal</span>" es invocado ("<span class="codigo">Animal</span>" es la superclase de "<span class="codigo">Perro</span>")</li>
<li><span class="negritas">3. </span>El constructor de "<span class="codigo">Object</span>" es invocado ("<span class="codigo">Object</span>" es la última superclase de todas las clases, entonces "<span class="codigo">Animal</span>" extiende de "<span class="codigo">Object</span>" a pesar de que no escribimos explícitamente "<span class="codigo">extends Object</span>" en la declaración de la clase "<span class="codigo">Animal</span>". Esto es Implícito), en este punto estamos en el cima de la pila.</li>
<li><span class="negritas">4. </span>Se asignan los valores explícitos a las variables de instancia. Nos referimos a las variables que declaramos como "<span class="codigo">int x = 24</span>", donde 24 es el valor explicito (en comparación con el valor por defecto) de la variable de instancia.</li>
<li><span class="negritas">5. </span>El constructor de <span class="codigo">Object</span> se completa.</li>
<li><span class="negritas">6. </span>Se asignan los valores explícitos a las variables de instancia de "<span class="codigo">Animal</span>" (si tuviera).</li>
<li><span class="negritas">7. </span>El constructor de "<span class="codigo">Animal</span>" completa.</li>
<li><span class="negritas">8. </span>Se asignan los valores explícitos a las variables de instancia de "<span class="codigo">Perro</span>" (si tuviera).</li>
<li><span class="negritas">9. </span>El constructor de "<span class="codigo">Perro</span>" completa.</li>
</ol><br />
<br />
A continuación mostramos como trabajan los constructores en la pila de llamadas:<br />
<br />
<ol><li><span class="negritas">4.-</span> <span class="codigo">Object()</span></li>
<li><span class="negritas">3.-</span> <span class="codigo">Animal()</span> llamada a <span class="codigo">super()</span></li>
<li><span class="negritas">2.-</span> <span class="codigo">Perro()</span> llamada a <span class="codigo">super()</span></li>
<li><span class="negritas">1.-</span> <span class="codigo">main()</span> llamada a new <span class="codigo">Perro()</span></li>
</ol><br />
<br />
<h3 class="subtitulo">Reglas para los constructores:</h3>A continuación mostramos lo que debemos recordar acerca de los constructores:<br />
<br />
<ul><li>Un constructor puede tener cualquier modificador de acceso, incluso "<span class="codigo">private</span>" (un constructor privado quiere decir que solo el código dentro de la clase misma puede instanciar un objeto de ese tipo, así que si la clase con constructor privado quiere permitir que una clase pueda instanciarla, la clase debe proveer un método estático o variable que permitan acceder a una instancia creada dentro de la clase).</li>
<li>El constructor debe tener el mismo nombre que la clase.</li>
<li>El constructor no debe retornar ningún tipo de valor.</li>
<li>Es legal (pero tonto) tener un método con el mismo nombre de la clase, pero esto no hace que sea un constructor. <span class="negritas">Si ven que tiene un tipo de retorno, entonces es un método en vez de un constructor</span>. Podemos tener ambos, un método y un constructor con el mismo nombre (el nombre de la clase) en una misma clase y esto no es problema para Java.</li>
<li>Si no escribimos ningún constructor dentro de la clase, un constructor por defecto será automáticamente generado por el compilador.</li>
<li>El constructor por defecto SIEMPRE es un constructor sin argumentos.</li>
<li>Si deseamos tener un constructor sin argumentos y ya hemos escrito algún otro constructor dentro de la clase, el compilador NO proporcionara un constructor sin argumentos (o cualquier otro constructor). Es decir si hemos escrito algún constructor con argumentos no vamos a tener un constructor sin argumentos a menos que nosotros mismos lo declaremos. </li>
<li>Todos los constructores tienen como primera declaración ya sea una llamada a un constructor sobre cargado "<span class="codigo">this()</span>" o una llamada al constructor de la superclase "<span class="codigo">super()</span>", aunque recuerden que esta llamada puede ser insertada por el compilador.</li>
<li>Si escribimos un constructor (en lugar de confiar en el constructor por defecto generado por el compilador), y no escribimos una llamada a <span class="codigo">this()</span> o a <span class="codigo">super()</span>, el compilador insertará una llamada sin argumentos a <span class="codigo">super()</span> por nosotros, como una primera declaración en el constructor.</li>
<li>Una llamada a <span class="codigo">super()</span> puede ser una llamada sin argumentos o podemos incluir argumentos en él.</li>
<li>Un constructor sin argumentos no es necesariamente el constructor por defecto, aunque el constructor por defecto siempre es uno sin argumentos. El constructor por defecto es el único que el compilador provee, pero podemos insertar nuestro propio constructor sin argumentos.</li>
<li>No podemos hacer una llamada a un método de instancia o acceder a una variable de instancia hasta que se ejecute el constructor <span class="codigo">super</span>.</li>
<li><span class="negritas">Solo variables y métodos estáticos pueden ser accedidos como parte de la llamada a <span class="codigo">super()</span> o <span class="codigo">this()</span></span>. Por ejemplo: <span class="codigo">super(Animal.NOMBRE)</span> está bien ya que <span class="codigo">NOMBRE</span> está declarada como variable estática.</li>
<li>Las clases abstractas tienen constructores, y estos constructores son siempre llamados cuando una clase concreta es instanciada.</li>
<li><span class="negritas">Las interfaces no tienen constructores</span>, las interfaces no son parte del árbol de herencia de un objeto.</li>
<li>La única forma de que un constructor pueda ser invocado es dentro de otro constructor, en decir, no podemos invocar a un constructor de la siguiente manera:</li>
</ul><pre><code>
public class Perro
{
public Perro(){} //Contructor
public void hacerAlgo()
{
Perro(); //llamada al constructor. Error!!
}
}
</code></pre><br />
<br />
<h3 class="subtitulo">Determinar si un constructor por defecto será creado</h3>El siguiente código muestra a la clase "<span class="codigo">Perro</span>" con dos constructores:<br />
<br />
<pre><code>
public class Perro
{
public Perro(){}
public Perro(String nombre){}
}
</code></pre><br />
<br />
¿El compilador insertará un constructor por defecto para la clase anterior? <span class="negritas">¡¡NO!!</span><br />
<br />
¿Y para la siguiente modificación en la clase?<br />
<br />
<pre><code>
public class Perro
{
public Perro(String nombre){}
}
</code></pre><br />
<br />
¿Ahora el compilador insertará un constructor por defecto? <span class="negritas">¡¡NO!!</span><br />
<br />
¿Y qué hay de esta clase?<br />
<br />
<pre><code>
public class Perro
{
}
</code></pre><br />
<br />
El compilador <span class="negritas">SI</span> generará un constructor por defecto para la clase anterior, porque la clase no tiene ningún constructor definido. Y ahora ¿Qué hay de esta siguiente clase?:<br />
<br />
<pre><code>
public class Perro
{
void Perro(){}
}
</code></pre><br />
<br />
Podría parecer que el compilador no crea un constructor ya que ya hay un constructor en la clase "<span class="codigo">Perro</span>". ¿Pero realmente hay un constructor? Revisemos nuevamente la clase anterior, pues eso no es un constructor, es solo un método que tiene el mismo nombre que la clase. Recordemos que <span class="negritas">el tipo de retorno es un claro indicador de que eso es un método y no un constructor</span>.<br />
<br />
¿Cómo se sabe con seguridad que un constructor por defecto será creado?<br />
<br />
<span class="negritas">Porque no declaramos NINGUN constructor en nuestra clase</span>.<br />
<br />
¿Cómo se verá el constructor por defecto creado por el compilador?<br />
<br />
<ul><li>El constructor por defecto tiene el mismo modificador de acceso que la clase.</li>
<li>El constructor por defecto no tiene argumentos.</li>
<li>El constructor por defecto incluye una llamada sin argumentos al súper constructor (<span class="codigo">super()</span>).</li>
</ul><br />
<br />
¿Qué sucede si el súper constructor tiene argumentos?<br />
<br />
Los constructores pueden tener argumentos así como los métodos, si tratamos de invocar a un método que toma por ejemplo un <span class="codigo">int</span> y no le pasamos nada al método, el compilador se comportará de la siguiente manera: <br />
<br />
<pre><code>
class Ejemplo
{
void tomaUnInt(int valor){}
}
class Test
{
public static void main(String … args)
{
Ejemplo e = new Ejemplo();
e.tomaUnInt(); //Se está tratando de invocar al metodo "tomaUnInt()" sin argumentos
}
}
</code></pre><br />
<br />
El compilador nos dirá que estamos tratando de invocar a "<span class="codigo">tomaUnInt()</span>" sin pasarle ningún un <span class="codigo">int</span>. El compilador, según la versión de la <span class="codigo">JVM</span>, responderá más o menos de la siguiente manera:<br />
<br />
<pre><code>
Test.java:7: tomaUnInt(int) in Ejemplo cannot be applied to ()e. tomaUnInt();
</code></pre><br />
<br />
Esto quiere decir que debemos pasar los mismos valores o variables que el método acepta y en el mismo orden en el que esté declarado en el método, a lo que queremos llegar es que este mecanismo funciona exactamente igual en los constructores.<br />
<br />
A continuación mostramos el código que genera el compilador con respecto a los constructores:<br />
<br />
<table><thead>
<tr><th>Código de clase (lo que nosotros escribimos)</th><th>Código de constructor generado por el compilador</th></tr>
</thead> <tbody>
<tr><td><pre><code>class Perro { }</code></pre></td> <td><pre><code>
class Perro
{
<span class="negritas">Perro()
{
super();
}</span>
}
</code></pre></td></tr>
<tr><td><pre><code>
class Perro
{
Perro(){}
}</code></pre></td> <td><pre><code>
class Perro
{
Perro()
{
super();
}
}</code></pre></td></tr>
<tr><td><pre><code>public class Perro { }</code></pre></td> <td><pre><code>
public class Perro
{
public Perro()
{
super();
}
}</code></pre></td></tr>
<tr><td><pre><code>
class Perro
{
Perro(String nombre){}
}</code></pre></td> <td><pre><code>
class Perro
{
Perro(String nombre)
{
super();
}
}</code></pre></td></tr>
<tr><td><pre><code>
class Perro
{
Perro(String nombre)
{
super();
}
}</code></pre></td> <td>Nada, el compilador no necesita insertar nada</td></tr>
<tr><td><pre><code>
class Perro
{
void Perro(){}
}</code></pre></td> <td><pre><code>
class Perro
{
void Perro(){}
Perro()
{
super();
}
}</code></pre><br />
<br />
("<span class="codigo">void Perro()</span>", es un método no un constructor)<br />
</td></tr>
</tbody> </table><br />
<br />
Si el súper constructor (el constructor de su inmediata súper clase o clase padre) tiene argumentos, debemos escribir en la llamada a <span class="codigo">super()</span> los argumentos adecuados.<br />
<br />
<span class="negritas">Un punto crucial</span>: si nuestra superclase no tiene un constructor sin argumentos, debemos escribir un constructor en la clase (subclase), porque necesitamos un lugar donde insertar, en la llamada a <span class="codigo">super</span>, los argumentos adecuados.<br />
<br />
El siguiente código da un ejemplo del problema:<br />
<br />
<pre><code>
class Animal
{
Animal(String nombre){}
}
class Perro extends Animal
{
Perro()
{
super(); //He aquí el problema
}
}
</code></pre><br />
<br />
El compilador nos arrojará algo como esto:<br />
<br />
<pre><code>
Perro.java:7: cannot resolve symbol
symbol : constructor Animal ()
location: class Animal
super(); // Problema!
^
</code></pre><br />
<br />
Otra forma de explicar es la siguiente: Si nuestra superclase no tiene un constructor sin argumentos entonces la subclase no será capaz de usar el constructor por defecto que te provee el compilador, es decir, el compilador puede solo insertar una llamada a "<span class="codigo">super()</span>" sin argumentos, no será capaz de compilar algo como esto:<br />
<br />
<pre><code>
class Animal
{
Animal(String nombre){}
}
class Perro extends Animal{}
</code></pre><br />
<br />
El compilador nos arrojará algo como esto:<br />
<br />
<pre><code>
Animal.java:4: cannot resolve symbol
symbol : constructor Animal ()
location: class Animal
class Perro extends Animal { }
^
</code></pre><br />
<br />
El compilador explícitamente está haciendo el código que se muestra a continuación, donde vamos a proveer a "<span class="codigo">Perro</span>" el mismo constructor que el compilador le proveerá:<br />
<br />
<pre><code>
class Animal
{
Animal(String nombre){}
}
class Perro extends Animal
{
//el constructor a continuación es idéntico al que el compilador proveerá
Perro()
{
super(); //¡¡Se invoca al constructor sin argumentos de "Animal" el cual no existe!!
}
}
</code></pre><br />
<br />
Una última cosa que debemos recordar es que los constructores no se heredan, estos no son métodos los cuales si se heredan. Estos no pueden ser sobre-escritos pero, como hemos visto a lo largo de esta parte de constructores, estos si se pueden sobrecargar. <br />
<br />
<br />
<h3 class="subtitulo">Sobrecarga de constructores</h3>Sobrecargar un constructor quiere decir que escribimos diferentes versiones del constructor, cada uno tiene diferente número de argumentos, como por ejemplo:<br />
<br />
<pre><code>
class Perro
{
Perro(){}
Perro(String nombre){}
}
</code></pre><br />
<br />
La clase "<span class="codigo">Perro</span>" anterior muestra dos constructores sobrecargados, uno de ellos toma un <span class="codigo">String</span> como argumento y el otro no tiene argumentos; este es exactamente igual al constructor que el compilador provee, pero recordemos que una vez que nosotros escribamos un constructor en la clase, como por ejemplo el que toma el <span class="codigo">String</span>, el compilador ya no nos proveerá un constructor por defecto. Si queremos un constructor sin argumentos para sobrecargar el que tiene argumentos nosotros mismo tendremos que escribirlo como en el ejemplo anterior.<br />
<br />
Sobrecargando un constructor proveemos diferentes maneras de que se pueda instanciar un objeto de nuestra clase, por ejemplo si sabemos el nombre del <span class="codigo">Perro</span>, podemos pasarselo a un constructor de "<span class="codigo">Perro</span>" que toma una cadena. Pero si no sabemos el nombre podemos llamar al constructor sin argumentos para que nos provea un nombre por defecto, a continuación mostramos un ejemplo de lo que estamos hablando:<br />
<br />
<pre><code>
1. public class Perro {
2. String nombre;
3. Perro (String nombre){
4. this.nombre = nombre;
5. }
6.
7. Perro(){
8. this(tomaNombreAleatorio());
9. }
10.
11. static String tomaNombreAleatorio(){
12. int x = (int) (Math.random() * 5);
13. String nombre = new String[]{"fido", "tango", "aguao", "rex" , "lassy"}[x];
14. return nombre;
15. }
16.
17. public static void main(String … args){
18. Perro a = new Perro();
19. System.out.println(a.nombre);
20. Perro b = new Perro("flafy");
21. System.out.println(b.nombre);
22. }
23. }
</code></pre><br />
<br />
Ejecutando el código unas cuantas veces tendremos un resultado como el siguiente:<br />
<br />
<pre><code>
% java Perro
tango
flafy
% java Perro
lassy
flafy
% java Perro
rex
flafy
% java Perro
aguao
flafy
</code></pre><br />
<br />
A continuación mostramos la pila de llamadas para la invocación del constructor cuando un constructor es sobrecargado.<br />
<br />
<ul><li><span class="negritas">4. </span><span class="codigo">Object</span></li>
<li><span class="negritas">3. </span><span class="codigo">Perro(String nombre)</span> llamada a <span class="codigo">super()</span></li>
<li><span class="negritas">2. </span><span class="codigo">Perro()</span> llamada a <span class="codigo">this(tomaNombreAleatorio())</span></li>
<li><span class="negritas">1. </span><span class="codigo">main()</span> llamada a <span class="codigo">new Perro()</span></li>
</ul><br />
<br />
Ahora vamos a describir el código desde la parte superior:<br />
<br />
<ul><li><span class="negritas">Línea 2:</span> declaramos una variable de instancia <span class="codigo">nombre</span> de tipo <span class="codigo">String</span></li>
<li><span class="negritas">Línea 3 – 5:</span> El constructor toma un <span class="codigo">String</span> y lo asigna a la variable de instancia <span class="codigo">nombre</span></li>
<li><span class="negritas">Línea 7:</span> Aquí viene lo interesante. Asumiendo que cada <span class="codigo">Animal</span> necesita de un <span class="codigo">nombre</span>, pero la persona que lo invoca no siempre debe saber que nombre será (código de llamada), entonces nosotros establecemos un nombre aleatorio. El constructor sin argumentos genera un nombre aleatorio invocando al método "<span class="codigo">tomaNombreAleatorio()</span>".</li>
<li><span class="negritas">Línea 8:</span> El constructor sin argumentos invoca a su propio constructor sobrecargado que toma un <span class="codigo">String</span> llamandolo de la misma manera que sería llamado si se hace una nueva instancia del objeto, pasándole un <span class="codigo">String</span> para el <span class="codigo">nombre</span>. La invocación al constructor sobrecargado se hace mediante la palabra reservada "<span class="codigo">this</span>", pero la utiliza como si fuese un nombre de método, <span class="codigo">this()</span>. Entonces en la línea 8 simplemente se está llamando al constructor sin parámetros que está en la línea 3, pasándole un <span class="codigo">nombre</span> aleatorio en lugar nosotros establecer el <span class="codigo">nombre</span>.</li>
<li><span class="negritas">Línea 11:</span> Notemos que el método "<span class="codigo">tomaNombreAleatorio()</span>" está marcado como estático (<span class="codigo">static</span>), eso es porque no podemos invocar a un método de instancia (en otras palabras, no estático) o acceder a una variable de instancia hasta después de que el super constructor haya sido ejecutado, y hasta que el súper constructor sea invocado desde el constructor en la línea 3, en lugar que de la línea 7, la línea 8 puede usar solo un método estático para generar un nombre. Si nosotros quisiéramos especificar algún nombre en específico en vez de que se genere aleatoriamente, por ejemplo, "<span class="codigo">Thor</span>", entonces en la línea 8 simplemente pondríamos <span class="codigo">this("Thor")</span> en lugar de llamar al método "<span class="codigo">tomaNombreAleatorio()</span>" para generar un <span class="codigo">nombre</span> aleatorio.</li>
<li><span class="negritas">Línea 12:</span> Esto no tiene nada que ver con el constructor pero es bueno aprenderlo, esto genera un numero entero aleatorio entre 0 y 4;</li>
<li><br />
<span class="negritas">Línea 13:</span> Estamos creando un nuevo <span class="codigo">String</span>, pero queremos que este <span class="codigo">String</span> sea seleccionado aleatoriamente desde una lista, entonces necesitamos hacer esto. Para explicarlo con mejor detalle, en esta sola línea de código hacemos lo siguiente:<br />
<ol><li><span class="negritas">1. </span>Declaramos una variable del tipo <span class="codigo">String</span>.</li>
<li><span class="negritas">2. </span>Creamos un arreglo de <span class="codigo">Strings</span> (anónimo ya que no asignamos el arreglo)</li>
<li><span class="negritas">3. </span>Obtenemos el <span class="codigo">String</span> en index <span class="codigo">[x]</span> (<span class="codigo">x</span> contiene el numero aleatorio generado en la línea 12) del recientemente creado arreglo de <span class="codigo">Strings</span>.</li>
<li><span class="negritas">4. </span>Asignamos el <span class="codigo">String </span>obtenido del arreglo a la variable de instancia "<span class="codigo">nombre</span>". Esto hubiera sido más fácil de leer si hubiéramos escrito esto:</li>
</ol><pre><code>
String[] lista = {"fido", "tango", "aguao", "rex" , "lassy"};
String nombre = lista[x];
</code></pre><br />
<br />
</li>
<li><span class="negritas">Línea 18: </span>Invocamos al constructor sin argumentos (generando un <span class="codigo">nombre</span> aleatorio que después será enviado al constructor que recibe un <span class="codigo">String</span>).</li>
<li><span class="negritas">Línea 20: </span>Invocamos al constructor sobrecargado que toma un <span class="codigo">String</span> que representa el <span class="codigo">nombre</span>.</li>
</ul><br />
<br />
El punto cable en el código antes escrito está en la línea <span class="negritas">8</span>, en lugar de llamar a <span class="codigo">super()</span>, estamos llamando a "<span class="codigo">this()</span>", y "<span class="codigo">this()</span>" siempre significa llamar a otro constructor en la misma clase. ¿Pero qué sucede al momento de llamar a "<span class="codigo">this()</span>"? Tarde o temprano el constructor "<span class="codigo">super()</span>" será llamado, una llamada a "<span class="codigo">this()</span>" solo significa que estamos retrasando lo inevitable, ya que algún constructor en algún lugar debe hacer una llamada a "<span class="codigo">super()</span>".<br />
<br />
La regla clave: <span class="negritas">la primera línea de un constructor debe ser una llamada a "<span class="codigo">super()</span>" o una llamada a "<span class="codigo">this()</span>"</span>.<br />
<br />
Sin excepciones. Si el constructor "<span class="codigo">A()</span>" tiene una llamada a "<span class="codigo">this()</span>", el compilador sabe que el constructor "<span class="codigo">A()</span>" no será e que invoque a "<span class="codigo">super()</span>".<br />
<br />
La regla anterior significa que un constructor nunca podrá tener ambas llamadas, es decir no podrá llamar a la vez a "<span class="codigo">this()</span>" y a "<span class="codigo">super()</span>". Porque alguna de estas llamadas debe ser la primera sentencia en un constructor, no podemos usar ambas en un mismo constructor. El compilador tampoco insertará una llamada a "<span class="codigo">super()</span>" si el constructor tiene una llamada a "<span class="codigo">this()</span>".<br />
<br />
¿Qué sucederá si nosotros tratamos de compilar el siguiente código?<br />
<br />
<pre><code>
class A
{
A()
{
this("test");
}
A(String s)
{
this();
}
}
</code></pre><br />
<br />
El compilador puede que no capte el problema (esto depende del compilador). Este asume que sabemos lo que estamos haciendo. ¿Captan el problema? Sabemos que el súper constructor siempre debe de ser llamado, entonces ¿Dónde podría ir la llamada a "<span class="codigo">super()</span>"? Recordemos que el compilador no inserta ningún constructor por defecto si nosotros ya insertamos uno o más constructores en nuestra clase, y cuando el compilador no inserta un constructor por defecto todavía puede insertar una llamada a <span class="codigo">super()</span> en algún constructor que no tenga explícitamente una llamada a <span class="codigo">super()</span>, a menos que, el constructor ya tenga una llamada a <span class="codigo">this()</span> y recordemos que un constructor no puede tener ambas llamadas (<span class="codigo">this()</span> y <span class="codigo">super()</span>). Entonces en el código anterior ¿Dónde va la llamada a <span class="codigo">super()</span>? Si los únicos dos constructores en la clase tienen una llamada a <span class="codigo">this()</span>, en efecto nosotros tenemos el mismo problema que tendríamos si escribimos los siguientes métodos: <br />
<br />
<pre><code>
public void ir()
{
hacerAlgo();
}
public void hacerAlgo()
{
ir();
}
</code></pre><br />
<br />
¿Ahora pueden ver el problema? Podemos ver que el stack explota. Ya que se llamaran uno a otro indefinidamente hasta que la maquina o la <span class="codigo">JVM</span> estalle (esperamos que lo segundo suceda primero… ¿o lo primero?). Regresando al ejemplo de los constructores podemos decir que dos constructores sobrecargados y ambos con una llamada a <span class="codigo">this()</span> son dos constructores llamándose uno a otro una y otra y otra vez, resultando en:<br />
<br />
<pre><code>
% java A
Exception in thread "main" java.lang.StackOverflowError
</code></pre><br />
<br />
El beneficio de tener constructores sobrecargados es que ofrecen formas flexibles de poder instanciar un objeto de nuestra clase. El beneficio de que un constructor invoque a otro constructor sobrecargado es evitar duplicación de código, en el ejemplo anterior no hubo algún otro código para establecer el nombre, pero imaginen que después de la línea 4 aún hay muchas cosas que podríamos hacer.<br />
<br />
<br />
<h2 class="titulo">Variables y métodos estáticos</h2>El modificador "<span class="codigo">static</span>" tiene un impacto tan profundo en el comportamiento de un método o una variable que debemos tratando como un concepto totalmente separado de los demás modificadores. Para entender la manera en que un miembro estático trabaja, primero veremos las razones por las cuales utilizaríamos alguno. <br />
<br />
Imaginen que tenemos una clase de utilidad la cual siempre se ejecuta de la misma manera, su única función es devolver, digamos, un número al azar. No nos importa en cuál instancia de la clase se ejecuta el método, ya que siempre se comporta de la misma manera. En otras palabras, el comportamiento del método no tiene ninguna dependencia en el estado (valores de variable de instancia) de un objeto. Entonces ¿Porque es necesario un objeto cuando el método nunca será instanciado? ¿Por qué no solo pedimos a la clase en si misma que ejecute el método?<br />
<br />
Ahora imaginemos otro escenario: supongamos que queremos mantener un contador corriendo para todas las instancias de una clase en particular. ¿En dónde incluiremos la variable? No va a trabajar si la incluimos como variable de instancia dentro de las clases en las cuales se quiere hacer el seguimiento, ya que el contador siempre se iniciara a un nuevo valor por defecto cada que nosotros creamos una nueva instancia.<br />
<br />
La respuesta a los dos escenarios anteriores (el método de utilidad que se ejecuta siempre de la misma manera, y el contador para mantener el total de instancias actualizado) es utilizar el modificador <span class="codigo">static</span>.<br />
<br />
Las variables y métodos marcados con <span class="codigo">static</span> <span class="negritas">pertenecen a la clase y no a una instancia en particular</span>. Podemos usar un método o variable <span class="codigo">static</span> <span class="negritas">sin tener instancias de esa clase</span>, solo necesitamos que la clase esté disponible para invocar un método <span class="codigo">static</span> o acceder a una variable <span class="codigo">static</span>.<br />
<br />
Una variable estática de una clase será compartida por todas las instancias de esa clase, sólo hay una copia.<br />
<br />
El siguiente código utiliza una variable <span class="codigo">static</span> que será utilizada como contador:<br />
<br />
<pre><code>
class Oveja
{
static int contadorOvejas = 0; //Declaramos e inicializamos la variable estática
public Oveja()
{
contadorOveja += 1; //Modificamos el valor en el constructor
}
public static void main(String … args)
{
new Oveja();
new Oveja();
new Oveja();
System.out.println("El contador de ovejas está ahora en: " + contadorOveja);
}
}
</code></pre><br />
<br />
En el código anterior, la variable estática "<span class="codigo">contadorOveja</span>" es establecida en cero cuando la clase "<span class="codigo">Oveja</span>" es cargada por primera vez por la <span class="codigo">JVM</span>, antes de que cualquier instancia de "<span class="codigo">Oveja</span>" sea creada (en realidad no se necesita inicializar la variable estática en cero, las variable estáticas obtienen los mismos valores por defecto que las variables de instancia obtienen). Cada vez que una instancia de la clase "<span class="codigo">Oveja</span>" es creada el constructor de "<span class="codigo">Oveja</span>" es ejecutado y la variable "<span class="codigo">contadorOveja</span>" es incrementada. Cuando este código es ejecutado, tres instancias de "<span class="codigo">Oveja</span>" son creadas en el "<span class="codigo">main()</span>", y el resultado es el siguiente:<br />
<br />
<pre><code>
El contador de ovejas está ahora en: 3
</code></pre><br />
<br />
Ahora imaginemos que sucedería si la variable "<span class="codigo">contadorOveja</span>" fuera una variable de instancia (es decir, una variable no estática):<br />
<br />
<pre><code>
class Oveja
{
int contadorOvejas = 0; //Declaramos e inicializamos la variable de instancia
public Oveja()
{
contadorOveja += 1; //Modificamos el valor en el constructor
}
public static void main(String... args)
{
new Oveja();
new Oveja();
new Oveja();
System.out.println("El contador de ovejas está ahora en: " + contadorOveja);
}
}
</code></pre><br />
<br />
Cuando este código es ejecutado, este puede todavía crear tres instancias de la clase "<span class="codigo">Oveja</span>" en el "<span class="codigo">main()</span>", pero el resultado es un error de compilación. No podemos compilar este código, mucho menos ejecutarlo, porque obtenemos el siguiente error:<br />
<br />
<pre><code>
Exception in thread "main" java.lang.RuntimeException: Uncompilable source code - non-static variable contadorOveja cannot be referenced from a static context
System.out.println("El contador de ovejas está ahora en: " + contadorOveja);
^
</code></pre><br />
<br />
La <span class="codigo">JVM</span> no sabe a cuál objeto "<span class="codigo">contadorOveja</span>" de "<span class="codigo">Oveja</span>" estamos intentando de acceder. El problema está en que el método "<span class="codigo">main()</span>" en sí mismo es un método estático. Un método estático no puede acceder a una variable no estática (variable de instancia) porque hay que recordar que para acceder a una variable estática no se necesita una instancia de la clase, ya que la variable pertenece a la clase misma.<br />
<br />
Eso no quiere decir que no hay instancias de la clase con vida en la heap, si las hay, pero el método estático no sabe nada de ellas.<br />
<br />
Lo mismo se aplica a los métodos de instancia, un método estático no pueden invocar directamente un método no estático.<br />
<br />
Piensen que <span class="negritas">static=clase</span>, <span class="negritas">no-estático=instancia</span>. Haciendo que el método llamado por la <span class="codigo">JVM</span> ("<span class="codigo">main()</span>") sea un método estático la <span class="codigo">JVM</span> no tiene que crear una instancia de la clase sólo para iniciar la ejecución de código.<br />
<br />
<br />
<h3 class="subtitulo">Accediendo a métodos y variables estáticos</h3>Puesto que no es necesario tener una instancia para invocar un método estático o acceder a una variable estática, entonces ¿cómo invocar o utilizar un miembro estático? ¿Cuál es la sintaxis? Sabemos que con un método de instancia regular, se utiliza el operador punto "<span class="codigo">.</span>" en una referencia, por ejemplo:<br />
<pre><code>
class Oveja
{
static int contadorOvejas = 0; //Declaramos e inicializamos la variable estática
public Oveja()
{
contadorOveja += 1; //Modificamos el valor en el constructor
}
public static void main(String... args)
{
new Oveja();
new Oveja();
new Oveja();
System.out.println("El contador de ovejas está en: " + Oveja.contadorOveja);
}
}
</code></pre><br />
<br />
Pero para que sea realmente confuso, el lenguaje Java también permite el uso de una variable de referencia de objeto para acceder a un miembro estático:<br />
<br />
<pre><code>
Oveja ov = new Oveja();
ov.contadorOveja; //Accediendo a la variable estática contadorOveja usando o
</code></pre><br />
<br />
En el código anterior, hemos instanciado una "<span class="codigo">Oveja</span>" asignando "<span class="codigo">new Oveja</span>" a la variable de referencia "<span class="codigo">ov</span>", y después usamos la referencia "<span class="codigo">ov</span>" para invocar al método estático. Pero a pesar de que se está utilizando una instancia específica para acceder al método estático, las reglas no han cambiado. Esto no es más que un truco de sintaxis que nos permite utilizar una variable de referencia a objeto (pero no el objeto que se refiere) para llegar a un método o variable estática, pero el miembro estático sigue ignorando la instancia utiliza para invocar al miembro estático. En el ejemplo de la <span class="codigo">Oveja</span>, el compilador sabe que la variable de referencia "<span class="codigo">ov</span>" es del tipo de <span class="codigo">Oveja</span>, por lo que el método estático de la clase <span class="codigo">Oveja</span> se ejecuta sin conocimiento o interés para la instancia de la <span class="codigo">Oveja</span> del otro extremo de la referencia "<span class="codigo">ov</span>". En otras palabras al compilador le importa solo que la variable de referencia "<span class="codigo">ov</span>" sea del tipo <span class="codigo">Oveja</span>.<br />
<br />
El compilador en realidad hace una transformación a la llamada que acabamos de hacer. Cuando ve que estamos tratando de invocar a un miembro estático, reemplaza la variable de instancia que estamos usando por la clase en la que está dicho miembro. Esto quiere decir que reemplazara esta llamada:<br />
<br />
<pre><code>
Oveja ov = new Oveja();
ov.contadorOveja;
</code></pre><br />
<br />
Por esta otra:<br />
<br />
<pre><code>
Oveja ov = new Oveja();
Oveja.contadorOveja;
</code></pre><br />
<br />
Así que como podemos ver, finalmente quedamos con una llamada al miembro estático, a través de la clase a la que pertenece dicho miembro.<br />
<br />
La siguiente imagen describe el efecto del modificador <span class="codigo">static</span> en métodos y variables:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhln_BHvqdZfT1W7wYdV_gz8z36_Zge-cZGXymRmsoaHamga5VSQdoW2vIE-YBymLE5qo1pyT4dHDakdIC7KzSDwI-tr6E49i7q_mnbBZQugZdqNCY_WGnY_42SvEQRYujBHhHn6nU8gEMI/s1600/C3_3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhln_BHvqdZfT1W7wYdV_gz8z36_Zge-cZGXymRmsoaHamga5VSQdoW2vIE-YBymLE5qo1pyT4dHDakdIC7KzSDwI-tr6E49i7q_mnbBZQugZdqNCY_WGnY_42SvEQRYujBHhHn6nU8gEMI/s1600/C3_3.png" /></a></div><br />
<br />
Finalmente, recuerden que los métodos estáticos no pueden ser sobrescritos. Pero esto no significa que no puedan ser redefinidos en una subclase. Redefinir y sobrescribir no son la misma cosa. A continuación mostramos un ejemplo sobre redefinir (no sobrescribir) un método marcado como <span class="codigo">static</span>:<br />
<br />
<pre><code>
class Animal
{
static void hacerAlgo()
{
System.out.println("a");
}
}
class Perro extends Animal
{
static void hacerAlgo()
{
System.out.println("b"); //Esto es redefinir, no sobrescribir
}
public static void main(String... args)
{
Animal[] a = {new Animal(), new Perro(), new Animal()};
for(int i = 0; i < a.length; i++ )
{
a[i].hacerAlgo(); //Invoca al método estático
}
}
}
</code></pre><br />
<br />
Ejecutando el código anterior se produce la siguiente salida:<br />
<br />
<pre><code>
a a a
</code></pre><br />
<br />
Recuerden, la sintaxis "<span class="codigo">a[i].hacerAlgo()</span>" es solo un atajo (un truco de sintaxis). El compilador lo sustituye con algo como "<span class="codigo">Animal.hacerAlgo()</span>", como vimos hace un momento.<br />
<br />
<br />
<h2 class="titulo">Cohesión y acoplamiento (Coupling and Cohesion):</h2>Estos dos temas, la cohesión y el acoplamiento, tienen que ver con <span class="negritas">la calidad de un diseño orientado a objetos</span>. En general un buen diseño pide un <span class="negritas">acoplamiento bajo</span> y evita un acoplamiento estrecho y un buen diseño orientado a objetos pide una <span class="negritas">alta cohesión</span> y evita la baja cohesión. Como con la mayoría de las discusiones sobre diseño de orientación a objetos, las metas de una aplicación son:<br />
<br />
<ul><li>Facilidad de creación. </li>
<li>Facilidad de mantenimiento.</li>
<li>Facilidad de mejora.</li>
</ul><br />
<br />
<br />
<h3 class="subtitulo">Acoplamiento</h3>Vamos a empezar haciendo un intento sobre la definición de acoplamiento. <span class="negritas">Acoplamiento es el grado en el cual una clase sabe acerca de otra clase</span>. Si el único conocimiento que la clase "<span class="codigo">A</span>" tiene acerca de la clase "<span class="codigo">B</span>", es lo que la clase "<span class="codigo">B</span>" ha puesto de manifiesto a través de su interface, se dice que la clase "<span class="codigo">A</span>" y "<span class="codigo">B</span>" están débilmente acopladas, lo cual es algo bueno. Si por otro lado, la clase "<span class="codigo">A</span>" se basa en una parte de la clase "<span class="codigo">B</span>", que no es parte de la interface de la clase "<span class="codigo">B</span>", entonces el acoplamiento entre estas dos clases es más estrecho, y esto no es algo bueno. En otras palabras, si la clase "<span class="codigo">A</span>" sabe más de lo que debería de la forma en que se implementó "<span class="codigo">B</span>", entonces "<span class="codigo">A</span>" y "<span class="codigo">B</span>" están estrechamente acopladas.<br />
<br />
Usando este segundo escenario, imaginemos lo que sucede cuando la clase "<span class="codigo">B</span>" sea mejorada. Es muy posible que el desarrollador que mejore a "<span class="codigo">B</span>" no tenga conocimiento acerca de "<span class="codigo">A</span>" ¿Y porque debería de tenerlo? El desarrollador de la clase "<span class="codigo">B</span>" debe pensar (y es correcto) que todas las mejoras que no quiebren o rompan la interfaz de la clase deben ser seguras, por lo que podría cambiar alguna parte que no tenga que ver con la interface en la clase. Si las dos clases están estrechamente acopladas, este cambio provocaría que la clase "<span class="codigo">A</span>" se quiebre.<br />
<br />
Veamos un ejemplo obvio de acoplamiento fuerte, que ha sido posible gracias a una pobre encapsulación:<br />
<br />
<pre><code>
class Impuestos
{
float ratio;
float calculaImpuestoVentaPeru()
{
RatioImpuestoVentas riv = new RatioImpuestoVentas();
ratio = riv.ratioVentas; /*Mal, esto debería ser una llamada a un
método get para obtener la variable, por ejemplo:
ratio = riv.getRatioVentas("PE"); */
}
}
class RatiosImpuestoVentas
{
public float ratioVentas; //Debería ser privado
public float ajusteRatioVentas; //Debería ser privado
public float getRatioVentas(String pais)
{
ratioVentas = new Impuestos().calculaImpuestoVentaPeru(); //Mal otra vez hacer basado en los cálculos de la región.
return ajusteRatioVentas;
}
}
</code></pre><br />
<br />
Todas las aplicaciones orientadas a objetos que no son triviales son una mezcla de muchas clases e interfaces trabajando juntas. Idealmente, todas las interacciones entre los objetos en un sistema orientado a objetos deben utilizar sus APIs, en otras palabras, los contratos de las clases de los objetos respectivos. Teóricamente, si todas las clases en una aplicación tienen bien diseñada sus APIs, entonces debería ser posible para todas las interacciones entre las clases el uso de las APIs de forma exclusiva. Como hemos comentado anteriormente en este capítulo, un aspecto de buen diseño de una clase y el diseño de la API es que las clases deben estar bien encapsuladas.<br />
<br />
<br />
<h3 class="subtitulo">Cohesión</h3>Mientras que el acoplamiento tiene que ver con cómo las clases interactúan con las otras clases, la cohesión es acerca de cómo una simple clase es diseñada. El termino cohesión es usado para indicar <span class="negritas">el grado para el cual una clase tiene un propósito simple bien enfocado </span>. Hay que mantener en mente que la cohesión es un concepto subjetivo al igual que el acoplamiento. Cuanto más enfocada sea la clase en una sola tarea, mayor su cohesión, lo cual es bueno. El beneficio clave de la alta cohesión, es que estas clases son mucho más fáciles de mantener (y menos frecuentes a ser cambiadas) que las clases con baja cohesión. Otro beneficio de la alta cohesión es que las clases con un propósito bien enfocado tienden a ser más reutilizables que otras clases. Veamos un ejemplo:<br />
<br />
<pre><code>
class ReportePresupuesto
{
void conectarBaseDatos(){}
void generarReportePresupuesto(){}
void guardarArchivo(){}
void imprimir(){}
}
</code></pre><br />
<br />
Ahora imaginen que su jefe llega y dice, "Eh, ¿sabes de la aplicación de contabilidad que estamos trabajando? Los clientes decidieron que también van a querer generar un informe de proyección de ingresos, y que quieren hacer algunos informes de inventario también".<br />
<br />
También les dice que se aseguren de que todos estos informes les permitan elegir una base de datos, seleccione una impresora, y guardar los informes generados a ficheros de datos ... ¡Ouch!<br />
<br />
En lugar de poner todo el código de impresión en una clase de informe, probablemente hubiera sido mejor usar el siguiente diseño desde el principio:<br />
<br />
<pre><code>
class ReportePresupuesto
{
Opciones getOpcionesReporte() {}
void generarReportePresupuesto(Opciones o){}
}
class ConexionBaseDatos
{
ConexionBD getConexion(){}
}
class Impresion
{
OpcionesImpresion getOpcionesImpresion(){}
}
class AlmacenArchivos
{
OpcionesGuardado getOpcionesGuardado(){}
}
</code></pre><br />
<br />
Este diseño es mucho más cohesivo. En lugar de una clase que hace todo, hemos roto el sistema en cuatro clases principales, cada uno con un rol muy específico (cohesión).<br />
<br />
Como hemos construido estas clases especializadas, reutilizables, y va a ser mucho más fácil escribir un nuevo informe, dado que ya tenemos la clase de conexión de bases de datos, la clase de impresión, y la clase para guardar archivos, y eso significa pueden ser reutilizados por otras clases pueden desear imprimir un informe.<br />
<br />
Este es el fin del segundo tutorial, donde hemos aprendido un poco sobre algunos de los conceptos más importantes de la orientación a objetos que son necesarios para el examen de certificación, y para nuestra vida como programadores en general.El la próxima entrega seguiremos aprendiendo más temas importantes para nosotros como programadores Java y en particular para los que deseen certificarse como programadores Java.<br />
<br />
Muchas gracias a Alan Cabrera Avanto, de Trujillo Perú por este tutorial.<br />
<br />
Saludos.<br />
<br />
<span class="negritas">Entradas Relacionadas:</span><br />
<br />
<ul><li><a href="http://www.javatutoriales.com/2010/10/sun-certified-java-programmer-6-cx-310.html">Parte 0: ¿Qué es una certificación?</a></li>
<li><a href="http://www.javatutoriales.com/2010/10/sun-certified-java-programmer-6-cx-310_10.html">Parte 1: Declaraciones y Controles de Acceso</a></li>
</ul></div>Alexhttp://www.blogger.com/profile/06974037481671868076noreply@blogger.comtag:blogger.com,1999:blog-2012232069289957092.post-33463316659090458002011-10-22T14:19:00.000-07:002012-06-29T07:39:43.265-07:00Struts 2 - Parte 3: Trabajo con Formularios<div style="text-align: justify;">En el desarrollo de aplicaciones web, una de las partes más importantes que existen (sino es que la más importante) es el manejo de datos que son recibidos del usuario a través de los formularios de nuestra aplicación.<br />
<br />
Aunque es algo que usamos (y hacemos) todos los días, el manejo de los datos de los formularios puede ser un poco engañoso, por no decir complicado, cuando comenzamos a trabajar con la recepción de múltiples valores para un mismo parámetro, o cuando de antemano no conocemos los nombres de los parámetros que recibiéremos; esto sin mencionar las validaciones para los distintos tipos de datos, la carga y descarga de archivos, etc.<br />
<br />
En este tutorial aprenderemos la forma en la que se trabaja con formularios en <span class="codigo">Struts 2</span>, y cómo manejar todas las situaciones mencionadas anteriormente. Concretamente aprenderemos cómo hacer <span class="negritas">7 cosas</span>: recepción de parámetros simples, cómo hacer que el framework llene de forma automática los atributos de un objeto si es que todos los datos del formulario pertenecen a ese objeto, a recibir múltiples valores para un mismo parámetro, cómo recibir parámetros cuando no conocemos el nombre de los mismos, a realizar validaciones de datos de varias maneras, cómo subir archivos al servidor, y cómo enviar archivos desde el servidor hacia nuestros clientes.<br />
<br />
<a name='more'></a>En <a href="http://www.javatutoriales.com/2011/06/struts-2-parte-1-configuracion.html">el primer tutorial de la serie</a> vimos los pasos básicos para enviar datos a nuestros <span class="codigo">Action</span>s a través de un formulario, sin embargo en esta ocasión veremos algunos conceptos un poco más avanzados que nos harán la vida más fácil cuando trabajemos con formularios.<br />
<br />
Lo primero que haremos es crear un nuevo proyecto en NetBeans. Vamos al menú "<span class="codigo">File -> New Project...</span>". En la ventana que aparece seleccionamos la categoría "<span class="codigo">Java Web</span>" y en el tipo de proyecto "<span class="codigo">Web Application</span>". Presionamos el botón "<span class="codigo">Next ></span>" y le damos un nombre y una ubicación a nuestro proyecto; presionamos nuevamente el botón "<span class="codigo">Next ></span>" y en este punto se nos preguntará el servidor que queremos usar. En nuestro caso usaremos el servidor "<span class="codigo">Tomcat 7.0</span>", con la versión 5 de JEE y presionamos el botón "<span class="codigo">Finish</span>".<br />
<br />
Una vez que tengamos nuestro proyecto debemos recordar agregar la biblioteca "<span class="codigo">Struts2</span>" (o "<span class="codigo">Struts2Anotaciones</span>" si van a hacer uso de anotaciones, como es mi caso ^_^), que creamos en <a href="http://www.javatutoriales.com/2011/06/struts-2-parte-1-configuracion.html">el primer tutorial de la serie</a>. Hacemos clic derecho sobre el nodo "<span class="codigo">Libraries</span>" del proyecto. En el menú que aparece seleccionamos la opción "<span class="codigo">Add Library...</span>". En la ventana que aparece seleccionamos la biblioteca "<span class="codigo">Struts2</span>" o "<span class="codigo">Struts2Anotaciones</span>" y presionamos "<span class="codigo">Add Library</span>". Con esto ya tendremos los jars de <span class="codigo">Struts 2</span> en nuestro proyecto.<br />
<br />
Ahora configuramos el filtro "<span class="codigo">struts2</span>" en el deployment descriptor. Abrimos el archivo "<span class="codigo">web.xml</span>" y colocamos el siguiente contenido, como se explicó en el primer tutorial de la serie:<br />
<br />
<pre><code>
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</code></pre><br />
<br />
Ahora procedemos a crear una clase que nos servirá como modelo de datos. Esta será una clase "<span class="codigo">Usuario</span>" la cual tendrá atributos de varios tipos para que veamos como <span class="codigo">Struts 2</span> realiza su "magia".<br />
<br />
Creamos un paquete para nuestro modelo de datos. En mi caso el primer paquete se llamará "<span class="codigo">com.javatutoriales.struts2.formularios.modelo</span>". Hacemos clic derecho sobre el nodo "<span class="codigo">Source packages</span>" del proyecto, en el menú que aparece seleccionamos la opción "<span class="codigo">new -> package</span>". En la ventana que aparece le damos el nombre correspondiente a nuestro paquete.<br />
<br />
Ahora, dentro de este paquete creamos la clase "<span class="codigo">Usuario</span>": sobre el paquete que acabamos de crear hacemos clic derecho y en el menú contextual que aparece seleccionaos la opción "<span class="codigo">new -> Java Class</span>". El la ventana que aparece le damos el nombre "<span class="codigo">Usuario</span>" y hacemos clic en el botón "<span class="codigo">Finish</span>". Con esto aparecerá en nuestro editor la clase "<span class="codigo">Usuario</span>" de la siguiente forma:<br />
<br />
<pre><code>
public class Usuario
{
}
</code></pre><br />
<br />
Antes que nada hacemos que esta clase implemente la interface "<span class="codigo">java.io.Serializable</span>" como deben hacerlo todas las clases de transporte de datos:<br />
<br />
<pre><code>
public class Usuario implements Serializable
{
}
</code></pre><br />
<br />
Ahora agregaremos unos cuantos atributos a esta clase, de distintos tipos, representado algunas de las características de nuestro usuario. Dentro de estos atributos incluiremos su nombre, edad, y fecha de nacimiento:<br />
<br />
<pre><code>
public class Usuario implements Serializable
{
private String nombre;
private String username;
private String password;
private int edad;
private Date fechaNacimiento;
}
</code></pre><br />
<br />
Recuerden que el "<span class="codigo">Date</span>" de "<span class="codigo">fechaNacimiento</span>" debe ser de tipo "<span class="codigo">java.util.Date</span>".<br />
<br />
Para terminar con la clase <span class="codigo">Usuario</span> agregaremos los <span class="codigo">getters</span> y los <span class="codigo">setters</span> de estos atributos, y adicionalmente dos constructores, uno que reciba todos los atributos, y uno vacio:<br />
<br />
<pre><code>
public class Usuario implements Serializable
{
private String nombre;
private String username;
private String password;
private int edad;
private Date fechaNacimiento;
public Usuario()
{
}
public Usuario(String nombre, String username, String password, int edad, Date fechaNacimiento)
{
this.nombre = nombre;
this.username = username;
this.password = password;
this.edad = edad;
this.fechaNacimiento = fechaNacimiento;
}
public int getEdad()
{
return edad;
}
public void setEdad(int edad)
{
this.edad = edad;
}
public Date getFechaNacimiento()
{
return fechaNacimiento;
}
public void setFechaNacimiento(Date fechaNacimiento)
{
this.fechaNacimiento = fechaNacimiento;
}
public String getNombre()
{
return nombre;
}
public void setNombre(String nombre)
{
this.nombre = nombre;
}
public String getPassword()
{
return password;
}
public void setPassword(String password)
{
this.password = password;
}
public String getUsername()
{
return username;
}
public void setUsername(String username)
{
this.username = username;
}
}
</code></pre><br />
<br />
Lo primero que haremos es recordar cómo podemos obtener parámetros planos o simples desde nuestros formularios:<br />
<br />
<br />
<h2 class="titulo">1. Recepción de parámetros simples</h2>En <a href="http://www.javatutoriales.com/2011/06/struts-2-parte-1-configuracion.html">el primer tutorial de la serie de <span class="codigo">Struts 2</span></a> vimos de una forma muy rápida cómo obtener parámetros de un formulario. Lo único que hay que hacer es:<br />
<br />
<ul><li>Colocar un formulario en nuestra <span class="codigo">JSP</span> usando la etiqueta <span class="codigo"><s:form></span></li>
<li>Colocar los campos del formulario usando las etiquetas correspondientes de <span class="codigo">Struts</span></li>
<li>Crear un <span class="codigo">Action</span> y colocar <span class="codigo">setters</span> para cada uno de los elementos que recibiremos a través del formulario</li>
<li>Procesar los datos del formulario en el método "<span class="codigo">execute</span>"</li>
</ul>Hagamos un pequeño ejemplo para refrescar la memoria.<br />
<br />
En este primer ejemplo obtendremos los datos para crear un nuevo objeto "<span class="codigo">Usuario</span>", el tipo que definimos anteriormente.<br />
<br />
Lo primero que hacemos es crear una nueva página <span class="codigo">JSP</span>, en la raíz de las páginas web, llamada "<span class="codigo">nuevo-usuario.jsp</span>". Hacemos clic derecho en el nodo "<span class="codigo">Web Pages</span>". En el menú que se abre seleccionamos la opción "<span class="codigo">New –> JSP...</span>". <br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjqNu2_It6UxxEVveaWdtzX48itFGvnq0h5rSTU94T-uU-BaHzZFtv250kdGLMmwysTopLFNMDJB3tadCS2K487ta5qq57cxdts3r-BOOznPkciehiUWQhHgDeDa7s7utyDY9FLZb2SYTJ7/s1600/S3_1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="307" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjqNu2_It6UxxEVveaWdtzX48itFGvnq0h5rSTU94T-uU-BaHzZFtv250kdGLMmwysTopLFNMDJB3tadCS2K487ta5qq57cxdts3r-BOOznPkciehiUWQhHgDeDa7s7utyDY9FLZb2SYTJ7/s400/S3_1.png" width="400" /></a></div><br />
<br />
Colocamos "<span class="codigo">nuevo-usuario</span>" como nombre de la página y presionamos el botón "<span class="codigo">Finish</span>".<br />
<br />
En esta página indicamos que se usará la biblioteca de etiquetas de <span class="codigo">Struts 2</span>:<br />
<br />
<pre><code>
<%@taglib uri="/struts-tags" prefix="s" %>
</code></pre><br />
<br />
Usamos la etiqueta "<span class="codigo"><s:form></span>" para colocar un formulario en nuestra página. En su atributo "<span class="codigo">action</span>" colocamos el nombre del <span class="codigo">Action</span> que se encargará de procesar los datos de este formulario:<br />
<br />
<pre><code>
<s:form action="datosUsuario">
</s:form>
</code></pre><br />
<br />
Dentro de este formulario colocaremos un campo para cada uno de los atributos que puede recibir un Usuario:<br />
<br />
<pre><code>
<s:form action="datosUsuario">
<s:textfield name="nombre" label="Nombre" />
<s:textfield name="username" label="Username" />
<s:password name="password" label="Password" />
<s:textfield name="edad" label="Edad" />
<s:textfield name="fechaNacimiento" label="Fecha de Nacimiento" />
<s:submit value="Enviar" />
</s:form>
</code></pre><br />
<br />
Ya que nuestro formulario está listo, crearemos el <span class="codigo">Action</span> que se encargará de procesar los datos del mismo. <br />
<br />
Creamos un nuevo paquete, llamado "<span class="codigo">actions</span>", a la misma altura que el paquete "<span class="codigo">modelo</span>". Dentro de este paquete creamos una nueva clase Java llamada "<span class="codigo">Usuario<span class="codigo">Action</span></span>". Haremos que esta clase extienda de "<span class="codigo">ActionSupport</span>":<br />
<br />
<pre><code>
public class UsuarioAction extends ActionSupport
{
}
</code></pre><br />
<br />
Recuerden que para recibir los datos del formulario debemos colocar los atributos en los que se almacenarán estos datos, y <span class="codigo">setters</span> para que los interceptores correspondientes puedan inyectar los valores dentro del <span class="codigo">Action</span>:<br />
<br />
<pre><code>
public class UsuarioAction extends ActionSupport
{
private String nombre;
private String username;
private String password;
private int edad;
private Date fechaNacimiento;
public void setEdad(int edad)
{
this.edad = edad;
}
public void setFechaNacimiento(Date fechaNacimiento)
{
this.fechaNacimiento = fechaNacimiento;
}
public void setNombre(String nombre)
{
this.nombre = nombre;
}
public void setPassword(String password)
{
this.password = password;
}
public void setUsername(String username)
{
this.username = username;
}
}
</code></pre><br />
<br />
Cuando esté <span class="codigo">Action</span> termine su ejecución (la cual aún no hemos implementado), queremos poder crear un <span class="codigo">Usuario</span> y mostrar sus datos en otra página. Para hacer eso debemos colocar un atributo que almacene una referencia al <span class="codigo">Usuario</span>, y un <span class="codigo">getter</span> para poder obtener esta referencia:<br />
<br />
<pre><code>
private Usuario usuario;
public Usuario getUsuario()
{
return usuario;
}
</code></pre><br />
<br />
Ahora sí, sobre-escribimos el método "<span class="codigo">execute</span>" para crear un nuevo objeto de tipo <span class="codigo">Usuario</span> y establecer sus datos usando los valores que recibimos del formulario:<br />
<br />
<pre><code>
@Override
public String execute() throws Exception
{
usuario = new Usuario();
usuario.setNombre(nombre);
usuario.setUsername(username);
usuario.setPassword(password);
usuario.setEdad(edad);
usuario.setFechaNacimiento(fechaNacimiento);
return SUCCESS;
}
</code></pre><br />
<br />
Y eso es todo, ya hemos creado un nuevo <span class="codigo">Usuario</span> con los datos que recibimos a través del formulario de captura.<br />
<br />
Para que este ejemplo funcione aún tenemos que hacer un par de cosas. Primero marcaremos nuestra clase con la anotación "<span class="codigo">@Action</span>" para indicar que esta clase debe ser tratada con un <span class="codigo">Action</span> de <span class="codigo">Struts 2</span>, como lo vimos en <a href="http://www.javatutoriales.com/2011/06/struts-2-parte-1-configuracion.html">el primer tutorial de la serie</a>:<br />
<br />
<pre><code>
@Namespace(value="/")
@Action(value="datosUsuario", results={@Result(name="success", location="/datos-usuario.jsp")})
public class UsuarioAction extends ActionSupport
{
}
</code></pre><br />
<br />
Como podemos ver, el nombre de nuestro <span class="codigo">Action</span> es "<span class="codigo">datosUsuario</span>" (que es el mismo que colocamos en el atributo "<span class="codigo">action</span>" del formulario que creamos hace un momento). Si todo el proceso de nuestro <span class="codigo">Action</span> sale bien seremos enviados a la página "<span class="codigo">datosUsuario.jsp</span>". Esta página solamente mostrará los datos del usuario que se acaba de crear. Antes de ver esta página veamos cómo queda la clase "<span class="codigo">UsuarioAction</span>":<br />
<br />
<pre><code>
@Namespace(value="/")
@Action(value="datosUsuario", results={@Result(name="success", location="/datos-usuario.jsp")})
public class UsuarioAction extends ActionSupport
{
private String nombre;
private String username;
private String password;
private int edad;
private Date fechaNacimiento;
private Usuario usuario;
@Override
public String execute() throws Exception
{
usuario = new Usuario();
usuario.setNombre(nombre);
usuario.setUsername(username);
usuario.setPassword(password);
usuario.setEdad(edad);
usuario.setFechaNacimiento(fechaNacimiento);
return SUCCESS;
}
public Usuario getUsuario()
{
return usuario;
}
public void setEdad(int edad)
{
this.edad = edad;
}
public void setFechaNacimiento(Date fechaNacimiento)
{
this.fechaNacimiento = fechaNacimiento;
}
public void setNombre(String nombre)
{
this.nombre = nombre;
}
public void setPassword(String password)
{
this.password = password;
}
public void setUsername(String username)
{
this.username = username;
}
}
</code></pre><br />
<br />
Ahora sí, creamos una nueva página <span class="codigo">JSP</span>; el nombre de esta página será "<span class="codigo">datos-usuario</span>". En esta página haremos uso de algunas etiquetas de <span class="codigo">Struts</span>, por lo que deberemos indicarlo usando la directiva "<span class="codigo">taglib</span>" correspondiente: <br />
<br />
<pre><code>
<%@taglib uri="/struts-tags" prefix="s" %>
</code></pre><br />
<br />
En esta página solamente mostraremos los datos del usuario que acabamos de crear. Recordemos que para esto debemos usar la etiqueta "<span class="codigo"><s:property></span>". Mostramos cada uno de los datos anteriores del usuario:<br />
<br />
<pre><code>
Nombre: <strong><s:property value="<span class="negritas">usuario.nombre</span>" /></strong> <br />
Username: <strong><s:property value="<span class="negritas">usuario.username</span>" /></strong> <br />
Password: <strong><s:property value="<span class="negritas">usuario.password</span>" /></strong> <br />
Edad: <strong><s:property value="<span class="negritas">usuario.edad</span>" /></strong> <br />
Fecha de Nacimiento: <strong><s:property value="<span class="negritas">usuario.fechaNacimiento</span>" /></strong>
</code></pre><br />
<br />
Cuando entremos a la siguiente página:<br />
<br />
<pre><code>
<a href="http://localhost:8080/formularios/nuevo-usuario.jsp">http://localhost:8080/formularios/nuevo-usuario.jsp</a>
</code></pre><br />
<br />
Veremos el formulario para capturar los datos del usuario. En mi caso, con los siguientes datos:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgBd8RhRB4jMweWY7XDb35k6rOiHbV0-b-VVNZSkL0qHBCp_gxSbnqV9R5-a1h7kP00-lTkRkKeZ0T8b5i9ixiwx8-J8orHr7Y0N7evFuo2m1RwM3A2ouBVUo8F6UEh1KMAileyRNL_A8z5/s1600/S3_3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="207" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgBd8RhRB4jMweWY7XDb35k6rOiHbV0-b-VVNZSkL0qHBCp_gxSbnqV9R5-a1h7kP00-lTkRkKeZ0T8b5i9ixiwx8-J8orHr7Y0N7evFuo2m1RwM3A2ouBVUo8F6UEh1KMAileyRNL_A8z5/s400/S3_3.png" width="400" /></a></div><br />
<br />
Obtengo la siguiente salida:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjccNLx8aOzK3kK8SZLdDcsiQQeJiT4sdyXflZXR-TIZMBBX-qXp0s_aJUrOkqtWRK2nb7ygIwngyOca3zqqssT5u_Xwmi0y8PGXp_qLh8KcybihLMLPyHLFTkwnRCTIvZPI0MyUXJIb0D0/s1600/S3_4.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="207" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjccNLx8aOzK3kK8SZLdDcsiQQeJiT4sdyXflZXR-TIZMBBX-qXp0s_aJUrOkqtWRK2nb7ygIwngyOca3zqqssT5u_Xwmi0y8PGXp_qLh8KcybihLMLPyHLFTkwnRCTIvZPI0MyUXJIb0D0/s400/S3_4.png" width="400" /></a></div><br />
<br />
En el ejemplo anterior podemos ver que aunque hemos pasado pocos datos al formulario, todos los hemos establecido en el objeto "<span class="codigo">Usuario</span>", que se creó dentro método "<span class="codigo">execute</span>", y los pasamos "como van", es decir sin realizar ninguna transformación o procesamiento sobre ellos. Cuando tenemos 5 o 6 atributos esto puede no ser algo muy pesado. Pero ¿qué pasa si tenemos que llenar un objeto con 30 o 40 propiedades? Nuestro <span class="codigo">Action</span> sería muy grande por todos los atributos y los <span class="codigo">setters</span> y <span class="codigo">getters</span> de las propiedades; el trabajo dentro del método "<span class="codigo">execute</span>" también sería bastante cansado llamar a los <span class="codigo">setters</span> de nuestro modelos para pasarle los parámetros.<br />
<br />
Para esos casos <span class="codigo">Struts 2</span> proporciona una forma de simplificarnos estas situaciones usando un mecanismo conocido como "<span class="codigo">Model Driven</span>" el cual veremos a continuación.<br />
<br />
<br />
<h2 class="titulo">2. Model Driven</h2><span class="codigo">Model Driven</span> es una forma que <span class="codigo">Struts 2</span> proporciona para poder establecer todos los parámetros que se reciben de un formulario directamente dentro de un objeto. Este objeto es conocido como el <span class="negritas">modelo</span>. <br />
<br />
Usando este modelo nos evitamos estar llamando nosotros mismos a los <span class="codigo">setters</span> de este objeto modelo. <br />
<br />
Esto también permite que si realizamos validaciones de datos del formulario (lo cual veremos cómo hacer un poco más adelante) estas se realizarán sobre este objeto modelo.<br />
<br />
Un interceptor especial, llamado model driven interceptor, se encarga de manejar todo esto de forma automática ^_^.<br />
<br />
Extenderemos nuestro ejemplo para usar model driven.<br />
<br />
Creamos una nueva clase Java, en el paquete "<span class="codigo">actions</span>", llamada "<span class="codigo">UsuarioActionMD</span>". Esta clase extenderá de "<span class="codigo">ActionSupport</span>":<br />
<br />
<pre><code>
public class UsuarioActionMD extends ActionSupport
{
}
</code></pre><br />
<br />
Para indicarle a <span class="codigo">Struts 2</span> que este <span class="codigo">Action</span> será <span class="codigo">Model Driven</span>, la clase "<span class="codigo">UsuarioActionMD</span>" debe implementar la interface "<span class="codigo">ModelDriven</span>", indicando de qué tipo de objeto será usado como modelo:<br />
<pre><code>
public class UsuarioActionMD extends ActionSupport implements ModelDriven<Usuario>
{
}
</code></pre><br />
<br />
Ahora pondremos un objeto <span class="codigo">Usuario</span> dentro de nuestra clase, con una variable de instancia, que será el objeto que usaremos como modelo:<br />
<br />
<pre><code>
public class UsuarioActionMD extends ActionSupport implements ModelDriven<Usuario>
{
private Usuario usuario = new Usuario();
}
</code></pre><br />
<br />
Es importante tener creado este objeto antes de que nuestro <span class="codigo">Action</span> reciba alguna petición, por lo que podemos inicializarlo en la misma declaración o en el constructor del <span class="codigo">Action</span>, en caso de tener alguno.<br />
<br />
La interface "<span class="codigo">ModelDriven</span>" tiene tan solo un método: "<span class="codigo">getModel</span>". Esté método no recibe ningún parámetro, y regresa el objeto que estamos usando como modelo:<br />
<br />
<pre><code>
public Usuario getModel()
{
return usuario;
}
</code></pre><br />
<br />
Lo único que queda es realizar algún proceso en el método "<span class="codigo">execute</span>", que podría ser almacenar al <span class="codigo">Usuario</span> en alguna base de datos, enviarlo por algún stream, etc. Como en este caso no haremos nada con el <span class="codigo">Usuario</span> más que regresarlo para poder mostrar sus datos en una página, nuestro método "<span class="codigo">execute</span>" solo regresará un valor de "<span class="codigo">SUCCESS</span>". <br />
<br />
<pre><code>
@Override
public String execute() throws Exception
{
return SUCCESS;
}
</code></pre><br />
<br />
Noten que en esta ocasión no es necesario tener un <span class="codigo">getter</span> para poder obtener la instancia del "<span class="codigo">Usuario</span>" desde nuestra <span class="codigo">JSP</span>.<br />
<br />
Finalmente, anotamos nuestra clase para que <span class="codigo">Struts 2</span> lo reconozca como un <span class="codigo">Action</span>. El nombre del <span class="codigo">Action</span> será "<span class="codigo">datosUsuario</span>". Para diferenciar este <span class="codigo">Action</span> del anterior (que tiene el mismo nombre) lo colocaremos en el namespace "<span class="codigo">modeldriven</span>".<br />
<br />
El <span class="codigo">Action</span> al terminar de ejecutarse nos enviará a la página "<span class="codigo">/modeldriven/datos-usuario.jsp</span>":<br />
<br />
<pre><code>
@Namespace(value = "/modeldriven")
@Action(value="datosUsuario", results={@Result(location="/modeldriven/datos-usuario.jsp")})
public class UsuarioActionMD extends ActionSupport implements ModelDriven<Usuario>
{
}
</code></pre><br />
<br />
La clase "<span class="codigo">UsuarioActionMD</span>" completa (omitiendo los imports) queda de la siguiente forma:<br />
<br />
<pre><code>
@Namespace(value = "/modeldriven")
@Action(value="datosUsuario", results={@Result(location="/modeldriven/datos-usuario.jsp")})
public class UsuarioActionMD extends ActionSupport implements ModelDriven<Usuario>
{
private Usuario usuario = new Usuario();
public Usuario getModel()
{
return usuario;
}
@Override
public String execute() throws Exception
{
return SUCCESS;
}
}
</code></pre><br />
<br />
Podemos ver que el código de este <span class="codigo">Action</span> es mucho más pequeño que el que hicimos para el ejemplo anterior.<br />
<br />
Ahora antes de crear la página que se encargará de mostrar el resutado, creamos un nuevo directorio para nuestras páginas web. Para eso hacemos clic derecho sobre el nodo "<span class="codigo">WebPages</span>" de nuestro proyecto. En el menú que aparece seleccionamos la opción "<span class="codigo">New -> Folder</span>":<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjinkFEeJFJfMXDOYwcCdnRhJfxAmiuKoWxyjicc2Z4TwryAUNLQVIJRZcPNcFrjrKJys8e3K_TkQMEw0SApXBo2D0IOE_OBtK-SNscgtRAHvVB2o88nmLPhMz5wMbO45QTj59DyGoNiiT9/s1600/S3_5.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="321" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjinkFEeJFJfMXDOYwcCdnRhJfxAmiuKoWxyjicc2Z4TwryAUNLQVIJRZcPNcFrjrKJys8e3K_TkQMEw0SApXBo2D0IOE_OBtK-SNscgtRAHvVB2o88nmLPhMz5wMbO45QTj59DyGoNiiT9/s400/S3_5.png" width="400" /></a></div><br />
<br />
Damos "<span class="codigo">modeldriven</span>" como nombre del directorio y presionamos el botón "<span class="codigo">Finish</span>".<br />
<br />
Dentro de este directorio crearemos dos páginas. Primero crearemos la página que contendrá el formulario que nos permitirá capturar los datos del usuario. Hacemos clic derecho en el directorio "<span class="codigo">modeldriven</span>" del nodo "<span class="codigo">Web Pages</span>". En el menú que se abre seleccionamos la opción "<span class="codigo">New –> JSP...</span>". Damos "<span class="codigo">nuevo-usuario</span>" como nombre de la página y presionamos el botón "<span class="codigo">Finish</span>".<br />
<br />
El formulario de la página será idéntico al que creamos en la primer parte del tutorial, con la única diferencia de que enviará los datos al <span class="codigo">Action</span> "<span class="codigo">datosUsuarioMD</span>":<br />
<br />
<pre><code>
<s:form action="datosUsuario">
<s:textfield name="<span class="negritas">nombre</span>" label="Nombre" />
<s:textfield name="<span class="negritas">username</span>" label="Username" />
<s:password name="<span class="negritas">password</span>" label="Password" />
<s:textfield name="<span class="negritas">edad</span>" label="Edad" />
<s:textfield name="<span class="negritas">fechaNacimiento</span>" label="Fecha de Nacimiento" />
<s:submit value="Enviar" />
</s:form>
</code></pre><br />
<br />
La segunda página, que se llamará "<span class="codigo">datos-usuario.jsp</span>" y que también estará en el directorio "<span class="codigo">modeldriven</span>", mostrará los valores de los atributos del objeto "<span class="codigo">usuario</span>" que se usa como modelo. Esta página también será muy parecida a la que creamos anteriormente, solo que en esta ocasión no será necesario indicar que los atributos que estamos usando corresponden al objeto "<span class="codigo">usuario</span>":<br />
<br />
<pre><code>
Nombre: <strong><s:property value="nombre" /></strong> <br />
Username: <strong><s:property value="username" /></strong> <br />
Password: <strong><s:property value="password" /></strong> <br />
Edad: <strong><s:property value="edad" /></strong> <br />
Fecha de Nacimiento: <strong><s:property value="fechaNacimiento" /></strong>
</code></pre><br />
<br />
Al ejecutar la aplicación y entrar en la siguiente dirección:<br />
<br />
<pre><code>
<a href="http://localhost:8080/formularios/modeldriven/nuevo-usuario.jsp">http://localhost:8080/formularios/modeldriven/nuevo-usuario.jsp</a>
</code></pre><br />
<br />
Veremos un formulario muy parecido al anterior:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj0iNX_ray3vFtZ6Fsqe8onv3snijiG0PGSKqMVvuS8Rn07RXv6Q1rma2ttVPruKYQFjbJ3hAsSeZo1tIMZKdkBLIPa60uPsRbBuDP3pL8K3OeG77ba2R_99LxRe9FRlnjfk_uWpTyFAt1D/s1600/S3_6.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="202" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj0iNX_ray3vFtZ6Fsqe8onv3snijiG0PGSKqMVvuS8Rn07RXv6Q1rma2ttVPruKYQFjbJ3hAsSeZo1tIMZKdkBLIPa60uPsRbBuDP3pL8K3OeG77ba2R_99LxRe9FRlnjfk_uWpTyFAt1D/s400/S3_6.png" width="400" /></a></div><br />
<br />
Cuando llenemos los datos y enviemos el formulario, deberemos ver una salida también muy parecida a la anterior:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEia0GX_r-M1zP585F10Wy655OcO4Xt9Th_piQkc-MOl-DYnkHCxDsIBOuo0Whws41bxc5i7MWpjMHt8_l1DRsMPYTJi12r_9N0IVH_1XYa1Qs-QjL28tmaUMwNtM5jxtTAD-ouUABFhiCpK/s1600/S3_7.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="202" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEia0GX_r-M1zP585F10Wy655OcO4Xt9Th_piQkc-MOl-DYnkHCxDsIBOuo0Whws41bxc5i7MWpjMHt8_l1DRsMPYTJi12r_9N0IVH_1XYa1Qs-QjL28tmaUMwNtM5jxtTAD-ouUABFhiCpK/s400/S3_7.png" width="400" /></a></div><br />
<br />
Pero ¿qué ocurre si queremos recibir un parámetro que no sea un atributo del "<span class="codigo">usuario</span>"? En ese caso, solo tenemos que agregar un nuevo atributo, con su correspondiente <span class="codigo">setter</span> para establecer su valor (y su <span class="codigo">getter</span> si es que pensamos recuperarlo posteriormente) dentro de nuestro <span class="codigo">Action</span>. <br />
<br />
Modifiquemos un poco el ejemplo anterior para ver esto. Agregaremos al formulario un campo de texto para que el usuario proporcione un número de confirmación:<br />
<br />
<pre><code>
<s:form action="datosUsuario">
<s:textfield name="nombre" label="Nombre" />
<s:textfield name="username" label="Username" />
<s:password name="password" label="Password" />
<s:textfield name="edad" label="Edad" />
<s:textfield name="fechaNacimiento" label="Fecha de Nacimiento" />
<span class="negritas"><s:textfield name="numero" label="Número de Confirmación" /></span>
<s:submit value="Enviar" />
</s:form>
</code></pre><br />
<br />
También cambiaremos nuestro <span class="codigo">Action</span>. Colocamos un atributo de tipo entero para contener este número, junto con sus correspondientes <span class="codigo">setter</span> (para establecer el valor desde el formulario) y <span class="codigo">getter</span> (para obtener el valor desde la <span class="codigo">JSP</span>):<br />
<br />
<pre><code>
private int numero;
public void setNumero(int numero)
{
this.numero = numero;
}
public int getNumero()
{
return numero;
}
</code></pre><br />
<br />
De igual forma modificaremos la página "<span class="codigo">datos-usuarios.jsp</span>" del directorio "<span class="codigo">modeldriven</span>" para poder mostrar el valor del número de confirmación:<br />
<br />
<pre><code>
Nombre: <strong><s:property value="nombre" /></strong> <br />
Username: <strong><s:property value="username" /></strong> <br />
Password: <strong><s:property value="password" /></strong> <br />
Edad: <strong><s:property value="edad" /></strong> <br />
Fecha de Nacimiento: <strong><s:property value="fechaNacimiento" /></strong><br />
<span class="negritas">Número de confirmación: <strong><s:property value="numero" /></strong></span>
</code></pre><br />
<br />
Al ejecutar la aplicación y entrar al formulario, veremos el campo que hemos agregado:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgBpFjEehT6IEOhxFefua_M1covd6cvEYDrDANSRLd7hPj3jEnq7_fgI9VuLgZSAjoU_ekHVJEjRiv6UKyUbLFjhprqmWlPAOLFRo03CHjxdADBtoTzIF4CiBfXvP8chSPnVfpL5rfjmSbf/s1600/S3_8.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="202" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgBpFjEehT6IEOhxFefua_M1covd6cvEYDrDANSRLd7hPj3jEnq7_fgI9VuLgZSAjoU_ekHVJEjRiv6UKyUbLFjhprqmWlPAOLFRo03CHjxdADBtoTzIF4CiBfXvP8chSPnVfpL5rfjmSbf/s400/S3_8.png" width="400" /></a></div><br />
<br />
Al enviar los datos del formulario obtendremos la siguiente pantalla: <br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgSKItreC73ih2AQfkC3GLwaINRKNEZ46qzk8rYjJBHdFadUj1XE5rwE2QvvVpQKYNcGqK54m6c8_qMYf3MwlOxYex0gnxZqv-ghvKchG5GyrqUVh9k9nti516gk_DbxVENHSgNmEFRg-JI/s1600/S3_9.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="202" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgSKItreC73ih2AQfkC3GLwaINRKNEZ46qzk8rYjJBHdFadUj1XE5rwE2QvvVpQKYNcGqK54m6c8_qMYf3MwlOxYex0gnxZqv-ghvKchG5GyrqUVh9k9nti516gk_DbxVENHSgNmEFRg-JI/s400/S3_9.png" width="400" /></a></div><br />
<br />
Ahora veremos cómo podemos hacer para recibir un conjunto de valores en un solo atributo de nuestro <span class="codigo">Action</span>.<br />
<br />
<br />
<h2 class="titulo">3. Recepción de múltiples parámetros</h2>En los ejemplos anteriores cada uno de nuestros atributos recibe solo un valor de algún tipo. Sin embargo algunas veces nos será conveniente recibir un conjunto de parámetros, ya sea como un arreglo de valores, o como una lista de valores para poder procesar cada uno de sus elementos.<br />
<br />
Esto es útil para cuando, por ejemplo, tenemos campos de formulario que se van generando dinámicamente, o que tienen el mismo significado en datos (por ejemplo cuando tenemos varios campos para cargar archivos adjuntos en correos electrónicos, o para poder subir varias imágenes de una vez a un sitio):<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjdlgkeQS-g3Gi-ZX5Z1c5OXyHwbRMkYoQjLkeYCRl1zCG7EIMG9vTzEMlLjOr_BurVaDL1kvGI2XhUHMFTPdYpH9c-GqA8FehChZhCXQZO0zPrH4ILKBkpG42DjSoakPcJxdMVkV3xPWnt/s1600/S3_10.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="296" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjdlgkeQS-g3Gi-ZX5Z1c5OXyHwbRMkYoQjLkeYCRl1zCG7EIMG9vTzEMlLjOr_BurVaDL1kvGI2XhUHMFTPdYpH9c-GqA8FehChZhCXQZO0zPrH4ILKBkpG42DjSoakPcJxdMVkV3xPWnt/s400/S3_10.png" width="400" /></a></div><br />
<br />
Poder obtener estos parámetros es sencillo gracias a los interceptores de <span class="codigo">Struts 2</span>.<br />
<br />
Veamos un ejemplo parecido a la pantalla anterior. Tendremos un formulario que nos permitirá recibir un conjunto de correos electrónicos de personas para ser procesados en el servidor. Colocaremos de forma estática 5 campos (claro que estos podrían ir aumentando de forma dinámica usando JavaScript).<br />
<br />
Aunque el formulario tendrá 5 campos para correos, el usuario podría no llenarlos todos, por lo que no sabremos cuántos elementos estaremos recibiendo.<br />
<br />
El formulario tendrá además un campo que permitirá que el usuario coloque su correo nombre, para saber quién está enviando los correos.<br />
<br />
Comencemos primero con el formulario que mostrará los campos para los correos. Crearemos un nuevo directorio, en la raíz de las páginas web del proyecto, llamado "<span class="codigo">multidatos</span>" (lo sé, no es un nombre muy original, pero refleja el cometido del ejemplo):<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiWF9Vo6hhgR92fO_TOrtdW-d62ivBAQ564YolqlwCMFGgCIfU87EW2VrqQg22rgCRwq8Wq6lMLKb_6G6LQeQygyuyrBb0jK9ECxNolG-HZVRTr2OZk3sjP5FydhmG_g4ukALCUpE4h3xK6/s1600/S3_11.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiWF9Vo6hhgR92fO_TOrtdW-d62ivBAQ564YolqlwCMFGgCIfU87EW2VrqQg22rgCRwq8Wq6lMLKb_6G6LQeQygyuyrBb0jK9ECxNolG-HZVRTr2OZk3sjP5FydhmG_g4ukALCUpE4h3xK6/s1600/S3_11.png" /></a></div><br />
<br />
En este directorio crearemos una nueva JSP llamada "<span class="codigo">datos.jsp</span>". Esta página contendrá el formulario con los 6 campos mencionados antes: un campo para que el usuario coloque su nombre, y 5 para colocar una dirección de correo electrónico:<br />
<br />
<pre><code>
<s:form action="envioCorreo">
<s:textfield name="nombre" label="Nombre" />
<s:textfield name="correo" label="Correo" />
<s:textfield name="correo" label="Correo" />
<s:textfield name="correo" label="Correo" />
<s:textfield name="correo" label="Correo" />
<s:textfield name="correo" label="Correo" />
<s:submit value="Enviar" />
</s:form>
</code></pre><br />
<br />
No olviden indicar que usaremos las etiquetas de <span class="codigo">Struts 2</span>, con la directiva <span class="codigo">taglib</span> correspondiente:<br />
<br />
<pre><code>
<%@taglib uri="/struts-tags" prefix="s" %>
</code></pre><br />
<br />
Podemos ver en el formulario que los 5 campos para colocar los correos electrónicos son exactamente iguales, todos tienen por nombre "<span class="codigo">correo</span>". Esto es necesario para podamos recibir los valores como un solo conjunto de datos. <br />
<br />
Veamos el <span class="codigo">Action</span> que recibirá estos valores. Primero crearemos una nueva clase java, llamada "<span class="codigo">MultiplesDatosAction</span>", en el paquete "<span class="codigo"><span class="codigo">Action</span>s</span>". Esta clase extenderá de <span class="codigo">ActionSupport</span>:<br />
<br />
<pre><code>
public class MultiplesDatosAction extends ActionSupport
{
}
</code></pre><br />
<br />
Ahora colocaremos el atributo que nos permitirá recibir los múltiples parámetros. Para esto debemos colocar un arreglo del tipo de datos que estemos esperando. En este caso será un arreglo de <span class="codigo">Strings</span>:<br />
<br />
<pre><code>
private String[] correos;
</code></pre><br />
<br />
En realidad, ese es todo el secreto para poder recibir estos múltiples parámetros ^_^. Uno de los interceptores de <span class="codigo">Struts 2</span> se encargará de recibir los parámetros del formulario que tienen el mismo nombre; este interceptor colocará los parámetros en un arreglo o una colección, dependiendo de qué es lo que estemos esperando, y lo colocará en nuestro <span class="codigo">Action</span> usando el <span class="codigo">setter</span> apropiado.<br />
<br />
Según la explicación anterior, debemos colocar el <span class="codigo">setter</span> del atributo llamado "<span class="codigo">correos</span>" para poder establecer el arreglo de correos, y su getter para poder obtenerlo posteriormente:<br />
<br />
<pre><code>
public String[] getCorreos()
{
return correos;
}
public void setCorreos(String[] correos)
{
this.correos = correos;
}
</code></pre><br />
<br />
Ahora que tenemos el arreglo de valores colocaremos un atributo para mantener el nombre del usuario, con su correspondiente <span class="codigo">getter</span> y <span class="codigo">setter</span>:<br />
<br />
<pre><code>
private String nombre;
public String getNombre()
{
return nombre;
}
public void setNombre(String nombre)
{
this.nombre = nombre;
}
</code></pre><br />
<br />
Como en esta ocasión no se realizará ningún proceso sobre los datos, por lo que nuestro método "<span class="codigo">execute</span>" solo regresará un valor de "<span class="codigo">SUCCESS</span>":<br />
<br />
<pre><code>
@Override
public String execute() throws Exception
{
return SUCCESS;
}
</code></pre><br />
<br />
Para terminar anotaremos esta clase. Primero indicamos que este <span class="codigo">Action</span> estará en el namespace "<span class="codigo">multidatos</span>". Además responderá al nombre de "<span class="codigo">envioCorreo</span>" (el mismo que pusimos en el formulario). Al terminar el proceso, el <span class="codigo">Action</span> nos enviará a una página llamada "<span class="codigo">datos-enviados.jsp</span>" del directorio "<span class="codigo">multidatos</span>":<br />
<br />
<pre><code>
@Namespace(value="/multidatos")
@Action(value="envioCorreo", results={@Result(location="/multidatos/datos-enviados.jsp")})
public class MultiplesDatosAction extends ActionSupport
{
}
</code></pre><br />
<br />
El código completo de la clase "<span class="codigo">MultiplesDatosAction</span>" queda de la siguiente forma:<br />
<br />
<pre><code>
@Namespace(value="/multidatos")
@Action(value="envioCorreo", results={@Result(location="/multidatos/datos-enviados.jsp")})
public class MultiplesDatosAction extends ActionSupport
{
private String[] correos;
private String nombre;
@Override
public String execute() throws Exception
{
return SUCCESS;
}
public String getNombre()
{
return nombre;
}
public void setNombre(String nombre)
{
this.nombre = nombre;
}
public String[] getCorreos()
{
return correos;
}
public void setCorreos(String[] correos)
{
this.correos = correos;
}
}
</code></pre><br />
<br />
Ahora solo falta crear la página a la que regresaremos al terminar el <span class="codigo">Action</span>.<br />
<br />
Creamos, en el directorio "<span class="codigo">multidatos</span>" de las páginas web, una nueva <span class="codigo">JSP</span> llamada "<span class="codigo">datos-enviados.jsp</span>":<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEilyXe-G54nmqiHWztGVOmDi5aq5L-cLWkSLBpLc_-VaidoaasJB6tdQ39h_iL5QXXVy8H1T-4ZkTjx2ylwm4re-M5oLtkIjI85kjSADUrt_vj_m5SMIePh8jlZrQxF0JvmAo2hwMutSe4J/s1600/S3_12.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEilyXe-G54nmqiHWztGVOmDi5aq5L-cLWkSLBpLc_-VaidoaasJB6tdQ39h_iL5QXXVy8H1T-4ZkTjx2ylwm4re-M5oLtkIjI85kjSADUrt_vj_m5SMIePh8jlZrQxF0JvmAo2hwMutSe4J/s400/S3_12.png" width="201" /></a></div><br />
<br />
En esta página lo que haremos será mostrar los mismos datos que recibimos del formulario. <br />
<br />
Recuerden que primero debemos indicar que usaremos las etiquetas de <span class="codigo">Struts 2</span>, usando el <span class="codigo">taglib</span> correspondiente:<br />
<br />
<pre><code>
<%@taglib uri="/struts-tags" prefix="s" %>
</code></pre><br />
<br />
Primero mostraremos el valor del nombre del usuario, de la misma manera en la que hemos venido haciéndolo a lo largo de los ejemplos del tutorial:<br />
<br />
<pre><code>
<s:property value="nombre" />
</code></pre><br />
<br />
Ahora mostraremos los valores de nuestro arreglo de cadenas, para eso <span class="codigo">Struts 2</span> proporciona una etiqueta especial que nos permite iterar a través de todos los valores de un arreglo o colección, la etiqueta "<span class="codigo">iterator</span>". <br />
<br />
La etiqueta "<span class="codigo">iterator</span>" recibirá el arreglo o colección a través del cual ciclaremos para obtener sus valores. Indicamos este arreglo o colección usando el atributo "<span class="codigo">value</span>". En este caso indicaremos que el valor a través del cual queremos iterar es arreglo llamado "<span class="codigo">correos</span>":<br />
<br />
<pre><code>
<s:iterator value="correos">
</s:iterator>
</code></pre><br />
<br />
Ya podemos ciclar a través de todos los elementos del arreglo, ahora necesitamos una manera de obtener el elemento actual dentro del ciclo. Para esto usamos el atributo "<span class="codigo">var</span>", en este indicaremos el nombre de la variable que mantendrá el valor del elemento actual. No debemos de preocuparnos por crear esta variable, ya que <span class="codigo">Struts 2</span> la creara automáticamente y la pondrá a nuestra disposición. Esta variable será del tipo de elementos que sea mantenido por nuestro arreglo o colección:<br />
<br />
<pre><code>
<s:iterator value="correos" var="correo">
</s:iterator>
</code></pre><br />
<br />
Algunas veces querremos poder obtener alguna información relativa a la iteración, como el índice del elemento actual, el número total de elementos que tiene el arreglo o colección, si el elemento actual es par o impar, etc. Para obtener esta información podemos indicarle a la etiqueta "<span class="codigo">iterator</span>" que la coloque, como una instancia de la clase "<span class="codigo">IteratorStatus</span>" y que la ponga a nuestra disposición en una variable. Para esto, indicamos el nombre de la variable en el atributo "<span class="codigo">status</span>":<br />
<br />
<pre><code>
<s:iterator value="correos" var="correo" status="estatus">
</s:iterator>
</code></pre><br />
<br />
El estatus es una variable que no será colocada dentro en la raíz del stack de <span class="codigo">Struts 2</span> (el cual explicamos en <a href="http://www.javatutoriales.com/2011/06/struts-2-parte-2-ognl.html">el segundo tutorial de la serie</a>), por lo que es necesario hacer referencia a el usando el operador "<span class="codigo">#</span>".<br />
<br />
Ahora que podemos iterar a través de cada uno de los elementos de nuestro arreglo, solo hace falta mostrar el valor de cada uno de estos elementos, adicionalmente también mostraremos su índice. Colocaremos estos valores en una lista para que se vea más presentable:<br />
<br />
<pre><code>
<ul>
<s:iterator value="correos" var="correo" status="estatus">
<li><s:property value="#estatus.index" /> - <s:property value="correo" /></li>
</s:iterator>
</ul>
</code></pre><br />
<br />
Ahora, cuando coloquemos los siguientes datos en el formulario:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhDAdQ8SNjKPCca_-p1ibxQJfBRxmR0UBB6v_JTtjCZf18mdP8qBcn9jOeXq3nm5ovmz9q4F62PMwYIAKGTFX9wPeuFBicozwnPxy__J4ndLXJ2Lo0PrMRbkIhz9qZpVywrcgvdWt-mMyVs/s1600/S3_13.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="170" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhDAdQ8SNjKPCca_-p1ibxQJfBRxmR0UBB6v_JTtjCZf18mdP8qBcn9jOeXq3nm5ovmz9q4F62PMwYIAKGTFX9wPeuFBicozwnPxy__J4ndLXJ2Lo0PrMRbkIhz9qZpVywrcgvdWt-mMyVs/s400/S3_13.png" width="400" /></a></div><br />
<br />
Obtendremos la siguiente salida:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhWmwFJFC4OFb6izHj_2eDNAE0uBm_BtRdT5r_9H7c_XOC9_wmd7goZCKUEfRY72DdKfMLwFXcjtGYxyBABMuMmBuGJZ7mRd07WUCqjuRLw4CHxroMMHqmfWhzMup_88DoVQQPBZ2CjcTV0/s1600/S3_14.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="170" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhWmwFJFC4OFb6izHj_2eDNAE0uBm_BtRdT5r_9H7c_XOC9_wmd7goZCKUEfRY72DdKfMLwFXcjtGYxyBABMuMmBuGJZ7mRd07WUCqjuRLw4CHxroMMHqmfWhzMup_88DoVQQPBZ2CjcTV0/s400/S3_14.png" width="400" /></a></div><br />
<br />
Podemos ver que de una forma sencilla colocamos el conjunto de valores dentro del arreglo. Pero qué pasa si por alguna razón no podemos o no queremos usar un arreglo, y preferimos trabajar con una colección, como un <span class="codigo">Set</span> o un <span class="codigo">List</span>, lo único que tenemos que hacer es cambiar el tipo del atributo "<span class="codigo">correos</span>" (junto con sus correspondientes <span class="codigo">getters</span> y <span class="codigo">setters</span>). Usemos un "<span class="codigo">Set</span>", el cual es una colección que no permite elementos duplicados.<br />
<br />
Modifiquemos el atributo "<span class="codigo">correos</span>" para que quede de la siguiente forma:<br />
<br />
<pre><code>
private Set<String> correos;
public Set<String> getCorreos()
{
return correos;
}
public void setCorreos(Set<String> correos)
{
this.correos = correos;
}
</code></pre><br />
<br />
Al introducir los siguientes datos en el formulario:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiwxA2fIEs7kYdI9UWwzKJhepXj2807VZJhoPWId7pTA8JERtc5Fwk5Os7O-fH9KufKbrwQCKefa7Jresw8kXoiqvrUasCJdgVIPoY8tL1SmXllzZAVmZGYkhDpFF5OREE6DQsus8R6Vann/s1600/S3_15.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="170" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiwxA2fIEs7kYdI9UWwzKJhepXj2807VZJhoPWId7pTA8JERtc5Fwk5Os7O-fH9KufKbrwQCKefa7Jresw8kXoiqvrUasCJdgVIPoY8tL1SmXllzZAVmZGYkhDpFF5OREE6DQsus8R6Vann/s400/S3_15.png" width="400" /></a></div><br />
<br />
Obtendremos la siguiente salida:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEickgb5oh6nwHJCrtI-xG4IBlM4ULRdqNLgG1rXNfGhQ_yd_h_iD31qEU6QL-CrSBYW2uIaTf5oRhpQwoUltlAFBmv5FmwXNCYtBCL1oUr0BRslCTjeiMkmPuRxP4r4dVeBOffwO9dwxuUQ/s1600/S3_16.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="170" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEickgb5oh6nwHJCrtI-xG4IBlM4ULRdqNLgG1rXNfGhQ_yd_h_iD31qEU6QL-CrSBYW2uIaTf5oRhpQwoUltlAFBmv5FmwXNCYtBCL1oUr0BRslCTjeiMkmPuRxP4r4dVeBOffwO9dwxuUQ/s400/S3_16.png" width="400" /></a></div><br />
<br />
Podemos ver que en realidad no fue necesario realizar grandes cambios para modificar la forma en la que recibimos los datos. Si quisiéramos que "<span class="codigo">correos</span>" fuera una Lista, lo único que debemos hacer es, nuevamente, cambiar solo el tipo de dato.<br />
<br />
El arreglo o la colección que usemos para mantener los valores, pueden ser de cualquier tipo de dato (cadenas, fechas, wrappers, archivos, etc).<br />
<br />
Los ejemplos anteriores funcionan bien cuándo sabemos cuántos y cuáles serán los parámetros que estamos esperando recibir. Pero ¿y si no supiéramos de antemano los parámetros que recibiremos?<br />
<br />
Como pueden imaginarse, <span class="codigo">Struts 2</span> proporciona una manera elegante de manejar estas situaciones.<br />
<br />
<br />
<h2 class="titulo">4. Recepción de Parámetros Desconocidos</h2>Algunas veces estamos esperando recibir, dentro de nuestro <span class="codigo">Action</span>, un conjunto de parámetros de los cuales no conocemos ni su cantidad ni sus nombres, como por ejemplo cuando estamos haciendo un filtrado de datos y no sabes qué filtros recibiremos y cuáles serán los valores de estos filtros, o cuando la generación de componentes se realiza de forma dinámica.<br />
<br />
Para estos casos <span class="codigo">Struts 2</span> proporciona una manera de indicar que deberá entregarnos todos los parámetros en un objeto tipo "<span class="codigo">java.util.Map</span>". Las llaves de este mapa representarán los nombres de los parámetros, y sus valores representarán un arreglo de <span class="codigo">Strings</span> con los valores correspondientes de cada parámetro. ¿Por qué un arreglo de <span class="codigo">Strings</span>? Porque como acabamos de ver, algunos de los parámetros que recibimos pueden tener más de un valor.<br />
<br />
Para lograr que <span class="codigo">Struts 2</span> nos proporcione estos parámetros, nuestro <span class="codigo">Action</span> debe implementar la interface "<span class="codigo">ParameterAware</span>". Esta interface se ve de la siguiente forma:<br />
<br />
<pre><code>
public interface ParameterAware
{
void setParameters(Map<String, String[]> parameters);
}
</code></pre><br />
<br />
O sea, que esta interface solo tiene un método llamado "<span class="codigo">setParameters</span>", que tiene como argumento el mapa de parámetros recibidos en la petición.<br />
<br />
Veamos un ejemplo para entender cómo funciona esta interface.<br />
<br />
Primero crearemos un nuevo directorio, dentro de nuestras páginas web, llamado "<span class="codigo">multiparametros</span>". Dentro de este directorio crearemos dos nuevas <span class="codigo">JSP</span>s. La primera, llamada "<span class="codigo">datos.jsp</span>" contendrá un pequeño formulario que nos permitirá enviar los parámetros dinámicos a nuestro <span class="codigo">Action</span>. La segunda, llamada "<span class="codigo">parametros.jsp</span>", mostrara el nombre y el valor de cada uno de los parámetros que hemos colocado en el formulario. Comencemos creando la pagina "<span class="codigo">datos.jsp</span>". <br />
<br />
Primero, indicaremos que haremos uso de las etiquetas de <span class="codigo">Struts 2</span> usando el taglib correspondiente:<br />
<br />
<pre><code>
<%@taglib prefix="s" uri="/struts-tags" %>
</code></pre><br />
<br />
Ahora crearemos un formulario, de una forma un poco distinta a como lo hemos estado haciendo hasta ahora. Primero indicaremos, con la etiqueta "<span class="codigo"><s:form></span>" que haremos uso de un formulario, los datos del mismo serán procesados por un <span class="codigo">Action</span> cuyo nombre será "<span class="codigo">multiparametros</span>":<br />
<br />
<pre><code>
<s:form action="multiparametros">
</s:form>
</code></pre><br />
<br />
Para que sea más claro entender un paso que haremos un poco más adelante, en vez de dejar que <span class="codigo">Struts 2</span> sea quien defina la estructura de nuestro formulario, lo haremos nosotros mismos. Para eso debemos indicar, en el atributo "<span class="codigo">theme</span>" del formulario, el valor "<span class="codigo">simple</span>":<br />
<br />
<pre><code>
<s:form action="multiparametros" theme="simple">
</s:form>
</code></pre><br />
<br />
Ahora colocaremos un elemento en el formulario, un campo de entrada de texto usando el la etiqueta "<span class="codigo"><s:textfield></span>":<br />
<br />
<pre><code>
<s:form action="multiparametros" id="formulario" theme="simple">
<s:textfield id="valor1" name="valor1" />
</s:form>
</code></pre><br />
<br />
Como en esta ocasión no se colocará una etiqueta de forma automática en el campo de texto, deberemos ponerla nosotros mismos, usando la etiqueta "<span class="codigo"><s:label></span>", de la siguiente manera:<br />
<br />
<pre><code>
<s:form action="multiparametros" theme="simple">
<s:label for="valor1" value="Valor 1: " />
<s:textfield id="valor1" name="valor1" />
</s:form>
</code></pre><br />
<br />
Como pueden ver hemos tenido que colocar un valor en el atributo "<span class="codigo">id</span>" del textfield, y poner este mismo valor en el atributo "<span class="codigo">for</span>" de la etiqueta "<span class="codigo"><s:label></span>". <br />
<br />
Como estamos haciendo nosotros mismos el acomodo de los componentes, también debemos colocar un salto de línea entre los elementos del formulario; de lo contrario aparecerían todos en la misma línea. Para eso usamos la etiqueta "<span class="codigo"><br></span>":<br />
<br />
<pre><code>
<s:form action="multiparametros" theme="simple">
<s:label for="valor1" value="Valor 1: " />
<s:textfield id="valor1" name="valor1" /> <br />
</s:form>
</code></pre><br />
<br />
El siguiente paso consiste en colocar el botón de envió del formulario, usando la etiqueta "<span class="codigo"><s:submit></span>":<br />
<br />
<pre><code>
<s:form action="multiparametros" theme="simple">
<s:label for="valor1" value="Valor 1: " />
<s:textfield id="valor1" name="valor1" /> <br />
<s:submit value="Enviar" />
</s:form>
</code></pre><br />
<br />
Hasta ahora tenemos un formulario que se ve de la siguiente forma:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEidAz2CSxo1NDtXGnWPZjrx2KWR1gHE2v2s_UGiCEQ6L1iP7dTK_GdIayhVvON0toXbHqmlug3ZDVypHn0yiqX5ysAafwnjBkLEptDdGWjtbVIxfC1dCgN-O80ry0El11vBDjifDXDrPD5f/s1600/S3_17.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="217" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEidAz2CSxo1NDtXGnWPZjrx2KWR1gHE2v2s_UGiCEQ6L1iP7dTK_GdIayhVvON0toXbHqmlug3ZDVypHn0yiqX5ysAafwnjBkLEptDdGWjtbVIxfC1dCgN-O80ry0El11vBDjifDXDrPD5f/s400/S3_17.png" width="400" /></a></div><br />
<br />
Podemos ver que tenemos un solo parámetro, sin embargo para el ejemplo necesitamos varios campos de texto. Haremos que estos se agreguen de forma dinámica usando una biblioteca de JavaScript: <a href="http://jquery.com/"><span class="codigo">jQuery</span></a>.<br />
<br />
Colocaremos un botón que al presionarlo agregará un nuevo campo para introducir texto, junto con su etiqueta y salto de línea correspondiente.<br />
<br />
El <span class="codigo">HTML</span> del botón es el siguiente:<br />
<br />
<pre><code>
<button id="btnAgregar">Agregar Elemento</button>
</code></pre><br />
<br />
Y el código de <span class="codigo">jQuery</span> para agregar los elementos al formulario es el siguiente:<br />
<br />
<pre><code>
$(document).ready(function ()
{
$("#btnAgregar").click(function()
{
var num = $("input[type=text]").length;
var numeroSiguiente = num + 1;
var elementoNuevo = $("#valor" + num).clone().attr('id', 'valor'+numeroSiguiente).attr("name", "valor"+numeroSiguiente);
var etiquetaNueva = $("label[for=valor"+num+"]").clone().attr("for", "valor"+numeroSiguiente).text("Valor " + numeroSiguiente + ": ");
$("#valor" + num).after(elementoNuevo);
elementoNuevo.before(etiquetaNueva);
etiquetaNueva.before("<br />");
});
});
</code></pre><br />
<br />
Como este no es un tutorial de <span class="codigo">jQuery</span> no explicaré el código anterior ^_^. Pero el resultado es el siguiente:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgvtNOG_lDkOxVRJ2FECZH-NUUZKY56_KfhdAHNIxkSFvSmzoagOTVJi49YctxHQcJSbrivxFBwNyM7h2tyqq7bTdo_QpoFZNbPXHJMq9n403TmslLTxYazPwOCvLHT1nZMtsh2B1GpXBOX/s1600/S3_18.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="217" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgvtNOG_lDkOxVRJ2FECZH-NUUZKY56_KfhdAHNIxkSFvSmzoagOTVJi49YctxHQcJSbrivxFBwNyM7h2tyqq7bTdo_QpoFZNbPXHJMq9n403TmslLTxYazPwOCvLHT1nZMtsh2B1GpXBOX/s400/S3_18.png" width="400" /></a></div><br />
<br />
Cada vez que presionemos el botón "<span class="codigo">Agregar Elemento</span>" se agregará un nuevo campo de entrada, de la siguiente forma:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjgIuO7NgDWo0ukdGDYYzXNRCRWLxt2tYLSVDNVuGj48hRE8IOT9NA7-WKGJ-WtWGa_Ykm3ynbttYZh5QuebQ6l_kNKgZ1TDXe5aUXyoPUIaCi5bqnZXKqzFfU546h326kEuWwwegIfh-is/s1600/S3_19.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="217" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjgIuO7NgDWo0ukdGDYYzXNRCRWLxt2tYLSVDNVuGj48hRE8IOT9NA7-WKGJ-WtWGa_Ykm3ynbttYZh5QuebQ6l_kNKgZ1TDXe5aUXyoPUIaCi5bqnZXKqzFfU546h326kEuWwwegIfh-is/s400/S3_19.png" width="400" /></a></div><br />
<br />
Ahora que tenemos un formulario pasemos a crear nuestro <span class="codigo">Action</span>. Creamos una nueva clase <span class="codigo">Java</span>, en el paquete "<span class="codigo"><span class="codigo">Action</span>s</span>". El nombre de esta clase será "<span class="codigo">MultiplesParametrosAction</span>", extenderá de "<span class="codigo">ActionSupport</span>" e implementará la interface "<span class="codigo">ParameterAware</span>":<br />
<br />
<pre><code>
public class MultiplesParametrosAction extends ActionSupport implements ParameterAware
{
}
</code></pre><br />
<br />
Debemos colocar una variable que nos permita almacenar la referencia al mapa que los interceptores inyectarán con la lista de parámetros recibidos:<br />
<br />
<pre><code>
private Map<String, String[]> parametros;
</code></pre><br />
<br />
Además debemos implementar el método "<span class="codigo">setParameters</span>" de la interface "<span class="codigo">ParameterAware</span>":<br />
<br />
<pre><code>
public void setParameters(Map<String, String[]> parametros)
{
this.parametros = parametros;
}
</code></pre><br />
<br />
Adicionalmente pondremos un <span class="codigo">getter</span> para el mapa de parámetros, para poder recuperarlo desde la <span class="codigo">JSP</span> correspondiente:<br />
<br />
<pre><code>
public Map<String, String[]> getParametros()
{
return parametros;
}
</code></pre><br />
<br />
Nuestro método "<span class="codigo">execute</span>" nuevamente será muy simple y solo regresara el valor "<span class="codigo">SUCESS</span>":<br />
<br />
<pre><code>
@Override
public String execute() throws Exception
{
return SUCCESS;
}
</code></pre><br />
<br />
El siguiente paso es anotar nuestra clase para convertirla en un <span class="codigo">Action</span>. Las anotaciones (y la explicación) serán las mismas que hemos venido usando hasta ahora:<br />
<br />
<pre><code>
@Namespace(value="/multiparametros")
@Action(value="multiparametros", results={@Result(location="/multiparametros/parametros.jsp")})
public class MultiplesParametrosAction extends ActionSupport implements ParameterAware
{
}
</code></pre><br />
<br />
La clase "<span class="codigo">MultiplesParametrosAction</span>" completa queda de la siguiente forma:<br />
<br />
<pre><code>
@Namespace(value="/multiparametros")
@Action(value="multiparametros", results={@Result(location="/multiparametros/parametros.jsp")})
public class MultiplesParametrosAction extends ActionSupport implements ParameterAware
{
private Map<String, String[]> parametros;
public void setParameters(Map<String, String[]> parametros)
{
this.parametros = parametros;
}
public Map<String, String[]> getParametros()
{
return parametros;
}
@Override
public String execute() throws Exception
{
return SUCCESS;
}
}
</code></pre><br />
<br />
Finalmente debemos crear el contenido de la pagina "<span class="codigo">parametros.jsp</span>". Nuevamente usaremos una etiqueta "<<span class="codigo">s:iterator</span>>". En este caso en vez de pasarle una lista, le pasaremos el mapa con los parámetros que recibimos desde el <span class="codigo">Action</span>, y nuevamente colocaremos una variable en donde se colocará el valor del elemento actual del ciclo:<br />
<br />
<pre><code>
<s:iterator value="parametros" var="parametro">
</s:iterator>
</code></pre><br />
<br />
Como un <span class="codigo">Map</span> tiene dos elementos importantes, la llave y el valor, debemos obtener cada uno de estos valores para mostrarlos en nuestra página. Para tener acceso a la llave usamos la propiedad "<span class="codigo">key</span>" del elemento actual, y para obtener el valor, usamos la propiedad "<span class="codigo">value</span>" la cual nos regresará el arreglo de cadenas asociado con el parámetro. Para obtener el primer valor de este parámetro debemos accederlo usando la sintaxis de los arreglos, como vimos en el segundo tutorial de la serie:<br />
<br />
<pre><code>
<s:iterator value="parametros" var="parametro">
<s:property value="#parametro.key" />: <s:property value="#parametro.value[0]" />
</s:iterator>
</code></pre><br />
<br />
Para que todo se vea más ordenado, colocaremos los resultados en una lista:<br />
<br />
<pre><code>
<ul>
<s:iterator value="parametros" var="parametro">
<li><s:property value="#parametro.key" />: <s:property value="#parametro.value[0]" /></li>
</s:iterator>
</ul>
</code></pre><br />
<br />
Ahora que ya tenemos el código listo ejecutemos la aplicación; cuando entremos a la dirección:<br />
<br />
<pre><code>
<a href="http://localhost:8080/formularios/multiparametros/datos.jsp">http://localhost:8080/formularios/multiparametros/datos.jsp</a>
</code></pre><br />
<br />
Veremos el formulario que creamos:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEglrHaeaZitTM03uTyVMjyvsxbUJftJB463z3bvaJxvN5DSxyDYuPrRFuSr8TCeczqvw7bgv62uFSSe-KBr9rLyiG-SncklkC05O2EXp9huwFjP7ZhOgptt__LGh5IwYynBLP2cfxHlpOAB/s1600/S3_68.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="165" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEglrHaeaZitTM03uTyVMjyvsxbUJftJB463z3bvaJxvN5DSxyDYuPrRFuSr8TCeczqvw7bgv62uFSSe-KBr9rLyiG-SncklkC05O2EXp9huwFjP7ZhOgptt__LGh5IwYynBLP2cfxHlpOAB/s400/S3_68.png" width="400" /></a></div><br />
<br />
Si ingresamos los siguientes datos, y enviamos el formulario:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiwUGDx2lQuv-VZapOi9hFZkiIHtMg0NvXEuYdSJH0YnyK37vHdd8k1x9sQ9Q8v4h_MWPONTXSFdKZpwgeg8TCEUtxLTs_M3AcHWqE96OKyanBSzyDDT00WStsWCWJb921Ux1Lz31wk-kV4/s1600/S3_69.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="165" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiwUGDx2lQuv-VZapOi9hFZkiIHtMg0NvXEuYdSJH0YnyK37vHdd8k1x9sQ9Q8v4h_MWPONTXSFdKZpwgeg8TCEUtxLTs_M3AcHWqE96OKyanBSzyDDT00WStsWCWJb921Ux1Lz31wk-kV4/s400/S3_69.png" width="400" /></a></div><br />
<br />
Veremos la siguiente salida:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi-RbZ_UoHxfUGpfkau3AhjOzJ7UGmiip-a-slHXVkJKyWoknrHWIBl8O7J9SWXrYR7wvVtgiUWD4RYmweaqizOCu8saljDxpU0Ul3EAwMCxvx42Y86Q-QRnDr5bpuUIHItaFos8YRTUsbB/s1600/S3_70.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="165" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi-RbZ_UoHxfUGpfkau3AhjOzJ7UGmiip-a-slHXVkJKyWoknrHWIBl8O7J9SWXrYR7wvVtgiUWD4RYmweaqizOCu8saljDxpU0Ul3EAwMCxvx42Y86Q-QRnDr5bpuUIHItaFos8YRTUsbB/s400/S3_70.png" width="400" /></a></div><br />
<br />
Como podemos ver, aunque no tenemos ni idea del nombre o la cantidad de parámetros que recibiremos en la petición, <span class="codigo">Struts 2</span> aún nos proporciona una forma sencilla y elegante de manejarlos. <br />
<br />
Hasta ahora hemos visto varias maneras de recibir parámetros desde nuestros formularios. Sin embargo otra parte muy importante del trabajo con estos últimos es el poder realizar algunas validaciones sobre los datos que recibimos. En la siguiente sección veremos cómo usar validaciones en nuestros formularios.<br />
<br />
<br />
<h2 class="titulo">5. Validaciones</h2>Una parte importante del trabajo con formularios es que podamos asegurarnos que la información de los mismos cumpla con ciertos requisitos para poder procesarlos. Por ejemplo que las direcciones de correo electrónico cumplan con el formato establecido, o que ciertos valores estén dentro de un rango valido, o simplemente que se estén proporcionando los campos obligatorios del formulario.<br />
<br />
Hacer esto con <span class="codigo">Struts 2</span> es muy simple, ya que proporciona varias maneras de realizar validaciones de forma automática para los datos de nuestros formularios.<br />
<br />
Las validaciones son realizadas por dos interceptores: "<span class="codigo">validation</span>" y "<span class="codigo">workfllow</span>". El primero realiza las validaciones y crea una lista de errores específicos para cada uno de los campos que no pase la validación. Posteriormente el interceptor "<span class="codigo">workflow</span>" verifica si hay errores de validación; si encuentra algún error regresa un resultado "<span class="codigo">input</span>", por lo que <span class="negritas">es necesario que proporcionemos un result con este nombre</span>.<br />
<br />
<span class="codigo">Struts 2</span> tiene básicamente tres formas de realizar validaciones:<br />
<br />
<ul><li>Validaciones mediante un archivo <span class="codigo">XML</span></li>
<li>Validaciones mediante anotaciones</li>
<li>Validaciones manuales</li>
</ul><br />
<br />
Primero comencemos creando la estructura con los elementos que usaremos en las validaciones:<br />
<br />
Lo primero que haremos será crear un nuevo directorio en las páginas web, llamado "<span class="codigo">validaciones</span>". Dentro de este directorio crearemos una nueva <span class="codigo">JSP</span> llamada "<span class="codigo">formulario.jsp</span>":<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiaBCtLlTXoDAPgTD-OQLoTHJcyFiTMZv1ploUD4Kx503ysjrkEtxSp78ErsVo__uuPpCgOQzyRDHPwHf-PkUclGlU9_ryMuhyphenhyphenOoNx5cJhsNMfxteQnsktz-RB-EkvTq_bCnlVLxu9GLQpf/s1600/S3_20.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiaBCtLlTXoDAPgTD-OQLoTHJcyFiTMZv1ploUD4Kx503ysjrkEtxSp78ErsVo__uuPpCgOQzyRDHPwHf-PkUclGlU9_ryMuhyphenhyphenOoNx5cJhsNMfxteQnsktz-RB-EkvTq_bCnlVLxu9GLQpf/s320/S3_20.png" width="221" /></a></div><br />
<br />
Dentro de esta página crearemos un formulario sencillo que solicitará al usuario algunos datos básicos como nombre, correo electrónico, teléfono, etc. No olviden colocar en la JSP la directiva taglib correspondiente para indicar que esta hará uso de las bibliotecas de etiquetas de Struts. El formulario quedará, por el momento, de la siguiente forma:<br />
<br />
<pre><code>
<s:form action="validacionDatos">
<s:textfield name="nombre" label="Nombe" />
<s:textfield name="username" label="Username" />
<s:password name="password" label="Password" />
<s:textfield name="email" label="Email" />
<s:textfield name="edad" label="Edad" />
<s:textfield name="telefono" label="Telefono" />
<s:submit value="Enviar" />
</s:form>
</code></pre><br />
<br />
Otra de las pequeñas ayudas que nos proporcionan las etiquetas de <span class="codigo">Struts 2</span> es que nos permiten indicar si cuáles campos son obligatorios, con lo que se le colocará una pequeña marca en la etiqueta para que el usuario sepa cuáles campos son los que está obligado a llenar. Esta marca es solo una ayuda visual, no realizará ningún proceso de validación del campo.<br />
<br />
Para agregar dicha marca e indicar que el campo será obligatorio, debemos colocar el valor de "<span class="codigo">true</span>" en el atributo "<span class="codigo">required</span>" de las etiquetas del formulario. En este caso todos los campos, con excepción del teléfono, serán requeridos:<br />
<br />
<pre><code>
<s:form action="validacionDatos">
<s:textfield name="nombre" label="Nombe" required="true" />
<s:textfield name="username" label="Username" required="true" />
<s:password name="password" label="Password" required="true" />
<s:textfield name="email" label="Email" required="true" />
<s:textfield name="edad" label="Edad" required="true" />
<s:textfield name="telefono" label="Telefono" />
<s:submit value="Enviar" />
</s:form>
</code></pre><br />
<br />
Con el código anterior obtenemos el siguiente formulario:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhm7bv0I4Su136WMdrPpI5r-CtRUYbAUzcLevD_EERrWcZOpKqHA2p9-Tfy-N1W_GxX4rPYVIJOfEzzlnKiOlGThZH6Wsvt_hJlOIBAAru2tDjiyv3yTUUWXenJa4mAuGSZ0KxbN-YAwTkj/s1600/S3_21.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="226" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhm7bv0I4Su136WMdrPpI5r-CtRUYbAUzcLevD_EERrWcZOpKqHA2p9-Tfy-N1W_GxX4rPYVIJOfEzzlnKiOlGThZH6Wsvt_hJlOIBAAru2tDjiyv3yTUUWXenJa4mAuGSZ0KxbN-YAwTkj/s400/S3_21.png" width="400" /></a></div><br />
<br />
Debido al tema ("<span class="codigo">theme</span>") que se usa por default para construir el formulario, los errores serán mostrados directamente sobre los campos que no pasen la validación. Sin embargo, si quisiéramos mostrar, de manera adicional, una lista con todos los errores del formulario, debemos agregar en nuestra <span class="codigo">JSP</span> la etiqueta "<span class="codigo"><s:fielderror /></span>". Solo la colocamos sobre el formulario, de la siguiente forma:<br />
<br />
<pre><code>
<s:fielderror />
<s:form <span class="codigo">Action</span>="validacionDatos">
<s:textfield name="nombre" label="Nombre" required="true" />
<s:textfield name="username" label="Username" required="true" />
<s:password name="password" label="Password" required="true" />
<s:textfield name="email" label="Email" required="true" />
<s:textfield name="edad" label="Edad" required="true" />
<s:textfield name="telefono" label="Telefono" />
<s:submit value="Enviar" />
</s:form>
</code></pre><br />
<br />
Ahora creamos el <span class="codigo">Action</span> que se encargará de procesar los datos anteriores. Para eso creamos una nueva clase llamada "<span class="codigo">ValidacionDatos</span>" en el paquete "<span class="codigo"><span class="codigo">Action</span>s</span>". Esta clase extenderá de "<span class="codigo">ActionSupport</span>" y tendrá las anotaciones que para este momento ya deben sernos más que familiares:<br />
<br />
<pre><code>
@Namespace(value="/validaciones")
@Action(value="validacionDatos", results={@Result(location="/validaciones/alta-exitosa.jsp")})
public class ValidacionDatos extends ActionSupport
{
}
</code></pre><br />
<br />
La clase "<span class="codigo">ValidacionDatos</span>" tendrá <span class="codigo">setters</span> y <span class="codigo">getters</span> para cada uno de los valores que recibiremos del formulario. En este caso los <span class="codigo">getters</span> son importantes por dos razones, la primera es que queremos que el formulario sea repoblado con los valores que el usuario ya habia introducido (los cuales solo pueden obtenerse a partir de los <span class="codigo">getters</span>); la segunda es que, por alguna razón que no alcanzo a entender, las validaciones se realizan sobre los <span class="codigo">getters</span> de las propiedades. La case "<span class="codigo">ValidacionDatos</span>" hasta ahora se ve de la siguiente forma:<br />
<br />
<pre><code>
public class ValidacionDatos extends ActionSupport
{
private String nombre;
private String username;
private String password;
private String email;
private int edad;
private String telefono;
public void setEdad(int edad)
{
this.edad = edad;
}
public void setEmail(String email)
{
this.email = email;
}
public void setNombre(String nombre)
{
this.nombre = nombre;
}
public void setPassword(String password)
{
this.password = password;
}
public void setTelefono(String telefono)
{
this.telefono = telefono;
}
public void setUsername(String username)
{
this.username = username;
}
public int getEdad()
{
return edad;
}
public String getEmail()
{
return email;
}
public String getNombre()
{
return nombre;
}
public String getPassword()
{
return password;
}
public String getTelefono()
{
return telefono;
}
public String getUsername()
{
return username;
}
}
</code></pre><br />
<br />
En su método "<span class="codigo">execute</span>" esta clase solo se encargará de imprimir los valores que se han recibido a través del formulario:<br />
<br />
<pre><code>
@Override
public String execute() throws Exception
{
System.out.println("nombre: " + nombre);
System.out.println("username: " + username);
System.out.println("password: " + password);
System.out.println("email: " + email);
System.out.println("edad: " + edad);
System.out.println("telefono: " + telefono);
return SUCCESS;
}
</code></pre><br />
<br />
Ahora crearemos la página que se encargará de mostrar el resultado del alta de usuario. Para esto crearemos una <span class="codigo">JSP</span> llamada "<span class="codigo">alta-exitosa.jsp</span>" en el directorio "<span class="codigo">validaciones</span>". Esta página será muy sencilla y solo mostrará el mensaje "<span class="codigo">Usuario dado de alta exitosamente.</span>"<br />
<br />
Finalmente, recordemos que si existe un error de validación el interceptor correspondiente regresará un resultado con valor "<span class="codigo">input</span>". Este resultado debe enviarnos a una página en la que se mostrará la lista de errores de datos de entrada. Normalmente esta página es el mismo formulario de entrada para que el usuario pueda corregir los datos que introdujo de forma incorrecta. Asi que modificaremos la anotación "<span class="codigo">@Action</span>" de la clase "<span class="codigo">ValidacionDatos</span>" para aregar un segundo resultado, que en caso de un error de entrada ("<span class="codigo">input</span>") nos regrese al formulario:<br />
<br />
<pre><code>
@Action(value = "validacionDatos", results =
{
@Result(location = "/validaciones/alta-exitosa.jsp"),
@Result(name="input", location="/validaciones/formulario.jsp")
})
</code></pre><br />
<br />
Como podemos ver, en este caso si es necesario indicar de forma explícita el nombre del result usando el atributo "<span class="codigo">name</span>".<br />
<br />
Al ejecutar nuestra aplicación y entrar en la siguiente dirección:<br />
<br />
<pre><code>
<a href="http://localhost:8080/formularios/validaciones/formulario.jsp">http://localhost:8080/formularios/validaciones/formulario.jsp</a>
</code></pre><br />
<br />
Deberemos ver el formulario que creamos hace un momento. Llenemos un par de campos para ver el resultado obtenido:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiW9XzgGtWUz0td9XEyVoyPyRAxAJeXr9BrnPU99JsdQ8V-xVP9Y4Vo9k_oH9v6IkQVXQ2P2GIHfJOSvAY6d0TS0yUSQLrZopztSB4g7iHikn5li54P8kCPEVSdbAkdS_AH-C_oCI77vI9C/s1600/S3_22.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="226" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiW9XzgGtWUz0td9XEyVoyPyRAxAJeXr9BrnPU99JsdQ8V-xVP9Y4Vo9k_oH9v6IkQVXQ2P2GIHfJOSvAY6d0TS0yUSQLrZopztSB4g7iHikn5li54P8kCPEVSdbAkdS_AH-C_oCI77vI9C/s400/S3_22.png" width="400" /></a></div><br />
<br />
Al enviar los datos del formulario deberemos ver la siguiente salida:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjqJpFuzT2-tnta49Ck4U18ghXMrZRrGYl-m8jtMMx16IxheexiChPPoG9QR9_t0suioLM6OxxSC1hccAY6VFK8jxTXIOBh52V8qMtCzvkIa5_FpiAwbYka-rur7saA9Tdf-ZWIdASJWTOY/s1600/S3_23.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="226" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjqJpFuzT2-tnta49Ck4U18ghXMrZRrGYl-m8jtMMx16IxheexiChPPoG9QR9_t0suioLM6OxxSC1hccAY6VFK8jxTXIOBh52V8qMtCzvkIa5_FpiAwbYka-rur7saA9Tdf-ZWIdASJWTOY/s400/S3_23.png" width="400" /></a></div><br />
<br />
Si analizamos la salida anterior veremos que esta no es correcta, ya que el usuario no ha proporcionado todos los datos requeridos, por lo que debería ver un mensaje de error. Solucionaremos esto agregando validaciones en los datos. Primero veremos cómo hacer validaciones usando archivos <span class="codigo">XML</span>.<br />
<br />
<br />
<h3 class="subtitulo">5.1 Validaciones usando archivos XML</h3>Para realizar validaciones usando archivos en <span class="codigo">XML</span> debemos crear un archivo por cada una de las clases que será validada. El nombre de este archivo debe seguir una convención muy sencilla:<br />
<br />
<pre><code>
<NombreDeNuestroAction>-validation.xml
</code></pre><br />
<br />
O sea, que debe tener el mismo nombre del <span class="codigo">Action</span> + la cadena "<span class="codigo">-validation</span>" y la extensión "<span class="codigo">.xml</span>". El archivo debe estar colocado en el mismo directorio que la clase <span class="codigo">Action</span> que validará.<br />
<br />
Dentro de este archivo se indicará cuáles campos serán validados y qué validaciones se le aplicarán. Cada una de estas validaciones tiene un parámetro obligatorio, que es el nombre del campo que validará. Adicionalmente pueden o no tener más parámetros. <br />
<br />
<span class="codigo">Struts 2</span> ya viene con algunas validaciones predeterminadas que podemos usar, aunque también tenemos la posibilidad de crear nuestras propias validaciones en caso de ser necesario. Las validaciones incuidas con <span class="codigo">Struts 2</span> se muestran en la siguiente tabla: <br />
<br />
<table><thead>
<tr><th>Validador</th><th>Descripción</th></tr>
</thead> <tbody>
<tr><td><span class="codigo">required</span></td><td>Verifica que el campo especificado no sea nulo.</td></tr>
<tr><td><span class="codigo">requiredstring</span></td><td>Verifica que un campo de tipo <span class="codigo">String</span> no sea nulo y que tenga un longitud mayor a <span class="codigo">0</span>.</td></tr>
<tr><td><span class="codigo">stringlength</span></td><td>Verifica que una cadena tenga una cierta longitud.</td></tr>
<tr><td><span class="codigo">int, long, y short</span></td><td>Verifica que el <span class="codigo">int</span>, <span class="codigo">long</span>, o <span class="codigo">short</span> especificado estén dentro de un rango determinado.</td></tr>
<tr><td><span class="codigo">double</span></td><td>Verifica que el valor <span class="codigo">double</span> especificado esté dentro de cierto rango.</td></tr>
<tr><td><span class="codigo">date</span></td><td>Verifica que la fecha proporcionada esté dentro del rango proporcionado. Por default se usa el formato <span class="codigo">Date.SHORT</span> para indicar las fechas.</td></tr>
<tr><td><span class="codigo">email</span></td><td>Verifica que el campo cumpla con el formato de una dirección de email válida</td></tr>
<tr><td><span class="codigo">url</span></td><td>Verifica que el campo cumpla con el formato de una <span class="codigo">URL</span> válida.</td></tr>
<tr><td><span class="codigo">visitor</span></td><td>Permite enviar la validación a las propiedades del objeto del <span class="codigo">Action</span> usando los propios de archivos de validación del objeto. Esto permite usar <span class="codigo">ModelDriven</span> y administrar las validaciones de los objetos de modelo en un solo lugar. </td></tr>
<tr><td><span class="codigo">conversion</span></td><td>Verifica si ocurre un error de conversión en el campo.</td></tr>
<tr><td><span class="codigo">expression</span></td><td>Realiza una validación basada en una expresión regular en <span class="codigo">OGNL</span>.</td></tr>
<tr><td><span class="codigo">fieldexpression</span></td><td>Valida un campo usando una expresión <span class="codigo">OGNL</span>.</td></tr>
<tr><td><span class="codigo">regex</span></td><td>Valida un campo usando una expresión regular.</td></tr>
</tbody> </table><br />
<br />
Sabiendo qué validadores podemos aplicar a los campos del formulario, pasemos a crear nuestro archivo de validación.<br />
<br />
Creamos en el paquete "<span class="codigo">actions</span>" un nuevo archivo <span class="codigo">XML</span> llamado "<span class="codigo">ValidacionDatos-validation.xml</span>":<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhS1dqS-XXj0ngFNHfkBKFVcB8hSgqn-ZsIENW-XDyh-Cvi8AD5LqGWKKOU9xCmsR8CdtpOoTwxHP-YxdQb5cxKnLyFDayofmmXyXdoV4lntF812pv7CRSc_9ey-pndwm36lIhEt0jiYwnt/s1600/S3_24.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhS1dqS-XXj0ngFNHfkBKFVcB8hSgqn-ZsIENW-XDyh-Cvi8AD5LqGWKKOU9xCmsR8CdtpOoTwxHP-YxdQb5cxKnLyFDayofmmXyXdoV4lntF812pv7CRSc_9ey-pndwm36lIhEt0jiYwnt/s320/S3_24.png" width="309" /></a></div><br />
<br />
Todas las validaciones deben estar contenidas dentro del elemento "<span class="codigo"><validators></span>":<br />
<br />
<pre><code>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.2//EN" "http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd">
<validators>
</validators>
</code></pre><br />
<br />
Lo siguiente es indicar cada campo que será validado usando el elemento "<span class="codigo"><field></span>", e indicamos el nombre del campo que queremos validar en su atributo "<span class="codigo">name</span>":<br />
<br />
<pre><code>
<field name="nombre">
</field>
</code></pre><br />
<br />
Posteriormente indicamos cada una de las validaciones que se aplicarán a ese campo, usando el elemento "<span class="codigo"><field-validator></span>" y su atributo "<span class="codigo">type</span>":<br />
<br />
<pre><code>
<field name="nombre">
<field-validator type="requiredstring">
</field-validator>
</field>
</code></pre><br />
<br />
Para agregar un parámetro a la validación usamos el elemento "<span class="codigo"><param></span>" y colocamos el nombre del parámetro en su atributo "<span class="codigo">name</span>". En este caso usaremos el parámetro "<span class="codigo">trim</span>" del validador "<span class="codigo">requiredstring</span>" para indicar que deseamos que se eliminen los espacios en blanco del inicio y fin de la cadena (en caso de existir) antes de que la validación sea aplicada:<br />
<br />
<pre><code>
<field-validator type="requiredstring">
<param name="trim">true</param>
</field-validator>
</code></pre><br />
<br />
Para terminar, debemos agregar un mensaje que será mostrado en caso de que la validación no sea exitosa. Para esto usamos el elemento "<span class="codigo"><message></span>":<br />
<br />
<pre><code>
<field-validator type="requiredstring">
<param name="trim">true</param>
<message>El nombre del usuario es un campo obligatorio.</message>
</field-validator>
</code></pre><br />
<br />
Y esto es todo, con esto el campo "<span class="codigo">nombre</span>" del formulario ha quedado validado.<br />
<br />
Si necesitáramos agregar más validaciones a este campo, solo bastaría con agregar un segundo elemento "<span class="codigo"><field-validator></span>". Agregamos un validador de tipo "<span class="codigo">stringlength</span>" para asegurarnos que el nombre que introduzca el usuario tenga una longitud mínima de <span class="codigo">4</span> caracteres, y una longitud máxima de <span class="codigo">12</span>. El proceso es básicamente el mismo por lo que no lo explicaré, pero al final la validación queda de la siguiente forma:<br />
<br />
<pre><code>
<field-validator type="stringlength">
<param name="trim">true</param>
<param name="minLength">4</param>
<param name="maxLength">12</param>
<message>El nombre del usuario debe tener entre 4 y 12 caracteres</message>
</field-validator>
</code></pre><br />
<br />
Hasta ahora nuestro archivo de validaciones se ve de la siguiente forma:<br />
<br />
<pre><code>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.2//EN" "http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd">
<validators>
<field name="nombre">
<field-validator type="requiredstring">
<param name="trim">true</param>
<message>El nombre del usuario es un campo obligatorio.</message>
</field-validator>
<field-validator type="stringlength">
<param name="trim">true</param>
<param name="minLength">4</param>
<param name="maxLength">12</param>
<message>El nombre del usuario debe tener entre 4 y 12 caracteres</message>
</field-validator>
</field>
</validators>
</code></pre><br />
<br />
El proceso es básicamente el mismo para todos los campos del formulario que vamos a validar. No explicaré cada una de las validaciones de los campos porque creo que la idea se entiende bastante en este momento, pero el archivo final de validaciones queda de la siguiente forma:<br />
<br />
<pre><code>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.2//EN" "http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd">
<validators>
<field name="nombre">
<field-validator type="requiredstring">
<param name="trim">true</param>
<message>El nombre del usuario es un campo obligatorio.</message>
</field-validator>
<field-validator type="stringlength">
<param name="trim">true</param>
<param name="minLength">4</param>
<param name="maxLength">12</param>
<message>El nombre del usuario debe tener entre 4 y 12 caracteres</message>
</field-validator>
</field>
<field name="username">
<field-validator type="requiredstring">
<param name="trim">true</param>
<message>El username es un campo obligatorio.</message>
</field-validator>
<field-validator type="stringlength">
<param name="trim">true</param>
<param name="minLength">6</param>
<message>El username debe tener al menos 6 caracteres</message>
</field-validator>
</field>
<field name="password">
<field-validator type="requiredstring">
<param name="trim">true</param>
<message>La contraseña es un campo obligatorio.</message>
</field-validator>
<field-validator type="stringlength">
<param name="trim">true</param>
<param name="minLength">6</param>
<param name="maxLength">8</param>
<message>La contraseña debe tener entre 6 y 8 caracteres</message>
</field-validator>
<field-validator type="regex">
<param name="expression">^\w*(?=\w*\d)(?=\w*[a-z])(?=\w*[A-Z])\w*$</param>
<message>La contraseña debe ser alfanumérica, debe tener al menos una letra mayúscula, una letra minúscula, y al menos un número.</message>
</field-validator>
</field>
<field name="email">
<field-validator type="requiredstring">
<param name="trim">true</param>
<message>El correo electrónico es un campo obligatorio.</message>
</field-validator>
<field-validator type="email">
<message>El correo electrónico está en un formato inválido.</message>
</field-validator>
</field>
<field name="edad">
<field-validator type="required">
<message>La edad es un campo obligatorio.</message>
</field-validator>
<field-validator type="conversion">
<message>La edad debe contener solo números enteros.</message>
</field-validator>
<field-validator type="int">
<param name="min">0</param>
<param name="max">200</param>
<message>La edad proporcionada no está dentro del rango permitido.</message>
</field-validator>
</field>
</validators>
</code></pre><br />
<br />
Ahora que todo está listo podemos ejecutar nuestra aplicación y entrar a la siguiente dirección:<br />
<br />
<pre><code>
<a href="http://localhost:8080/formularios/validaciones/formulario.jsp">http://localhost:8080/formularios/validaciones/formulario.jsp</a>
</code></pre><br />
<br />
Nuevamente veremos nuestro formulario anterior. Si no colocamos ningún valor en ningún campo y simplemente presionamos el botón "<span class="codigo">Enviar</span>" veremos la siguiente lista de errores:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5oyQYmVsIfmQtgzV_MQIcLU2Lv78uk2ly8VbDkr0Lq89ps47KoQ_D5hyphenhyphenOXqoHm8WWCmnGfP31CPhSBDDbxyfnagzylEGZ5O-rQaLEQk6aWq_NBYD8Thz0jGpdyB6ywjJyG_h-uu2-7YX_/s1600/S3_25.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="288" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5oyQYmVsIfmQtgzV_MQIcLU2Lv78uk2ly8VbDkr0Lq89ps47KoQ_D5hyphenhyphenOXqoHm8WWCmnGfP31CPhSBDDbxyfnagzylEGZ5O-rQaLEQk6aWq_NBYD8Thz0jGpdyB6ywjJyG_h-uu2-7YX_/s400/S3_25.png" width="400" /></a></div><br />
<br />
Podemos ver que estamos obteniendo los errores que definimos en el archivo de validaciones, con los mensajes correspondientes. Si observamos el código fuente de la pagina generada veremos que cada uno de los errores marcados está colocado en un elemento que tiene como clase CSS "<span class="codigo">errorMessage</span>", por lo que podemos personalizar la forma en la que se muestran los errores de validación de la aplicación a través de una hoja de estilos.<br />
<br />
Si ahora colocamos algunos valores incorrectos en los campos del formulario:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi3yYXg7DBpqJqQwAI_jsyGn3jJKrNPZZ5wb0Y6ZDDw5rJhqEAeFEtaBBfOOS_HO5PmjBaihPcB1Buy_HrNXDD5EPGnig5mlyc5KNWOVUdrPpuJI9VAGZvZIGKF8Z59vgHtQ6VHZxu5lCzX/s1600/S3_26.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="158" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi3yYXg7DBpqJqQwAI_jsyGn3jJKrNPZZ5wb0Y6ZDDw5rJhqEAeFEtaBBfOOS_HO5PmjBaihPcB1Buy_HrNXDD5EPGnig5mlyc5KNWOVUdrPpuJI9VAGZvZIGKF8Z59vgHtQ6VHZxu5lCzX/s400/S3_26.png" width="400" /></a></div><br />
<br />
Obtendremos una lista distinta de errores:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiDQ_c9MrkVJqR9jrZhs27EYXzV3iSUy4OQa3qcZqS_WNXEKMK8cWNREygQETtLWIdjSxQLAQey9qjneuqlbnYIXPui3CEQP0WriVTO9PCF5O79i2peuuAlTHaBECfr5_so1K72w4ukaJwB/s1600/S3_27.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="251" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiDQ_c9MrkVJqR9jrZhs27EYXzV3iSUy4OQa3qcZqS_WNXEKMK8cWNREygQETtLWIdjSxQLAQey9qjneuqlbnYIXPui3CEQP0WriVTO9PCF5O79i2peuuAlTHaBECfr5_so1K72w4ukaJwB/s400/S3_27.png" width="400" /></a></div><br />
<br />
Si ahora colocamos datos correctos:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgcbgs1exksTGFoEL_DRPm6qHMcm5-CGXy6bJcJAy_DvCaRdox55TYLajD77X4M06A1rRivahc_hZCQ8FkBq7EsqJhGM-T8_SrXRsI37KtqNKYz1OGgLZntYXSca8Q2OC_g0jm7kjV9UBX6/s1600/S3_28.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="157" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgcbgs1exksTGFoEL_DRPm6qHMcm5-CGXy6bJcJAy_DvCaRdox55TYLajD77X4M06A1rRivahc_hZCQ8FkBq7EsqJhGM-T8_SrXRsI37KtqNKYz1OGgLZntYXSca8Q2OC_g0jm7kjV9UBX6/s400/S3_28.png" width="400" /></a></div><br />
<br />
Veremos la página correspondiente:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhIziZy-NcaDsdLomwcd3F7PaTGN10JRHhfMZssvybyCI4-vqMFZ1YO7JHvt0ntDrmxYAj2yvOtJX4HUoSxJwFM6543c5d1P5P8HJ62M0v2a1qa2Ou_cEQNOOjfu1Ipi3TbOBNeeykwLWfr/s1600/S3_29.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="157" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhIziZy-NcaDsdLomwcd3F7PaTGN10JRHhfMZssvybyCI4-vqMFZ1YO7JHvt0ntDrmxYAj2yvOtJX4HUoSxJwFM6543c5d1P5P8HJ62M0v2a1qa2Ou_cEQNOOjfu1Ipi3TbOBNeeykwLWfr/s400/S3_29.png" width="400" /></a></div><br />
<br />
Por lo que los datos han sido validados correctamente.<br />
<br />
A pesar de que la mayoría de las veces realizaremos validaciones como las anteriores, o sea validaciones sobre los campos de un formulario, algunas veces será necesario realizar validaciones que no son sobre los campos (llamadas <span class="negritas">validaciones planes</span>).<br />
<br />
Las validaciones planas son un tipo especial de validación que no está atada a un campo específico, como la validación de expresiones. Por ejemplo, agreguemos una validación extra que verifique que el "<span class="codigo">nombre</span>" y el "<span class="codigo">username</span>" no sean iguales. <br />
<br />
Para agregar una validación plana en vez de usar el elemento "<span class="codigo"><field></span>" usamos el elemento "<span class="codigo"><validator></span>", que es muy similar a "<span class="codigo"><field-validator></span>", indicamos usando su atributo "<span class="codigo">type</span>" el tipo de validación que se realizará; si la validación necesita algún parámetro lo agregamos con el elemento "<span class="codigo"><param></span>"; el mensaje que se mostrará en caso de que la validación no pase lo colocamos con el elemento "<span class="codigo"><message></span>".<br />
<br />
La validación para verificar que el nombre del <span class="codigo">usuario</span> y el <span class="codigo">username</span> no sean iguales queda de la siguiente forma (pueden colocarla después de todas las validaciones "<span class="codigo">field</span>"):<br />
<br />
<pre><code>
<validator type="expression">
<param name="expression">!nombre.equals(username)</param>
<message>El nombre de usuario y el username no deben ser iguales.</message>
</validator>
</code></pre><br />
<br />
Como estas validaciones no son realizadas sobre ningún campo, sus mensajes de error no se mostrarán usando la etiqueta "<span class="codigo"><s:fielderror /></span>", si queremos verlos en la <span class="codigo">JSP</span> debemos usar la etiqueta "<span class="codigo"><s:actionerror /></span>":<br />
<br />
<pre><code>
<s:fielderror />
<s:actionerror />
</code></pre><br />
<br />
Ahora, si volvemos a ejecutar la aplicación e introducimos el mismo nombre de usuario y contraseña:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi45nToFe3ltNEsH0ps4ngPEVwQ1lYz6I10xLbj5FyuFFag7K01T9j42dYiURAU1Kx8Hm2qZH_4Hnr2V0s25MDJ1kotfYGgrPFWtWRr-AVIwbmtJ7J-p9iIwOvbp9EO1nD_rF1WYvEME9G2/s1600/S3_30.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="157" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi45nToFe3ltNEsH0ps4ngPEVwQ1lYz6I10xLbj5FyuFFag7K01T9j42dYiURAU1Kx8Hm2qZH_4Hnr2V0s25MDJ1kotfYGgrPFWtWRr-AVIwbmtJ7J-p9iIwOvbp9EO1nD_rF1WYvEME9G2/s400/S3_30.png" width="400" /></a></div><br />
<br />
Y presionamos el botón enviar obtendremos el siguiente resultado:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhYFMxUlFtVXc2GXoZ2zEz_50Bb_I_yyu7_oHXIozuedmmYyM6yW1tQapPhhRL850cDThpg1ajQRymUMnlX_oJclTmf1hoU18Shbk4Qj45NrAO3uE4UEdA0eY7b_8glrofx0nAnfEDKuV-C/s1600/S3_31.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="157" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhYFMxUlFtVXc2GXoZ2zEz_50Bb_I_yyu7_oHXIozuedmmYyM6yW1tQapPhhRL850cDThpg1ajQRymUMnlX_oJclTmf1hoU18Shbk4Qj45NrAO3uE4UEdA0eY7b_8glrofx0nAnfEDKuV-C/s400/S3_31.png" width="400" /></a></div><br />
<br />
Con lo que podemos comprobar que estas validaciones también funcionan correctamente.<br />
<br />
Ahora que vimos cómo realizar validaciones usando archivos <span class="codigo">XML</span> veamos cómo realizar esto mismo usando anotaciones.<br />
<br />
<br />
<h3 class="subtitulo">5.2 Validaciones usando anotaciones</h3>Realizar validaciones de los datos de un formulario usando anotaciones es un proceso muy simple. Modificaremos un poco la clase "<span class="codigo">ValidacionDatos</span>" que creamos hace un momento (de hecho yo crearé una clase aparte para no modificar el código que ya teníamos).<br />
<br />
Las anotaciones que podemos usar para realizar las validaciones se encuentran en el paquete "<span class="codigo">com.opensymphony.xwork2.validator.annotations</span>", y tenemos las siguientes validaciones disponibles:<br />
<br />
<ul><li><span class="codigo">@ConversionErrorFieldValidator</span></li>
<li><span class="codigo">@DateRangeFieldValidator</span></li>
<li><span class="codigo">@DoubleRangeFieldValidator</span></li>
<li><span class="codigo">@EmailValidator</span></li>
<li><span class="codigo">@ExpressionValidator</span></li>
<li><span class="codigo">@FieldExpressionValidator</span></li>
<li><span class="codigo">@IntRangeFieldValidator</span></li>
<li><span class="codigo">@RegexFieldValidator</span></li>
<li><span class="codigo">@RequiredFieldValidator</span></li>
<li><span class="codigo">@RequiredStringValidator</span></li>
<li><span class="codigo">@StringLengthFieldValidator</span></li>
<li><span class="codigo">@UrlValidator</span></li>
<li><span class="codigo">@VisitorFieldValidator</span></li>
</ul><br />
<br />
Como podemos ver, existe una anotación por cada una de las validaciones que se tienen cuando trabajamos con los archivos <span class="codigo">XML</span>; la explicación para cada una de las validaciones también es la misma que para el caso anterior.<br />
<br />
Esta anotación puede ser colocada tanto en el <span class="codigo">setter</span> como en el <span class="codigo">getter</span> de la propiedad que queremos validar. También podemos usar varias validaciones para una misma propiedad.<br />
<br />
Veamos un primer ejemplo validando el campo "<span class="codigo">nombre</span>". En la clase "<span class="codigo">ValidacionDatos</span>" primero verificaremos que el nombre del usuario no esté vacio cuando se reciba del fomulario, para esto usaremos la anotación "<span class="codigo">@RequiredStringValidator</span>", yo la pondré en el <span class="codigo">setter</span> de la propiedad:<br />
<br />
<pre><code>
@RequiredStringValidator
public void setNombre(String nombre)
{
this.nombre = nombre;
}
</code></pre><br />
<br />
Todas las anotaciones de validaciones proporcionan una serie de atributos que nos permiten configurar el comportamiento de la validación. Casi en todos los casos lo único que tendremos que configurar es el mensaje que se mostrará en caso de que la validación no sea correcta. Para eso usamos el atributo "<span class="codigo">message</span>":<br />
<br />
<pre><code>
@RequiredStringValidator(message = "El nombre de usuario es un campo obligatorio.")
public void setNombre(String nombre)
{
this.nombre = nombre;
}
</code></pre><br />
<br />
También validaremos que el nombre tenga una cierta longitud, o sea que sea mayor a cierto número de caracteres y menor a otro cierto número. Para eso usamos la anotación la anotación "<span class="codigo">@StringLengthFieldValidator</span>" que colocamos justo debajo de la anotación anterior:<br />
<br />
<pre><code>
@RequiredStringValidator(message = "El nombre de usuario es un campo obligatorio.")
@StringLengthFieldValidator(minLength="4", maxLength="12", message="El nombre del usuario debe tener entre 4 y 12 caracteres")
public void setNombre(String nombre)
{
this.nombre = nombre;
}
</code></pre><br />
<br />
Haremos lo mismo para cada uno de los campos que necesitemos validar. Nuevamente, no explicaré cada una de las anotaciones ya que son bastante intuitivas, pero al final quedan de la siguiente forma:<br />
<br />
<pre><code>
@RequiredFieldValidator(message="La edad es un campo obligatorio.")
@ConversionErrorFieldValidator(message="La edad debe contener solo números enteros.")
@IntRangeFieldValidator(min="0", max="200", message="La edad proporcionada no está dentro del rango permitido.")
public void setEdad(int edad)
{
this.edad = edad;
}
@RequiredStringValidator(message="El correo electrónico es un campo obligatorio.")
@EmailValidator(message="El correo electrónico está en un formato inválido.")
public void setEmail(String email)
{
this.email = email;
}
@RequiredStringValidator(message = "El nombre de usuario es un campo obligatorio.")
@StringLengthFieldValidator(minLength = "4", maxLength = "12", message = "El nombre del usuario debe tener entre 4 y 12 caracteres")
public void setNombre(String nombre)
{
this.nombre = nombre;
}
@RequiredStringValidator(message="La contraseña es un campo obligatorio.")
@StringLengthFieldValidator(minLength="6", maxLength="8", message="La contraseña debe tener entre 6 y 8 caracteres")
@RegexFieldValidator(expression="^\\w*(?=\\w*\\d)(?=\\w*[a-z])(?=\\w*[A-Z])\\w*$", message="La contraseña debe ser alfanumérica, debe tener al menos una letra mayúscula, una letra minúscula, y al menos un número.")
public void setPassword(String password)
{
this.password = password;
}
public void setTelefono(String telefono)
{
this.telefono = telefono;
}
@RequiredStringValidator(message="El username es un campo obligatorio.")
@StringLengthFieldValidator(minLength="6", message="El username debe tener al menos 6 caracteres")
public void setUsername(String username)
{
this.username = username;
}
</code></pre><br />
<br />
Probemos nuevamente que todo funcione correctamente. Al ejecutar la aplicación y entrar en nuestro formulario, en la siguiente dirección:<br />
<br />
<pre><code>
<a href="http://localhost:8080/formularios/validaciones/formulario.jsp">http://localhost:8080/formularios/validaciones/formulario.jsp</a>
</code></pre><br />
<br />
(Bueno, de hecho mi dirección es un poco distinto porque cree el contenido en una página alterna ^^) Debemos ver el formulario que ya teníamos anteriormente:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhx58RxIb4EA9snRgZF8X0EkRzpN3UwPiFLXO2-9-bqArTnmg5VYLB6MhcDIhlAZ9eKdXHnRDi9CPDmz-5JmX32VkYSWcmH292NVqI8AnosyPC-cMJBUR1FJuMMKaNeDP9jWeAAtMMASQt9/s1600/S3_32.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="130" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhx58RxIb4EA9snRgZF8X0EkRzpN3UwPiFLXO2-9-bqArTnmg5VYLB6MhcDIhlAZ9eKdXHnRDi9CPDmz-5JmX32VkYSWcmH292NVqI8AnosyPC-cMJBUR1FJuMMKaNeDP9jWeAAtMMASQt9/s400/S3_32.png" width="400" /></a></div><br />
<br />
Al hacer clic en el botón "<span class="codigo">Enviar</span>", con los campos vacios, obtendremos la siguiente salida:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgSj0nft3iSiU0y2D_HNu0NzfO6Ty-r3EnEoCLW_fBMT7sYsf7PVkY6yIm04zLnz-LbpyKlgEgBFeph1k4RdEH5evtx7QMTpoBruqyXO8SNYeHEyJhUHfj173mVZMvezbL66oezmhQPJtmh/s1600/S3_33.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="231" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgSj0nft3iSiU0y2D_HNu0NzfO6Ty-r3EnEoCLW_fBMT7sYsf7PVkY6yIm04zLnz-LbpyKlgEgBFeph1k4RdEH5evtx7QMTpoBruqyXO8SNYeHEyJhUHfj173mVZMvezbL66oezmhQPJtmh/s400/S3_33.png" width="400" /></a></div><br />
<br />
Podemos ver que, hasta el momento, las validaciones han funcionado correctamente. Si colocamos algunos datos de forma equivocada:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiGaS-GdKaIZCc3Ybi3OSVb-DqLsJ-KWrxCdbNwZmliodkZAsG3rZN76dtDWX_pbP9JgnI8gNoAasnYW9vse8_FzeQ-zv91T8iIMD95hcvoKHNJerD79bromCZNojY0i0IZuGaSe7xlpVTN/s1600/S3_34.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="138" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiGaS-GdKaIZCc3Ybi3OSVb-DqLsJ-KWrxCdbNwZmliodkZAsG3rZN76dtDWX_pbP9JgnI8gNoAasnYW9vse8_FzeQ-zv91T8iIMD95hcvoKHNJerD79bromCZNojY0i0IZuGaSe7xlpVTN/s400/S3_34.png" width="400" /></a></div><br />
<br />
Obtendremos la siguiente salida:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgih-_jmzBXz31Ap1CYxj01frZFxIfIwegZwMI55_7LhGGeDJSBWE0d0oBNFGCOJbptICGk3KOUERaMaJXbmNOv9nGuGi2iWLx5rB6vfez11XfNFwAFYRT8bWETNKA9DKjiMjIIUajRt05O/s1600/S3_35.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="191" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgih-_jmzBXz31Ap1CYxj01frZFxIfIwegZwMI55_7LhGGeDJSBWE0d0oBNFGCOJbptICGk3KOUERaMaJXbmNOv9nGuGi2iWLx5rB6vfez11XfNFwAFYRT8bWETNKA9DKjiMjIIUajRt05O/s400/S3_35.png" width="400" /></a></div><br />
<br />
Si ahora, colocamos los datos de forma correcta:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiMKZ6oROIwFyyx0rmzrI0VnDOJqtL1HHTLQt8wzyZDh1MSp9nvx-hyzgMMsRwgpsxvXKRMFjMUeWFPOLVu3Ks_YrIqBheFVFtIPGPqtBN5RHWeKP-dsBDbeU9IJFkFbFjU_HVB5wpTOWV_/s1600/S3_36.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="191" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiMKZ6oROIwFyyx0rmzrI0VnDOJqtL1HHTLQt8wzyZDh1MSp9nvx-hyzgMMsRwgpsxvXKRMFjMUeWFPOLVu3Ks_YrIqBheFVFtIPGPqtBN5RHWeKP-dsBDbeU9IJFkFbFjU_HVB5wpTOWV_/s400/S3_36.png" width="400" /></a></div><br />
<br />
Obtendremos la salida que estamos esperando:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiPgAOlkU14X2B059mvtA9yc4LBmcdCWc_U4srNsMD_SMabhjY-ekfQBtj4QMET2mU5UCujoWxyOudLTFS2tLIEfr7vZ0P9xl0BQvig4r8V_teoIjMKsSD9bkNT6j054QCMaW9ZCi9-M-F8/s1600/S3_37.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="191" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiPgAOlkU14X2B059mvtA9yc4LBmcdCWc_U4srNsMD_SMabhjY-ekfQBtj4QMET2mU5UCujoWxyOudLTFS2tLIEfr7vZ0P9xl0BQvig4r8V_teoIjMKsSD9bkNT6j054QCMaW9ZCi9-M-F8/s400/S3_37.png" width="400" /></a></div><br />
<br />
Como podrán imaginar, todas las validaciones anteriores son validaciones de campos. Usando anotaciones también podemos realizar validaciones planas (las validaciones que no están relacionadas con algún campo del formulario). Solo que en este caso las validaciones planas se colocan directamente en el método "<span class="codigo">execute</span>" del <span class="codigo">Action</span>, usando una anotación especial: "<span class="codigo">@Validations</span>". En los atributos de esta anotación podemos indicar cada una de las validaciones que se necesiten en la aplicación (campos requeridos, emails, urls, longitud de las cadenas, rango de enteros, fechas, etc.). En nuestro caso lo que nos interesa es realizar una validación sobre una expresión. Para esto usamos el atributo "<span class="codigo">expressions</span>" de la anotación "<span class="codigo">@Validations</span>":<br />
<br />
<pre><code>
@Override
@Validations(expressions={})
public String execute() throws Exception
</code></pre><br />
<br />
En este atributo colocamos, usando la anotación "<span class="codigo">@ExpressionValidator</span>", la expresión que será validada, en este caso queremos asegurarnos de que el nombre del usuario y su username no son iguales, por lo que la validación queda de la siguiente forma:<br />
<br />
<pre><code>
@ExpressionValidator(expression="!nombre.equals(username)", message="El nombre de usuario y el username no deben ser iguales.")
</code></pre><br />
<br />
La anotación completa queda de la siguiente forma:<br />
<br />
<pre><code>
@Override
@Validations(expressions =
{
@ExpressionValidator(expression = "!nombre.equals(username)", message = "El nombre de usuario y el username no deben ser iguales.")
})
public String execute() throws Exception
</code></pre><br />
<br />
Cuando ejecutemos nuevamente la aplicación, poniendo el mismo nombre de usuario y contraseña:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgo_d3xvUUdxbzg07she25ozrPg_Tk9kqg5ZhTP66Ottb1BqnHWQCOjt1yOjzjsx5TAPiChmnHSEl8R4AOx0mC4xcV3GK1LCyd0D9J3dRyK3RRS4kYVPQt0dtGJseWj4GJ2mQyTZxcKnans/s1600/S3_38.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="191" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgo_d3xvUUdxbzg07she25ozrPg_Tk9kqg5ZhTP66Ottb1BqnHWQCOjt1yOjzjsx5TAPiChmnHSEl8R4AOx0mC4xcV3GK1LCyd0D9J3dRyK3RRS4kYVPQt0dtGJseWj4GJ2mQyTZxcKnans/s400/S3_38.png" width="400" /></a></div><br />
<br />
Obtendremos el mensaje correspondiente de error:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgWnzl_Z_IEWuxQVsU9cs_NeQnwn3yJqWueKUgBq6wmEnYiw04kDMiaRbWDUZHfcY5wma_0sd3S5OtC-waOxcmIWVc-7JIX2O80uvfIE2paRcaIs-RQwyoTwlYsj6wVqal26xHY7tiBC442/s1600/S3_39.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="191" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgWnzl_Z_IEWuxQVsU9cs_NeQnwn3yJqWueKUgBq6wmEnYiw04kDMiaRbWDUZHfcY5wma_0sd3S5OtC-waOxcmIWVc-7JIX2O80uvfIE2paRcaIs-RQwyoTwlYsj6wVqal26xHY7tiBC442/s400/S3_39.png" width="400" /></a></div><br />
<br />
Cuando coloquemos los datos correctos deberíamos dejar de ver mensajes de error.<br />
<br />
Los dos tipos de validaciones que hemos visto nos permiten verificar que los datos que proporcionemos sean correctos, siempre y cuando conozcamos de antemano los valores, los rangos, o las reglas que los campos del formulario pueden tener pero ¿qué pasa si no conocemos estos valores? por ejemplo, en el caso de que debamos comparar un valor contra un registro de la base de datos o contra algún web service o alguna cosa más sofisticada, las validaciones anteriores nos servirán de poco.<br />
<br />
Afortunadamente, para esos casos, <span class="codigo">Struts 2</span> nos permite realizar un tercer tipo de validación: las validaciones manuales.<br />
<br />
<br />
<h3 class="subtitulo">5.3 Validaciones Manuales</h3>Ya sé qué es lo que deben estar pensando: que las validaciones manuales son las que estamos acostumbrados a realizar, que no es un mecanismo automático que nos ayude a simplificar nuestro trabajo de validación de datos. Si es así, no están tan equivocados ^_^; bueno, más o menos.<br />
<br />
Las validaciones manuales son aquellas que son tan particulares a nuestro proceso de negocio que estas no se pueden tomar de alguna plantilla, como los ejemplos que mencioné anteriormente.<br />
<br />
Este tipo de validación, aunque las tenemos que realizar nosotros mismos, sigue siendo administrado por el framework. Para ello nos proporciona una interface llamada "<span class="codigo">Validateable</span>", que proporciona únicamente un método:<br />
<br />
<pre><code>
public void validate();
</code></pre><br />
<br />
Afortunadamente para nosotros, la clase "<span class="codigo">ActionSupport</span>" implementa esta interface, por lo que lo unico que tenemos que hacer es sobre-escribirlo en nuestros <span class="codigo">Action</span>s y, ayudados de algunos métodos auxiliares de la clase "<span class="codigo">ActionSupport</span>", enviar los errores correspondientes en caso de que estos existan.<br />
<br />
Para poder indicar si existe algún error en las validaciones nos ayudamos del método "<span class="codigo">addFieldError</span>", en caso de que exista un error de la validación de un campo, y de "<span class="codigo">addActionError</span>", en caso de existir un error que no esté relacionado con un campo.<br />
<br />
"<span class="codigo">addFieldError</span>" recibe dos parámetros, el primero es el nombre del campo que generó el error y el segundo es la descripción del error. "<span class="codigo">addActionError</span>" recibe un solo parámetro que es la descripción del error.<br />
<br />
Como se podrán imaginar, si existe algún error en un campo este se mostrará en el campo correspondiente del formulario y en la etiqueta "<span class="codigo"><s:fielderror /></span>". Si existe un error de otro tipo este se mostrará en la etiqueta "<span class="codigo"><s:actionerror /></span>".<br />
<br />
Ahora que conocemos la teoría sobre las validaciones manuales pasemos verlas en la práctica. Para esto modificaremos las validaciones que ya tenemos en nuestra clase.<br />
<br />
Lo primero que debemos hacer es sobre-escribir el método "<span class="codigo">validate</span>":<br />
<br />
<pre><code>
@Override
public void validate()
{
}
</code></pre><br />
<br />
Las validaciones que realizaremos serán muy simples: si el username es "<span class="codigo">programadorjava</span>" (supongamos que este fue un nombre que se trajo de la base de datos, para comprobar que no haya nombres repetidos) o el correo es "<span class="codigo"><a href="mailto:programadorjavablog@gmail.com">programadorjavablog@gmail.com</a></span>" entonces no se le permitirá al usuario completar el registro:<br />
<br />
<pre><code>
@Override
public void validate()
{
if("programadorjava".equals(username))
{
addFieldError("username", "El username seleccionado ya se encuentra ocupado, por favor seleccione otro.");
}
if("programadorjavablog@gmail.com".equals(email))
{
addFieldError("email", "El correo electrónico proporcionado ya ha sido registrado anteriormente.");
}
}
</code></pre><br />
<br />
El framework detectará de forma automática si hemos agregado algo a los campos de error, si es así regresará esta lista de errores a nuestro resultado "<span class="codigo">input</span>", de lo contrario continuará con la ejecución del método "<span class="codigo">execute</span>".<br />
<br />
Si ejecutamos la aplicación y entramos a nuestro formulario, proporcionando alguno de los datos reservados y que son validados dentro del método "<span class="codigo">validate</span>":<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiMiwLcWf1_kGuomm8u_4f_OXupyaORFEyyXAaER806BkHYQFRPzJ54a6AusOQvITy3vSIRaHNbkDbGSzDfNZu-UT7Ks_azkruwWUafEEVtMmL5oCrk-GXPWNaTx0CrhDpDPqPhvPrA9TcN/s1600/S3_40.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="202" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiMiwLcWf1_kGuomm8u_4f_OXupyaORFEyyXAaER806BkHYQFRPzJ54a6AusOQvITy3vSIRaHNbkDbGSzDfNZu-UT7Ks_azkruwWUafEEVtMmL5oCrk-GXPWNaTx0CrhDpDPqPhvPrA9TcN/s400/S3_40.png" width="400" /></a></div><br />
<br />
Veremos los mensajes de error que hemos establecido:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjjJTYV6LX48dmlL1aO7v2DpoZD0mcHAEgL-t49OTKXwryyAdsY3j85jp_q5EfzRdOcWS9ZeK9CGsMnQPQnsrhqX1VP7OIg-ZHQz9vc6b6-JIaEoOixG3U0xb2NoKAcGrxZlllpwvtcsK_A/s1600/S3_41.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="202" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjjJTYV6LX48dmlL1aO7v2DpoZD0mcHAEgL-t49OTKXwryyAdsY3j85jp_q5EfzRdOcWS9ZeK9CGsMnQPQnsrhqX1VP7OIg-ZHQz9vc6b6-JIaEoOixG3U0xb2NoKAcGrxZlllpwvtcsK_A/s400/S3_41.png" width="400" /></a></div><br />
<br />
Podemos comprobar nuevamente que las validaciones han funcionado correctamente ^_^.<br />
<br />
Como vemos, realizar validaciones usando <span class="codigo">Struts 2</span> es muy sencillo y puede ahorrarnos bastante tiempo que podemos dedicar a mejorar otros aspectos de nuestra aplicación.<br />
<br />
Ahora veremos cómo realizar otro de los trabajos que normalmente hacemos al hacer uso de formularios, la carga de archivos desde el cliente al servidor:<br />
<br />
<br />
<h2 class="titulo" name="uploadArchivos">6. Carga de archivos</h2>Hacer carga o upload de archivos desde un cliente hacia nuestro servidor, ya sea para almacenarlos o para procesarlos, es una operación que no siempre es sencilla de realizar. Sin embargo <span class="codigo">Struts 2</span> cuenta con una manera que hace que lograr esto sea tan sencillo como el resto de las cosas que hasta ahora hemos aprendido.<br />
<br />
<span class="codigo">Struts 2</span> proporciona el soporte para la carga de archivos conforme a la especificación de <span class="codigo">HTML</span>, esto nos permite subir uno o varios archivos desde el cliente al servidor.<br />
<br />
Cuando un archivo es cargado, este será almacenado en un directorio temporal por el interceptor correspondiente (<span class="codigo">fileUpload</span>). El archivo deberá entonces ser procesado o movido a otra ubicación, por nuestro <span class="codigo">Action</span>, ya que al terminar la petición el interceptor se encargará de eliminar este archivo temporal.<br />
<br />
Veamos cómo realizar la carga de archivos con un ejemplo.<br />
<br />
Lo primero que haremos será crear un nuevo directorio en las páginas web, llamado "<span class="codigo">carga</span>". Dentro de este directorio crearemos una nueva <span class="codigo">JSP</span> llamada "<span class="codigo">formulario.jsp</span>":<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg4NKgyc81Wijvqk4FvrKggjogxKkNwyTu9MQzBdE4oeAWz85vFwG_jIPF5hXJgE0lG3oEO_6fd9Dn8a2Nsa9FvGHds2-SPxrct3ZncArnFzeIm5JvBctFeEmvjQoWW6HSM9IIcXulXI5Qi/s1600/S3_42.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg4NKgyc81Wijvqk4FvrKggjogxKkNwyTu9MQzBdE4oeAWz85vFwG_jIPF5hXJgE0lG3oEO_6fd9Dn8a2Nsa9FvGHds2-SPxrct3ZncArnFzeIm5JvBctFeEmvjQoWW6HSM9IIcXulXI5Qi/s1600/S3_42.png" /></a></div><br />
<br />
Como hasta ahora, debemos indicar en la <span class="codigo">JSP</span> que haremos uso de la biblioteca de etiquetas de <span class="codigo">Struts 2</span>:<br />
<br />
<pre><code>
<%@taglib prefix="s" uri="/struts-tags" %>
</code></pre><br />
<br />
Lo siguiente es crear un formulario. Este será un poco distinto a los que hemos creado hasta ahora. Lo primero es que los datos del formulario deben codificarse de una forma especial antes de que estos sean enviados al servidor. Afortunadamente es el navegador el que se encarga de hacer esta codificación, lo único que nosotros debemos hacer es indicar, usando el atributo "<span class="codigo">enctype</span>" del formulario, cuál codificación será; cuando cargamos archivos estos deben ir codificados en "<span class="codigo">multipart/form-data</span>". <br />
<br />
Además los datos deben ser enviados por <span class="codigo">POST</span> (en los formularios <span class="codigo">HTML</span> el método por default para enviar datos es <span class="codigo">GET</span> (por la <span class="codigo">URL</span>), pero en <span class="codigo">Struts 2</span> por default se envían por <span class="codigo">POST</span> (en el cuerpo del mensaje de la petición), así que no debemos agregarle ninguna cosa extra en este caso). El formulario, hasta ahora, se ve de la siguiente forma:<br />
<br />
<pre><code>
<s:form enctype="multipart/form-data">
</s:form>
</code></pre><br />
<br />
Los archivos son cargados con un tipo de campo especial el cual es generado usado la etiqueta "<span class="codigo"><s:file /></span>". Esta etiqueta funciona de la misma forma que las demás que hemos venido usando hasta el momento:<br />
<br />
<pre><code>
<s:form action="cargaArchivo" enctype="multipart/form-data">
<s:file name="archivo" label="Archivo" />
</s:form>
</code></pre><br />
<br />
En este caso he llamado a mi archivo "<span class="codigo">archivo</span>" pero podría tener cualquier nombre, como "<span class="codigo">imagen</span>", "<span class="codigo">reporte</span>", "<span class="codigo">datos</span>", etc.<br />
<br />
Agregaré otro campo solo para que vemos que podemos subir archivos junto con datos "<span class="codigo">planos</span>" del formulario:<br />
<br />
<pre><code>
<s:form action="cargaArchivo" enctype="multipart/form-data">
<s:file name="archivo" label="Archivo" />
<s:textfield name="autor" label="Autor" />
</s:form>
</code></pre><br />
<br />
Terminaremos el formulario agregando un botón de envío de formulario y una etiqueta que nos mostrará cualquier error que pudiera ocurrir en el proceso de carga:<br />
<br />
<pre><code>
<s:actionerror />
<s:form action="cargaArchivo" enctype="multipart/form-data">
<s:file name="archivo" label="Archivo" />
<s:textfield name="autor" label="Autor" />
<s:submit value="Enviar" />
</s:form>
</code></pre><br />
<br />
Como ven, el formulario es tan sencillo como todos los que hemos estado haciendo a lo largo del tutorial.<br />
<br />
Ahora crearemos el <span class="codigo">Action</span> que se encargará de procesar los datos de este formulario, y que es donde se encuentra la parte interesante del ejemplo ^^.<br />
<br />
Creamos una nueva clase en el paquete "<span class="codigo">actions</span>" llamada "<span class="codigo">CargaArchivo</span>". Esta clase extenderá de "<span class="codigo">ActionSupport</span>":<br />
<br />
<pre><code>
public class CargaArchivo extends ActionSupport
{
}
</code></pre><br />
<br />
Agregaremos una propiedad de tipo <span class="codigo">String</span> para almacenar el nombre del autor:<br />
<br />
<pre><code>
public class CargaArchivo extends ActionSupport
{
private String autor;
}
</code></pre><br />
<br />
Ahora viene la parte importante, <span class="negritas">para poder obtener una referencia al archivo, debemos colocar, en nuestro <span class="codigo">Action</span>, una propiedad de tipo "<span class="codigo">File</span>"</span>:<br />
<br />
<pre><code>
public class CargaArchivo extends ActionSupport
{
private String autor;
private File archivo;
}
</code></pre><br />
<br />
Con esto, cada vez que se cargue un archivo usando el formulario, la variable de tipo "<span class="codigo">File</span>" que tenemos, hará referencia a este archivo. Lo que nos queda es agregar los respectivos <span class="codigo">setters</span> para las dos propiedades anteriores:<br />
<br />
<pre><code>
public class CargaArchivo extends ActionSupport
{
private String autor;
private File archivo;
public void setArchivo(File archivo)
{
this.archivo = archivo;
}
public void setAutor(String autor)
{
this.autor = autor;
}
}
</code></pre><br />
<br />
Ahora sobrecargaremos el método "<span class="codigo">execute</span>" de nuestro <span class="codigo">Action</span> para mover el archivo a ua nueva ubicación en nuestro sistema de archivos:<br />
<br />
<pre><code>
@Override
public String execute() throws Exception
{
File nuevoArchivo = new File("/", archivo.getName());
archivo.renameTo(nuevoArchivo);
return SUCCESS;
}
</code></pre><br />
<br />
Podemos ver que colocaremos el nuevo archivo en el directorio raíz del sistema operativo y que el nombre del archivo será el mismo que el del archivo que estamos subiendo.<br />
<br />
Al terminar el proceso mostraremos un poco de información con respecto al archivo. La información del archivo se verá en la página que enviemos como resultado "<span class="codigo">SUCCESS</span>", por lo que deberemos proporcionar los <span class="codigo">getters</span> correspondientes para esta información; en este caso mostraremos el nombre del archivo y la ruta en la que queda almacenado:<br />
<br />
<pre><code>
public String getNombre()
{
return archivo.getName();
}
public String getRuta()
{
return archivo.getAbsolutePath();
}
</code></pre><br />
<br />
Como ven estamos obteniendo la información directamente del objeto <span class="codigo">File</span> que representa al archivo que estamos cargando.<br />
<br />
Crearemos la página que mostrará los resultados por lo que en el directorio "<span class="codigo">carga</span>" creamos una nueva <span class="codigo">JSP</span> llamada "<span class="codigo">archivoCargado</span>". Dentro de este archivo hacemos indicamos usando la directiva "<span class="codigo">taglib</span>" que haremos uso de la biblioteca de etiquetas de <span class="codigo">Struts 2</span>. Después, usando la etiqueta "<span class="codigo"><s:property></span>" mostraremos los dos valores de los atributos anteriores:<br />
<br />
<pre><code>
Nombre: <s:property value="nombre" /><br />
Ruta: <s:property value="ruta" />
</code></pre><br />
<br />
Anotaremos este clase como ya sabemos hacerlo, para que el framework la trate como un <span class="codigo">Action</span>:<br />
<br />
<pre><code>
@Namespace(value = "/carga")
@Action(value = "cargaArchivo", results =
{
@Result(location = "/carga/archivoCargado.jsp"),
@Result(name="input", location = "/carga/formulario.jsp")
})
public class CargaArchivo extends ActionSupport
</code></pre><br />
<br />
Ya tenemos todo asi que procedemos a ejecutar la aplicación y a ingresar a la siguiente dirección:<br />
<br />
<pre><code>
<a href="http://localhost:8080/formularios/carga/formulario.jsp">http://localhost:8080/formularios/carga/formulario.jsp</a>
</code></pre><br />
<br />
Con lo que veremos el formulario que creamos:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMbJKqlljCFbwLkTLfDzKD-bg5-fyzYdukCQKU33PPNL8dJKQQhLPg2Oq6i04ajDiyhVF9HvH4tphsMH3mboVcIvPcNwbdumXlhC1aMgRbVH0o7Ew4AfSjhZuNukgJEaSoVeoFfO8FYMTF/s1600/S3_43.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="198" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMbJKqlljCFbwLkTLfDzKD-bg5-fyzYdukCQKU33PPNL8dJKQQhLPg2Oq6i04ajDiyhVF9HvH4tphsMH3mboVcIvPcNwbdumXlhC1aMgRbVH0o7Ew4AfSjhZuNukgJEaSoVeoFfO8FYMTF/s400/S3_43.png" width="400" /></a></div><br />
<br />
Podemos ver que en el campo de tipo "<span class="codigo">file</span>" que declaramos, existe un pequeño botón que dice "<span class="codigo">Examinar...</span>". Al presionar este botón se abrirá un cuadro de dialogo que nos permitirá seleccionar un archivo; en mi caso seleccionaré una imagen al azar de mi biblioteca de imágenes:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgqoT9p-GXZT14RivYuC4_T-xZCdggUFMiJWWRdUsljmNGVNqEdnATVwa9KBxGR37c6-BDUlVzAg4dcfn3PdHiKGKtfHqnoOXtCAPfhIz7d2Ea-bGF-7b2llHPSIrEIMoeT9HxbxaXAzWVQ/s1600/S3_44.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="280" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgqoT9p-GXZT14RivYuC4_T-xZCdggUFMiJWWRdUsljmNGVNqEdnATVwa9KBxGR37c6-BDUlVzAg4dcfn3PdHiKGKtfHqnoOXtCAPfhIz7d2Ea-bGF-7b2llHPSIrEIMoeT9HxbxaXAzWVQ/s400/S3_44.png" width="400" /></a></div><br />
<br />
Cuando presionemos el botón "<span class="codigo">Enviar</span>" de nuestro formulario, veremos en la página resultante una salida similar a la siguiente:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhuGH1F4hJsWOv5Nmxt5EjrcoytIZ6soolodnSWqf8W5Z4fYQOWJE07cVc_pU92E0oH7Y2q68xCJMDa7NjXAXQkZqbffuNJpd8o2Jxt9K8hgPfoKyWe0_69KVpLx1-JejTJGvDvrO6-32Ec/s1600/S3_45.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="168" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhuGH1F4hJsWOv5Nmxt5EjrcoytIZ6soolodnSWqf8W5Z4fYQOWJE07cVc_pU92E0oH7Y2q68xCJMDa7NjXAXQkZqbffuNJpd8o2Jxt9K8hgPfoKyWe0_69KVpLx1-JejTJGvDvrO6-32Ec/s400/S3_45.png" width="400" /></a></div><br />
<br />
Podemos notar antes que nada que el archivo que habíamos subido, llamado "<span class="codigo">logoJT.png</span>", ahora se llama "<span class="codigo">upload_5829876d_132a34219c9__7ffb_00000000.tmp</span>". Esto ocurre porque cuando enviamos un archivo usando un formulario, el archivo no viaja como tal por la red; son sus bytes los que son enviados (lo sé técnicamente sería lo mismo ya que un archivo es un conjunto de bytes), el interceptor "<span class="codigo">fileUpload</span>" toma estos bytes y los coloca en un archivo nuevo al cual le coloca un nombre extraño. <br />
<br />
Si observamos el directorio a donde hemos movido el archivo que habíamos obtenido notaremos que nuestro archivo efectivamente ha quedado ahí, pero con el nuevo nombre (y extensión) que le ha colocado el interceptor:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEieHj16fT_O59Hd9kAaXztrL7RuMvv8KTyOoKeDlWpfRIJ1u8HewnCJ4P_k1op2-NRI-CQ5Ai_euWySmcNLI7aWQwDx5FKrMR6tnuBFG_Oqr4z2Sax78w2JUdC9WTabOdjM_0eEyAy5yyFF/s1600/S3_46.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="281" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEieHj16fT_O59Hd9kAaXztrL7RuMvv8KTyOoKeDlWpfRIJ1u8HewnCJ4P_k1op2-NRI-CQ5Ai_euWySmcNLI7aWQwDx5FKrMR6tnuBFG_Oqr4z2Sax78w2JUdC9WTabOdjM_0eEyAy5yyFF/s400/S3_46.png" width="400" /></a></div><br />
<br />
Podemos ver que no tenemos información del nombre original del archivo ni de su tipo de contenido. Como podemos imaginar, ambos son datos muy importantes que nos pueden servir en algún momento.<br />
<br />
Afortunadamente para nosotros, <span class="codigo">Struts 2</span> proporciona una forma en la que podemos obtener esta información de una forma fácil y sencilla (como nos tiene mal acostumbrados este framework ^^).<br />
<br />
Para obtener esta información debemos proporcionar dos atributos extra para el archivo (con sus correspondientes <span class="codigo">getters</span>). El nombre de estos atributos (en realidad solo los <span class="codigo">setters</span>, pero creo que es más fácil entender de esta forma) debe seguir una cierta convención si queremos que <span class="codigo">Struts 2</span> proporcione la información de manera correcta.<br />
<br />
Para obtener el nombre original del archivo debemos proporcionar un atributo, de tipo <span class="codigo">String</span>, cuyo identificador sea "<span class="codigo"><nombre_del_campo_del_archivo>FileName</span>"; o sea que si el identificador del campo del archivo es "<span class="codigo">documento</span>", el identificador del campo para el nombre del archivo debe ser "<span class="codigo">documentoFileName</span>", si el identificador del campo es "<span class="codigo">miArchivo</span>", el campo para el nombre debe ser "<span class="codigo">miArchivoFileName</span>".<br />
<br />
Para obtener el tipo de contenido del archivo, o sea el "<span class="codigo">content type</span>", debemos hacer algo similar y proporcionar un campo cuyo nombre sea "<span class="codigo"><nombre_del_campo_del_archivo>ContentType</span>". Los campos para estos dos datos, junto con sus <span class="codigo">setters</span>, queda de la siguiente forma:<br />
<br />
<pre><code>
private String archivoFileName;
private String archivoContentType;
public void setArchivoContentType(String archivoContentType)
{
this.archivoContentType = archivoContentType;
}
public void setArchivoFileName(String archivoFileName)
{
this.archivoFileName = archivoFileName;
}
</code></pre><br />
<br />
Ahora con estos datos podemos modificar un poco nuestro <span class="codigo">Action</span> dejando el método "<span class="codigo">execute</span>" de la siguiente forma:<br />
<br />
<pre><code>
public String execute() throws Exception
{
File nuevoArchivo = new File("/", archivoFileName);
archivo.renameTo(nuevoArchivo);
return SUCCESS;
}
</code></pre><br />
<br />
Ahora el nombre con el que guardaremos el archivo que recibimos, es el mismo nombre del archivo original.<br />
<br />
Agregaremos también un <span class="codigo">getter</span> para cada una de las propiedades que no hemos utilizado aún, o sea para "<span class="codigo">autor</span>" y "<span class="codigo">archivoContentType</span>" para poder leerlos desde la <span class="codigo">JSP</span> del resultado:<br />
<br />
<pre><code>
public String getArchivoContentType()
{
return archivoContentType;
}
public String getAutor()
{
return autor;
}
</code></pre><br />
<br />
Al final, nuestra clase "<span class="codigo">CargaArchivo</span>" queda de la siguiente forma:<br />
<br />
<pre><code>
@Namespace(value = "/carga")
@Action(value = "cargaArchivo", results =
{
@Result(location = "/carga/archivoCargado.jsp"),
@Result(name="input", location = "/carga/formulario.jsp")
})
public class CargaArchivo extends ActionSupport
{
private String autor;
private File archivo;
private String archivoFileName;
private String archivoContentType;
@Override
public String execute() throws Exception
{
File nuevoArchivo = new File("/", archivoFileName);
archivo.renameTo(nuevoArchivo);
return SUCCESS;
}
public String getArchivoContentType()
{
return archivoContentType;
}
public String getAutor()
{
return autor;
}
public void setArchivoContentType(String archivoContentType)
{
this.archivoContentType = archivoContentType;
}
public void setArchivoFileName(String archivoFileName)
{
this.archivoFileName = archivoFileName;
}
public String getNombre()
{
return archivoFileName;
}
public String getRuta()
{
return archivo.getAbsolutePath();
}
public void setArchivo(File archivo)
{
this.archivo = archivo;
}
public void setAutor(String autor)
{
this.autor = autor;
}
}
</code></pre><br />
<br />
Debemos modificar ahora la <span class="codigo">JSP</span> "<span class="codigo">archivoCargado</span>" del directorio "<span class="codigo">carga</span>" para que quede de la siguiente forma:<br />
<br />
<pre><code>
Nombre: <s:property value="nombre" /><br />
Ruta: <s:property value="ruta" /><br />
Autor: <s:property value="autor" /><br />
Content Type: <s:property value="archivoContentType" />
</code></pre><br />
<br />
Con todos los datos que necesitamos. volvemos a ejecutar la aplicación, y debemos ver el mismo formulario de las últimas veces:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhJwdvQVn0R8r51n8P5XULGe84JKJEM-z4VXEZCIg_Neq6IM7xRfwxaUUIMDINCF7DuXWBLfrDdZu1HBmdVTtphAN-SZ3z2HI0LHmpg9peNg6sAQtDTSl_c57dm4isfpQykJ4_UX_cHpPxN/s1600/S3_47.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="172" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhJwdvQVn0R8r51n8P5XULGe84JKJEM-z4VXEZCIg_Neq6IM7xRfwxaUUIMDINCF7DuXWBLfrDdZu1HBmdVTtphAN-SZ3z2HI0LHmpg9peNg6sAQtDTSl_c57dm4isfpQykJ4_UX_cHpPxN/s400/S3_47.png" width="400" /></a></div><br />
<br />
Subimos nuevamente nuestro archivo, y al presionar el botón "<span class="codigo">Enviar</span>" deberemos ver la siguiente pantalla:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi3AVbueVFdE2l-KgwHeZ3nGaZATeBVYUHHzxywTs_9CV5LDjIgJlQzp4Jpvw_zOlhYI35iGBpQjRP0nMaIGbjkQokXMoG4euWgZEkJZapllNDLU2rCzP9jSwOe8dsIphbmnj0flrqO2mK6/s1600/S3_48.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="135" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi3AVbueVFdE2l-KgwHeZ3nGaZATeBVYUHHzxywTs_9CV5LDjIgJlQzp4Jpvw_zOlhYI35iGBpQjRP0nMaIGbjkQokXMoG4euWgZEkJZapllNDLU2rCzP9jSwOe8dsIphbmnj0flrqO2mK6/s400/S3_48.png" width="400" /></a></div><br />
<br />
Como podemos observar, ahora se está indicando el nombre original de la imagen, y el tipo de archivo que se subió (que en este caso es "<span class="codigo">image/png</span>"). Si vamos nuevamente al directorio raíz de nuestro sistema operativo veremos que ahora la imagen se ha almacenado de forma correcta, y podemos ver un preview de la misma:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi4XW1GDg8Pwow0sJ6cNRaChHY8uYCWBiT_sJR4buS29GkgBcUoFX-gFDk0VVhjLe0Iaij8Ve3AumTmToT5zRRV0M5guSvV753R-fQogwRhRbFMjKrvRVXxQPRMJgDiwVt40ZE_ixHks2Xu/s1600/S3_49.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="196" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi4XW1GDg8Pwow0sJ6cNRaChHY8uYCWBiT_sJR4buS29GkgBcUoFX-gFDk0VVhjLe0Iaij8Ve3AumTmToT5zRRV0M5guSvV753R-fQogwRhRbFMjKrvRVXxQPRMJgDiwVt40ZE_ixHks2Xu/s400/S3_49.png" width="400" /></a></div><br />
<br />
Con lo que podemos comprobar que la carga se realizó de forma correcta ^_^.<br />
<br />
<br />
<br />
Hagamos una segunda prueba, intentemos subir el siguiente archivo (<span class="negritas">como tip para lo que viene a continuación, fíjense en el tamaño del archivo</span>).<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg7Q4S57EkYI6CNMYm8b1DmweE1reDIQgCV_ar11xdy0fkIlmf-ASADbcdVyJ7oVx5fA56x6aZ5eitiOPsf-YVK3Ag_11KNnxaN37H9-kyL_K6zH71vsKL_2ArjaWRTEmNb0IzB7_AlgNaK/s1600/S3_50.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="281" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg7Q4S57EkYI6CNMYm8b1DmweE1reDIQgCV_ar11xdy0fkIlmf-ASADbcdVyJ7oVx5fA56x6aZ5eitiOPsf-YVK3Ag_11KNnxaN37H9-kyL_K6zH71vsKL_2ArjaWRTEmNb0IzB7_AlgNaK/s400/S3_50.png" width="400" /></a></div><br />
<br />
Al tratar de enviar este archivo veremos que obtenemos... un mensaje de error:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjj-7OxrDihpEeGPr8j7iDhaSSaAAqqxKtIfa_dyucbUEr6iU7gdMke9kst6d8ls4wQQCe-Jau6odxnbcQ5hcaP5bg9YrY8azoWhiNpj3APfXlnUwpAvcTGZdj1KeY0PZnUx8001UeM4y1K/s1600/S3_51.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="175" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjj-7OxrDihpEeGPr8j7iDhaSSaAAqqxKtIfa_dyucbUEr6iU7gdMke9kst6d8ls4wQQCe-Jau6odxnbcQ5hcaP5bg9YrY8azoWhiNpj3APfXlnUwpAvcTGZdj1KeY0PZnUx8001UeM4y1K/s400/S3_51.png" width="400" /></a></div><br />
<br />
Lo que el mensaje básicamente dice es que el tamaño de nuestro archivo excede el tamaño máximo de <span class="codigo">2MB</span> que <span class="codigo">Struts 2</span> tiene configurado por defecto. ¿Qué podemos hacer entonces para subir archivos que sean más grandes? Como deben estarse imaginando, <span class="codigo">Struts 2</span> proporciona una forma de configurar el tamaño máximo de los archivos que se pueden cargar, a través de una constante o a través de un parámetro del interceptor "<span class="codigo">fileUpload</span>". Estos dos valores no son exactamente para lo mismo, pero lo explicaremos en su debido momento.<br />
<br />
Primero veamos cómo establecer este valor como una constante.<br />
<br />
Como recordarán, del primer tutorial de la serie, hay dos formas de definir las constantes de <span class="codigo">Struts 2</span>, la primera es en el archivo "<span class="codigo">struts.xml</span>" que normalmente usamos cuando realizamos una configuración con <span class="codigo">XML</span>. El nombre de la constante que debemos definir es "<span class="codigo">struts.multipart.maxSize</span>". <br />
<br />
Esta variable quedaría de la siguiente forma en el archivo "<span class="codigo">struts.xml</span>" si quisiéramos que el tamaño máximo del archivo fuera de <span class="codigo">10MB</span>:<br />
<br />
<pre><code>
<constant name="struts.multipart.maxSize" value="10485760" />
</code></pre><br />
<br />
En donde el valor indica el peso máximo que puede tener el archivo el bytes (así es, leyeron bien: bytes). El valor es calculado de la siguiente forma:<br />
<br />
<pre><code>
10 * 1024 * 1024
//MB KB Bytes
</code></pre><br />
<br />
Si no estamos usando un archivo de configuración, como es nuestro caso, la constante se define como un parámetro de inicialización en el filtro de <span class="codigo">Struts 2</span>:<br />
<br />
<pre><code>
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
<span class="negritas"><init-param>
<param-name>struts.multipart.maxSize</param-name>
<param-value>5097152</param-value>
</init-param></span>
</filter>
</code></pre><br />
<br />
Si intentamos nuevamente subir nuestro archivo, veremos la siguiente salida:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhaaUpPqz8dNrux5Iw8DSWfIZDUJ083aA6mVSbobPxw4OjAmqvkplNBtNeZtndA57sNilPA7wlMHQLJuGkcnMTTEcjEHT92lEyD7Hg3D3piHrqp_au2V3PFj37K0bAiKWvpZVl7abPqwro7/s1600/S3_52.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="135" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhaaUpPqz8dNrux5Iw8DSWfIZDUJ083aA6mVSbobPxw4OjAmqvkplNBtNeZtndA57sNilPA7wlMHQLJuGkcnMTTEcjEHT92lEyD7Hg3D3piHrqp_au2V3PFj37K0bAiKWvpZVl7abPqwro7/s400/S3_52.png" width="400" /></a></div><br />
<br />
Podemos ver que en esta ocasión logramos subir el archivo de forma correcta.<br />
<br />
El utilizar la constante anterior modifica el límite del tamaño de los archivos en toda la aplicación. Esto es bueno si deseamos aumentar el límite en el tamaño de los archivos que se vaya a subir en cualquier formulario de la aplicación. ¿Pero qué pasaría si necesitáramos que en algún formulario particular el tamaño máximo de los archivos a subir fuera menor que en el resto de la aplicación? Para estos casos es que existe una forma para especificar un límite para un <span class="codigo">Action</span> particular, estableciendo el parámetro "<span class="codigo">maximumSize</span>" del interceptor "<span class="codigo">fileUpload</span>" en el <span class="codigo">Action</span> que queremos modificar.<br />
<br />
Ojo con lo dicho anteriormente, esta segunda forma <span class="negritas">lo único que permite es hacer que el tamaño máximo para un <span class="codigo">Action</span> particular sea menor que el resto de la aplicación</span>, lo contrario (que el tamaño máximo para un <span class="codigo">Action</span> sea mayor que para el resto de la aplicación) no se puede hacer.<br />
<br />
Para personalizar el valor de este interceptor para un <span class="codigo">Action</span>, cuando trabajamos con el archivo "<span class="codigo">struts.xml</span>", usamos el elemento "<span class="codigo"><interceptor-ref></span>" para indicar qué interceptores son los que queremos aplicar a un <span class="codigo">Action</span> particular y los valores de los parámetros de dichos interceptores. Por ejemplo, para el <span class="codigo">Action</span> "<span class="codigo">cargaArchivo</span>", si estuviéramos trabajando con un el archivo "<span class="codigo">struts.xml</span>" quedaría de la siguiente forma para que el tamaño máximo de un archivo sea de <span class="codigo">2MB (2* 1024 * 1024)</span>:<br />
<br />
<pre><code>
<action name="cargaArchivo" class="com.javatutoriales.struts2.formularios.actions.CargaArchivo">
<interceptor-ref name="fileUpload">
<param name="maximumSize">2097152</param>
</interceptor-ref>
<interceptor-ref name="defaultStack"/>
<result>/carga/archivoCargado.jsp</result>
<result name="input">/carga/formulario.jsp</result>
</action>
</code></pre><br />
<br />
Para cuando trabajamos con anotaciones la configuración queda de la siguiente forma:<br />
<pre><code>
@InterceptorRefs(value =
{
@InterceptorRef(value = "fileUpload", params =
{
"maximumSize", "2097152"
}),
@InterceptorRef(value = "defaultStack")
})
</code></pre><br />
<br />
Como de esta forma lo que hacemos es indicar los interceptores que se le aplicarán a este <span class="codigo">Action</span>, debemos decir que además del interceptor "<span class="codigo">fileUpload</span>" queremos aplicar el resto de los interceptores que se aplican normalmente a un <span class="codigo">Action</span> y que se encuentran en el "<span class="codigo">defaultStack</span>", como explicamos en <a href="http://www.javatutoriales.com/2011/06/struts-2-parte-1-configuracion.html">el primer tutorial de la serie</a>.<br />
<br />
Si intentamos subir nuevamente nuestro archivo obtendremos el siguiente mensaje de error:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEizME8sjbnhXpPOdb70LF7BG-UXtkzfEpRFp2RpmWrTmqai5Gi4Qtrv1k58MXpdLj0Tr32myYfAJJItoWjbm-ZT6CcR56YvE0CpWxuNFi_O6yeCnwQcqV50Jv62FVi22RRP7PQ7pJ0f7HMm/s1600/S3_53.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="135" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEizME8sjbnhXpPOdb70LF7BG-UXtkzfEpRFp2RpmWrTmqai5Gi4Qtrv1k58MXpdLj0Tr32myYfAJJItoWjbm-ZT6CcR56YvE0CpWxuNFi_O6yeCnwQcqV50Jv62FVi22RRP7PQ7pJ0f7HMm/s400/S3_53.png" width="400" /></a></div><br />
<br />
Podemos ver que este mensaje es ligeramente distinto al que habíamos obtenido anteriormente, pero nos sirve para comprobar que efectivamente el límite se ha modificado.<br />
<br />
Si estamos realizando la carga de varios archivos, el tamaño máximo será el de la suma del tamaño de todos los archivos, y no de cada archivo individual. Además el tamaño máximo de archivos que el framework soporta, según la documentación oficial, es de <span class="codigo">2GB</span>.<br />
<br />
Adicionalmente a restringir el tamaño máximo de los archivos que podemos cargar, <span class="codigo">Struts 2</span> nos permite limitar también el tipo (<span class="codigo">content-type</span>) de los archivos que se cargarán, estableciendo el parámetro "<span class="codigo">allowedTypes</span>" del interceptor "<span class="codigo">fileUpload</span>". Supongamos que para el <span class="codigo">Action</span> anterior, solo queremos permitir que se carguen archivos de imagen en formato "<span class="codigo">png</span>". Para lograr esto debemos configurar el interceptor "<span class="codigo">fileUpload</span>" para nuestro <span class="codigo">Action</span> de la siguiente forma:<br />
<br />
Si usamos el archivo "<span class="codigo">struts.xml</span>":<br />
<pre><code>
<action name="cargaArchivo" class="com.javatutoriales.struts2.formularios.actions.CargaArchivo">
<interceptor-ref name="fileUpload">
<param name="maximumSize">2097152</param>
<param name="allowedTypes">image/png</param>
</interceptor-ref>
<interceptor-ref name="defaultStack"/>
<result>/carga/archivoCargado.jsp</result>
<result name="input">/carga/formulario.jsp</result>
</action>
</code></pre><br />
<br />
Si usamos anotaciones:<br />
<br />
<pre><code>
@InterceptorRefs(value =
{
@InterceptorRef(value = "fileUpload", params =
{
"maximumSize", "2097152", "allowedTypes","image/png"
}),
@InterceptorRef(value = "defaultStack")
})
</code></pre><br />
<br />
Al intentar subir cualquier archivo que no sea una imagen en formato <span class="codigo">png</span>, obtendremos el siguiente error:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg2n4I7Fie1qNlrFSfyWpPfDrrGoBemH4yYZlxc-qjicqvjmxrPK77zS3GnDT4V3p1wGo_40II2XlEcJnnu-SufDnRtW_eE30u3jct_RFGCSnqwwt_tOxIUkG6qIDnQ9UHgR3AkMk-dJbue/s1600/S3_54.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="135" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg2n4I7Fie1qNlrFSfyWpPfDrrGoBemH4yYZlxc-qjicqvjmxrPK77zS3GnDT4V3p1wGo_40II2XlEcJnnu-SufDnRtW_eE30u3jct_RFGCSnqwwt_tOxIUkG6qIDnQ9UHgR3AkMk-dJbue/s400/S3_54.png" width="400" /></a></div><br />
<br />
Podemos indicar varios formatos permitidos separando cada elemento con comas, por ejemplo:<br />
<br />
<pre><code>
"image/jpg, image/png, image/ico"
</code></pre><br />
<br />
Para terminar de hablar de carga de archivos debemos saber cómo personalizar los mensajes de error que se generan al cargar archivos. Los mensajes que vimos anteriormente, aunque bastante claros tal vez no sean los que querremos que nuestros usuarios finales vean en la aplicación. Para personalizar estos mensajes tenemos tres llaves que nos permiten indicar los mensajes que queremos mostrar en cada caso. Estas llaves son:<br />
<br />
<ul><li><span class="codigo">struts.messages.error.uploading</span> - Un error general que ocurre y que impide subir un archivo</li>
<li><span class="codigo">struts.messages.error.file.too.large</span> - Ocurre cuando un archivo cargado es más grande que el tamaño máximo especificado</li>
<li><span class="codigo">struts.messages.error.content.type.not.allowed</span> - Ocurre cuando el archivo cargado no es del tipo permitido (content-type) para la carga</li>
</ul><br />
<br />
Estas llaves debemos colocarlas en un archivo de propiedades que contendrá los mensajes de error de la aplicación. Para crear este archivo hacemos clic derecho sobre el nodo "<span class="codigo">Source Package</span>" del panel de proyectos. En el menú contextual que aparece seleccionamos la opción "<span class="codigo">New -> Properties File...</span>" (si no tienen esa opción en el menú, seleccionen la opción "<span class="codigo">Other...</span>" y en la ventana que se abre seleccionen la categoría "<span class="codigo">Other</span>" y el tipo de archivo "<span class="codigo">Properties File</span>"):<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgoN0HBLp7PTPOpfr7uz26I64TyJiy7tkky9RNS3xacOBHkyS5mzTPFO9wGPsUbQ3RKAh_xwNzKCy9a-_5LaXDipOeB28VFpPHmd9-XlhnCJ9nDxT5XqBfeJG6YBW_WWUNHw_9WpcTi0Gx8/s1600/S3_55.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="305" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgoN0HBLp7PTPOpfr7uz26I64TyJiy7tkky9RNS3xacOBHkyS5mzTPFO9wGPsUbQ3RKAh_xwNzKCy9a-_5LaXDipOeB28VFpPHmd9-XlhnCJ9nDxT5XqBfeJG6YBW_WWUNHw_9WpcTi0Gx8/s400/S3_55.png" width="400" /></a></div><br />
<br />
Llamaremos a este archivo "<span class="codigo">struts-mensajes</span>" (el IDE se encargará de colocar automáticamente la extensión <span class="codigo">.properties</span>). Damos clic en el botón "<span class="codigo">Finish</span>" y veremos aparecer en el editor nuestro archivo de propiedades. En este archivo colocaremos los textos que los usuarios verán en caso de que ocurra algún error, por ejemplo podemos poner:<br />
<br />
<pre><code>
struts.messages.error.file.too.large=El archivo proporcionado supera el tamaño máximo
struts.messages.error.content.type.not.allowed=El archivo proporcionado no es del tipo adecuado
</code></pre><br />
<br />
Ya que tenemos definidos los mensajes, lo siguiente que debemos hacer es indicarle a <span class="codigo">Struts 2</span> dónde se localiza este archivo. Para ello (si, adivinaron) usamos una constante para indicar el nombre del archivo (<span class="negritas">el cual será buscado a partir del directorio raíz de los paquetes de la aplicación, o sea en el nodo "<span class="codigo">Source Packages</span>"</span>. La constante que usamos es "<span class="codigo">struts.custom.i18n.resources</span>", y como ya hemos visto, podemos colocarle en el archivo "<span class="codigo">struts.xml</span>" de la sigueinte forma:<br />
<br />
<pre><code>
<constant name="struts.custom.i18n.resources" value="struts-mensajes" />
</code></pre><br />
<br />
O como un parámetro de inicialización en el filtro de <span class="codigo">struts</span>:<br />
<br />
<pre><code>
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
<init-param>
<param-name>struts.custom.i18n.resources</param-name>
<param-value>struts-mensajes</param-value>
</init-param>
</filter>
</code></pre><br />
<br />
Cuando volvamos a ejecutar la aplicación veremos los siguientes mensajes de error:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEivPAzEoWi0pknlnVOGjcq13VpanLBBsGpDgGdysDDs_Lb9JDu_eu95oBM-5uCdvos30dLNTRfBdzWdfoLkn_F8i2jRgg43ZKcwIsOA-pG_qs0xBgMpNySVEEUwsZJMLlQtURISviN44lYK/s1600/S3_56.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEivPAzEoWi0pknlnVOGjcq13VpanLBBsGpDgGdysDDs_Lb9JDu_eu95oBM-5uCdvos30dLNTRfBdzWdfoLkn_F8i2jRgg43ZKcwIsOA-pG_qs0xBgMpNySVEEUwsZJMLlQtURISviN44lYK/s400/S3_56.png" width="400" /></a></div><br />
<br />
Esto ya está mejor, ya que podemos colocar un texto tan descriptivo como queramos, sin embargo ahora hemos perdido algo de información importante que los otros mensajes nos daban, como el nombre del archivo que estamos subiendo, y algunos parámetros particulares para cada error.<br />
<br />
Afortunadamente una vez más <span class="codigo">Struts 2</span> nos da una solución a esto ya que dentro de los mensajes podemos colocar algo llamado "<span class="codigo">placeholders</span>" o contenedores, que básicamente es un espacio en nuestro mensaje donde <span class="codigo">Struts 2</span> se encargará de colocar un parámetro. Los parámetros que proporciona cada tipo de error son:<br />
<br />
<ul><li><ul>Para "<span class="codigo">struts.messages.error.file.too.large</span>":
<li><span class="codigo">0</span> - Nombre del parámetro que causó el error (en nuestro ejemplo "<span class="codigo">archivo</span>")</li>
<li><span class="codigo">1</span> - Nombre del archivo que causó el error </li>
<li><span class="codigo">2</span> - Nombre del archivo en el servidor (el archivo temporal creado por el interceptor)</li>
<li><span class="codigo">3</span> - Tamaño del archivo cargado</li>
</ul></li>
<li><ul>Para "<span class="codigo">struts.messages.error.content.type.not.allowed</span>":
<li><span class="codigo">0</span> - Nombre del parámetro que causó el error (en nuestro ejemplo "<span class="codigo">archivo</span>")</li>
<li><span class="codigo">1</span> - Nombre del archivo que causó el error</li>
<li><span class="codigo">2</span> - Nombre del archivo en el servidor (el archivo temporal creado por el interceptor)</li>
<li><span class="codigo">3</span> - <span class="codigo">Content-type</span> del archivo cargado</li>
</ul></li>
</ul><br />
<br />
Como podemos ver, en ambos casos los parámetros son prácticamente los mismos.<br />
<br />
Podemos modificar los mensajes anteriores para que queden de la siguiente forma:<br />
<br />
<pre><code>
struts.messages.error.file.too.large=El archivo "{1}" supera el tamaño máximo permitido 2097152 bytes (2MB). El tamaño de su archivo es de {3} bytes
struts.messages.error.content.type.not.allowed=El archivo "{1}" no es de un tipo permitido (image/png). Su archivo es de tipo "{3}"
</code></pre><br />
<br />
Con lo que obtendríamos los siguientes mensajes de error:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj0czuHeSNwAsGVZwX5hku-o0itkum-Xkfy1gPmpBEJ3p2QUlh-mYEoKlYtAaHD7OaZca99nyhdnHXGdJSTjsGuMqvvTBOg4e9KobHN6tO-IEmQALyk1pFcx-Zokyt_y2g43ducfX3LFCX0/s1600/S3_57.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj0czuHeSNwAsGVZwX5hku-o0itkum-Xkfy1gPmpBEJ3p2QUlh-mYEoKlYtAaHD7OaZca99nyhdnHXGdJSTjsGuMqvvTBOg4e9KobHN6tO-IEmQALyk1pFcx-Zokyt_y2g43ducfX3LFCX0/s400/S3_57.png" width="400" /></a></div><br />
<br />
Que ya son mucho más claros para nuestros usuarios finales ^_^.<br />
<br />
Como una nota final sobre la carga de archivos debemos decir que existe una constante más, "<span class="codigo">struts.multipart.saveDir</span>", que podemos usar para indicar dónde queremos que se guarden los archivos temporales de las cargas que genera <span class="codigo">Struts 2</span>. El uso de esta variable sale del propósito de este tutorial ^_^.<br />
<br />
Ahora que hemos hablado bastante de la carga de archivos, pasemos al último tema del tutorial. Veamos como envías archivos desde nuestro servidor a los clientes usando <span class="codigo">Struts 2</span>:<br />
<br />
<br />
<h2 class="titulo">7. Descarga de Archivos</h2>El poder enviar información binaria o archivos a los usuarios, aunque no es estrictamente parte del trabajo con formularios, es también una parte importante del desarrollo de aplicaciones web ya que tiene muchas aplicaciones prácticas como el enviar un reporte que generamos de forma dinámica, o un archivo que tengamos almacenado en algún lugar, como una base de datos o un directorio externo de la aplicación, o también mostrar imagenes que se hayan cargado o generado de forma dinámica en el sistema.<br />
<br />
El realizar la descarga o envío de archivos es una tarea muy simple de hacer cuando trabajamos con <span class="codigo">Struts 2</span>, ya que nos proporciona un tipo de resultado especial que permite enviar bytes directamente al "<span class="codigo">ServletOutputStream</span>" del "<span class="codigo">HttpServletResponse</span>".<br />
<br />
Dedicaremos un tutorial completo a hablar de tipos de resultados, así que no entraré en muchos detalles de cómo funcionan estos, pero debemos saber que existen varios tipos de resultados, cada uno de los cuales altera de alguna forma la respuesta que enviamos al cliente. Podemos establecer algunos parámetros para indicar alguna información a nuestro <span class="codigo">result</span>, entre otras cosas.<br />
<br />
El <span class="codigo">result</span> que nos ayudará en este caso es llamado "<span class="codigo">Stream Result</span>", y básicamente es un <span class="codigo">result</span> que permite enviar un flujo de bytes al cliente. Los parámetros que acepta este <span class="codigo">result</span> son:<br />
<br />
<ul><li><span class="codigo">contentType</span>: El tipo de contenido que se está enviando al usuario, por default es "<span class="codigo">text/plain</span>" </li>
<li><span class="codigo">contentLength</span>: En número de bytes que se le enviarán al usuario (el navegador lo usa para mostrar de forma correcta una barra de progreso).</li>
<li><span class="codigo">contentDisposition</span>: Establece la cabecera "<span class="codigo">content disposition</span>" que especifica el nombre del archivo, por lo regular ponemos algo como "<span class="codigo">attachment;filename="documento.pdf"</span>" que muestra un cuadro de dialogo para guardar el documento, su valor por default es "<span class="codigo">inline</span>" que nos muestra el documento en el mismo navegador</li>
<li><span class="codigo">inputName</span>: El nombre del "<span class="codigo">InputStream</span>" que tiene los bytes del archivo que enviaremos al usuario (quedará más claro con el ejemplo), por default es "<span class="codigo">inputStream</span>"</li>
<li><span class="codigo">bufferSize</span>: El tamaño del buffer que se usa para del flujo de entrada al de salida, por default es "<span class="codigo">1024</span>"</li>
<li><span class="codigo">allowCaching</span>: Indica si el archivo debe ser guardado en caché por el cliente, por default es "<span class="codigo">true</span>"</li>
<li><span class="codigo">contentCharSet</span>: El tipo de caracteres que tiene el contenido que enviamos al archivo, no tiene valor por default y si no lo establecemos no pasa nada ^_^.</li>
</ul><br />
<br />
Todos los atributos anteriores son opcionales, y por lo regular solo establecemos dos o tres.<br />
<br />
Veremos tres ejemplos de este tipo de <span class="codigo">result</span>.<br />
<br />
Lo primero que haremos es crear un nuevo directorio, dentro de las páginas, llamado "<span class="codigo">descarga</span>":<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg3RSTvrqxizEv3sxt8hEWaClfLZ4n_ibi7SGEWMhVIcuiFUzHulJCRQU4m2JTG_rwL-eJd0Gm7t90Eex8fXeUHaiiznI4waIf7KrUI9uWwzVXnqVqauNx95PqxlFVFhkHxZddMFaIQLvLy/s1600/S3_58.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg3RSTvrqxizEv3sxt8hEWaClfLZ4n_ibi7SGEWMhVIcuiFUzHulJCRQU4m2JTG_rwL-eJd0Gm7t90Eex8fXeUHaiiznI4waIf7KrUI9uWwzVXnqVqauNx95PqxlFVFhkHxZddMFaIQLvLy/s1600/S3_58.png" /></a></div><br />
<br />
Para el primer ejemplo tomaremos un archivo que se encuentra en un directorio de nuestra aplicación y se lo enviaremos al usuario. Pueden tomar cualquier archivo que les guste, de preferencia alguno que puedan ver en el navegador como un<span class="codigo">PDF</span>. Yo tomaré un archivo al azar de mi máquina y lo colocaré en el directorio raíz de la aplicación web, o sea en el directorio "<span class="codigo">web</span>" de la aplicación que estamos desarrollando:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjg24dVnqc-zFTKCdfh1Sc_pmY9WPTGukWMMiVtpqYryAuEt_FXtc82vTeKhBiCxLCPaceyiHdnr8i1B6FoZ7CMkjlhiPMFmVwLv5ES0PLZFZFTG2apHjnjevL9dd3UlMFZk8ZFIPDVt64A/s1600/S3_59.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="345" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjg24dVnqc-zFTKCdfh1Sc_pmY9WPTGukWMMiVtpqYryAuEt_FXtc82vTeKhBiCxLCPaceyiHdnr8i1B6FoZ7CMkjlhiPMFmVwLv5ES0PLZFZFTG2apHjnjevL9dd3UlMFZk8ZFIPDVt64A/s400/S3_59.png" width="400" /></a></div><br />
<br />
Abriré este archivo solo para ver el contenido y que cuando lo descarguemos podamos ver que es el mismo:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiE5i_Xy2tCAsO3pH5k7F3mVhmoGo9bPdnC-5-ggR5sJt4LYbU05C1drTrzUaZmvtHj-QP6E1wcVIrhEh_NhxqGpreKugtmBLrC6O3xUHT4tQkhwihzDi50CFAxd1GbhSG5ZYZZIa4XAqBH/s1600/S3_60.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiE5i_Xy2tCAsO3pH5k7F3mVhmoGo9bPdnC-5-ggR5sJt4LYbU05C1drTrzUaZmvtHj-QP6E1wcVIrhEh_NhxqGpreKugtmBLrC6O3xUHT4tQkhwihzDi50CFAxd1GbhSG5ZYZZIa4XAqBH/s400/S3_60.png" width="311" /></a></div><br />
<br />
Ahora crearemos una nueva clase llamada "<span class="codigo">MuestraArchivo</span>", en el paquete "<span class="codigo">actions</span>". Esta clase extenderá de "<span class="codigo">ActionSupport</span>":<br />
<br />
<pre><code>
public class MuestraArchivo extends ActionSupport
{
}
</code></pre><br />
<br />
Lo primero que debemos hacer es colocar en nuestro <span class="codigo">Action</span> un atributo de tipo "<span class="codigo">java.io.InputStream</span>". Esta variable contendrá los bytes del archivo que se le enviará al cliente. Existen muchas formas de obtener una referencia a un "<span class="codigo">InputStream</span>" de manera que solo tengamos que indicar la fuente donde está el archivo, y alguna clase especial se encargue del resto. En el peor de los casos tendremos que obtener estos bytes uno a uno y colocarlos en un objeto especial, pero tampoco es tan complicado; nosotros usaremos las dos formas en distintos ejemplos. <br />
<br />
Si le damos a esta variable el nombre de "<span class="codigo">inputStream</span>" no tendremos que declarar nada más en la configuración del <span class="codigo">result</span>. Como es lo más fácil para el momento, lo haremos así y agregaremos el correspondiente <span class="codigo">getter</span> de la propiedad:<br />
<br />
<pre><code>
public class MuestraArchivo extends ActionSupport
{
private InputStream inputStream;
public InputStream getInputStream()
{
return inputStream;
}
}
</code></pre><br />
<br />
Ahora sobre-escribiremos el método "<span class="codigo">execute</span>" para poder obtener un flujo de lectura ("<span class="codigo">InputStream</span>") a un archivo, usando una clase especial, un "<span class="codigo">FileInputStream</span>". Para obtener este flujo lo único que debemos saber es la ubicación del archivo. Aquí hay que hacer un paréntesis para explicar un poco cómo se obtienen las rutas dentro de nuestra aplicación cuando estamos trabajando con aplicaciones web.<br />
<br />
Dentro de la especificación de <span class="codigo">Servlets</span> y <span class="codigo">JSP</span>s se indica que cuando se hace la instalación o deploy de una aplicación web en un servidor (más específicamente en un contenedor de <span class="codigo">Servlets</span> y <span class="codigo">JSP</span>s) este debe quedar en un directorio de la máquina donde está instalado el servidor. ¿En cuál directorio?... esa es la cuestión interesante.<br />
<br />
En la especificación no se indica dónde debe quedar cada aplicación que despleguemos, así que una vez instalada la aplicación, no podemos estar seguros de en dónde se encuentra físicamente dentro del servidor, por lo tanto si quisiéramos simplemente leer un archivo de nuestra aplicación no podríamos hacerlo ya que no sabríamos que ruta indicar; y todo esto en nuestro servidor local, si hiciéramos la instalación en un servidor remoto, rentado, o del cliente, sería aún peor ya que tal vez ni siquiera sabríamos el sistema operativo del mismo. <br />
<br />
Para remediar esto, la especificación también proporciona una forma de obtener la ruta a cualquier recurso de nuestra aplicación. Para hacer esto debemos obtener una referencia al "<span class="codigo">ServletContext</span>" de la aplicación y luego usar su método "<span class="codigo">getRealPath</span>" indicando el recurso que del cual queremos obtener la ruta, iniciando desde la raíz de la aplicación (lo que vendría siendo el directorio "<span class="codigo">web</span>" en <span class="codigo">NetBeans</span>).<br />
<br />
Ahora bien, como estamos usando un framework que se encarga de ocultar algunos detalles de la implementación web de la aplicación (como todo buen framework), no podemos obtener de una forma directa el "<span class="codigo">ServletContext</span>" ya que no tenemos ninguna referencia a la interface de <span class="codigo">Servlets</span> dentro de nuestros <span class="codigo">Action</span>s.<br />
<br />
Para remediar esto, <span class="codigo">Struts 2</span> nos proporciona una clase auxiliar que ayuda a obtener la referencia al "<span class="codigo">ServletContext</span>" de una forma sencilla (de hecho también podemos obtener la referencia al "<span class="codigo">HttpServletRequest</span>" y al "<span class="codigo">HttpServletResponse</span>"). Esta clase es "<span class="codigo">ServletActionContext</span>", y tiene una serie de métodos estáticos que permiten obtener referencias a los objetos anteriores.<br />
<br />
Después de toda esa explicación, espero que quede claro el siguiente paso dentro de nuestro ejemplo.<br />
<br />
Ya tenemos declarada una instancia de "<span class="codigo">InputStream</span>" y hemos colocado un archivo <span class="codigo">.pdf</span>, llamado "<span class="codigo">Ingenieria de software</span>", en la raíz de la aplicación web, ahora crearemos un flujo de entrada para leer ese archivo y así enviarlo al cliente. Hacerlo en realidad es más fácil que decirlo, ya que <span class="codigo">Java</span> proporciona una clase llamada "<span class="codigo">FileInputStream</span>" que hace todo esto de forma automática, lo único que debemos hacer es indicarle la ruta en la que está almacenado nuestro archivo, de la siguiente forma:<br />
<br />
<pre><code>
@Override
public String execute() throws Exception
{
String ruta = Servlet<span class="codigo">Action</span>Context.getServletContext().getRealPath("/Ingenieria de software.pdf");
inputStream = new FileInputStream(ruta);
return SUCCESS;
}
</code></pre><br />
<br />
Esto es todo lo que debemos hacer para poder enviar el archivo al usuario, el framework se encargará de hacer el resto.<br />
<br />
El paso final en este <span class="codigo">Action</span> es colocar las anotaciones, que en este momento ya debemos conocer de memoria, para indicarle a <span class="codigo">Struts 2</span> que debe tratar esta clase como un <span class="codigo">Action</span>. Primero que nada indicamos el namespace en el que se colocará al <span class="codigo">Action</span>, y se le dará un nombre al mismo:<br />
<br />
<pre><code>
@Namespace(value = "/descarga")
@Action(value = "muestraArchivo)
public class MuestraArchivo extends ActionSupport
</code></pre><br />
<br />
Lo siguiente es indicar el resultado de la ejecución de esté <span class="codigo">Action</span>, la respuesta que será enviada al cliente, para lo cual usamos el atributo "<span class="codigo">results</span>" y la anotación "<span class="codigo">@Result</span>". En esta ocasión, como estamos usando un tipo distinto de <span class="codigo">result</span>, debemos indicarlo dentro de la anotación usando su atributo "<span class="codigo">type</span>". En este caso debemos indicar que el result es de tipo "<span class="codigo">stream</span>" o flujo de bytes, de la siguiente forma:<br />
<br />
<pre><code>
@Namespace(value = "/descarga")
@Action(value = "muestraArchivo", results =
{
@Result(type = "stream")
})
public class MuestraArchivo extends ActionSupport
</code></pre><br />
<br />
El último paso es establecer alguno de los parámetros en el <span class="codigo">result</span>, para eso usamos el atributo "<span class="codigo">params</span>" de esta anotación. Este atributo recibe como argumento <span class="negritas">un arreglo de cadenas</span>, donde <span class="negritas">los elementos nones representan el nombre del argumento que se quiere establecer</span>, y <span class="negritas">los pares representan el valor de dicho argumento</span>. En este ejemplo solo usaré el atributo "<span class="codigo">contentType</span>" para indicar que el tipo de archivo que regresaré al usuario es un archivo <span class="codigo">pdf</span> ("<span class="codigo">application/pdf</span>"). Si están usando un archivo de algún otro tipo, una búsqueda rápida en Google les dará el tipo de contenido de su archivo:<br />
<br />
<pre><code>
@Namespace(value = "/descarga")
@Action(value = "muestraArchivo", results =
{
@Result(type = "stream", params =
{
"contentType", "application/pdf"
})
})
public class MuestraArchivo extends ActionSupport
</code></pre><br />
<br />
Nuestra clase "<span class="codigo">MuestraArchivo</span>" queda de la siguiente forma:<br />
<br />
<pre><code>
@Namespace(value = "/descarga")
@Action(value = "muestraArchivo", results =
{
@Result(type = "stream", params =
{
"contentType", "application/pdf"
})
})
public class MuestraArchivo extends ActionSupport
{
private InputStream inputStream;
@Override
public String execute() throws Exception
{
String ruta = ServletActionContext.getServletContext().getRealPath("/Ingenieria de software.pdf");
inputStream = new FileInputStream(ruta);
return SUCCESS;
}
public InputStream getInputStream()
{
return inputStream;
}
}
</code></pre><br />
<br />
Si estuviéramos trabajando con archivos de configuración, el <span class="codigo">Action</span> quedaría de la siguiente forma:<br />
<br />
<pre><code>
<action name="muestraArchivo" class="com.javatutoriales.struts2.formularios.actions.MuestraArchivo ">
<result type="stream">
<param name="contentType">application/pdf</param>
</result>
</action>
</code></pre><br />
<br />
Para este tipo de <span class="codigo">result</span>, hablando estrictamente, no se necesita una página para mostrarlo, basta con colocar el nombre del <span class="codigo">Action</span> en la barra de direcciones del navegador; nosotros crearemos una solo para ver cómo podemos invocarlos. Dentro del directorio "<span class="codigo">descarga</span>" de las páginas web del proyecto creamos una nueva <span class="codigo">JSP</span> llamada "<span class="codigo">archivo</span>". Dentro de esta <span class="codigo">JSP</span>, indicamos que haremos uso de la biblioteca de etiquetas de <span class="codigo">Struts 2</span>, con la directiva "<span class="codigo">taglib</span>" correspondiente:<br />
<br />
<pre><code>
<%@taglib prefix="s" uri="/struts-tags" %>
</code></pre><br />
<br />
Ahora usaremos la etiqueta "<span class="codigo"><s:a></span>" para crear un enlace a nuestro <span class="codigo">Action</span>. Esta etiqueta es smuy fácil de utilizar, solo hay que indicar el nombre del <span class="codigo">Action</span>, en su atributo "<span class="codigo">action</span>", su namespace, en su atributo "<span class="codigo">namespace</span>", junto con el texto que tendrá el enlace, de la siguiente forma:<br />
<br />
<pre><code>
<s:a action="muestraArchivo" namespace="/descarga">Ver archivo</s:a>
</code></pre><br />
<br />
Ahora que está todo listo, ejecutamos nuestra aplicación e ingresamos a la siguiente dirección:<br />
<br />
<pre><code>
<a href="http://localhost:8080/formularios/descarga/archivo.jsp">http://localhost:8080/formularios/descarga/archivo.jsp</a>
</code></pre><br />
<br />
Con lo que debemos ver la siguiente página:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEghVBuuz7K-k_Oo_0CwrNVLg-tjeHQXYGtWQBjgg7DpKsQrX2bNQLHxDfZn3c4UX4LHmf3guPS_kfA-lD26oAhz1OZkB2u8Zx-fF6GM79MWcb9z6JB0hjDotIiXwL6I6Rb_quoOHd46j5lQ/s1600/S3_61.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="172" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEghVBuuz7K-k_Oo_0CwrNVLg-tjeHQXYGtWQBjgg7DpKsQrX2bNQLHxDfZn3c4UX4LHmf3guPS_kfA-lD26oAhz1OZkB2u8Zx-fF6GM79MWcb9z6JB0hjDotIiXwL6I6Rb_quoOHd46j5lQ/s400/S3_61.png" width="400" /></a></div><br />
<br />
Lo único que tiene esta página es un enlace a nuestro <span class="codigo">Action</span>, cuando entramos en la misma deberemos ver el siguiente resultado:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgDMlsYttALqxOKqGwAqLSlb-a5C489G4Ixhh2QnogT12rhja9Pw1in6NMSLKIr_VPO3_dZ1x_LtMrl-m7u9VobjHN7Z_WvjSlNbMa8e_ei5uZbYg62dVP0_jYZhzV6vqwN1wGLnILKQWvR/s1600/S3_62.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="298" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgDMlsYttALqxOKqGwAqLSlb-a5C489G4Ixhh2QnogT12rhja9Pw1in6NMSLKIr_VPO3_dZ1x_LtMrl-m7u9VobjHN7Z_WvjSlNbMa8e_ei5uZbYg62dVP0_jYZhzV6vqwN1wGLnILKQWvR/s400/S3_62.png" width="400" /></a></div><br />
<br />
Como podemos ver, el documento se ha incrustado en nuestro navegador, por lo que nuestro primer ejemplo ha funcionado de forma correcta ^_^.<br />
<br />
En el ejemplo anterior, el archivo que se envío al usuario se mostró dentro del mismo navegador. Esto nos sirve con ciertos tipos de archivos y bajo ciertas circunstancias, pero no siempre querremos que el usuario vea los archivos en su navegador, algunas veces querremos que sea forzoso que los descargue a su computadora. <br />
<br />
En nuestro segundo ejemplo veremos cómo hacer este cambio. Afortunadamente es algo muy sencillo de hacer, basta con agregar otro parámetro, "<span class="codigo">contentDisposition</span>", a nuestro <span class="codigo">result</span>. "<span class="codigo">contentDisposition</span>", como pueden ver en la lista anterior de parámetros, especifica cómo será enviado este archivo al cliente, si "<span class="codigo">inline</span>" (para mostrarse en el navegador) o "<span class="codigo">attachment</span>" (para que el archivo sea descargado en la máquina del cliente). Ambas opciones nos permiten indicar un "<span class="codigo">filename</span>" que es el nombre que tendrá el archivo al ser enviado.<br />
<br />
Como el código del <span class="codigo">Action</span> que se necesita para hacer este ejemplo, es muy parecido al anterior, crearé una nueva clase llamada "<span class="codigo">DescargaArchivo</span>" y copiaré el código anterior.<br />
<br />
Agregaremos este parámetro a nuestro <span class="codigo">result</span>, y como nombre del archivo colocaremos simplemente "<span class="codigo">tutorial.pdf</span>":<br />
<br />
<pre><code>
@Namespace(value = "/descarga")
@Action(value = "descargaArchivo", results =
{
@Result(type = "stream", params =
{
"contentType", "application/pdf",
"contentDisposition","attachment;filename=\"tutorial.pdf\""
})
})
</code></pre><br />
<br />
Con archivos <span class="codigo">XML</span> quedaría de la siguiente forma:<br />
<br />
<pre><code>
<result type="stream">
<param name="contentType">application/pdf</param>
<param name="contentDisposition">attachment;filename="tutorial.pdf"</param>
</result>
</code></pre><br />
<br />
Agregamos la liga correspondiente a este nuevo <span class="codigo">Action</span> en la página "<span class="codigo">archivo.jsp</span>":<br />
<br />
<pre><code>
<s:a action="descargaArchivo" namespace="/descarga">Descargar archivo</s:a>
</code></pre><br />
<br />
Con este simple cambio, al volver a ejecutar nuestra aplicación y entrar en nuestro <span class="codigo">Action</span>, ahora en lugar de ver el documento en pantalla veremos el cuadro de diálogo del navegador que nos pregunta qué queremos hacer con el archivo:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhPJggaCPlXc6eDGUjTL-i75UBIxcZ86W1PqcRvVVXP827U8pmXv0w11EBuLeLkAA6gO7OGUL7O10OavSBh-pbPwC27XOez9bHux9GOCuqm6picHBV7euBSOnvCqPgNnM88AwHEznQ_JZeQ/s1600/S3_63.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="227" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhPJggaCPlXc6eDGUjTL-i75UBIxcZ86W1PqcRvVVXP827U8pmXv0w11EBuLeLkAA6gO7OGUL7O10OavSBh-pbPwC27XOez9bHux9GOCuqm6picHBV7euBSOnvCqPgNnM88AwHEznQ_JZeQ/s400/S3_63.png" width="400" /></a></div><br />
<br />
Podemos ver en la imagen anterior, que el nombre que tendrá el archivo es el mismo nombre que estamos regresando como el "<span class="codigo">filename</span>". En esta ocasión hemos puesto el nombre de forma estática, pero esta no será siempre la forma en la que querremos indicar el nombre del archivo, en la mayoría de las ocasiones este nombre deberá ser generado de forma dinámica. Para lograr esto, todas las propiedades de todos los <span class="codigo">results</span> de <span class="codigo">Struts 2</span> permiten establecer sus valores usando expresiones, esto es colocar una marca que indique que esa propiedad deberá ser leída de algún <span class="codigo">getter</span> de la clase.<br />
<br />
En este caso indicaremos que el nombre del archivo deberá ser obtenido usando el método "<span class="codigo">getNombreArchivo</span>" de la clase:<br />
<br />
<pre><code>
@Result(type = "stream", params =
{
"contentType", "application/pdf",
"contentDisposition","attachment;filename=\"${nombreArchivo}\""
})
</code></pre><br />
<br />
Podemos ver que solo colocamos "<span class="codigo">nombreArchivo</span>" entre los signos "<span class="codigo">${</span>" y "<span class="codigo">}</span>" (que indican que lo que esté contenido entre ambos es una expresión) y el framework automáticamente sabrá que debe buscar un <span class="codigo">getter</span>. <br />
<br />
Con archivos <span class="codigo">XML</span> queda exactamente igual:<br />
<br />
<pre><code>
<result type="stream">
<param name="contentType">application/pdf</param>
<param name="contentDisposition">attachment;filename="${nombreArchivo}"</param>
</result>
</code></pre><br />
<br />
En este caso, nuestro método "<span class="codigo">getNombreArchivo</span>" regresará una cadena estática, pero en una aplicación real esta podría ser generada de forma dinámica:<br />
<br />
<pre><code>
public String getNombreArchivo()
{
return "documento.pdf";
}
</code></pre><br />
<br />
Ejecutamos nuevamente la aplicación y podremos ver que ahora el nombre del archivo está siendo efectivamente leído a través del <span class="codigo">getter</span> correspondiente:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiubj10iZli129_ultLoKo-HhVT5eTR-g9Mv8SBo1c3Ryug6_3yC5ocMljW-6e1i-aLhxXnP1Co-KnXR5x_zJLsRPLigX0X0IEyfyHN3D7nODYwGUL6LYjphjxfb310huroYfsG95jG8WDL/s1600/S3_64.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="227" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiubj10iZli129_ultLoKo-HhVT5eTR-g9Mv8SBo1c3Ryug6_3yC5ocMljW-6e1i-aLhxXnP1Co-KnXR5x_zJLsRPLigX0X0IEyfyHN3D7nODYwGUL6LYjphjxfb310huroYfsG95jG8WDL/s400/S3_64.png" width="400" /></a></div><br />
<br />
Hasta ahora nuestra descarga parece realizarse de forma correcta, pero si prestamos un poco de atención al cuadro de descarga podremos ver que hay algo que no nos agrada mucho:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgQyfZkOvQH4jBX81S8aUR6n37HkWSPU7zfG_tCdtguNdJRoki-7Ts4OKT_rVbShAsxvyy-Tkk_aalrecovPgwxIgJ2kVkBfMVdbHTsYyVbwxzV6qa-sWXt1FCTfKSnwgKA3hKXONC5wmly/s1600/S3_65.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgQyfZkOvQH4jBX81S8aUR6n37HkWSPU7zfG_tCdtguNdJRoki-7Ts4OKT_rVbShAsxvyy-Tkk_aalrecovPgwxIgJ2kVkBfMVdbHTsYyVbwxzV6qa-sWXt1FCTfKSnwgKA3hKXONC5wmly/s1600/S3_65.png" /></a></div><br />
<br />
¿Lo han notado? Y no, no me refiero a mi velocidad de descarga (aunque a mí no me agrada mucho que digamos ^^). El cuadro de descarga nos dice que no conoce el tamaño del archivo que estamos enviando al cliente, solo sabe la cantidad de información que ya ha descargado. Esto es muy molesto para los clientes, porque no saben cuánto tiempo deberán esperar para descargar su archivo.<br />
<br />
Remediemos este pequeño error, para eso usaremos un parámetro más que nos proporciona el result "<span class="codigo">stream</span>", "<span class="codigo">contentLength</span>". Este parámetro, como su nombre lo indica, permite especificar cuál es el tamaño del archivo que estamos enviando, para que el navegador pueda colocar la barra de progreso de descarga del archivo de forma correcta. <br />
<br />
Para usar esta propiedad deberemos hacer unos pequeños cambios en nuestro código. Antes que nada debemos agregar un atributo, con su correspondiente <span class="codigo">getter</span>, que contenga el número de bytes que pesa nuestro archivo:<br />
<br />
<pre><code>
private long bytesArchivo;
public long getBytesArchivo()
{
return bytesArchivo;
}
</code></pre><br />
<br />
Ahora debemos modificar el código del método "<span class="codigo">execute</span>" para que obtenga el dato anterior, para eso haremos uso de otro de los constructores de la clase "<span class="codigo">FileInputStream</span>" que hemos estado usando para leer el archivo. Este segundo constructor recibe, en vez de la ruta en la que se encuentra el archivo a leer, un objeto de tipo "<span class="codigo">File</span>" que representa el archivo; por lo que ahora crearemos un objeto de tipo "<span class="codigo">File</span>" y se lo pasaremos al constructor de "<span class="codigo">FileInputStream</span>":<br />
<br />
<pre><code>
@Override
public String execute() throws Exception
{
String ruta = ServletActionContext.getServletContext().getRealPath("/Ingenieria de software.pdf");
File archivo = new File(ruta);
inputStream = new FileInputStream(archivo);
return SUCCESS;
}
</code></pre><br />
<br />
¿Por qué hemos hecho esto? Bueno, porque la clase "<span class="codigo">File</span>" contiene un método que nos permite obtener justamente el tamaño del archivo, de la siguiente forma:<br />
<br />
<pre><code>
@Override
public String execute() throws Exception
{
String ruta = ServletActionContext.getServletContext().getRealPath("/Ingenieria de software.pdf");
File archivo = new File(ruta);
bytesArchivo = archivo.length();
inputStream = new FileInputStream(archivo);
return SUCCESS;
}
</code></pre><br />
<br />
Con esto hemos terminado con las modificaciones al método "<span class="codigo">execute</span>" y lo único que falta es agregar el parámetro correspondiente a nuestro <span class="codigo">result</span>, para esto nuevamente haremos uso de una expresión:<br />
<br />
<pre><code>
@Result(type = "stream", params =
{
"contentType", "application/pdf",
"contentDisposition","attachment;filename=\"${nombreArchivo}\"",
"contentLength", "${bytesArchivo}"
})
</code></pre><br />
<br />
Con archivos <span class="codigo">XML</span> queda de la siguiente forma:<br />
<br />
<pre><code>
<result type="stream">
<param name="contentType">application/pdf</param>
<param name="contentDisposition">attachment;filename="${nombreArchivo}"</param>
<param name="contentLength">${bytesArchivo}</param>
</result>
</code></pre><br />
<br />
Y esto es todo. Ahora cuando volvamos a intentar descargar nuestro archivo obtendremos el siguiente dialogo de descargar:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgl2SEPqY7TQF8dFQ7IqF39bWeepBsbwcgn2C2hr8lpxaImleGPLH0h5dwozET959R9lm4b45vpg1Oc9isXsXEErejJ4oyRolB8q6q6c7CMxZbcnQ7TVuMvv6TCFsQdaX8t3uxPfQIOlv3G/s1600/S3_66.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="227" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgl2SEPqY7TQF8dFQ7IqF39bWeepBsbwcgn2C2hr8lpxaImleGPLH0h5dwozET959R9lm4b45vpg1Oc9isXsXEErejJ4oyRolB8q6q6c7CMxZbcnQ7TVuMvv6TCFsQdaX8t3uxPfQIOlv3G/s400/S3_66.png" width="400" /></a></div><br />
<br />
En esta ocasión se indica el tamaño del archivo y el tiempo que hace falta para concluir con la descarga, por lo que nuestro segundo ejemplo ha funcionado correctamente ^^.<br />
<br />
Ahora pasaremos a ver el tercer y último ejemplo. En este veremos un uso aún más común del <span class="codigo">result</span> "<span class="codigo">stream</span>": <span class="negritas">enviar imágenes del servidor al navegador y mostrárselas al usuario</span>.<br />
<br />
Para este último ejemplo del tutorial crearemos una nueva clase llamada "<span class="codigo">GeneradorImagenes</span>", dentro del paquete "<span class="codigo">actions</span>", esta clase extenderá de "<span class="codigo">ActionSupport</span>":<br />
<br />
<pre><code>
public class GeneradorImagen extends ActionSupport
{
}
</code></pre><br />
<br />
Como regresaremos un flujo de bytes al usuario, debemos declarar una variable de tipo "<span class="codigo">InputStream</span>" con su correspondiente <span class="codigo">getter</span>. El identificador de esta variable puede ser cualquiera que queramos, en mi caso lo llamaré "<span class="codigo">imagenDinamica</span>":<br />
<br />
<pre><code>
private InputStream imagenDinamica;
public InputStream getImagenDinamica()
{
return imagenDinamica;
}
</code></pre><br />
<br />
Ahora viene la parte interesante, generaremos la imagen de manera dinámica y la guardaremos de forma que al ser enviada al navegador este pueda entenderla para mostrarla. <br />
<br />
Sobre-escribimos el método "<span class="codigo">execute</span>" e indicamos cuál será el alto y el ancho de nuestra imagen:<br />
<br />
<pre><code>
@Override
public String execute() throws Exception
{
final int ANCHO_IMAGEN = 260;
final int ALTO_IMAGEN = 130;
}
</code></pre><br />
<br />
El hecho de que las variables estén marcadas como <span class="codigo">final</span> es solo para asegurarme de que su valor no cambie durante la ejecución del método.<br />
<br />
Ahora crearemos un objeto que nos permita manipular de una forma sencilla imágenes generadas por nosotros mismos. Para esto haremos uso de la clase "<span class="codigo">BufferedImage</span>". Esta clase recibe en su constructor el ancho y alto de la imagen, además de indicar el tipo de la imagen que será creada, en nuestro caso será una sencilla imagen en <span class="codigo">RGB</span>:<br />
<br />
<pre><code>
BufferedImage imagen = new BufferedImage(ANCHO, ALTO, BufferedImage.TYPE_INT_RGB);
</code></pre><br />
<br />
Existen muchas formas de establecer los colores de cada uno de los pixeles de una "<span class="codigo">BufferedImage</span>": podemos obtener un objeto "<span class="codigo">Graphics2D</span>" usando su método "<span class="codigo">createGraphics()</span>" y posteriormente dibujar sobre este usando el API 2D de Java. Aunque la forma más rápida para este ejemplo es usar directamente su método "<span class="codigo">setRGB</span>". A este método se le indica la posición del pixel que queremos establecer, en coordenadas "x,y", y el color del pixel en exadecimal, o sea "<span class="codigo">0xFF0000</span>" para el rojo, "<span class="codigo">0x00FF00</span>" para el verde, y "<span class="codigo">0x0000FF</span>" para el azul.<br />
<br />
Crearemos dos ciclos, uno para recorrer todos las filas de la imagen, y otro para recorrer todas sus columnas. Dentro del ciclo más anidado estableceremos el color del pixel apropiado usando una instancia de la clase "<span class="codigo">Color</span>" que recibe el valor correspondiente al rojo, verde, azul, y la transparencia del color. Los valores deben están dentro del un rango de <span class="codigo">0</span> a <span class="codigo">255</span>, siendo <span class="codigo">0</span> el tono más obscuro (negro) y <span class="codigo">255</span> el más claro (blanco). Si el color se sale del rango la imagen no se mostrará, por lo que usaremos el operador modulo "<span class="codigo">%</span>" para hacer que los valores se queden en este rango. Como el valor del pixel debe ser pasado como un entero, usaremos al final el método "<span class="codigo">getRGB()</span>" de "<span class="codigo">Color</span>" para obtener el valor correspondiente al color que estamos estableciendo:<br />
<br />
<pre><code>
for(int alto = 0; alto < ALTO_IMAGEN; alto++)
{
for(int ancho = 0; ancho < ANCHO_IMAGEN; ancho++)
{
imagen.setRGB(ancho, alto, new Color((ancho*alto)%255, (ancho*alto)%255, (ancho*alto)%255, 255).getRGB());
}
}
</code></pre><br />
<br />
También pudimos haber hecho algo más sencillo como:<br />
<br />
<pre><code>
imagen.setRGB(ancho, alto, ancho*alto);
</code></pre><br />
<br />
Pero no se obtiene el mismo resultado, y es un poco menos claro lo que está pasando ^_^.<br />
<br />
Nuestra imagen ya tiene color, ahora debemos transformarla a un formato que pueda ser entendido por un navegador web. Para esto haremos uso de la clase "<span class="codigo">ImageIO</span>", la cual tiene un método estático, "<span class="codigo">write</span>", que permite convertir las imágenes en formatos "<span class="codigo">png</span>", "<span class="codigo">jpg</span>", "<span class="codigo">gif</span>" y "<span class="codigo">bmp</span>". Este método recibe como parámetros la imagen que queremos dibujar, el formato en el queremos que quede la imagen, y un "<span class="codigo">OutputStream</span>" en donde quedarán los bytes de la imagen.<br />
<br />
Para el ultimo parámetro usaremos un tipo de "<span class="codigo">OutputStream</span>" que nos permita recuperar los bytes de esa imagen de una forma simple, por lo que usaramos un objeto de tipo "<span class="codigo">ByteArrayOutputStream</span>":<br />
<br />
<pre><code>
ByteArrayOutputStream bytesImagen = new ByteArrayOutputStream();
ImageIO.write(imagen, "png", bytesImagen);
</code></pre><br />
<br />
El último paso es crear el "<span class="codigo">InputStream</span>" desde el cual <span class="codigo">Struts 2</span> leerá nuestra imagen. La imagen ya está en un arreglo de bytes, el del objeto de tipo "<span class="codigo">ByteArrayOutputStream</span>", por lo que usaremos un objeto que nos permita usar este arreglo de bytes para crear un "<span class="codigo">InputStream</span>". Usaremos un objeto de tipo "<span class="codigo">ByteArrayInputStream</span>" que en su constructor recibe un arreglo de bytes, en este caso el arreglo de bytes que representa nuestra imagen:<br />
<br />
<pre><code>
imagenDinamica = new ByteArrayInputStream(bytesImagen.toByteArray());
</code></pre><br />
<br />
Eso es todo. Nuestro método "<span class="codigo">execute</span>" queda de la siguiente forma:<br />
<pre><code>
public String execute() throws Exception
{
final int ANCHO_IMAGEN = 260;
final int ALTO_IMAGEN = 130;
BufferedImage imagen = new BufferedImage(ANCHO_IMAGEN, ALTO_IMAGEN, BufferedImage.TYPE_INT_RGB);
for(int alto = 0; alto < ALTO_IMAGEN; alto++)
{
for(int ancho = 0; ancho < ANCHO_IMAGEN; ancho++)
{
imagen.setRGB(ancho, alto, new Color((ancho*alto)%255, (ancho*alto)%255, (ancho*alto)%255, 255).getRGB());
}
}
ByteArrayOutputStream bytesImagen = new ByteArrayOutputStream();
ImageIO.write(imagen, "png", bytesImagen);
imagenDinamica = new ByteArrayInputStream(bytesImagen.toByteArray());
return SUCCESS;
}
</code></pre><br />
<br />
Como ven, es más sencillo de lo que parecía ^_^.<br />
<br />
Para terminar con nuestro <span class="codigo">Action</span> debemos colocar las anotaciones que indican que esta clase debe ser tratada como un <span class="codigo">Action</span> de <span class="codigo">Struts 2</span>. Casi todas ya las hemos visto hasta el cansancio, por lo que solo comentaré que nuestro <span class="codigo">result</span> debe ser de tipo "<span class="codigo">stream</span>" y que le estableceremos dos parámetros: "<span class="codigo">inputName</span>" que indica el nombre de la propiedad de tipo "<span class="codigo">InputStream</span>" que contiene los bytes que serán regresados al usuario, en nuestro caso esta es "<span class="codigo">imagenDinamica</span>". <br />
<br />
El otro parámetro que debemos establecer es el "<span class="codigo">contentType</span>" que indica el formato de nuestra imagen, en este caso "<span class="codigo">image/png</span>". También podríamos indicar el tamaño de la imagen usando el parámetro "<span class="codigo">contentLength</span>", pero esto queda como ejercicio para el lector ^_^!.<br />
<br />
<pre><code>
@Namespace(value = "/descarga")
@Action(value = "imagenGenerada", results =
{
@Result(type = "stream", params =
{
"inputName", "imagenDinamica",
"contentType", "image/png"
})
})
</code></pre><br />
<br />
Con archivos <span class="codigo">XML</span> quedaría de la siguiente forma:<br />
<pre><code>
<result type="stream">
<param name="inputName">imagenDinamica</param>
<param name="contentType">application/pdf</param>
</result>
</code></pre><br />
<br />
La clase "<span class="codigo">GeneradorImagen</span>" queda finalmente de la siguiente forma:<br />
<pre><code>
@Namespace(value = "/descarga")
@Action(value = "imagenGenerada", results =
{
@Result(type = "stream", params =
{
"inputName", "imagenDinamica",
"contentType", "image/png"
})
})
public class GeneradorImagen extends ActionSupport
{
private InputStream imagenDinamica;
@Override
public String execute() throws Exception
{
final int ANCHO_IMAGEN = 260;
final int ALTO_IMAGEN = 130;
BufferedImage imagen = new BufferedImage(ANCHO_IMAGEN, ALTO_IMAGEN, BufferedImage.TYPE_INT_RGB);
for(int alto = 0; alto < ALTO_IMAGEN; alto++)
{
for(int ancho = 0; ancho < ANCHO_IMAGEN; ancho++)
{
imagen.setRGB(ancho, alto, new Color((ancho*alto)%255, (ancho*alto)%255, (ancho*alto)%255, 255).getRGB());
}
}
ByteArrayOutputStream bytesImagen = new ByteArrayOutputStream();
ImageIO.write(imagen, "png", bytesImagen);
imagenDinamica = new ByteArrayInputStream(bytesImagen.toByteArray());
return SUCCESS;
}
public InputStream getImagenDinamica()
{
return imagenDinamica;
}
}
</code></pre><br />
<br />
El último paso consiste en indicar en una <span class="codigo">JSP</span> que debe mostrar una imagen usando este <span class="codigo">Action</span>. Para eso usaremos la etiqueta "<span class="codigo"><img></span>" de <span class="codigo">HTML</span>. Para no crear una nueva <span class="codigo">JSP</span> colocaremos esta etiqueta en la página "<span class="codigo">archivo.jsp</span>" que hemos estado usando. En esta etiqueta debemos indicar que la fuente (el source) de la imagen en nuestro <span class="codigo">Action</span>, de la siguiente forma:<br />
<br />
<pre><code>
<img src="imagenGenerada.action" alt="imagen" />
</code></pre><br />
<br />
Así de simple. Ahora si volvemos a ejecutar nuestra aplicaciones, debemos ver la siguiente salida<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgB4skUzmqHfYShkgwTblntdDF3tTHvFH9nvQLjLv4A7k2tlkKJwWUyTa0zPBLTa_Ow_ys2JntYYJyKKDyU9KhWPYm2iCHvGjHbsgaGwOrSU4Ov89Ky0o43vBw6mbwSNSLj0R1v1OVMccKa/s1600/S3_67.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="173" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgB4skUzmqHfYShkgwTblntdDF3tTHvFH9nvQLjLv4A7k2tlkKJwWUyTa0zPBLTa_Ow_ys2JntYYJyKKDyU9KhWPYm2iCHvGjHbsgaGwOrSU4Ov89Ky0o43vBw6mbwSNSLj0R1v1OVMccKa/s400/S3_67.png" width="400" /></a></div><br />
<br />
No es la mejor imagen del mundo, pero es dinámica y es nuestra ^_^<br />
<br />
Con esto damos fin a este largo tutorial en el que hemos visto todo lo que debemos saber para el trabajo de formularios con <span class="codigo">Struts 2</span>.<br />
<br />
Cualquier duda, comentario, sugerencia, aclaración o corrección pueden dejar un comentario o enviar un correo a "<span class="codigo"><a href="mailto:programadorjavablog@gmail.com">programadorjavablog@gmail.com</a></span>".<br />
<br />
Saludos y gracias.<br />
<br />
<span class="negritas">Descarga los archivos de este tutorial desde aquí:</span><br />
<br />
<ul><li><a href="https://sites.google.com/site/javatutoriales/struts2/TrabajoconFormularios.zip?attredirects=0&d=1"><span class="ligaArchivo">Trabajo con Formularios</span></a></li>
</ul><br />
<br />
<span class="negritas">Entradas Relacionadas:</span><br />
<br />
<ul><li><a href="http://www.javatutoriales.com/2011/06/struts-2-parte-1-configuracion.html">Parte 1: Configuración</a></li>
<li><a href="http://www.javatutoriales.com/2011/06/struts-2-parte-2-ognl.html">Parte 2: OGNL</a></li>
<li><a href="http://www.javatutoriales.com/2011/12/struts-2-parte-4-scopes-de-objetos-web.html">Parte 4: Scopes de Objetos Web</a></li>
<li><a href="http://www.javatutoriales.com/2012/04/struts-2-parte-5-tipos-de-results.html">Parte 5: Tipos de Results</a></li>
<li><a href="http://www.javatutoriales.com/2012/06/struts-2-parte-6-interceptores.html">Parte 6: Interceptores</a></li>
</ul></div>Alexhttp://www.blogger.com/profile/06974037481671868076noreply@blogger.comtag:blogger.com,1999:blog-2012232069289957092.post-43370640501895939012011-06-21T21:39:00.000-07:002012-06-29T07:38:53.948-07:00Struts 2 - Parte 2: OGNL<div style="text-align: justify;"><span class="codigo">OGNL</span> es el acrónimo de <span class="codigo">Object Graph Navigation Language</span>, un lenguaje de expresiones muy poderoso que nos permite leer valores de objetos Java. Este lenguaje nos permite leer valores y ejecutar métodos (que regresen algún valor) para mostrar los valores o resultados de los mismos en nuestras páginas <span class="codigo">JSP</span> creadas usando las etiquetas de <span class="codigo">Struts</span>. Además proporciona una conversión automática de tipos que permite convertir datos desde texto <span class="codigo">HTTP</span> a objetos Java.<br />
<br />
En este tutorial aprenderemos a usar este sencillo pero poderoso lenguaje dentro de nuestras aplicaciones, así como los objetos implícitos que tiene y cómo acceder a ellos. Además veremos cómo obtener valores de constantes, variables, y elementos enumerados, que se encuentran en nuestras clases.<br />
<br />
<a name='more'></a><span class="codigo">Struts</span> no funciona exactamente usando una versión estándar de <span class="codigo">OGNL</span>, usa una versión propia a la que agrega ciertas características interesantes.<br />
<br />
<span class="codigo">OGNL</span> usa un contexto estándar de nombres para evaluar las expresiones, esto quiere decir que dependiendo de qué tan "<span class="negritas">profundo</span>" esté nuestro objeto en el grafo, podremos hacer referencia a él de distintas formas. El objeto de más alto nivel en <span class="codigo">OGNL</span> es un <span class="codigo">Map</span>, al cual llamamos "<span class="negritas">mapa de contexto</span>" (<span class="codigo">context map</span>) o simplemente "<span class="negritas">contexto</span>".<br />
<br />
<span class="codigo">OGNL</span> maneja siempre <span class="negritas">un objeto raíz</span> dentro del contexto. Este objeto raíz <span class="negritas">es el objeto default al que se hacen las llamadas</span>, a menos que se indique lo contrario. Cuando usamos una expresión, <span class="negritas">las propiedades del objeto raíz pueden ser referenciadas sin ninguna marca especial</span>, esto quiere decir que si nuestro objeto tiene una propiedad llamada "<span class="codigo">nombre</span>", hacemos referencia a él simplemente con la expresión "<span class="codigo">nombre</span>". Las referencias a otros objetos son marcadas con un signo de número (<span class="codigo">#</span>).<br />
<br />
Para entender esto veamos un ejemplo de cómo funciona esto en el <span class="codigo">OGNL</span> estándar. Supongamos que hay dos objetos en el mapa de contexto de <span class="codigo">OGNL</span>: "<span class="codigo">foo</span>" y "<span class="codigo">bar</span>", y que el objeto "<span class="codigo">foo</span>" <span class="negritas">es el objeto raíz</span>. El siguiente código muestra cómo resuelve <span class="codigo">OGNL</span> los valores pedidos:<br />
<br />
<pre><code>
#foo.blah //regresa foo.getBlah()
#bar.blah //regresa bar.getBlah()
blah //regresa foo.getBlah(), porque foo es la raíz
</code></pre><br />
<br />
Esto quiere decir que <span class="codigo">OGNL</span> permite que haya varios objetos en el contexto, pero solo podemos acceder a los miembros del objeto raíz directamente. También es importante mencionar aquí que cuando hacemos referencia a una propiedad como "<span class="codigo">blah</span>", <span class="codigo">OGNL</span> buscará un método "<span class="codigo">getBlah()</span>", que regrese algún valor y no reciba parámetros, para obtener el valor que mostrará.<br />
<br />
Cuando queremos invocar métodos usamos el nombre del método junto con paréntesis, como en una invocación normal de Java. En el caso anterior pudimos haber hecho algo como lo siguiente:<br />
<br />
<pre><code>
#foo.getBlah()
#bar.getBlah()
</code></pre><br />
<br />
En el <span class="codigo">OGNL</span> estándar solo se tiene una raíz, sin embargo en el <span class="codigo">OGNL</span> de <span class="codigo">Struts</span> 2 se tiene un "<span class="codigo">ValueStack</span>", el cual permite simular la existencia de varias raíces. Todos los objetos que pongamos en el "<span class="codigo">ValueStack</span>" se comportarán como la raíz del mapa de contexto.<br />
<br />
En el caso de <span class="codigo">Struts</span> 2, el framework establece el contexto como un objeto de tipo "<span class="codigo">ActionContext</span>", que es el contexto en el cual se ejecuta un Action (cada contexto es básicamente un contenedor de objetos que un Action necesita para su ejecución, como los objetos "<span class="codigo">session</span>", "<span class="codigo">parameters</span>", "<span class="codigo">locale</span>", etc.), y el "<span class="codigo">ValueStack</span>" como el objeto raíz. El "<span class="codigo">ValueStack</span>" es un conjunto de muchos objetos, pero para <span class="codigo">OGNL</span> este aparenta ser solo uno.<br />
<br />
Debido al "<span class="codigo">ValueStack</span>", al que algunas veces llamamos solo "<span class="codigo">stack</span>", en vez de que nuestras expresiones tengan que obtener el objeto que queremos del <span class="codigo">stack</span> y después obtener las propiedades de él (como en el ejemplo de <span class="codigo">#bar.blah</span>), el <span class="codigo">OGNL</span> de <span class="codigo">Struts 2</span> tiene un "<span class="codigo">PropertyAccessor</span>" especial que buscará automáticamente en todos los objetos del <span class="codigo">stack</span> (de arriba a abajo) hasta que encuentre un objeto con la propiedad que estamos buscando.<br />
<br />
Siempre que <span class="codigo">Struts</span> 2 ejecuta uno de nuestros Actions, los coloca en la cima del <span class="codigo">stack</span>, es por eso que en <a href="http://www.javatutoriales.com/2011/06/struts-2-parte-1-configuracion.html">el tutorial anterior</a> hacíamos referencia a la propiedad llamada "<span class="codigo">mensaje</span>", del Action correspondiente, solamente indicando el nombre de la propiedad. <span class="codigo">Struts 2</span> busca en el <span class="codigo">stack</span> un objeto que tenga un <span class="codigo">getter</span> para esa propiedad, en este caso nuestro Action.<br />
<br />
Veamos otro ejemplo. Supongamos que el <span class="codigo">stack</span> contiene dos objetos: "<span class="codigo">Animal</span>" y "<span class="codigo">Persona</span>". Ambos objetos tienen una propiedad "<span class="codigo">nombre</span>", "<span class="codigo">Animal</span>" tiene una propiedad "<span class="codigo">raza</span>", y "<span class="codigo">Persona</span>" tiene una propiedad "<span class="codigo">salario</span>". "<span class="codigo">Animal</span>" está en la cima del <span class="codigo">stack</span>, y "<span class="codigo">Persona</span>" está debajo de él. Si hacemos llamadas simples, como las mostradas a continuación, <span class="codigo">OGNL</span> resuelve los valores de la siguiente forma:<br />
<br />
<pre><code>
raza //llama a animal.getRaza()
salario //llama a persona.getSalario()
nombre //llama a animal.getNombre(), porque animal está en la cima del stack
</code></pre><br />
<br />
En el ejemplo anterior, se regresó el valor del "<span class="codigo">nombre</span>" del <span class="codigo">Animal</span>, que está en la cima del <span class="codigo">stack</span>. Normalmente este es el comportamiento deseado, pero algunas veces nos interesa recuperar el valor de la propiedad de un objeto que se encuentra más abajo en el <span class="codigo">stack</span>. Para hacer esto, <span class="codigo">Struts 2</span> agrega soporte para índices en el <span class="codigo">ValueStack</span>. Todo lo que debemos hacer es:<br />
<br />
<pre><code>
[0].nombre //llama a animal.getNombre()
[1].nombre //llama a persona.getNombre()
</code></pre><br />
<br />
Con esto le indicamos a <span class="codigo">OGNL</span> a partir de cuál índice queremos que inicie la búsqueda (digamos que cortamos el <span class="codigo">stack</span> a partir del índice que le indicamos).<br />
<br />
Suficiente teoría <span class="codigo">:D</span>. Comencemos a ver lo anterior en código. Crearemos un nuevo proyecto web en <span class="codigo">NetBeans</span>. Para esto vamos al menú "<span class="codigo">File -> New Project...</span>". En la ventana que aparece seleccionamos la categoría "<span class="codigo">Java Web</span>" y en el tipo de proyecto "<span class="codigo">Web Application</span>":<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjw1SScb7rwpSnbxH7wdGtu8NhBVlX28Ny8IbyOQikA6wlK8Gf121qRGOqILgDfXN0rghcCnnSoc60hL3Pit5PA8FOxK01A-f-IYvDvfG6ZpBMiAUkvBiEjYKsaOOn_Qk9IN284WM6HwYGr/s1600/S2_1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="273" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjw1SScb7rwpSnbxH7wdGtu8NhBVlX28Ny8IbyOQikA6wlK8Gf121qRGOqILgDfXN0rghcCnnSoc60hL3Pit5PA8FOxK01A-f-IYvDvfG6ZpBMiAUkvBiEjYKsaOOn_Qk9IN284WM6HwYGr/s400/S2_1.png" width="400" /></a></div><br />
<br />
Presionamos el botón "<span class="codigo">Next ></span>" y le damos un nombre y una ubicación a nuestro proyecto; presionamos nuevamente el botón "<span class="codigo">Next ></span>" y en este punto se nos preguntará el servidor que queremos usar. En nuestro caso usaremos el servidor "<span class="codigo">Tomcat 7.0</span>", con la versión 5 de JEE y presionamos el botón "<span class="codigo">Finish</span>":<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi1jkzXvheDsnJg6NV0WIzeFNZyW1-WIGJE43xb9ipGPF83r3NF6LG6E385e_qCQK0IFhirth0iFCmUowagUNAk6dL2tpK_F6uJ34aMYfJt63z0DhYXSA6Qu96vkrL1SAWfeU2b2irK_YP-/s1600/S2_2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="255" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi1jkzXvheDsnJg6NV0WIzeFNZyW1-WIGJE43xb9ipGPF83r3NF6LG6E385e_qCQK0IFhirth0iFCmUowagUNAk6dL2tpK_F6uJ34aMYfJt63z0DhYXSA6Qu96vkrL1SAWfeU2b2irK_YP-/s400/S2_2.png" width="400" /></a></div><br />
<br />
Con esto aparecerá en nuestro editor una página "<span class="codigo">index.jsp</span>".<br />
<br />
Ahora agregamos la librería "<span class="codigo">Struts2Anotaciones</span>" que creamos en <a href="http://www.javatutoriales.com/2011/06/struts-2-parte-1-configuracion.html">el tutorial anterior</a>. Hacemos clic derecho en el nodo "<span class="codigo">Libraries</span>" del panel de proyectos. En el menú que aparece seleccionamos la opción "<span class="codigo">Add Library...</span>". En la ventana que aparece seleccionamos la biblioteca "<span class="codigo">Struts2Anotaciones</span>" y presionamos "<span class="codigo">Add Library</span>". Con esto ya tendremos los jars de <span class="codigo">Struts 2</span> en nuestro proyecto:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgEV9cRL0YqYLHkRiaxn0iWnCB7OTISuDC5iHXiFMipnoGj1fCrKB55bzHU5W1aHcgw2X0nVFmj3Ms-Q6VCGe5Qezrjb-EVfqEICJtLNhPp7D4_tdqEEaPHbnQ1NbcPYDUwHHtNKpkLQL76/s1600/S2_3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="296" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgEV9cRL0YqYLHkRiaxn0iWnCB7OTISuDC5iHXiFMipnoGj1fCrKB55bzHU5W1aHcgw2X0nVFmj3Ms-Q6VCGe5Qezrjb-EVfqEICJtLNhPp7D4_tdqEEaPHbnQ1NbcPYDUwHHtNKpkLQL76/s320/S2_3.png" width="320" /></a></div><br />
<br />
Finalmente abrimos el archivo "<span class="codigo">web.xml</span>" y agregamos la configuración del filtro de <span class="codigo">Struts 2</span>, de la misma forma que lo hicimos en <a href="http://www.javatutoriales.com/2011/06/struts-2-parte-1-configuracion.html">el tutorial anterior</a>:<br />
<br />
<pre><code>
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</code></pre><br />
<br />
Ya tenemos todo listo para comenzar. Lo primero que haremos en este ejemplo es crear un paquete que contendrá nuestras clases "<span class="codigo">Animal</span>" y "<span class="codigo">Persona</span>". En mi caso el paquete se llamará "<span class="codigo">com.javatutoriales.struts2.ognl.modelo</span>". En el agregamos dos clases: "<span class="codigo">Animal</span>" y "<span class="codigo">Persona</span>".<br />
<br />
La clase "<span class="codigo">Animal</span>" tendrá, como habíamos dicho, dos atributos de tipo <span class="codigo">String</span>: "<span class="codigo">nombre</span>" y "<span class="codigo">raza</span>", con sus correspondientes <span class="codigo">setters</span> y <span class="codigo">getters</span>:<br />
<br />
<pre><code>
public class Animal
{
private String nombre;
private String raza;
public String getNombre()
{
return nombre;
}
public void setNombre(String nombre)
{
this.nombre = nombre;
}
public String getRaza()
{
return raza;
}
public void setRaza(String raza)
{
this.raza = raza;
}
}
</code></pre><br />
<br />
La clase "<span class="codigo">Persona</span>" también contendrá dos atributos de tipo <span class="codigo">String</span>: "<span class="codigo">nombre</span>" y "<span class="codigo">salario</span>", con sus correspondientes <span class="codigo">setters</span> y <span class="codigo">getters</span>:<br />
<br />
<pre><code>
public class Persona
{
private String nombre;
private String salario;
public String getNombre()
{
return nombre;
}
public void setNombre(String nombre)
{
this.nombre = nombre;
}
public String getSalario()
{
return salario;
}
public void setSalario(String salario)
{
this.salario = salario;
}
}
</code></pre><br />
<br />
Ahora crearemos el Action que se encargará de poner una instancia de cada una de estas clases en el "<span class="codigo">ValueStack</span>". Creamos una nueva clase llamada "<span class="codigo">StackAction</span>" y hacemos que esta extienda de "<span class="codigo">ActionSupport</span>":<br />
<br />
<pre><code>
public class StackAction extends ActionSupport
{
}
</code></pre><br />
<br />
Agregamos las anotaciones correspondientes, y que explicamos en el tutorial anterior. Haremos que este Action responda al nombre de "<span class="codigo">stack</span>" y que nos envíe a una página llamada"<span class="codigo">/stack.jsp</span>":<br />
<br />
<pre><code>
@Namespace(value="/")
@Action(value="stack", results={@Result(location="/stack.jsp")})
public class StackAction extends ActionSupport
{
}
</code></pre><br />
<br />
Lo siguiente que haremos es sobre-escribir el método "<span class="codigo">execute</span>" del Action para obtener una referencia al <span class="codigo">ValueStack</span>. La forma de hacer esto último es a través de un método estático de la clase "<span class="codigo">ActionContext</span>":<br />
<br />
<pre><code>
@Override
public String execute() throws Exception
{
ValueStack stack = ActionContext.getContext().getValueStack();
}
</code></pre><br />
<br />
Una vez que tenemos esta referencia solo nos resta crear una instancia de cada una de nuestras clases, establecer los valores de sus parámetros, y agregarlos al <span class="codigo">ValueStack</span> usando su método "<span class="codigo">push</span>":<br />
<br />
<pre><code>
@Override
public String execute() throws Exception
{
ValueStack stack = ActionContext.getContext().getValueStack();
Animal animal = new Animal();
animal.setNombre("nombre del animal");
animal.setRaza("perro labrador");
Persona persona = new Persona();
persona.setNombre("nombre de la persona");
persona.setSalario("realmente poco");
stack.push(persona);
stack.push(animal);
return SUCCESS;
}
</code></pre><br />
<br />
<span class="nota">Nota: Por lo regular no agregamos objetos al <span class="codigo">stack</span> de forma manual como lo estamos haciendo para este ejemplo, dejamos que sea <span class="codigo">Struts</span> quien agregue los objetos necesarios de forma automática. Esto solo lo hacemos en casos en los que no queda otra opción... como en este ejemplo ^_^!</span><br />
<br />
Agregamos primero la referencia de la <span class="codigo">Persona</span> y luego la del <span class="codigo">Animal</span> porque, como en toda buena pila, el último elemento que se agregue al <span class="codigo">ValueStack</span> será el que quede en su cima.<br />
<br />
Ahora creamos la página "<span class="codigo">stack.jsp</span>" en el directorio raíz de las páginas web. En esta página indicamos que se usará la biblioteca de etiquetas de <span class="codigo">Struts 2</span>:<br />
<br />
<pre><code>
<%@taglib uri="/struts-tags" prefix="s" %>
</code></pre><br />
<br />
Usando la etiqueta "<span class="codigo"><s:property></span>" mostraremos los valores de "<span class="codigo">raza</span>", "<span class="codigo">salario</span>", y "<span class="codigo">nombre</span>". Al colocarlos de esta forma, <span class="codigo">Struts</span> buscará estos valores en todos los objetos que estén en el <span class="codigo">ValueStack</span>:<br />
<br />
<pre><code>
<ul>
<li><strong>Raza: </strong> <s:property value="raza" /></li>
<li><strong>Salario: </strong> <s:property value="salario" /></li>
<li><strong>Nombre: </strong> <s:property value="nombre" /></li>
</ul>
</code></pre><br />
<br />
Ya está todo listo para correr el ejemplo. Ejecutamos nuestra aplicación, y entramos a la siguiente dirección:<br />
<br />
<pre><code>
<a href="http://localhost:8080/ognl/stack.action">http://localhost:8080/ognl/stack.action</a>
</code></pre><br />
<br />
Deberemos ver una pantalla como la siguiente:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvFE2ixbYZhOe_9ktXQm_dGV0ZJ1KzsnBoO_8lsWhcoG4Kl28xR21GaCjJsflj1GcNVCmRKMvmiuFWPt7IuQeQlwJEykIlSZPTq2g02TenyXdYUDdw9FOeBsT73cob0vKFesvzdF9yB9g_/s1600/S2_4.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="242" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvFE2ixbYZhOe_9ktXQm_dGV0ZJ1KzsnBoO_8lsWhcoG4Kl28xR21GaCjJsflj1GcNVCmRKMvmiuFWPt7IuQeQlwJEykIlSZPTq2g02TenyXdYUDdw9FOeBsT73cob0vKFesvzdF9yB9g_/s400/S2_4.png" width="400" /></a></div><br />
<br />
Como podemos ver, la teoría es correcta ^_^. Al buscar la propiedad "<span class="codigo">raza</span>", la encuentra en el objeto tipo "<span class="codigo">Animal</span>", mostrando el valor de la misma; cuando busca la propiedad salario la encuentra en el objeto de tipo "<span class="codigo">Persona</span>"; y al buscar la propiedad "<span class="codigo">nombre</span>", que tienen ambos objetos, muestra el valor del objeto que se encuentra en la cima del <span class="codigo">stack</span>, o sea el de "<span class="codigo">Animal</span>".<br />
<br />
Ahora hagamos una segunda prueba haciendo uso de los índices del <span class="codigo">ValueStack</span>. Agreguemos las instrucciones que habíamos visto anteriormente: ("<span class="codigo">[0].nombre</span>" y "<span class="codigo">[1].nombre</span>") usando la etiqueta "<span class="codigo">s:property</span>":<br />
<br />
<pre><code>
<ul>
<li><strong>Animal: </strong><s:property value="[0].nombre" /></li>
<li><strong>Persona: </strong><s:property value="[1].nombre" /></li>
</ul>
</code></pre><br />
<br />
Si volvemos a ejecutar nuestra aplicación veremos la siguiente salida:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiRRnzyWwgTTdFM3qLUDt8Ds0oz0pyPdZo7fz2nboSLgE0O8ySh4wyJTSolIv86gYR8SFzWvDbGLQ9V-O1ebI3X4YeKt4dWlFetJderk8PDNVVUqkcBXyypA2xsHlJ1axFESJnVRTK78RG1/s1600/S2_5.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="242" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiRRnzyWwgTTdFM3qLUDt8Ds0oz0pyPdZo7fz2nboSLgE0O8ySh4wyJTSolIv86gYR8SFzWvDbGLQ9V-O1ebI3X4YeKt4dWlFetJderk8PDNVVUqkcBXyypA2xsHlJ1axFESJnVRTK78RG1/s400/S2_5.png" width="400" /></a></div><br />
<br />
Como podemos ver, efectivamente, el uso de índices permite que seleccionemos valores de objetos que se encuentran a una profundidad mayor en el <span class="codigo">ValueStack</span>.<br />
<br />
Solo para recordar. Cuando <span class="codigo">Struts 2</span> ejecuta un Action como consecuencia de una petición, este action es colocado en la cima del <span class="codigo">ValueStack</span>, es por esto que podemos acceder a sus atributos haciendo una llamada directa al nombre del mismo, como lo hicimos en <a href="http://www.javatutoriales.com/2011/06/struts-2-parte-1-configuracion.html">el tutorial anterior</a>.<br />
<br />
Hagamos nuevamente una prueba para explicar un poco más en detalle lo que ocurre en ese caso. Primero crearemos una clase llamada "<span class="codigo">SaludoAction</span>", la cual será igual a la <a href="http://www.javatutoriales.com/2011/06/struts-2-parte-1-configuracion.html">del tutorial anterior</a>:<br />
<br />
<pre><code>
@Namespace("/")
@Action(value = "saludo", results ={@Result(location = "/saludo.jsp")})
public class SaludoAction extends ActionSupport
{
private String mensaje;
@Override
public String execute() throws Exception
{
mensaje = "Hola Mundo!!";
return SUCCESS;
}
public String getMensaje()
{
return mensaje;
}
}
</code></pre><br />
<br />
En este caso nuestro Action tiene un atributo "<span class="codigo">mensaje</span>" que en el método "<span class="codigo">execute</span>" será establecido con un valor. También se proporciona un <span class="codigo">getter</span> para este atributo.<br />
<br />
Crearemos una página <span class="codigo">JSP</span> llamada "<span class="codigo">saludo.jsp</span>", en la raíz de las páginas web de la aplicación. En esta página mostraremos el valor del atributo "<span class="codigo">mensaje</span>" usando la biblioteca de etiquetas de <span class="codigo">Struts 2</span>:<br />
<br />
<pre><code>
<s:property value="mensaje" />
</code></pre><br />
<br />
Ejecutamos la aplicación, y al acceder a la siguiente dirección:<br />
<br />
<pre><code>
<a href="http://localhost:8080/ognl/saludo.action">http://localhost:8080/ognl/saludo.action</a>
</code></pre><br />
<br />
Debemos ver la siguiente página:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgqA6yR5HVGuKEA1LSsPRANqJl_BzPdyKSn4ALRS-ATj6H5zs8BgdH1pSrHyTmk7QzZoDLxeuZLnvXj03gdfk1tzC6-e4ZNDARHyMnIpxBG6bB1YVTgnbBEyGMU70_xAjTYC_djno4jDAIt/s1600/S2_6.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="242" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgqA6yR5HVGuKEA1LSsPRANqJl_BzPdyKSn4ALRS-ATj6H5zs8BgdH1pSrHyTmk7QzZoDLxeuZLnvXj03gdfk1tzC6-e4ZNDARHyMnIpxBG6bB1YVTgnbBEyGMU70_xAjTYC_djno4jDAIt/s400/S2_6.png" width="400" /></a></div><br />
<br />
Una vez más, solo para que quede claro. Cuando realizamos una petición para el Action, el <span class="codigo">DispatcherFilter</span> ejecutará el método "<span class="codigo">execute</span>" del Action, después colocará este Action en la cima del <span class="codigo">ValueStack</span> con lo cual lo tendremos disponible.<br />
<br />
Cuando hacemos la petición para el atributo "<span class="codigo">mensaje</span>", usando la etiqueta "<span class="codigo">s:property</span>", <span class="codigo">Struts</span> busca en el <span class="codigo">ValueStack</span>, de arriba a abajo, un objeto que tenga un método "<span class="codigo">getMensaje()</span>". Como el primer objeto que encuentra con el método "<span class="codigo">getMensaje()</span>" es "<span class="codigo">SaludoAction</span>" (de hecho es el primer objeto en el que busca) ejecuta este método mostrando el valor correspondiente mostrando la pantalla que vimos anteriormente.<br />
<br />
Además del <span class="codigo">ValueStack</span>, <span class="codigo">Struts 2</span> coloca otros objetos en el <span class="codigo">ActionContext</span>, incluyendo <span class="codigo">Map</span>s que representan los contextos de "<span class="codigo">application</span>", "<span class="codigo">session</span>", y "<span class="codigo">request</span>". Una representación de esto puede verse en la siguiente imagen:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiMsZBufegYRug8f5Aymg1Mw1yAJabPqghYdNH_8brmWxziS2n392sTUUImPy0BIkJ5SZwupm56LDG0StIxV3F_hcNaw2iZ4ofBOnh2Natd9VnsXiicbzpFLkZyTHB0T5Z87tllVd6443yf/s1600/S2_7.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="156" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiMsZBufegYRug8f5Aymg1Mw1yAJabPqghYdNH_8brmWxziS2n392sTUUImPy0BIkJ5SZwupm56LDG0StIxV3F_hcNaw2iZ4ofBOnh2Natd9VnsXiicbzpFLkZyTHB0T5Z87tllVd6443yf/s640/S2_7.png" width="640" /></a></div><br />
<br />
Recuerden que para hacer referencia a los objetos del contexto que NO son la raíz <span class="negritas">debemos preceder el nombre del objeto con el símbolo de gato (<span class="codigo">#</span>)</span>.<br />
<br />
Hablaré brevemente de estos objetos:<br />
<br />
<ul><li>"<span class="codigo">application</span>" representa el <span class="codigo">ApplicationContext</span>, o sea, el contexto completo de la aplicación.</li>
<li>"<span class="codigo">session</span>" representa el <span class="codigo">HTTPSession</span>, o sea, la sesión del usuario.</li>
<li>"<span class="codigo">request</span>" representa el <span class="codigo">ServletRequest</span>, o sea, la petición que se está sirviendo.</li>
<li>"<span class="codigo">parameters</span>" representa los parámetros que son enviados en la petición, ya sea por <span class="codigo">GET</span> o por <span class="codigo">POST</span>.</li>
<li>"<span class="codigo">attr</span>" representa los atributos de los objetos implícitos. Cuando preguntamos por un atributo, usando "<span class="codigo">attr</span>", este busca el atributo en los siguientes scopes: <span class="codigo">page</span>, <span class="codigo">request</span>, <span class="codigo">session</span>, <span class="codigo">application</span>. Si lo encuentra, regresa el valor del atributo y no continúa con la búsqueda, sino regresa un valor nulo.</li>
</ul><br />
Veamos un ejemplo de esto. No entraré en muchos detalles sobre el manejo de la sesión ya que eso lo dejaré para el próximo tutorial.<br />
<br />
Creamos una nueva clase Java llamada "<span class="codigo">DatosAction</span>" que extienda de "<span class="codigo">ActionSupport</span>". Colocaremos algunas anotaciones para la configuración de <span class="codigo">Struts 2</span>:<br />
<br />
<pre><code>
@Namespace(value = "/")
@Action(value = "datos", results ={@Result(location = "/datos.jsp")})
public class DatosAction extends ActionSupport
{
}
</code></pre><br />
<br />
Sobre-escribimos el método "<span class="codigo">execute</span>" de esta clase para obtener el <span class="codigo">ActionContext</span>. Una vez teniendo este contexto podemos obtener la sesión y podemos agregar objetos a ella. En este caso agregaré una cadena solo para poder obtenerla después:<br />
<br />
<pre><code>
@Override
public String execute() throws Exception
{
ActionContext.getContext().getSession().put("datoSesion", "dato en la sesion");
return SUCCESS;
}
</code></pre><br />
<br />
Ahora creamos una <span class="codigo">JSP</span> llamada "<span class="codigo">datos.jsp</span>" en el directorio raíz de las páginas web. En esta página indicaremos que usaremos la biblioteca de etiquetas de <span class="codigo">Struts 2</span>; y obtendremos, usando la etiqueta "<span class="codigo"><s:property></span>", el valor del atributo "<span class="codigo">datoSesion</span>" que colocamos en la sesión del usuario.<br />
<br />
Como el objeto "<span class="codigo">session</span>" no es la raíz del mapa de contexto, es necesario hacer referencia a él de la siguiente forma:<br />
<br />
<pre><code>
#session
</code></pre><br />
<br />
Y podemos obtener cualquiera de sus atributos mediante el nombre del atributo. Por lo que la etiqueta queda de la siguiente forma:<br />
<br />
<pre><code>
<s:property value="#session.datoSesion" />
</code></pre><br />
<br />
También podemos hacerlo de esta otra forma:<br />
<br />
<pre><code>
<s:property value="#session['datoSesion']" />
</code></pre><br />
<br />
Cuando ejecutemos la aplicación debemos ver la siguiente salida:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg3yXTtzfO6Yi_qjTRTTFX7hdYonwcfllgjA7eyxmHah42mvFxouwfoivTSGf0YvVOMageeOIhet8pokI8JOwoZiFuuN8qbZBIKVJN7HZyo17xjBubNp1IviQ3BxjRE6Be0MfjS39FvU14p/s1600/S2_8.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="242" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg3yXTtzfO6Yi_qjTRTTFX7hdYonwcfllgjA7eyxmHah42mvFxouwfoivTSGf0YvVOMageeOIhet8pokI8JOwoZiFuuN8qbZBIKVJN7HZyo17xjBubNp1IviQ3BxjRE6Be0MfjS39FvU14p/s400/S2_8.png" width="400" /></a></div><br />
<br />
Ahora, dijimos que "<span class="codigo">parameters</span>" representa los parámetros que se reciben, ya sea por <span class="codigo">GET</span> o por <span class="codigo">POST</span>.<br />
<br />
Colocaremos una etiqueta para obtener el valor de un parámetro llamado dato:<br />
<br />
<pre><code>
<s:property value="#parameters.dato" />
</code></pre><br />
<br />
Y accedemos a la siguiente <span class="codigo">URL</span>:<br />
<br />
<pre><code>
<a href="http://localhost:8080/ognl/datos.action?dato=Alex">http://localhost:8080/ognl/datos.action?dato=Alex</a>
</code></pre><br />
<br />
En donde estamos pasando, por <span class="codigo">GET</span>, un parámetro llamado "<span class="codigo">dato</span>" igualado al valor "<span class="codigo">Alex</span>". Cuando entremos en la dirección anterior veremos una pantalla como la siguiente:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgVHp8gtabRS_V2qxA7T1EkX8nNeru605MUUCspUA1OGNTUHM2kOG8IA8qKOqr6ICZ5SpFNC4_GOnK_MBlhELVVhcSwYgRzzdTn0-tO_CLHOkkAzomLEEtohXXFdVZF8qObobvtTwcIVLRS/s1600/S2_9.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="242" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgVHp8gtabRS_V2qxA7T1EkX8nNeru605MUUCspUA1OGNTUHM2kOG8IA8qKOqr6ICZ5SpFNC4_GOnK_MBlhELVVhcSwYgRzzdTn0-tO_CLHOkkAzomLEEtohXXFdVZF8qObobvtTwcIVLRS/s400/S2_9.png" width="400" /></a></div><br />
<br />
Si ahora, usamos el objeto "<span class="codigo">attr</span>" para buscar el valor del atributo "<span class="codigo">datoSesion</span>", de la siguiente forma:<br />
<br />
<pre><code>
<s:property value="#attr.datoSesion" />
</code></pre><br />
<br />
Obtendremos la siguiente pantalla:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhsezfR3QHH17RKP_s9GiCZYaBo_wUVAQq4y3BuGLYKH2iGnvvIyuCZI-J4ioyjTcCrLaPbETlQcFmQQCf8cghXcUsnG6mFa6_R4o27ISqrLa8JdiNcJReOqZEhMmnwJ1-EJiIt2xeZ_E6I/s1600/S2_10.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="242" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhsezfR3QHH17RKP_s9GiCZYaBo_wUVAQq4y3BuGLYKH2iGnvvIyuCZI-J4ioyjTcCrLaPbETlQcFmQQCf8cghXcUsnG6mFa6_R4o27ISqrLa8JdiNcJReOqZEhMmnwJ1-EJiIt2xeZ_E6I/s400/S2_10.png" width="400" /></a></div><br />
<br />
Con <span class="codigo">OGNL</span> no solo es posible acceder a los objetos dentro del <span class="codigo">ActionContext</span>, sino prácticamente a cualquier objeto java que sea visible. Para ilustrar esto veamos otro ejemplo.<br />
<br />
<br />
<h2 class="titulo">Obteniendo Valores de Constantes y Variables con <span class="codigo">OGNL</span></h2>Primero crearemos una nueva clase Java que contendrá una serie de atributos, métodos, y constantes. La clase se llamará "<span class="codigo">Constantes</span>" (aunque también contendrá valores variables). Esta clase tendrá una variable de instancia llamada "<span class="codigo">atributo</span>" (con su correspondiente <span class="codigo">getter</span>), una constante llamada "<span class="codigo">valor</span>", y una enumeración llamada "<span class="codigo">Datos</span>" que tendrá un método "<span class="codigo">getDato()</span>":<br />
<br />
<pre><code>
public class Constantes
{
private String atributo = "atributo de instancia";
public final static String valor = "variable estatica";
private static enum Datos {PRIMERO, SEGUNDO, TERCERO; public String getDato(){ return "dato";} };
public String getAtributo()
{
return atributo;
}
}
</code></pre><br />
<br />
Sé que el atributo "<span class="codigo">valor</span>" no sigue la convención de las constantes de Java, pero colocarla de esta forma ayudará a hacer las explicaciones más sencillas ^_^.<br />
<br />
Ahora, para que las cosas sean un poco más interesantes, agregaremos unos cuantos métodos.<br />
<br />
Primero agregaremos un método de instancia que regrese una cadena y no reciba parámetros; y una versión sobrecargada de este método que reciba una cadena:<br />
<br />
<pre><code>
public String metodoDeInstancia()
{
return "metodo de instancia";
}
public String metodoDeInstancia(String mensaje)
{
return mensaje;
}
</code></pre><br />
<br />
Agregaremos también las versiones estáticas de estos métodos:<br />
<br />
<pre><code>
public static String metodoEstatico()
{
return "metodo estatico";
}
public static String metodoEstatico(String mensaje)
{
return mensaje;
}
</code></pre><br />
<br />
La clase "<span class="codigo">Constantes</span>" completa queda de la siguiente forma:<br />
<br />
<pre><code>
public class Constantes
{
private String atributo = "atributo de instancia";
public final static String valor = "variable estatica";
private static enum Datos {PRIMERO, SEGUNDO, TERCERO; public String getDato(){ return "dato";} };
public String metodoDeInstancia()
{
return "metodo de instancia";
}
public String metodoDeInstancia(String mensaje)
{
return mensaje;
}
public static String metodoEstatico()
{
return "metodo estatico";
}
public static String metodoEstatico(String mensaje)
{
return mensaje;
}
public String getAtributo()
{
return atributo;
}
}
</code></pre><br />
<br />
Como podemos ver, esta clase no tiene nada de especial: ninguna anotación, no usa ninguna librería, ni nada por el estilo.<br />
<br />
Ahora creamos una página llamada "<span class="codigo">constantes.jsp</span>", en el directorio raíz de las páginas web.<br />
<br />
Para crear un objeto nuevo con <span class="codigo">OGNL</span> podemos usar el operador "<span class="codigo">new</span>" y el "fully qualified class name". Por ejemplo, para crear un nuevo objeto de la clase "<span class="codigo">Constantes</span>" haríamos lo siguiente:<br />
<br />
<pre><code>
<s:property value="new com.javatutoriales.struts2.ognl.Constantes()" />
</code></pre><br />
<br />
Teniendo esta instancia podemos obtener los valores de los atributos del objeto e invocar sus métodos, como lo haríamos en un objeto normal. Por ejemplo, para obtener el valor del atributo "<span class="codigo">atributo</span>" se puede hacer de la siguiente forma:<br />
<br />
<pre><code>
<s:property value="new com.javatutoriales.struts2.ognl.Constantes().atributo" />
</code></pre><br />
<br />
Con esto, si entramos a la siguiente dirección:<br />
<br />
<pre><code>
<a href="http://localhost:8080/ognl/constantes.jsp">http://localhost:8080/ognl/constantes.jsp</a>
</code></pre><br />
<br />
Obtenemos la siguiente pantalla:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgFWsefH6TdY8c4NGLH9B-Ro7VFxw4EAOFKKOY73zvRR2EcxA9h8Wa3nZq9jOsGy1elN2TzHOJuyxacssq2p0DpAOQmME4ecOW1DoaxYk4BYhMqgdIs-IikqS9ULcO1gyQLRazLylS0JbVT/s1600/S2_11.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="242" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgFWsefH6TdY8c4NGLH9B-Ro7VFxw4EAOFKKOY73zvRR2EcxA9h8Wa3nZq9jOsGy1elN2TzHOJuyxacssq2p0DpAOQmME4ecOW1DoaxYk4BYhMqgdIs-IikqS9ULcO1gyQLRazLylS0JbVT/s400/S2_11.png" width="400" /></a></div><br />
<br />
Para invocar un método de instancia se hace de la misma forma (no olviden colocar los paréntesis al final):<br />
<br />
<pre><code>
<s:property value="new com.javatutoriales.struts2.ognl.Constantes().metodoDeInstancia()" />
</code></pre><br />
<br />
Con esto, obtenemos la siguiente pantalla:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi0uAu-XmkwIWRfBbg7mrqE4hQRKHI-qUTAzB30wI3_6zv7Pq6EAgPmZMb20g2B15ijzT4lyM-5GCuLgWFJMQ7cnbB-CKZR-gBq5MCfi629ObUk4Yy2q2jISdaSXn2p3LNVr17EUZ3uqybo/s1600/S2_12.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="242" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi0uAu-XmkwIWRfBbg7mrqE4hQRKHI-qUTAzB30wI3_6zv7Pq6EAgPmZMb20g2B15ijzT4lyM-5GCuLgWFJMQ7cnbB-CKZR-gBq5MCfi629ObUk4Yy2q2jISdaSXn2p3LNVr17EUZ3uqybo/s400/S2_12.png" width="400" /></a></div><br />
<br />
Podemos invocar los miembros estáticas exactamente de la misma forma:<br />
<br />
<pre><code>
<s:property value="new com.javatutoriales.struts2.ognl.Constantes().metodoEstatico()" />
<s:property value="new com.javatutoriales.struts2.ognl.Constantes().valor" />
</code></pre><br />
<br />
Con esto obtenemos la siguiente pantalla:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgzP8iD3RVWK7m8FJ5DAX-w76qTh_AqUlo8bO5xxRq5JsuK0NH4_4Qo5ysMfsWNz0at2sjD_edllC4tqLvWd6uDIZFRdARj7IAouDWYmcKZRLoFhMVl8a20BNv5QjlF97B9a22GlYC-_PqV/s1600/S2_13.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="242" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgzP8iD3RVWK7m8FJ5DAX-w76qTh_AqUlo8bO5xxRq5JsuK0NH4_4Qo5ysMfsWNz0at2sjD_edllC4tqLvWd6uDIZFRdARj7IAouDWYmcKZRLoFhMVl8a20BNv5QjlF97B9a22GlYC-_PqV/s400/S2_13.png" width="400" /></a></div><br />
<br />
El valor de la constante "<span class="codigo">valor</span>" no se muestra, porque recuerden que cuando llamamos a los atributos de esta forma:<br />
<br />
<pre><code>
<s:property value="new com.javatutoriales.struts2.ognl.Constantes().valor" />
</code></pre><br />
<br />
<span class="codigo">OGNL</span> busca un método llamado "<span class="codigo">getValor()</span>" en la clase "<span class="codigo">Constantes</span>", y este método no existe.<br />
<br />
Ahora bien, una de las características de los miembros estáticos es que no es necesario tener una instancia de la clase en la que existe el miembro para poder llamar a estos miembros, pero aquí estamos creando una nueva instancia de las clases para llamarlos. También es posible hacer estas llamadas sin una instancia de la clase. En este caso debemos usar una notación especial de <span class="codigo">OGNL</span>.<br />
<br />
En esta notación, debemos indicar el nombre completo de la clase que contiene al miembro estático, precedida por una arroba ("<span class="codigo">@</span>"). También se debe indicar el miembro que se quiere llamar precedido por una arroba.<br />
<br />
Por ejemplo, para usar la constante "<span class="codigo">valor</span>", la etiqueta debe estar colocada de esta forma:<br />
<br />
<pre><code>
<s:property value="@com.javatutoriales.struts2.ognl.Constantes@valor" />
</code></pre><br />
<br />
Con lo que obtenemos:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi4yrj4kMVxFTr7nGfpH-kxppV3bvwREHlP78cF8AYMmJ00fanHkv9P0PQWROMwmMTL_wq58g-XSeQ9m2biuNTU9OTu3tbnZo0L96YEDji70XoCmlbIV8nTosXzVPWn-VoG1BTtShmBW0FF/s1600/S2_14.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="242" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi4yrj4kMVxFTr7nGfpH-kxppV3bvwREHlP78cF8AYMmJ00fanHkv9P0PQWROMwmMTL_wq58g-XSeQ9m2biuNTU9OTu3tbnZo0L96YEDji70XoCmlbIV8nTosXzVPWn-VoG1BTtShmBW0FF/s400/S2_14.png" width="400" /></a></div><br />
<br />
Y para invocar el método estático, de esta forma:<br />
<br />
<pre><code>
<s:property value="@com.javatutoriales.struts2.ognl.Constantes@metodoEstatico()" />
</code></pre><br />
<br />
Con esta etiqueta obtenemos la siguiente salida:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhWAPUpOotvvmFlKAPIKiVkGQHPazx585JiKVG9BNrpX8CTOSjLZFTPA90X_-lLly5UDcwDyG0XOct03dPRcsNwEg7n6clUyzcfQlphkl_b4pUameaR4lNpp4OwJZjvrKz9QTuzQ_zEFj40/s1600/S2_15.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="242" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhWAPUpOotvvmFlKAPIKiVkGQHPazx585JiKVG9BNrpX8CTOSjLZFTPA90X_-lLly5UDcwDyG0XOct03dPRcsNwEg7n6clUyzcfQlphkl_b4pUameaR4lNpp4OwJZjvrKz9QTuzQ_zEFj40/s400/S2_15.png" width="400" /></a></div><br />
<br />
Bueno, en este caso parece que también ha habido un problema al invocar el método estático ^_^. En realidad lo que ocurre es que por default <span class="codigo">Struts 2</span> impide la invocación de métodos estáticos desde <span class="codigo">OGNL</span>. <br />
<br />
¿Entonces qué podemos hacer? Pues <span class="codigo">Struts</span> proporciona una forma para habilitar esta opción. Podemos colocar el valor de la constante "<span class="codigo">struts.ognl.allowStaticMethodAccess</span>" en "<span class="codigo">true</span>". <br />
<br />
<a href="http://www.javatutoriales.com/2011/06/struts-2-parte-1-configuracion.html">En el tutorial anterior</a> hablamos un poco de la constantes (muy poco, casi nada ^_^!), y declaramos un par de ellas en el archivo "<span class="codigo">struts.xml</span>". Sin embargo, ahora no tenemos un archivo "<span class="codigo">struts.xml</span>" ya que estamos usando anotaciones. Entonces ¿qué podemos hacer? (parece que salimos de un problema para entrar en otro, ¿no les ha pasado?).<br />
<br />
Las constantes en <span class="codigo">Struts 2</span> pueden ser declaradas en tres lugares:<br />
<br />
<ul><li>El archivo de configuración "<span class="codigo">struts.xml</span>".</li>
<li>El archivo "<span class="codigo">struts.properties</span>" (del que aún no hemos hablado).</li>
<li>Como un parámetro de inicio en el deployment descriptor (el archivo "<span class="codigo">web.xml</span>").</li>
</ul><br />
Como de los tres archivos anteriores solo tenemos el archivo "<span class="codigo">web.xml</span>" será aquí en donde agregamos esta constante. Para esto debemos modificar la declaración del filtro de <span class="codigo">Struts 2</span>, dejándola de la siguiente forma:<br />
<br />
<pre><code>
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
<init-param>
<param-name>struts.ognl.allowStaticMethodAccess</param-name>
<param-value>true</param-value>
</init-param>
</filter>
</code></pre><br />
<br />
Ahora sí, si ejecutamos nuevamente el ejemplo debemos ver la siguiente salida:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgyHnjDXmbR7KWqGyoM-lHsjkWHhe0OswlbwycsZkfsPDiT6XSJE27mPdY2VgDLNUpdo10EL2H-vuct9bfDVz7l9EUUTikcX1wATodJDlnfdjPxKX0x9YWjq1O4-nnUeoKZzJlqMfHSWcEs/s1600/S2_16.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="242" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgyHnjDXmbR7KWqGyoM-lHsjkWHhe0OswlbwycsZkfsPDiT6XSJE27mPdY2VgDLNUpdo10EL2H-vuct9bfDVz7l9EUUTikcX1wATodJDlnfdjPxKX0x9YWjq1O4-nnUeoKZzJlqMfHSWcEs/s400/S2_16.png" width="400" /></a></div><br />
<br />
Podemos ver que en este caso, se pudo obtener el valor de la variable estática de forma directa.<br />
<br />
Para invocar las versiones de los métodos que reciben un parámetro basta con pasar el parámetro que esperan recibir:<br />
<br />
<pre><code>
<s:property value="@com.javatutoriales.struts2.ognl.Constantes@metodoEstatico('Hola Amigos Estaticos')" />
<s:property value="new com.javatutoriales.struts2.ognl.Constantes().metodoDeInstancia('Hola Amigos')" />
</code></pre><br />
<br />
Inclusive podemos pasar como parámetro uno de los atributos obtenidos anteriormente:<br />
<br />
<pre><code>
<li><s:property value="new com.javatutoriales.struts2.ognl.Constantes().metodoDeInstancia(@com.javatutoriales.struts2.ognl.Constantes@valor)" /></li>
</code></pre><br />
<br />
Con estos cambios obtenemos la siguiente pantalla:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgIcLYfISSu7TdlcWQRHV-Bd93HSpfzMps56qGwseHdscJC0Yyy63pFWhXqZ3ulzIeWftEVXTPryV7XLVEkalDkQEbaAEE4twSDmg3HFjz7EiWm69R1UYbmfG3TnylJ15ukfoHIn1dnDt4E/s1600/S2_17.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="242" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgIcLYfISSu7TdlcWQRHV-Bd93HSpfzMps56qGwseHdscJC0Yyy63pFWhXqZ3ulzIeWftEVXTPryV7XLVEkalDkQEbaAEE4twSDmg3HFjz7EiWm69R1UYbmfG3TnylJ15ukfoHIn1dnDt4E/s400/S2_17.png" width="400" /></a></div><br />
<br />
Otra parte interesante del uso de <span class="codigo">OGNL</span> viene cuando queremos obtener los valores de las constantes de una enumeración. Si recuerdan, nuestra clase "<span class="codigo">Constantes</span>" tiene una enumeración llamada "<span class="codigo">Datos</span>". Como cada uno de los valores de una enumeración es una constante (o sea una variable publica, estática, y final) obtener sus valores es igual a obtener cualquier otro valor de una variable estática. O sea que tenemos que usar la sintaxis de la doble arroba.<br />
<br />
Ahora, el secreto aquí viene dado en el sentido de que las enumeraciones son en realidad un tipo especial de clase Java. Como aquí la enumeración está dentro de otra clase, la primera se convierte en una clase interna de la segunda. El fully qualified class name de una clase interna es el nombre de la primer clase, seguida de un signo de dólar (<span class="codigo">$</span>) seguido del nombre de la clase interna. Por lo tanto, el fully qualified class name de la enumeración "<span class="codigo">Datos</span>" es "<span class="codigo">com.javatutoriales.struts2.ognl.Constantes$Datos</span>". Teniendo el nombre de esta enumeración, podemos obtener los valores de sus constes de la siguiente forma:<br />
<br />
<pre><code>
<s:property value="@com.javatutoriales.struts2.ognl.Constantes$Datos@PRIMERO" />
<s:property value="@com.javatutoriales.struts2.ognl.Constantes$Datos@SEGUNDO" />
</code></pre><br />
<br />
E igual que en los casos anteriores, teniendo estos valores podemos invocar cualquier método que exista dentro de la enumeración, como en este caso el método "<span class="codigo">getDato()</span>" que invocamos de esta forma:<br />
<br />
<pre><code>
<s:property value="@com.javatutoriales.struts2.ognl.Constantes$Datos@TERCERO.dato" />
</code></pre><br />
<br />
Que también podría ser de esta forma:<br />
<br />
<pre><code>
<s:property value="@com.javatutoriales.struts2.ognl.Constantes$Datos@TERCERO.getDato()" />
</code></pre><br />
<br />
En cualquiera de los dos casos, la salida obtenida es la siguiente:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg0YOWTqYSQk3cvpvA3OGgq1hZjMOddk1QnQ2pjlON3y3UKvM9vgRXNz1IGC_22Psj-dO27HWQtSMwwV2PQZfB8xX5IhRSR54C9kzrGQupsx6f6O7jSu3hpb5ynTjKW7mw1bnUAfUfsO2YF/s1600/S2_18.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="242" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg0YOWTqYSQk3cvpvA3OGgq1hZjMOddk1QnQ2pjlON3y3UKvM9vgRXNz1IGC_22Psj-dO27HWQtSMwwV2PQZfB8xX5IhRSR54C9kzrGQupsx6f6O7jSu3hpb5ynTjKW7mw1bnUAfUfsO2YF/s400/S2_18.png" width="400" /></a></div><br />
<br />
Por último, veremos que en <span class="codigo">OGNL</span> también se pueden declarar y acceder valores de tipos indexados, en particular de arreglos, listas, y mapas.<br />
<br />
<br />
<h2 class="titulo">Obtenido Valores de Tipos Indexados con <span class="codigo">OGNL</span></h2>Comencemos viendo los arreglos, que son los elementos más "complicados" de definir.<br />
<br />
Cuando queremos definir un arreglo de objetos o de primitivos, debemos usar la misma sintaxis que se usa para definir los arreglos anónimos. Por ejemplo, para definir un arreglo con los enteros del 1 al 5 lo hacemos de la siguiente forma:<br />
<br />
<pre><code>
new int[] {1, 2, 3, 4, 5}
</code></pre><br />
<br />
Colocando esto en la etiqueta "<span class="codigo"><s:property></span>", de esta forma:<br />
<br />
<pre><code>
<s:property value="new int[]{1, 2, 3, 4, 5}" />
</code></pre><br />
<br />
Obtenemos la siguiente salida:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgrCf61iPKhHOLbDbhggaCe09bjla76N2l9sWGVTRYCiXdJ6Bl6rdcoGdDc3cW7C3U_NHCyatZeBtYfAItOLofHPpLJ_tQTj0BAixKtbMj9WXNVwQ0sKjl0RQmj32_Ip-FoLtAtuHebxwr6/s1600/S2_19.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="242" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgrCf61iPKhHOLbDbhggaCe09bjla76N2l9sWGVTRYCiXdJ6Bl6rdcoGdDc3cW7C3U_NHCyatZeBtYfAItOLofHPpLJ_tQTj0BAixKtbMj9WXNVwQ0sKjl0RQmj32_Ip-FoLtAtuHebxwr6/s400/S2_19.png" width="400" /></a></div><br />
<br />
Podemos colocar estos mismos elementos dentro de una etiqueta "<span class="codigo"><s:select></span>", que nos ayuda a crear listas desplegables (o combo boxes, o como quiera que gusten llamarle): <br />
<br />
<pre><code>
<s:select name="valores" list="new int[]{1, 2, 3, 4, 5}" />
</code></pre><br />
<br />
Obteniendo la siguiente salida:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgVPDfKdxT1dWw-8ee0pGNZnry90HG91ncbIkLycRmiMkbFugUnWa5C7tU9gCoAmgToAb8-24kVL2p9WF4eyfpDWn9Yp3o1n1mLopGUdOvOW8gI5eseGzGxeX-yPOzO4gfOoaOCk4QJLjLy/s1600/S2_20.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="242" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgVPDfKdxT1dWw-8ee0pGNZnry90HG91ncbIkLycRmiMkbFugUnWa5C7tU9gCoAmgToAb8-24kVL2p9WF4eyfpDWn9Yp3o1n1mLopGUdOvOW8gI5eseGzGxeX-yPOzO4gfOoaOCk4QJLjLy/s400/S2_20.png" width="400" /></a></div><br />
<br />
Para crear un arreglo con todos sus valores inicializados a <span class="codigo">null</span>, podemos hacerlo de la siguiente forma:<br />
<br />
<pre><code>
new int[5]
</code></pre><br />
<br />
Si por alguna razón no podemos (o no queremos) hacer uso de arreglos, sino de algún tipo de colección, como una lista, podemos también construir una "al vuelo" usando la siguiente sintaxis:<br />
<br />
<pre><code>
{'uno', 'dos', 'tres', 'cuatro', 'cinco'}
</code></pre><br />
<br />
Con esto obtenemos la siguiente salida:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEialCqtXKaDaAVPPO9JKqMnp04Qr-iP40FTLz6haGkYd_92zSrATJ5oOFmchdWdcXh4l1a7PDWC1p23GBJvmBHgWuTBQNv0UClOoyVJo7Z8VbT4TVSLQe8LYRrrZV8lFoQGwujX7s9sBYIS/s1600/S2_21.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="242" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEialCqtXKaDaAVPPO9JKqMnp04Qr-iP40FTLz6haGkYd_92zSrATJ5oOFmchdWdcXh4l1a7PDWC1p23GBJvmBHgWuTBQNv0UClOoyVJo7Z8VbT4TVSLQe8LYRrrZV8lFoQGwujX7s9sBYIS/s400/S2_21.png" width="400" /></a></div><br />
<br />
De la misma forma que con los arreglos, si colocamos esto dentro de una etiqueta "<span class="codigo"><s:select></span>" obtenemos la siguiente salida:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjTSKsoKst3ZG4p2dpM6gtRAnXIS2sRprWSxM3G_03O97pnwSo9pMj2vVUfXLFuzO6vmfhHmoCdKouy4D9aJ2i7dGgrqQABe7m2kNGPdvVP1whOROi2Ulek7fy2M_ndW83fVz2RpZYXDF3-/s1600/S2_22.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="242" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjTSKsoKst3ZG4p2dpM6gtRAnXIS2sRprWSxM3G_03O97pnwSo9pMj2vVUfXLFuzO6vmfhHmoCdKouy4D9aJ2i7dGgrqQABe7m2kNGPdvVP1whOROi2Ulek7fy2M_ndW83fVz2RpZYXDF3-/s400/S2_22.png" width="400" /></a></div><br />
<br />
Finalmente, para crear un <span class="codigo">Map</span>, podemos usar la siguiente sintaxis:<br />
<br />
<pre><code>
#{'uno':'1', 'dos':'2', 'tres':'3', 'cuatro':'4', 'cinco':'5'}
</code></pre><br />
<br />
Donde el primer elemento es la llave, y el segundo es el valor. Con esto obtenemos la siguiente salida:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhL7gDDLb0-Xd6ddKQLAJXZDzuVGKnuihXojUyJ_RhvNQT6U6jeDt_FOm-JcacY5a2ERJDlaBiMTS5WDOT4Pt9hy4-qfrv7WpTwrDzwKfGN4Lrn6F06_thdqSkGHbybrf_7bLHdlvY07AZd/s1600/S2_23.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="242" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhL7gDDLb0-Xd6ddKQLAJXZDzuVGKnuihXojUyJ_RhvNQT6U6jeDt_FOm-JcacY5a2ERJDlaBiMTS5WDOT4Pt9hy4-qfrv7WpTwrDzwKfGN4Lrn6F06_thdqSkGHbybrf_7bLHdlvY07AZd/s400/S2_23.png" width="400" /></a></div><br />
<br />
Igual que en los ejemplos anteriores, si colocamos esto en una etiqueta "<span class="codigo"><s:select></span>", obtenemos la siguiente pantalla:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEik0Q34sBTaL0gpd4b9sm69CtbF6q3XDtj2kggJ1lyf1qp7X9tq_l26uA08hvg7-WgguISD7JStdxTlZsusM6aCvSgfSwlsiHw_RrkpZZcUJZrBAz5m9FzXnbkXz7CfZ9YOEYNHPq9RngkT/s1600/S2_24.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="242" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEik0Q34sBTaL0gpd4b9sm69CtbF6q3XDtj2kggJ1lyf1qp7X9tq_l26uA08hvg7-WgguISD7JStdxTlZsusM6aCvSgfSwlsiHw_RrkpZZcUJZrBAz5m9FzXnbkXz7CfZ9YOEYNHPq9RngkT/s400/S2_24.png" width="400" /></a></div><br />
<br />
Si vemos el código fuente del elemento generado podremos observar que la llave es usada como el atributo "<span class="codigo">value</span>" de cada opción, y el valor se usa como la etiqueta que será mostrada:<br />
<br />
<pre><code>
<select name="mapas" id="mapas">
<option value="uno">1</option>
<option value="dos">2</option>
<option value="tres">3</option>
<option value="cuatro">4</option>
<option value="cinco">5</option>
</select>
</code></pre><br />
<br />
Si por alguna razón necesitamos alguna implementación particular de un <span class="codigo">Map</span>, podemos indicarlo de la siguiente forma:<br />
<br />
<pre><code>
#@java.util.TreeMap@{'uno':'1', 'dos':'2', 'tres':'3', 'cuatro':'4', 'cinco':'5'}
</code></pre><br />
<br />
Por ejemplo, colocando los siguientes elementos:<br />
<br />
<pre><code>
<s:property value="#@java.util.LinkedHashMap@{'uno':'1', 'dos':'2', 'tres':'3', 'cuatro':'4', 'cinco':'5'}" />
<s:property value="#@java.util.TreeMap@{'uno':'1', 'dos':'2', 'tres':'3', 'cuatro':'4', 'cinco':'5'}" />
<s:property value="#@java.util.HashMap@{'uno':'1', 'dos':'2', 'tres':'3', 'cuatro':'4', 'cinco':'5'}" />
</code></pre><br />
<br />
Obtenemos la siguiente salida:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh3K6b9DJfWjOg_ZjND6F-Z5RQB5bb0kv_Rg2p52IImOPUUo195n8I72WNmpCHhMHIGKlOasEhO2qXsXUKpyW4knchKlPBUE39EVmZM_0E7DFVeRC6ckBpDde0BsYh1GMzjpy3lEd9i_6t_/s1600/S2_25.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="242" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh3K6b9DJfWjOg_ZjND6F-Z5RQB5bb0kv_Rg2p52IImOPUUo195n8I72WNmpCHhMHIGKlOasEhO2qXsXUKpyW4knchKlPBUE39EVmZM_0E7DFVeRC6ckBpDde0BsYh1GMzjpy3lEd9i_6t_/s400/S2_25.png" width="400" /></a></div><br />
<br />
Podemos determinar si en una colección existe un elemento podemos usar los operadores "<span class="codigo">in</span>" y "<span class="codigo">not in</span>":<br />
<br />
<pre><code>
<s:if test="'foo' in {'foo','bar'}">
muahahaha
</s:if>
<s:else>
boo
</s:else>
<s:if test="'foo' not in {'foo','bar'}">
muahahaha
</s:if>
<s:else>
boo
</s:else>
</code></pre><br />
<br />
Si necesitamos realizar la selección de un subconjunto de una colección (que en términos formales se llama proyección), <span class="codigo">OGNL</span> proporciona un conjunto de comodines para eso:<br />
<br />
<ul><li>"<span class="codigo">?</span>" – Para obtener todos los elementos que coinciden con la lógica de selección.</li>
<li>"<span class="codigo">^</span>" – Para seleccionar solo el primer elemento que coincida con la lógica de selección.</li>
<li>"<span class="codigo">$</span>" - Para seleccionar solo el último elemento que coincida con la lógica de selección.</li>
</ul><br />
Por ejemplo, si tuviéramos una clase llamada "<span class="codigo">Persona</span>" con un atributo "<span class="codigo">genero</span>", y un objeto con una colección de <span class="codigo">Persona</span>s llamada "<span class="codigo">familiares</span>". Si solo queremos obtener las <span class="codigo">Persona</span>s con genero "<span class="codigo">masculino</span>", podemos hacerlo de la siguiente forma:<br />
<br />
<pre><code>
persona.familiares.{? #this.genero = ‘masculino’}
</code></pre><br />
<br />
Queda al lector hacer un ejemplo de esto ^_^.<br />
<br />
Como nota final, hay que decir que algunas veces (en muy raras ocasiones en realidad) <span class="codigo">OGNL</span> entiende las instrucciones que le damos como cadenas y no como expresiones. Para estos casos existe una forma de decirle que interprete lo que ve como una expresión, y esto es envolviendo dicha expresión entre los caracteres "<span class="codigo">%{</span>" y "<span class="codigo">}</span>".<br />
<br />
Por ejemplo, para indicarle que la siguiente línea debe ser tomada como una expresión:<br />
<br />
<pre><code>
<s:property value="constantes.atributo" />
</code></pre><br />
<br />
Lo indicamos de la siguiente forma:<br />
<br />
<pre><code>
<s:property value="%{constantes.atributo}" />
</code></pre><br />
<br />
Como pudimos ver a lo largo de los ejemplos, esto casi nunca es necesario, pero si alguna vez, por alguna razón una de las expresiones que están creando no funciona, pueden resolverlo de esta forma ^_^.<br />
<br />
Esto es todo en este tutorial. Como pudimos ver <span class="codigo">OGNL</span> es un lenguaje de expresiones bastante completo (y eso que no hablamos de los operadores que tiene) con el que podemos obtener (y establecer) prácticamente cualquier valor de cualquier clase que necesitemos en nuestros desarrollos.<br />
<br />
Espero que el tutorial les sea de utilidad y, como siempre, no olviden dejar sus dudas, comentarios y sugerencias.<br />
<br />
Saludos y gracias ^_^<br />
<br />
<span class="negritas">Descarga los archivos de este tutorial desde aquí:</span><br />
<ul><li><a href="https://sites.google.com/site/javatutoriales/struts2/OGNL.zip"><span class="ligaArchivo">OGNL</span></a></li>
</ul><br />
<br />
<span class="negritas">Entradas Relacionadas:</span><br />
<br />
<ul><li><a href="http://www.javatutoriales.com/2011/06/struts-2-parte-1-configuracion.html">Parte 1: Configuración</a></li>
<li><a href="http://www.javatutoriales.com/2011/10/struts-2-parte-3-trabajo-con.html">Parte 3: Trabajo con Formularios</a></li>
<li><a href="http://www.javatutoriales.com/2011/12/struts-2-parte-4-scopes-de-objetos-web.html">Parte 4: Scopes de Objetos Web</a></li>
<li><a href="http://www.javatutoriales.com/2012/04/struts-2-parte-5-tipos-de-results.html">Parte 5: Tipos de Results</a></li>
<li><a href="http://www.javatutoriales.com/2012/06/struts-2-parte-6-interceptores.html">Parte 6: Interceptores</a></li>
</ul></div>Alexhttp://www.blogger.com/profile/06974037481671868076noreply@blogger.comtag:blogger.com,1999:blog-2012232069289957092.post-80814141106676798132011-06-19T18:59:00.000-07:002012-06-29T07:38:26.125-07:00Struts 2 - Parte 1: Configuración<div style="text-align: justify;">Cuando desarrollamos aplicaciones web es importante que estas puedan ser creadas de forma rápida y eficiente. Hoy en día existen muchos frameworks, los cuales nos proporcionan un cascarón para las aplicaciones. Nosotros solo debemos implementar la lógica propia de la aplicación, configurar el framework mediante algún mecanismo como anotaciones o archivos XML, y estos se encargan de hacer la mayor parte del trabajo tedioso o repetitivo.<br />
<br />
<span class="codigo">Struts 2</span> es un framework para el desarrollo de aplicaciones web, el cual hace que la implementación de las mismas sea más sencillo, más rápido, y con menos complicaciones. Además hace que estas sean más robustas y flexibles. El objetivo de <span class="codigo">Struts 2</span> es muy sencillo: <span class="negritas">hacer que el desarrollo de aplicaciones web sea simple para los desarrolladores</span>.<br />
<br />
En esta serie de tutoriales veremos cómo desarrollar aplicaciones usando este framework web, cómo configurar el controlador que implementa <span class="codigo">Struts 2</span>, y las distintas opciones que nos ofrece.<br />
<br />
<a name='more'></a><span class="codigo">Struts 2</span> es un framework de presentación, dentro de las capas en las que se divide una aplicación en la arquitectura <span class="codigo">JEE</span>, el cual implementa el controlador del patrón de diseño <span class="codigo"><a href="http://es.wikipedia.org/wiki/Modelo_Vista_Controlador">MVC (Modelo Vista Controlador)</a></span>, y que podemos configurar de varias maneras; además proporciona algunos componentes para la capa de vista. Por si fuera poco, proporciona una integración perfecta con otros frameworks para implementar la capa del modelo (como <span class="codigo"><a href="http://javatutoriales.blogspot.com/2009/05/hibernate-parte-1-persistiendo-objetos.html">Hibernate</a></span> y <span class="codigo"><a href="http://www.javatutoriales.com/2010/09/spring-parte-1-introduccion.html">Spring</a></span>).<br />
<br />
Para hacer más fácil presentar datos dinámicos, el framework incluye una biblioteca de etiquetas web. Las etiquetas interactúan con las validaciones y las características de internacionalización del framework, para asegurar que las entradas son válidas, y las salidas están localizadas. La biblioteca de etiquetas puede ser usada con <span class="codigo">JSP</span>, <span class="codigo">FreeMarker</span>, o <span class="codigo">Velocity</span>; también pueden ser usadas otras bibliotecas de etiquetas como <span class="codigo">JSTL</span> y soporta el uso de componentes <span class="codigo">JSF</span>.<br />
<br />
Además permite agregarle funcionalidades, mediante el uso de plugins, de forma transparente, ya que los plugins no tienen que ser declarados ni configurados de ninguna forma. Basta con agregar al <span class="codigo">classpath</span> el jar que contiene al plugin, y eso es todo.<br />
<br />
Como dije antes: el objetivo de <span class="codigo">Struts 2</span> es hacer que el desarrollo de aplicaciones web sea fácil para los desarrolladores. Para lograr esto, <span class="codigo">Struts 2</span> cuenta con características que permiten reducir la configuración gracias a que proporciona un conjunto inteligente de valores por default. Además hace uso de anotaciones y proporciona una forma de hacer la configuración de manera automática si usamos una serie de convenciones (y si hacemos uso de un plugin especial).<br />
<br />
<span class="codigo">Struts 2</span> no es precisamente el heredero de <span class="codigo">Struts 1</span>, sino que es la mezcla de dos framewoks: <span class="codigo">WebWork 2</span> y <span class="codigo">Struts</span> (aunque en realidad me parece que de <span class="codigo">Struts 1</span> solo tomó algunos nombres ^_^).<br />
<br />
<br />
<h2 class="titulo">Componentes de <span class="codigo">Struts 2</span></h2>Comencemos hablando un poco de los componentes que forman a <span class="codigo">Struts 2</span>.<br />
<br />
El corazón de <span class="codigo">Struts 2</span> es un filtro, conocido como el "<span class="codigo">FilterDispatcher</span>". Este es el punto de entrada del framework. A partir de él se lanza la ejecución de todas las peticiones que involucran al framework.<br />
<br />
Las principales responsabilidades del "<span class="codigo">FilterDispatcher</span>" son:<br />
<br />
<ul><li>Ejecutar los <span class="codigo">Action</span>s, que son los manejadores de las peticiones.</li>
<li>Comenzar la ejecución de la cadena de interceptores (de la que hablaremos en un momento).</li>
<li>Limpiar el "<span class="codigo">ActionContext</span>", para evitar fugas de memoria.</li>
</ul><br />
<span class="codigo">Struts 2</span> procesa las peticiones usando tres elementos principales:<br />
<br />
<ul><li>Interceptores</li>
<li>Acciones</li>
<li>Resultados</li>
</ul><br />
<h3 class="subtitulo">Interceptores</h3>Los interceptores son clases que siguen <a href="http://en.wikipedia.org/wiki/Interceptor_pattern">el patrón interceptor</a>. Estos permiten que se implementen funcionalidades cruzadas o comunes para todos los <span class="codigo">Action</span>s, pero que se ejecuten fuera del <span class="codigo">Action</span> (por ejemplo validaciones de datos, conversiones de tipos, población de datos, etc.).<br />
<br />
Los interceptores realizan tareas <span class="negritas">antes</span> y <span class="negritas">después</span> de la ejecución de un <span class="codigo">Action</span> y también pueden evitar que un <span class="codigo">Action</span> se ejecute (por ejemplo si estamos haciendo alguna validación que no se ha cumplido).<br />
<br />
Sirven para ejecutar algún proceso particular que se quiere aplicar a un conjunto de <span class="codigo">Action</span>s. De hecho muchas de las características con que cuenta <span class="codigo">Struts 2</span> son proporcionadas por los interceptores.<br />
<br />
Si alguna funcionalidad que necesitamos no se encuentra en los interceptores de <span class="codigo">Struts</span> <span class="negritas">podemos crear nuestro propio interceptor</span> y agregarlo a la cadena que se ejecuta por default. <br />
<br />
De la misma forma, podemos modificar la cadena de interceptores de <span class="codigo">Struts</span>, por ejemplo para quitar un interceptor o modificar su orden de ejecución.<br />
<br />
La siguiente tabla muestra solo algunos de los interceptores más importantes que vienen integrados y pre-configurados en <span class="codigo">Struts 2</span>:<br />
<br />
<table><thead>
<tr><th>Interceptor</th><th>Nombre</th><th>Descripción</th></tr>
</thead> <tbody>
<tr><td><span class="codigo">Alias</span></td><td><span class="codigo">alias</span></td><td>Permite que los parámetros tengan distintos nombres entre peticiones.</td></tr>
<tr><td><span class="codigo">Chaining</span></td><td><span class="codigo">chaining</span></td><td>Permite que las propiedades del <span class="codigo">Action</span> ejecutado previamente estén disponibles en el <span class="codigo">Action</span> actual</td></tr>
<tr><td><span class="codigo">Checkbox</span></td><td><span class="codigo">checkbox</span></td><td>Ayuda en el manejo de checkboxes agregando un parámetro con el valor "<span class="codigo">false</span>" para checkboxes que no están marcadas (o checadas)</td></tr>
<tr><td><span class="codigo">Conversion Error</span></td><td><span class="codigo">conversionError</span></td><td>Coloca información de los errores convirtiendo cadenas a los tipos de parámetros adecuados para los campos del <span class="codigo">Action</span>.</td></tr>
<tr><td><span class="codigo">Create Session</span></td><td><span class="codigo">createSession</span></td><td>Crea de forma automática una sesión <span class="codigo">HTTP</span> si es que aún no existe una.</td></tr>
<tr><td><span class="codigo">Execute and Wait</span></td><td><span class="codigo">execAndWait</span></td><td>Envía al usuario a una página de espera intermedia mientras el <span class="codigo">Action</span> se ejecuta en background.</td></tr>
<tr><td><span class="codigo">File Upload</span></td><td><span class="codigo">fileUpload</span></td><td>Hace que la carga de archivos sea más fácil de realizar.</td></tr>
<tr><td><span class="codigo">Logging</span></td><td><span class="codigo">logger</span></td><td>Proporciona un logging (salida a bitácora) simple, mostrando el nombre del <span class="codigo">Action</span> que se está ejecutando.</td></tr>
<tr><td><span class="codigo">Parameters</span></td><td><span class="codigo">params</span></td><td>Establece los parámetros de la petición en el <span class="codigo">Action</span>.</td></tr>
<tr><td><span class="codigo">Prepare</span></td><td><span class="codigo">prepare</span></td><td>Llama al método "<span class="codigo">prepare</span>" en los acciones que implementan la interface "<span class="codigo">Preparable</span>"</td></tr>
<tr><td><span class="codigo">Servlet Configuration</span></td><td><span class="codigo">servletConfig</span></td><td>Proporciona al <span class="codigo">Action</span> acceso a información basada en <span class="codigo">Servlets</span>.</td></tr>
<tr><td><span class="codigo">Roles</span></td><td><span class="codigo">roles</span></td><td>Permite que el <span class="codigo">Action</span> se ejecutado solo si el usuario tiene uno de los roles configurados.</td></tr>
<tr><td><span class="codigo">Timer</span></td><td><span class="codigo">timer</span></td><td>Proporciona una información sencilla de cuánto tiempo tardo el <span class="codigo">Action</span> en ejecutarse.</td></tr>
<tr><td><span class="codigo">Validation</span></td><td><span class="codigo">validation</span></td><td>Proporciona a los <span class="codigo">Action</span>s soporte para validaciones de datos.</td></tr>
<tr><td><span class="codigo">Workflow</span></td><td><span class="codigo">workflow</span></td><td>Redirige al result "<span class="codigo">INPUT</span>" sin ejecutar el <span class="codigo">Action</span> cuando una validación falla.</td></tr>
</tbody> </table><br />
<br />
<br />
Cada interceptor proporciona una característica distinta al <span class="codigo">Action</span>. Para sacar la mayor ventaja posible de los interceptores, un <span class="codigo">Action</span> permite que se aplique más de un interceptor. Para lograr esto <span class="codigo">Struts 2</span> permite crear pilas o <span class="negritas">stacks de interceptores</span> y aplicarlas a los <span class="codigo">Action</span>s. Cada interceptor es aplicado en el orden en el que aparece en el stack. También podemos formar pilas de interceptores en base a otras pilas ^_^.<br />
<br />
Una de estas pilas de interceptores es aplicada por default a todos los <span class="codigo">Action</span>s de la aplicación (aunque si queremos, también podemos aplicar una pila particular a un <span class="codigo">Action</span>).<br />
<br />
La siguiente tabla muestra algunos de los stacks de interceptores que vienen pre-configurados con <span class="codigo">Struts 2</span>:<br />
<br />
<table><thead>
<tr><th>Nombre del Stack</th><th>Interceptores Incluidos</th><th>Descripción</th></tr>
</thead> <tbody>
<tr><td><span class="codigo">basicStack</span></td> <td><span class="codigo">exception, servletConfig, prepare, checkbox, multiselect, actionMappingParams, params, conversionError</span></td><td>Los interceptores que se espera se usen en todos los casos, hasta los más básicos.</td></tr>
<tr><td><span class="codigo">validationWorkflowStack</span></td> <td><span class="codigo">basicStack, validation, workflow</span></td><td>Agrega validación y flujo de trabajo a las características del stack básico.</td></tr>
<tr><td><span class="codigo">fileUploadStack</span></td> <td><span class="codigo">fileUpload, basicStack</span></td><td>Agrega funcionalidad de carga de archivos a las características del stack básico.</td></tr>
<tr><td><span class="codigo">paramsPrepareParamsStack</span></td><td><span class="codigo">alias, i18n, checkbox, multiselect, params, servletConfig, prepare, chain, modelDriven, fileUpload, staticParams, actionMappingParams, params, conversionError, validation, workflow</span></td><td>Proporciona un stack completo para manejo de casi cualquier cosa que necesitemos en nuestras aplicaciones. El interceptor "<span class="codigo">params</span>" se aplica dos veces, la primera vez proporciona los parámetros antes de que el método "<span class="codigo">prepare</span>" sea llamado, y la segunda vez re-aplica los parámetros a los objetos que hayan podido ser recuperados durante la fase de preparación.</td></tr>
<tr><td><span class="codigo">defaultStack</span></td> <td><span class="codigo">alias, servletConfig, i18n, prepare, chain, debugging, scopedModelDriven, modelDriven, fileUpload, checkbox, multiselect, staticParams, actionMappingParams, params, conversionError, validation, workflow</span></td><td>Es la pila que se aplica por default a todos los Actions de la aplicación.</td></tr>
<tr><td><span class="codigo">executeAndWaitStack</span></td> <td><span class="codigo">execAndWait, defaultStack, execAndWait</span></td><td>Proporciona al stack básico las características de execute and wait, lo cual funciona para aplicaciones en las que deben subirse archivos que pueden tardar algún tiempo.</td></tr>
</tbody> </table><br />
<br />
<br />
<h3 class="subtitulo">Acciones</h3>Las acciones o <span class="codigo">Action</span>s son clases encargadas de realizar la lógica para servir una petición. Cada <span class="negritas"><span class="codigo">URL</span> es mapeada a una acción específica</span>, la cual proporciona la lógica necesaria para servir a cada petición hecha por el usuario.<br />
<br />
Estrictamente hablando, las acciones no necesitan implementar una interface o extender de alguna clase base. El único requisito para que una clase sea considerada un <span class="codigo">Action</span> es que debe tener <span class="negritas">un método</span> que <span class="negritas">no reciba argumentos</span> que <span class="negritas">regrese ya sea un <span class="codigo">String</span> o un objeto de tipo <span class="codigo">Result</span></span>. Por default el nombre de este método debe ser "<span class="codigo">execute</span>" aunque podemos ponerle el nombre que queramos y posteriormente indicarlo en el archivo de configuración de <span class="codigo">Struts</span>.<br />
<br />
Cuando el resultado es un <span class="codigo">String</span> (lo cual es lo más común), el <span class="codigo">Result</span> correspondiente se obtiene de la configuración del <span class="codigo">Action</span>. Esto se usa para generar una respuesta para el usuario, lo cual veremos un poco más adelante.<br />
<br />
Los <span class="codigo">Action</span>s pueden ser objetos java simples (<span class="codigo">POJO</span>s) que cumplan con el requisito anterior, aunque también pueden implementar la interface "<span class="codigo"><a href="http://struts.apache.org/2.2.3/xwork-core/apidocs/com/opensymphony/xwork2/Action.html">com.opensymphony.xwork2.Action</a></span>" o extender una clase base que proporciona <span class="codigo">Struts 2</span>: "<span class="codigo"><a href="http://struts.apache.org/2.2.3/xwork-core/apidocs/com/opensymphony/xwork2/ActionSupport.html">com.opensymphony.xwork2.ActionSupport</a></span>" (lo cual nos hace más sencilla su creación y manejo).<br />
<br />
La interface <span class="codigo">Action</span> proporciona un conjunto de constantes con los nombres de los <span class="codigo">Result</span>s más comunes. Esta interface luce de la siguiente forma:<br />
<br />
<pre><code>
public interface <span class="codigo">Action</span>
{
public static final String SUCCESS = "success";
public static final String NONE = "none";
public static final String ERROR = "error";
public static final String INPUT = "input";
public static final String LOGIN = "login";
public String execute() throws Exception;
}
</code></pre><br />
<br />
La clase "<span class="codigo">ActionSupport</span>" implementa la interface "<span class="codigo">Action</span>" y contiene una implementación del método "<span class="codigo">execute()</span>" que regresa un valor de "<span class="codigo">SUCCESS</span>". Además proporciona unos cuantos métodos para establecer mensajes, tanto de error como informativos, que pueden ser mostrados al usuario.<br />
<br />
<br />
<h3 class="subtitulo">Results</h3>Después que un <span class="codigo">Action</span> ha sido procesado se debe enviar la respuesta de regreso al usuario, esto se realiza usando <span class="codigo">result</span>s. Este proceso tiene dos componentes, el tipo del <span class="codigo">result</span> y el <span class="codigo">result</span> mismo.<br />
<br />
El tipo del <span class="codigo">result</span> indica <span class="negritas">cómo debe ser tratado</span> el resultado que se le regresará al cliente. Por ejemplo un tipo de <span class="codigo">Result</span> puede enviar al usuario de vuelta una <span class="codigo">JSP</span> (lo que haremos más a menudo), otro puede redirigirlo hacia otro sitio, mientras otro puede enviarle un flujo de bytes (para descargar un archivo por ejemplo).<br />
<br />
Entraremos en más detalles de los tipos de resultados de <span class="codigo">Struts 2</span> en otro tutorial. Por ahora solo hay que saber que el tipo de result por default es "<span class="codigo">dispatcher</span>".<br />
<br />
Un <span class="codigo">Action</span> puede tener más de un <span class="codigo">result</span> asociado. Esto nos permitirá enviar al usuario a una vista distinta dependiendo del resultado de la ejecución del <span class="codigo">Action</span>. Por ejemplo en caso de que todo salga bien, enviaremos al usuario al <span class="codigo">result</span> "<span class="codigo">sucess</span>", si algo sale mal lo enviaremos al <span class="codigo">result</span> "<span class="codigo">error</span>", o si no tiene permisos lo enviaremos al <span class="codigo">result</span> "<span class="codigo">denied</span>".<br />
<br />
<br />
<h2 class="titulo">Funcionamiento de <span class="codigo">Struts 2</span></h2>Ahora que conocemos a grandes rasgos los componentes que forman <span class="codigo">Struts 2</span>, veamos de forma general cómo son procesadas las peticiones por una aplicación desarrollada en <span class="codigo">Struts 2</span>. La siguiente imagen nos ayudará con esto:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEidcmctqa0SeDK47e2Kp55_C7cYcPnxCOOXC05c85aBZW5mM8X6t1GkGdVI_n-GnH94L9l6K4aIVizC2Shr-theh63rndwwpSGXo0hI7WF_lx44GoIfur9pa4Rqw66GB8cYGw9St4feBvJh/s1600/S1_1.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="165" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEidcmctqa0SeDK47e2Kp55_C7cYcPnxCOOXC05c85aBZW5mM8X6t1GkGdVI_n-GnH94L9l6K4aIVizC2Shr-theh63rndwwpSGXo0hI7WF_lx44GoIfur9pa4Rqw66GB8cYGw9St4feBvJh/s320/S1_1.png" /></a></div><br />
<br />
Los pasos que sigue una petición son:<br />
<br />
<ol><li><span class="negritas">1</span> - El navegador web hace una petición para un recurso de la aplicación (<span class="codigo">index.action</span>, <span class="codigo">reporte.pdf</span>, etc.). El filtro de <span class="codigo">Struts</span> (al cual llamamos <span class="codigo">FilterDispatcher</span> aunque esto no es del todo correcto, retomaré esto un poco más adelante) revisa la petición y determina el <span class="codigo">Action</span> apropiado para servirla.</li>
<li><span class="negritas">2</span> - Se aplican los interceptores, los cuales realizan algunas funciones como validaciones, flujos de trabajo, manejo de la subida de archivos, etc.</li>
<li><span class="negritas">3</span> - Se ejecuta el método adecuado del <span class="codigo">Action</span> (por default el método "<span class="codigo">execute</span>"), este método usualmente almacena y/o regresa alguna información referente al proceso.</li>
<li><span class="negritas">4</span> - El <span class="codigo">Action</span> indica cuál <span class="codigo">result</span> debe ser aplicado. El <span class="codigo">result</span> genera la salida apropiada dependiendo del resultado del proceso.</li>
<li><span class="negritas">5</span> - Se aplican al resultado los mismos interceptores que se aplicaron a la petición, pero en orden inverso.</li>
<li><span class="negritas">6</span> - El resultado vuelve a pasar por el <span class="codigo">FilterDispatcher</span> aunque este ya no hace ningún proceso sobre el resultado (por definición de la especificación de <span class="codigo">Servlets</span>, si una petición pasa por un filtro, su respuesta asociada pasa también por el mismo filtro).</li>
<li><span class="negritas">7</span> - El resultado es enviado al usuario y este lo visualiza.</li>
</ol><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEidcmctqa0SeDK47e2Kp55_C7cYcPnxCOOXC05c85aBZW5mM8X6t1GkGdVI_n-GnH94L9l6K4aIVizC2Shr-theh63rndwwpSGXo0hI7WF_lx44GoIfur9pa4Rqw66GB8cYGw9St4feBvJh/s1600/S1_1.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="165" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEidcmctqa0SeDK47e2Kp55_C7cYcPnxCOOXC05c85aBZW5mM8X6t1GkGdVI_n-GnH94L9l6K4aIVizC2Shr-theh63rndwwpSGXo0hI7WF_lx44GoIfur9pa4Rqw66GB8cYGw9St4feBvJh/s320/S1_1.png" /></a></div><br />
<br />
(Vuelvo a poner la imagen para que no tengan que regresar para verla ^_^).<br />
<br />
Este camino podría no siempre seguirse en orden. Por ejemplo, si el interceptor de validación de datos encuentra que hay algún problema, el <span class="codigo">Action</span> no se ejecutará, y será el mismo interceptor el que se encargará de enviar un <span class="codigo">Result</span> al usuario.<br />
<br />
Otra característica importante de <span class="codigo">Struts 2</span> es que no hace uso del lenguaje de expresiones propio de las <span class="codigo">JSP</span>s, sino que hace uso de <span class="codigo">OGNL</span> (<span class="codigo">Object-Graph Navigation Language</span>), un lenguaje de expresiones más poderoso. Hablaremos más de <span class="codigo">OGNL</span> en el siguiente tutorial.<br />
<br />
Comencemos viendo un pequeño ejemplo que nos mostrará cómo configurar nuestras aplicaciones con <span class="codigo">Struts 2</span>. Haremos una configuración haciendo uso de un archivo de configuración en <span class="codigo">XML</span> y otro haciendo uso de anotaciones.<br />
<br />
<span class="nota">Nota: Yo usaré dos proyectos, uno con un archivo XML y otro con anotaciones, para que el código de ambos no se mezcle.</span><br />
<br />
Lo primero que haremos es crear una biblioteca llamada "<span class="codigo">Struts2</span>". Para esto debemos descargar la última versión de <span class="codigo">Struts 2</span> (actualmente la <span class="negritas">2.2.3</span>) del <a href="http://struts.apache.org/download.cgi#struts231-SNAPSHOT">sitio de descargas de <span class="codigo">Struts 2</span></a>. Podemos descarga cualquiera de los paquetes. Yo les recomiendo que bajen el "<span class="codigo">lib</span>" o el paquete "<span class="codigo">all</span>". <br />
<br />
Una vez que tengamos el archivo, vamos al <span class="codigo">NetBeans</span> y nos dirigimos al menú "<span class="codigo">Tools -> Libraries</span>".<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiKCligr2ycKkTRkUMiXDNeqR8GwZQ4PTJ64xZTcaFCubDzWOtonF6cYKTwX47AHEa7tSP3INxxo6YQJbNB5_leN1X53TelcOmVoXaGQ5T25w6bdo487ZxUCVOtIq7PeEayZQahspujDUlp/s1600/S1_2.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="320" width="237" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiKCligr2ycKkTRkUMiXDNeqR8GwZQ4PTJ64xZTcaFCubDzWOtonF6cYKTwX47AHEa7tSP3INxxo6YQJbNB5_leN1X53TelcOmVoXaGQ5T25w6bdo487ZxUCVOtIq7PeEayZQahspujDUlp/s320/S1_2.png" /></a></div><br />
<br />
En la ventana que se abre presionamos el botón "<span class="codigo">New Library...</span>":<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgVSJ-UG_i-0QWlQlguACR81ogSH4MzYLFBbqLEcNQXc0_kDwAQq_NbhqpovaMep2BMQnOGQh0sV8cRQjhCAX89EB-yW_5PkNyFKZJnVzGueiqc57Ibbr6e4uCAdWgl5PQOgq-PDOhIxnUF/s1600/S1_3.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="244" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgVSJ-UG_i-0QWlQlguACR81ogSH4MzYLFBbqLEcNQXc0_kDwAQq_NbhqpovaMep2BMQnOGQh0sV8cRQjhCAX89EB-yW_5PkNyFKZJnVzGueiqc57Ibbr6e4uCAdWgl5PQOgq-PDOhIxnUF/s320/S1_3.png" /></a></div><br />
<br />
En esta nueva ventana colocamos como nombre de la biblioteca "<span class="codigo">Struts2</span>" y como tipo dejamos "<span class="codigo">Class Libraries</span>":<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjz9IMA25UFHg0gL73LIyqMbjurSgAtZJFbuRI3tVtJ4HrTur4Kov95ZHWNYUm9z-ey3gPaXuSk4rDYaTlalxRb2j9qz4Nes5x-XSqUGBF5FhsGXVDV62ibUSfUskhY0k7-6LaKS6rHqMd2/s1600/S1_4.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="184" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjz9IMA25UFHg0gL73LIyqMbjurSgAtZJFbuRI3tVtJ4HrTur4Kov95ZHWNYUm9z-ey3gPaXuSk4rDYaTlalxRb2j9qz4Nes5x-XSqUGBF5FhsGXVDV62ibUSfUskhY0k7-6LaKS6rHqMd2/s320/S1_4.png" /></a></div><br />
<br />
Ahora que tenemos nuestra biblioteca, presionamos el botón "<span class="codigo">Add Jar/Folder</span>" para agregar los nuevos archivos que conformarán la biblioteca:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjGEbQ_uG5xOexUVv8l7InItxnRX23lORP7l4G7En1bPDK4tOaHaRk0fp7e19O2p-2Y78GO4GF_xk-_q2Yi_u6k8TWiNtAq4dpfSJF5JQJY-fWXDTNdK77gU1_LeqKMaMXnHYkz1KQMp5Cj/s1600/S1_5.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="244" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjGEbQ_uG5xOexUVv8l7InItxnRX23lORP7l4G7En1bPDK4tOaHaRk0fp7e19O2p-2Y78GO4GF_xk-_q2Yi_u6k8TWiNtAq4dpfSJF5JQJY-fWXDTNdK77gU1_LeqKMaMXnHYkz1KQMp5Cj/s320/S1_5.png" /></a></div><br />
<br />
Agregamos los siguientes archivos:<br />
<br />
<ul><li><span class="codigo">commons-fileupload-1.2.2.jar</span></li>
<li><span class="codigo">commons-io-2.0.1.jar</span></li>
<li><span class="codigo">commons-lang-2.5.jar</span></li>
<li><span class="codigo">freemarker-2.3.16.jar</span></li>
<li><span class="codigo">javassist-3.11.0.GA.jar</span></li>
<li><span class="codigo">ognl-3.0.1.jar</span></li>
<li><span class="codigo">struts2-core-2.2.3.jar</span></li>
<li><span class="codigo">xwork-core-2.2.3.jar</span></li>
</ul><br />
Adicionalmente crearemos una segunda biblioteca para cuando trabajemos con anotaciones. Seguimos el mismo proceso para crear la biblioteca, que en este caso se llamará "<span class="codigo">Struts2Anotaciones</span>". Esta biblioteca debe incluir los siguientes jars:<br />
<br />
<ul><li><span class="codigo">asm-3.1.jar</span></li>
<li><span class="codigo">asm-commons-3.1.jar</span></li>
<li><span class="codigo">commons-fileupload-1.2.2.jar</span></li>
<li><span class="codigo">commons-io-2.0.1.jar</span></li>
<li><span class="codigo">commons-lang-2.5.jar</span></li>
<li><span class="codigo">freemarker-2.3.16.jar</span></li>
<li><span class="codigo">javassist-3.11.0.GA.jar</span></li>
<li><span class="codigo">ognl-3.0.1.jar</span></li>
<li><span class="codigo">struts2-core-2.2.3.jar</span></li>
<li><span class="codigo">xwork-core-2.2.3.jar</span></li>
<li><span class="codigo">struts2-convention-plugin-2.2.3.jar</span></li>
</ul><br />
O sea, todos los que tiene la biblioteca "<span class="codigo">Struts 2</span>" + el jar "<span class="codigo">asm-3.1.jar</span>" + el jar "<span class="codigo">asm-commons-3.1.jar</span>" + el jar "<span class="codigo">struts2-convention-plugin-2.2.3.jar</span>". <br />
<br />
Ahora tenemos dos bibliotecas de <span class="codigo">Struts 2</span>, una que usaremos cuando trabajemos con archivos de configuración en <span class="codigo">XML</span>, y otra que usaremos cuando hagamos nuestra configuración con anotaciones:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjRdcgQFr0_awkTpazpGbWr97QUe4hrZM4TGgR82SQATIYvoxtIQKqeTos6Tabv-Vd9GrgtosM9ZIAiL3TSsSexIApLvT-_MY2HRKO_RvHvjjMOK29wIHsIkp1bzo4plS0EScvJFopNDMYa/s1600/S1_6.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="244" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjRdcgQFr0_awkTpazpGbWr97QUe4hrZM4TGgR82SQATIYvoxtIQKqeTos6Tabv-Vd9GrgtosM9ZIAiL3TSsSexIApLvT-_MY2HRKO_RvHvjjMOK29wIHsIkp1bzo4plS0EScvJFopNDMYa/s320/S1_6.png" /></a></div><br />
<br />
Lo siguiente es crear un nuevo proyecto <span class="negritas">web</span> en <span class="codigo">NetBeans</span>. Para esto vamos al Menú "<span class="codigo">File->New Project...</span>". En la ventana que se abre seleccionamos la categoría "<span class="codigo">Java Web</span>" y en el tipo de proyecto "<span class="codigo">Web Application</span>": <br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjr5Oxgw5STkVxuCp8CLem6oUKhMTiZPx462TjvnPXW4m7IDGeEIQVkRmckbYIJqeR4bJqmJ468_bFOw8jfglLdfnEqYVsH8BDrRNTSqn56qyJVngKM9bK62necbk-q_k6DuMvEdI-ERnla/s1600/S1_7.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="221" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjr5Oxgw5STkVxuCp8CLem6oUKhMTiZPx462TjvnPXW4m7IDGeEIQVkRmckbYIJqeR4bJqmJ468_bFOw8jfglLdfnEqYVsH8BDrRNTSqn56qyJVngKM9bK62necbk-q_k6DuMvEdI-ERnla/s320/S1_7.png" /></a></div><br />
<br />
Le damos una ubicación y un nombre al proyecto, en mi caso será "<span class="codigo">Struts2Configuracion</span>". Presionamos el botón "<span class="codigo">Next</span>". En la siguiente pantalla deberemos configurar el servidor de aplicaciones que usaremos. Yo usaré la versión 7 de <span class="codigo"><a href="http://tomcat.apache.org/download-70.cgi">Tomcat</a></span> (si no lo tienen configurado, pueden seguir los mismos pasos que en <a href="http://www.javatutoriales.com/2009/02/instalacion-de-plugins-en-netbeans.html">el tutorial de instalación de plugins en <span class="codigo">NetBeans</span></a>). Como versión de <span class="codigo">Java EE</span> usaré la <span class="negritas">5</span> (pueden usar también la 6 si quieren, solo que en ese caso <span class="codigo">NetBeans</span> no genera el archivo "<span class="codigo">web.xml</span>" por default, y tendrán que crearlo a mano o usando el wizard correspondiente). De la misma forma, si quieren pueden modificar el <span class="codigo">context path</span> de la aplicación (el <span class="codigo">context path</span> es la ruta que se colocará en el navegador para acceder a nuestra aplicación desde nuestro servidor):<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwCXBWEO5bHXxAX8FUU3371xR2AOxUPkxPgxj7t5YBHRWnfdbZjGbx2nBhowXcyebwL3cWpH5f4Nxh5mzZ6-r_iEjzJmncdQgIMFYie-YHl55u7SkDGFv5oJT77dV5ummugU3yuXCFuXaz/s1600/S1_8.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="206" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwCXBWEO5bHXxAX8FUU3371xR2AOxUPkxPgxj7t5YBHRWnfdbZjGbx2nBhowXcyebwL3cWpH5f4Nxh5mzZ6-r_iEjzJmncdQgIMFYie-YHl55u7SkDGFv5oJT77dV5ummugU3yuXCFuXaz/s320/S1_8.png" /></a></div><br />
<br />
Presionamos el botón "<span class="codigo">Finish</span>", con lo que veremos aparecer la página "<span class="codigo">index.jsp</span>" en nuestro editor.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiwRv-Ughn1HB1a4Dcuz4QNzELy4cQL9Vgt_AA9OTEwKbsAyK4I_0nUVSFVl9tbjZMEc6P7guNrKWQw6WeomKOIqgnfGHfZjLdeWzu0Z_lxRZ3jvZj3iCVsXU-L7gOtG4YgF7TooAN1ZSOQ/s1600/S1_9.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="201" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiwRv-Ughn1HB1a4Dcuz4QNzELy4cQL9Vgt_AA9OTEwKbsAyK4I_0nUVSFVl9tbjZMEc6P7guNrKWQw6WeomKOIqgnfGHfZjLdeWzu0Z_lxRZ3jvZj3iCVsXU-L7gOtG4YgF7TooAN1ZSOQ/s320/S1_9.png" /></a></div><br />
<br />
Ahora agregaremos la biblioteca "<span class="codigo">Struts2</span>" que creamos hace un momento. Para esto hacemos clic derecho en el nodo "<span class="codigo">Libraries</span>" del panel de proyectos. En el menú que aparece seleccionamos la opción "<span class="codigo">Add Library...</span>":<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg5S07eLiQdFgZLBjYoOtOoHuwaG_3iPqGLWEC1L6n1fAI3wGqk_OkK4o2GCL3zDSx7sNZe4BAxvIYMkqctsgKm9yAIzmBxg2LOqpfLHvpMotkIiBOIiNTkh8VjWM8uRhrpuwqNjIrSvl-1/s1600/S1_10.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="262" width="255" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg5S07eLiQdFgZLBjYoOtOoHuwaG_3iPqGLWEC1L6n1fAI3wGqk_OkK4o2GCL3zDSx7sNZe4BAxvIYMkqctsgKm9yAIzmBxg2LOqpfLHvpMotkIiBOIiNTkh8VjWM8uRhrpuwqNjIrSvl-1/s320/S1_10.png" /></a></div><br />
<br />
En la ventana que aparece seleccionamos la biblioteca "<span class="codigo">Struts2</span>" y presionamos "<span class="codigo">Add Library</span>". Con esto ya tendremos los jars de <span class="codigo">Struts 2</span> en nuestro proyecto:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEizbruk0nS-JTn2GPOPZk3TkfdzEAmi_AB1o9M77X6hHUsBL1u9RgRzv28vD-kb73nqLtXIqPIsLD5FTQDYE5xy4sPu_CWJ4MsO84WJ8SNy3GRixahSNvI3FG-fjWT_w4HwQ_1sWN9jvDqL/s1600/S1_11.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="320" width="202" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEizbruk0nS-JTn2GPOPZk3TkfdzEAmi_AB1o9M77X6hHUsBL1u9RgRzv28vD-kb73nqLtXIqPIsLD5FTQDYE5xy4sPu_CWJ4MsO84WJ8SNy3GRixahSNvI3FG-fjWT_w4HwQ_1sWN9jvDqL/s320/S1_11.png" /></a></div><br />
<br />
Lo siguiente que debemos hacer es configurar el filtro de <span class="codigo">Struts 2</span>, para que todas las llamadas que se hagan a nuestra aplicación web pasen a través de este filtro. El filtro de <span class="codigo">Struts 2</span> es llamado "<span class="codigo">StrutsPrepareAndExecuteFilter</span>". En versiones anteriores de <span class="codigo">Struts 2</span> se usaba un filtro llamado "<span class="codigo">FilterDispatcher</span>" (es por eso que algunas veces hago referencia a este filtro usando ese nombre), sin embargo este ha sido marcado como deprecated y por lo tanto ya no debe ser usado.<br />
<br />
Abrimos el archivo de configuración de nuestra aplicación web, el deployment descriptor conocido sencillamente como el archivo "<span class="codigo">web.xml</span>".<br />
<br />
Para la declaración del filtro de <span class="codigo">Struts 2</span>, debemos usar el elemento "<span class="codigo"><filter></span>", dentro del cual debemos indicar un nombre para el filtro, por convención usamos el nombre "<span class="codigo">struts2</span>", y la clase que implementa dicho filtro, que en nuestro caso es "<span class="codigo">org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</span>". <br />
<br />
Después será necesario indicar qué peticiones pasarán por el filtro de <span class="codigo">Struts 2</span>. Para eso usamos el elemento "<span class="codigo"><filter-mapping></span>". Aquí indicamos que queremos que TODAS las peticiones que se hagan al sitio pasen a través del filtro, usando el patrón de URL "<span class="codigo">/*</span>".<br />
<br />
La declaración y mapeo del filtro quedan de la siguiente forma:<br />
<br />
<pre><code>
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</code></pre><br />
Ahora modificaremos la página "<span class="codigo">index.jsp</span>", para probar que la configuración funciona correctamente. En esta página colocaremos solo un texto que diga: "<span class="codigo">Bienvenido a Struts 2</span>".<br />
<br />
El siguiente paso es realizar la configuración de <span class="codigo">Struts 2</span>. Existen dos formas de hacer esto: la primera es usando un archivo de configuración en <span class="codigo">XML</span>, y la segunda es mediante anotaciones. Primero veremos cómo configurarlo usando un archivo de configuración <span class="codigo">XML</span>.<br />
<br />
<br />
<h2 class="titulo">Configuración de <span class="codigo">Struts 2</span> Usando un Archivo XML</h2>Para esta configuración debemos crear un archivo llamado "<span class="codigo">struts.xml</span>", que <span class="negritas">debe ser colocado en el paquete raíz de la aplicación</span>. En este archivo se especifica la relación entre la <span class="codigo">URL</span> de una petición, la clase Java que ejecutará la acción de la petición, y la página <span class="codigo">JSP</span> que se encargará de mostrar la respuesta a la petición del usuario.<br />
<br />
Para crear el archivo hacemos clic derecho sobre el nodo "<span class="codigo">Source Packages</span>" en el panel de proyectos. En el menú contextual que nos aparece seleccionamos la opción "<span class="codigo">New -> XML Document</span>":<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZKse8j4Bu4WGWAgM5XcmCAeqdIQiWhAcwsSnr_6_oi-HXM7VWedLZ5d-dIJyMf2Q0F2Xo8G5KQkQ1Vbal_x_Nt-ryhZN_5EkJnABrOOUjECY3rgfQccjpx7L3Epg__KodWJYWd-mBMcXG/s1600/S1_12.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="267" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZKse8j4Bu4WGWAgM5XcmCAeqdIQiWhAcwsSnr_6_oi-HXM7VWedLZ5d-dIJyMf2Q0F2Xo8G5KQkQ1Vbal_x_Nt-ryhZN_5EkJnABrOOUjECY3rgfQccjpx7L3Epg__KodWJYWd-mBMcXG/s320/S1_12.png" /></a></div><br />
<br />
Le damos como nombre del archivo "<span class="codigo">struts</span>", el wizard se encargará de colocarle la extensión "<span class="codigo">.xml</span>":<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhGQ8RI0FSbhCL3bAI745vUAvfAMcYJFevqNO4V8rhwKfZf6Vkf5V-Hu518q0abK2M9p5qckMQdg_QZX1AwAtMe9msRUMbhPWaeq6ftccEifSqD0iGBvMg7Rh2oEVPZkzXqFl-oDZB854Dl/s1600/S1_13.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="219" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhGQ8RI0FSbhCL3bAI745vUAvfAMcYJFevqNO4V8rhwKfZf6Vkf5V-Hu518q0abK2M9p5qckMQdg_QZX1AwAtMe9msRUMbhPWaeq6ftccEifSqD0iGBvMg7Rh2oEVPZkzXqFl-oDZB854Dl/s320/S1_13.png" /></a></div><br />
<br />
Presionamos el botón "<span class="codigo">Next</span>" y en la siguiente pantalla seleccionamos la opción "<span class="codigo">Well-formed Document</span>". Presionamos el botón "<span class="codigo">Finish</span>" y tendremos un archivo <span class="codigo">XML</span>, al cual debemos quitarle todo el contenido.<br />
<br />
Dentro de este archivo se configuran algunas constantes que modifican, de una manera mínima, el comportamiento del filtro de <span class="codigo">Struts</span>. También se configuran paquetes, que son conjuntos de acciones que podemos organizar debajo de un mismo <span class="codigo">namespace</span>. Dentro de los paquetes podemos definir un conjunto de acciones, cada una de las cuales responderá a una petición. Y dentro de las acciones podemos definir resultados, que son las páginas o vistas a las que se enviará al usuario dependiendo del resultado de una acción (como ya habíamos dicho: existen muchos tipos de resultados, no solo páginas <span class="codigo">JSP</span>, por ejemplo podemos hacer que un usuario descargue un archivo, o que visualice un reporte o una gráfica; incluso es posible redirigir a un usuario a otra acción).<br />
<br />
Este archivo de configuración debe iniciar con el elemento "<span class="codigo"><struts></span>":<br />
<br />
<pre><code>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
</struts>
</code></pre><br />
<br />
Dentro de estas etiquetas se configuran los demás elementos.<br />
<br />
Lo siguiente que haremos será configurar una constante que le indicará a <span class="codigo">Struts</span> que nos encontramos en la etapa de desarrollo, con esto generará más mensajes de salida para que sepamos si estamos haciendo algo mal, y nos mostrará los errores de una forma más clara. Para esto establecemos la constante "<span class="codigo">struts.devMode</span>" con un valor de "<span class="codigo">true</span>":<br />
<br />
<pre><code>
<constant name="struts.devMode" value="true" />
</code></pre><br />
<br />
Otra constante útil que podemos definir es "<span class="codigo">struts.configuration.xml.reload</span>". Esta constante permite que cuando modifiquemos los archivos de configuración de <span class="codigo">Struts 2</span> no tengamos que volver a hacer un deploy de la aplicación para que los cambios tomen efecto:<br />
<br />
<pre><code>
<constant name="struts.configuration.xml.reload" value="true" />
</code></pre><br />
<br />
<br />
Hasta ahora nuestro archivo de configuración debe verse de la siguiente manera:<br />
<br />
<pre><code>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<constant name="struts.devMode" value="true" />
<constant name="struts.configuration.xml.reload" value="true" />
</struts>
</code></pre><br />
<br />
Ahora definimos nuestro primer paquete, usando el elemento "<span class="codigo"><package></span>". Los paquetes nos permiten tener una configuración común para un conjunto de <span class="codigo">Action</span>s. Por ejemplo, se pude definir el conjunto de interceptores que se aplican sobre ellos, resultados comunes, manejo de excepciones, etc.<br />
<br />
Cada uno de los paquetes que declaremos debe tener un <span class="negritas">nombre</span> y <span class="negritas">extender de algún otro paquete</span>. Opcionalmente también pueden tener un <span class="codigo">namespace</span>, el cual es un fragmento que deberá será agregado a la <span class="codigo">URL</span> de la petición para hacer referencia a ese paquete. Por ejemplo, si definimos un paquete con el namespace "<span class="codigo">administracion</span>", y dentro de este hay un <span class="codigo">Action</span> cuyo nombre es "<span class="codigo">registro</span>", para ejecutar ese <span class="codigo">Action</span> deberemos hacer una petición a la siguiente <span class="codigo">URL</span>:<br />
<br />
<pre><code>
http://servidor:puerto/aplicacion/administracion/registro.action
</code></pre><br />
<br />
Cuando extendemos de otro paquete <span class="negritas">heredamos su configuración</span> (de hecho podemos tener paquetes abstractos con una configuración general, y hacer que varios paquetes extiendan de estos). Como en este momento no tenemos otro paquete, este debe extender de "<span class="codigo">struts-default</span>", que es el paquete base para la las aplicaciones de <span class="codigo">Struts 2</span>. Como nombre de nuestro paquete colocaremos "<span class="codigo">demo-struts</span>":<br />
<br />
<pre><code>
<package name="demo-struts" extends="struts-default">
</package>
</code></pre><br />
<br />
Como podemos ver, no hemos indicado ningún <span class="codigo">namespace</span> en nuestro paquete. Esto quiere decir que estamos usando el namespace por <span class="negritas">default</span> (o sea "<span class="codigo"></span>"). Cuando trabajamos con namespaces podemos usar principalmente 3 tipos: el namespace por <span class="negritas">default</span> (como en este caso), el namespace <span class="negritas">raíz</span> ("<span class="codigo">/</span>") en cual en la <span class="codigo">URL</span> de petición luce exactamente igual al namespace defult, o un namespace propio. Si invocamos un <span class="codigo">Action</span> usando un namespace distinto al default (como en el ejemplo anterior "<span class="codigo">adminsitracion/registro.action</span>") y <span class="codigo">Struts 2</span> no puede encontrar ningún <span class="codigo">Action</span> que coincida con nuestra petición, en ese namespace, lo buscará en el namespace default. O sea, en el ejemplo anterior, si no encuentra el <span class="codigo">Action</span> "<span class="codigo">registro</span>" en el namespace "<span class="codigo">administracion</span>", irá a buscarlo al namespace default.<br />
<br />
Algo importante que hay que decir sobre los namespaces, es que estos <span class="negritas">NO se comportar como nombres de directorios</span>. Esto quiere decir que si hacemos una petición a un <span class="codigo">Action</span> en el namespace "<span class="codigo">administracion/usuarios</span>", digamos: "<span class="codigo">adminsitracion/usuarios/registro.action</span>" y <span class="codigo">Struts 2</span> no encuentra el <span class="codigo">Action</span> "<span class="codigo">registro</span>" en ese namespace, irá a buscarlo al namespace por default, y no a un namespace "<span class="codigo">adminsitracion</span>".<br />
<br />
Ahora mapearemos nuestro primer <span class="codigo">Action</span>. Para esto usamos el elemento "<span class="codigo"><action></span>". Todas las acciones deben tener un nombre que <span class="negritas">debe ser único dentro de un namespace</span>. Las acciones deben estar asociadas con una clase que será la que ejecute la acción adecuada. Si no indicamos una clase, <span class="codigo">Struts 2</span> usará una clase por default. Esta clase por default lo que hace es regresar siempre un resultado exitoso o "<span class="codigo">SUCCESS</span>" de su ejecución. En este caso llamaremos a nuestra acción "<span class="codigo">index</span>":<br />
<br />
<pre><code>
<action name="index">
</action>
</code></pre><br />
<br />
Finalmente, debemos indicar el resultado de la ejecución de esta acción, con el elemento "<span class="codigo"><result></span>". Una acción puede tener varios resultados, dependiendo de la ejecución de la misma. Cada uno de estos resultados debe tener un nombre al que se hará referencia dentro de la acción para definir qué vista regresar al usuario. Se puede tener, por ejemplo, un resultado para el caso de una acción exitosa, otro para una situación de error, otra para cuando el usuario no ha ingresado todos los datos requeridos en un formulario, otra para cuando el usuario que ingresa es un administrador, otro para cuando es un usuario anónimo, etc.<br />
<br />
Definimos que en el caso de una ejecución exitosa (<span class="codigo">success</span>) se debe enviar al usuario a la página "<span class="codigo">index.jsp</span>", de la siguiente forma (noten que la ruta de la página debe indicarse a partir de la raíz del directorio web):<br />
<br />
<pre><code>
<result name="success">/index.jsp</result>
</code></pre><br />
<br />
El nombre por default de un <span class="codigo">result</span> es "<span class="codigo">success</span>", así que podemos dejar la línea anterior de la siguiente manera:<br />
<br />
<pre><code>
<result>/index.jsp</result>
</code></pre>Normalmente tratamos de usar los nombres de los <span class="codigo">result</span> que se definen dentro de la interface <span class="codigo">Action</span> ("<span class="codigo">success</span>", "<span class="codigo">error</span>", "<span class="codigo">input</span>", etc.), para poder hacer uso de estas constantes cuando nos referimos a ellos, pero en realidad podemos darles el nombre que queramos (como "<span class="codigo">exito</span>"o "<span class="codigo">fallo</span>") y regresar estos valores en el método "<span class="codigo">execute</span>".<br />
<br />
Esto es todo, por lo que nuestro archivo de configuración queda de la siguiente forma:<br />
<br />
<pre><code>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<constant name="struts.devMode" value="true" />
<constant name="struts.configuration.xml.reload" value="true" />
<package name="demo-struts" extends="struts-default">
<action name="index">
<result>/index.jsp</result>
</action>
</package>
</struts>
</code></pre><br />
<br />
Ejecutamos la aplicación y entramos a la <span class="codigo">URL</span>:<br />
<pre><code>
<a href="http://localhost:8080/struts2/index.action">http://localhost:8080/struts2/index.action</a>
</code></pre><br />
<br />
Noten que estamos haciendo referencia al <span class="codigo">Action</span> "<span class="codigo">index</span>" mediante su nombre y agregando al final de este el postfijo "<span class="codigo">action</span>". <span class="codigo">Struts 2</span> tiene configurados 2 postfijos que se usan por default para las acciones: "<span class="codigo">action</span>" y "", por lo que también podemos hacer la petición a la siguiente <span class="codigo">URL</span>:<br />
<br />
<pre><code>
<a href="http://localhost:8080/struts2/index">http://localhost:8080/struts2/index</a>
</code></pre><br />
<br />
Si todo está bien configurado deberemos ver una pantalla como la siguiente:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj3dn1cZx30KhnNlryTs00rhAJkSJO6WqWeZuEq9Kg2GAklo50CurerKGNlCAeZemKABsXVSaOkQEBAfh1WxjnxfKCVWzFpXxoIkg9oxZNiL92dSDYaQd46RFOxv_DaOuHI_KCNNCo7IhPC/s1600/S1_14.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="194" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj3dn1cZx30KhnNlryTs00rhAJkSJO6WqWeZuEq9Kg2GAklo50CurerKGNlCAeZemKABsXVSaOkQEBAfh1WxjnxfKCVWzFpXxoIkg9oxZNiL92dSDYaQd46RFOxv_DaOuHI_KCNNCo7IhPC/s320/S1_14.png" /></a></div><br />
<br />
Con este ejemplo pudimos ver algunos de los detalles de configuración de <span class="codigo">Struts 2</span>, sin embargo la salida es muy sencilla. Hagamos algo un poco más interesante: haremos que el mensaje de la página sea obtenido desde un <span class="codigo">Action</span>.<br />
<br />
Primero crearemos un nuevo paquete. Hacemos clic derecho en el nodo "<span class="codigo">Source Packages</span>", en el menú que aparece seleccionamos la opción "<span class="codigo">New -> Java Package...</span>":<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj83pDz6_z7V_WjK0SWkniIOBv5i03sDLjpz6RZv_HzL5JGobBoE8Y7VhEUF5rh-Mc8EdO_DMu09J7QnzuVVxVYpwso2irr2lRW2UJy9PO_ZsUOd2fIyD2znJuJA9KLmPeLb5yfGwyTZC6N/s1600/S1_15.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="320" width="310" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj83pDz6_z7V_WjK0SWkniIOBv5i03sDLjpz6RZv_HzL5JGobBoE8Y7VhEUF5rh-Mc8EdO_DMu09J7QnzuVVxVYpwso2irr2lRW2UJy9PO_ZsUOd2fIyD2znJuJA9KLmPeLb5yfGwyTZC6N/s320/S1_15.png" /></a></div><br />
<br />
El nombre de mi paquete será "<span class="codigo">com.javatutoriales.struts2.configuracion.actions</span>". Dentro de este paquete crearemos una nueva clase. Hacemos clic derecho en el paquete que acabamos de crear, en el menú que aparece seleccionamos la opción "<span class="codigo">New -> Java Class...</span>":<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiniRFD1TnVd9RXq2h3fQevI4SE0oA86gu5aF8hfifZ_ALdQ4jAzoJBdsgCn2dMwKeMwqlJDYlLvYXqRUUBetd3mYgYiCbWCKY668VoCM2Dq8l4-qG0UwQAwu8QhyphenhyphenLDX9oxxPowCn_h3KlG/s1600/S1_16.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="248" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiniRFD1TnVd9RXq2h3fQevI4SE0oA86gu5aF8hfifZ_ALdQ4jAzoJBdsgCn2dMwKeMwqlJDYlLvYXqRUUBetd3mYgYiCbWCKY668VoCM2Dq8l4-qG0UwQAwu8QhyphenhyphenLDX9oxxPowCn_h3KlG/s320/S1_16.png" /></a></div><br />
<br />
El nombre de la clase será "<span class="codigo">SaludoAction</span>". En esta clase colocaremos un atributo de tipo <span class="codigo">String</span> que se llame mensaje. También proporcionaremos un <span class="codigo">getter</span> para este atributo:<br />
<br />
<pre><code>
public class SaludoAction
{
private String mensaje;
public String getMensaje()
{
return mensaje;
}
}
</code></pre><br />
<br />
Ahora crearemos el método de negocio que realiza el proceso propio de la acción. Para esto <span class="codigo">Struts 2</span> siempre invoca un método llamado "<span class="codigo">excecute</span>" (aunque hay forma de modificar esto en el archivo de configuración). Este método debe regresar un <span class="codigo">String</span>, que indica el nombre de <span class="codigo">result</span> que debe ser mostrado al usuario.<br />
<br />
En este caso lo único que hará nuestro método "<span class="codigo">execute</span>" es establecer el valor del mensaje:<br />
<br />
<pre><code>
public String execute()
{
mensaje = "Bienvenido al mundo de <span class="codigo">Struts 2</span>";
}
</code></pre><br />
<br />
E indicaremos que se debe regresar el <span class="codigo">result</span> cuyo nombre sea "<span class="codigo">success</span>":<br />
<br />
<pre><code>
public String execute()
{
mensaje = "Bienvenido al mundo de <span class="codigo">Struts 2</span>";
return "success";
}
</code></pre><br />
<br />
Si se dan cuenta, estamos regresando el nombre del <span class="codigo">result</span> usando una cadena literal ("<span class="codigo">success</span>") esto parece sencillo en un principio, pero puede ser fuente de errores y dolores de cabeza si cometemos un error y escribimos, por ejemplo "<span class="codigo">succes</span>", o "<span class="codigo">sucess</span>". Como habíamos dicho antes, para simplificar esto (entre otras cosas que veremos más adelante) <span class="codigo">Struts</span> proporciona una clase base de la que podemos extender. Esta clase es "<span class="codigo">ActionSupport</span>", la cual implementa la interface "<span class="codigo">Action</span>" que define las siguientes constantes para regresar los nombres de los <span class="codigo">results</span> correspondientes:<br />
<br />
<ul><li><span class="codigo">ERROR</span></li>
<li><span class="codigo">INPUT</span></li>
<li><span class="codigo">LOGIN</span></li>
<li><span class="codigo">NONE</span></li>
<li><span class="codigo">SUCCESS</span></li>
</ul><br />
Modificamos nuestra clase para que quede de la siguiente forma:<br />
<br />
<pre><code>
public class SaludoAction extends ActionSupport
{
private String mensaje;
@Override
public String execute()
{
mensaje = "Bienvenido al mundo de Struts 2";
return SUCCESS;
}
public String getMensaje()
{
return mensaje;
}
}
</code></pre><br />
<br />
Ahora debemos modificar el archivo de configuración. En este caso agregaremos un <span class="codigo">Action</span> el cual responderá al nombre "<span class="codigo">saludo</span>", y la clase que se encargará de ejecutar la lógica de esta acción será "<span class="codigo">com.javatutoriales.struts2.configuracion.actions.SaludoAction</span>". En caso de un resultado exitoso (el único que se tiene) se mostrará al usuario la página "<span class="codigo">saludo.jsp</span>", de esta forma:<br />
<br />
<pre><code>
<action name="saludo" class="com.javatutoriales.struts2.configuracion.actions.SaludoAction">
<result>/saludo.jsp</result>
</action>
</code></pre><br />
<br />
Crearemos la página "<span class="codigo">saludo.jsp</span>" en el directorio raíz de las páginas web. Hacemos clic derecho en el nodo "<span class="codigo">Web Pages</span>". En el menú que se abre seleccionamos la opción "<span class="codigo">New –> JSP…</span>":<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgPlAiwtKO5wmhudN2y_HJCTOvBtM1fctOY6RVMKHLaHs8AmUdlnaSVQ7ZKmDvVGjoYtt3gHTBVNwGsanXd77zSQAdqM2rjbhik_FO_PuHF323h0m8HThksh58BLGq2djiOyhi6DbvWcIoe/s1600/S1_17.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="266" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgPlAiwtKO5wmhudN2y_HJCTOvBtM1fctOY6RVMKHLaHs8AmUdlnaSVQ7ZKmDvVGjoYtt3gHTBVNwGsanXd77zSQAdqM2rjbhik_FO_PuHF323h0m8HThksh58BLGq2djiOyhi6DbvWcIoe/s320/S1_17.png" /></a></div><br />
<br />
Damos "<span class="codigo">saludo</span>" como nombre de la página y presionamos el botón "<span class="codigo">Finish</span>".<br />
<br />
Dentro de la página usaremos las etiquetas de <span class="codigo">Struts</span> para mostrar el valor correspondiente a la propiedad del objeto. Indicamos que usaremos la biblioteca de etiquetas (taglib) "<span class="codigo">/struts-tags</span>". Por convención usaremos el prefijo "<span class="codigo">s</span>" para hacer referencia a esta biblioteca:<br />
<br />
<pre><code>
<%@taglib uri="/struts-tags" prefix="s" %>
</code></pre><br />
<br />
Dentro de esta biblioteca se encuentran contenidas todas las etiquetas de <span class="codigo">Struts 2</span>. Tiene etiquetas control de flujo (<span class="codigo">if</span>, <span class="codigo">iterator</span>), de datos (<span class="codigo">a</span>, <span class="codigo">bean</span>, <span class="codigo">i18n</span>), de formulario (<span class="codigo">checkbox</span>, <span class="codigo">form</span>, <span class="codigo">label</span>, <span class="codigo">submit</span>), para el manejo de componentes ajax (<span class="codigo">autocompleter</span>, <span class="codigo">tree</span>). En <a href="http://struts.apache.org/2.x/docs/tag-reference.html">el sitio de struts</a> pueden enontrar todas las etiquetas con las que cuenta <span class="codigo">Struts 2</span>.<br />
<br />
Usaremos una etiqueta de datos: "<span class="codigo">property</span>". Dentro de esta etiqueta debemos indicar el nombre de la propiedad que queremos mostrar, en este caso "<span class="codigo">mensaje</span>", usando su atributo "<span class="codigo">value</span>":<br />
<br />
<pre><code>
<s:property value="mensaje" />
</code></pre><br />
<br />
Nuestra página debe quedar de la siguiente forma:<br />
<br />
<pre><code>
<?xml version="1.0" encoding="UTF-8"?>
<%@taglib uri="/struts-tags" prefix="s" %>
<%@page pageEncoding="UTF-8" contentType="text/html;charset=UTF-8" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="es" >
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>JSP Page</title>
</head>
<body>
<h1><s:property value="mensaje" /></h1>
</body>
</html>
</code></pre><br />
<br />
Ahora, cuando entremos a la siguiente <span class="codigo">URL</span>:<br />
<br />
<pre><code>
<a href="http://localhost:8080/struts2/saludo.action">http://localhost:8080/struts2/saludo.action</a>
</code></pre><br />
<br />
Debemos ver el mensaje que establecimos en el <span class="codigo">Action</span>:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgOu5Xw7a4_K65DeMwPkG4LVIiZu7bPsz7FTX6CpjcLUpe4J_IwNfU40rYQK5nMmz08GKznpr5LvC8pM4yWKoixteOiSUgOuhcu0bWPGEsUIg70Na1b1S_THKSNjETwQJ6feFCurwq3zWC4/s1600/S1_18.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="182" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgOu5Xw7a4_K65DeMwPkG4LVIiZu7bPsz7FTX6CpjcLUpe4J_IwNfU40rYQK5nMmz08GKznpr5LvC8pM4yWKoixteOiSUgOuhcu0bWPGEsUIg70Na1b1S_THKSNjETwQJ6feFCurwq3zWC4/s320/S1_18.png" /></a></div><br />
<br />
Aumentaremos aún más el grado de complejidad del ejemplo y haremos que la página nos muestre un mensaje saludando al usuario, este proporcionará su nombre, junto con su número de la suerte, a través de un formulario.<br />
<br />
Creamos una nueva página con el formulario, a esta página la llamaremos "<span class="codigo">datos.jsp</span>", y la colocaremos en el directorio raíz de las páginas web.<br />
<br />
En esta página importaremos la biblioteca de etiquetas de <span class="codigo">Struts</span>, como lo habíamos hecho anteriormente:<br />
<br />
<pre><code>
<%@taglib uri="/struts-tags" prefix="s" %>
</code></pre><br />
<br />
Ahora crearemos un formulario con dos campos, uno para el nombre del usuario y otro para su número de la suerte. Para esto usaremos la etiqueta "<span class="codigo">form</span>", la cual nos permite definir un formulario, y dentro de estas colocaremos los campos del mismo. En este formulario debemos colocar un atributo, "<span class="codigo">action</span>", que indica el <span class="codigo">Action</span> al que se deben enviar los datos para ser procesados, en este caso la acción se llamará "<span class="codigo">saludoUsuario</span>", y lo crearemos en un momento:<br />
<br />
<pre><code>
<s:form action="saludoUsuario">
</s:form>
</code></pre><br />
<br />
Dentro de estas etiquetas colocaremos tres componentes, dos campos de entrada de texto (<span class="codigo">texfields</span>) y un botón para enviar la información (<span class="codigo">submit</span>). <span class="codigo">Struts 2</span> proporciona etiquetas para cada componente que podemos colocar en un formulario. Además proporciona distintos temas que pueden aplicarse a los sitios o a componentes específicos (<span class="codigo">normal</span>, <span class="codigo">xhtml</span>, <span class="codigo">xhtml_css</span>, y <span class="codigo">simple</span>). Hablaremos más de esto un poco más adelante. Gracias al tema por default (<span class="codigo">xhtml</span>) podemos colocar en las etiquetas información con respecto al formulario, como por ejemplo las etiquetas (labels) de los componentes, o indicar si estos datos son requeridos, como atributos de las etiquetas de <span class="codigo">Struts</span>:<br />
<br />
<pre><code>
<s:form action="saludoUsuario">
<s:textfield label="Nombre" name="nombre" />
<s:textfield label="Número de la suerte" name="numero" />
<s:submit value="Enviar" />
</s:form>
</pre></code><br />
<br />
Podemos ver que en los campos de texto (los textfield) hemos colocado, en los atributos "<span class="codigo">name</span>" correspondientes, los nombres "<span class="codigo">nombre</span>" y "<span class="codigo">numero</span>". Es con estos nombres que se hará referencia a las propiedades (o al menos a los <span class="codigo">setters</span>) del <span class="codigo">Action</span> que se encargará de procesar este formulario.<br />
<br />
La página debe haber quedado de la siguiente forma:<br />
<br />
<pre><code>
<?xml version="1.0" encoding="UTF-8"?>
<%@page pageEncoding="ISO-8859-1" contentType="text/html;charset=UTF-8" %>
<%@taglib uri="/struts-tags" prefix="s" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="es" >
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
<s:form action="saludoUsuario">
<s:textfield label="Nombre" name="nombre" />
<s:textfield label="Número de la suerte" name="numero" />
<s:submit value="Enviar" />
</s:form>
</body>
</html>
</pre></code><br />
<br />
Ahora crearemos el <span class="codigo">Action</span> que se encargará de procesar estos datos para saludar al usuario. Creamos una nueva clase Java, en el paquete "<span class="codigo">com.javatutoriales.struts2.configuracion.actions</span>", llamada "<span class="codigo">SaludoUsuarioAction</span>" y hacemos que extienda de "<span class="codigo">ActionSupport</span>":<br />
<br />
<pre><code>
public class SaludoUsuarioAction extends ActionSupport
{
}
</code></pre><br />
<br />
A esta clase le agregamos dos atributos, un <span class="codigo">String</span> llamado "<span class="codigo">nombre</span>" que contendrá el nombre del usuario, y un <span class="codigo">int</span> llamado "<span class="codigo">numero</span>" que contendrá el número de la suerte del usuario. También agregamos sus <span class="codigo">setters</span> corresponientes:<br />
<br />
<pre><code>
public class SaludoUsuarioAction extends ActionSupport
{
private String nombre;
private int numero;
public void setNombre(String nombre)
{
this.nombre = nombre;
}
public void setNumero(int numero)
{
this.numero = numero;
}
}
</code></pre><br />
<br />
Ahora agregamos un <span class="codigo">String</span>, llamado "<span class="codigo">mensaje</span>", que contendrá el mensaje que se regresará al usuario. Colocamos también un <span class="codigo">getter</span> para esta propiedad:<br />
<br />
<pre><code>
private String mensaje;
public String getMensaje()
{
return mensaje;
}
</code></pre><br />
<br />
Lo siguiente que haremos es sobre-escribir el método "<span class="codigo">execute</span>" estableciendo el mensaje del usuario:<br />
<br />
<pre><code>
@Override
public String execute() throws Exception
{
mensaje = "Bienvenido " + nombre + " al munddo de <span class="codigo">Struts 2</span>. Tu número de la suerte de hoy es " + numero;
return SUCCESS;
}
</code></pre><br />
<br />
La clase "<span class="codigo">SaludoUsuarioAction</span>" completa queda de la siguiente forma:<br />
<br />
<pre><code>
public class SaludoUsuarioAction extends ActionSupport
{
private String nombre;
private int numero;
private String mensaje;
@Override
public String execute() throws Exception
{
mensaje = "Bienvenido " + nombre + " al munddo de <span class="codigo">Struts 2</span>. Tu número de la suerte de hoy es " + numero;
return SUCCESS;
}
public String getMensaje()
{
return mensaje;
}
public void setNombre(String nombre)
{
this.nombre = nombre;
}
public void setNumero(int numero)
{
this.numero = numero;
}
}
</code></pre><br />
<br />
¿Por qué hemos colocado <span class="codigo">setters</span> de unas propiedades y <span class="codigo">getters</span> de otras? Bien, esto es porque algunas de las propiedades del action ("<span class="codigo">nombre</span>" y "<span class="codigo">mensaje</span>") serán establecidas por los interceptores. Estos valores son recibidos a través del formulario que creamos anteriormente. En el caso del atributo "<span class="codigo">mensaje</span>" este será leído por el framework para presentar los datos en una página <span class="codigo">JSP</span>.<br />
<br />
En otras palabras, debemos proporcionar <span class="codigo">setters</span> para las propiedades que serán establecidas por el framework, y <span class="codigo">getters</span> para las propiedades que serán leídas.<br />
<br />
Ahora haremos la página que mostrará el saludo al usuario. Agregamos una página llamada "<span class="codigo">saludoUsuario.jsp</span>". En esta página indicaremos que haremos uso de la biblioteca de etiquetas de <span class="codigo">Struts 2</span>, como lo hemos hecho anteriormente. También usaremos la etiqueta "<span class="codigo">property</span>" para mostrar el mensaje que generamos en el action:<br />
<pre><code>
<%@taglib uri="/struts-tags" prefix="s" %>
</code></pre>y<br />
<pre><code>
<s:property value="mensaje" />
</code></pre><br />
<br />
El último paso es configurar este <span class="codigo">Action</span> en el archivo "<span class="codigo">struts.xml</span>". Aquí indicaremos un <span class="codigo">Action</span> llamado "<span class="codigo">saludoUsuario</span>" que será procesado por la clase "<span class="codigo">SaludoUsuarioAction</span>". También indicaremos un <span class="codigo">result</span> que enviará al usuario a la página "<span class="codigo">saludoUsuario.jsp</span>":<br />
<br />
<pre><code>
<action name="saludoUsuario" class="com.javatutoriales.struts2.configuracion.actions.SaludoUsuarioAction">
<result>/saludoUsuario.jsp</result>
</action>
</code></pre><br />
<br />
Esto es todo. Ahora iniciamos nuestra aplicación y entramos a la siguiente dirección:<br />
<br />
<pre><code>
<a href="http://localhost:8080/struts2/datos.jsp">http://localhost:8080/struts2/datos.jsp</a>
</code></pre><br />
<br />
Deberemos ver un formulario como el siguiente:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEghsudNDQwlnZzrz_B3Fhijuly5YgXpVeaNF40_QZy6AM1EdsQudWU8KLOx-JsPKn0vK7DZrD3B9r72Tnr6x4TVvdnVp6WiNRF4qWOTTNW1gc_QBE0vlNEKkKC6cjoOClh2EqlmDtaP-jW8/s1600/S1_19.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="243" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEghsudNDQwlnZzrz_B3Fhijuly5YgXpVeaNF40_QZy6AM1EdsQudWU8KLOx-JsPKn0vK7DZrD3B9r72Tnr6x4TVvdnVp6WiNRF4qWOTTNW1gc_QBE0vlNEKkKC6cjoOClh2EqlmDtaP-jW8/s320/S1_19.png" /></a></div><br />
<br />
Como podemos ver, las etiquetas de <span class="codigo">Struts</span> han generado un formulario y lo han acomodado para que se vea bonito :D. Si revisamos el código fuente de la página podremos ver que dentro del formulario hay una tabla para acomodar los componentes:<br />
<br />
<pre><code>
<form id="saludoUsuario" name="saludoUsuario" action="/struts2/saludoUsuario.action" method="post">
<table class="wwFormTable">
<tr>
<td class="tdLabel">
<label for="saludoUsuario_nombre" class="label">Nombre:</label>
</td>
<td>
<input type="text" name="nombre" value="" id="saludoUsuario_nombre"/>
</td>
</tr>
<tr>
<td class="tdLabel">
<label for="saludoUsuario_numero" class="label">Número de la suerte:</label>
</td>
<td>
<input type="text" name="numero" value="" id="saludoUsuario_numero"/>
</td>
</tr>
<tr>
<td colspan="2">
<div align="right"><input type="submit" id="saludoUsuario_0" value="Enviar"/></div>
</td>
</tr>
</table>
</form>
</code></pre><br />
<br />
Aunque el formulario se ve muy bien de esta forma, el uso de tablas para el acomodo de los componentes se considera una mala práctica, veremos cómo hacerlo de forma correcta en algún otro tutorial ^_^.<br />
<br />
Ahora, cuando llenemos los datos del formulario y hagamos clic en el botón enviar el <span class="codigo">Action</span> procesará nuestros datos y nos enviará la respuesta correspondiente, por lo que deberemos ver la siguiente pantalla:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjTUNghQukEF73DiPRp0itelCMn7BPYfNXn8DsZ5RIAig6wiTcZDLbZogSB1HzdIDAeuVxeJFQAhpiWxVk5uZDcLJQpA87RntZyTdyY3Vj3L7JzM8xQPCMtRr_V8v31mS04eQlHmO_5hli9/s1600/S1_20.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="243" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjTUNghQukEF73DiPRp0itelCMn7BPYfNXn8DsZ5RIAig6wiTcZDLbZogSB1HzdIDAeuVxeJFQAhpiWxVk5uZDcLJQpA87RntZyTdyY3Vj3L7JzM8xQPCMtRr_V8v31mS04eQlHmO_5hli9/s320/S1_20.png" /></a></div><br />
<br />
Como podemos ver, los datos fueron establecidos correctamente y el mensaje mostrado es correcto. También podemos ver que en este caso ocurre algo interesante, el atributo "<span class="codigo">numero</span>" que establecimos como tipo "<span class="codigo">int</span>" fue tratado como eso, como un número, no como un <span class="codigo">String</span>, como ocurre normalmente en los <span class="codigo">Servlets</span>. Esto es debido a que los interceptores se encargan de transformar los datos que reciba al tipo indicado. Lo mismo ocurre con valores booleanos, arreglos, fechas, archivos, etc. Sin que tengamos que hacer la conversión manual.<br />
<br />
Ahora que veremos cómo configurar nuestra aplicación usando anotaciones.<br />
<br />
<br />
<h2 class="titulo">Configuración de <span class="codigo">Struts 2</span> Usando Anotaciones</h2>Cuando usamos anotaciones para realizar la configuración de una aplicación con <span class="codigo">Struts 2</span>, necesitamos hacer uso de un plugin, el plugin "<span class="codigo">convention</span>" (el cual se encuentra en el jar "<span class="codigo">struts2-convention-plugin-2.2.3</span>" que agregamos para la biblioteca de "<span class="codigo">Struts2Anotaciones</span>"). Este plugin proporciona la característica de poder crear una aplicación <span class="codigo">Struts</span>, siguiendo una serie de convenciones de nombres tanto para los <span class="codigo">results</span>, los <span class="codigo">Action</span>s, en fin, para todo lo relacionado con <span class="codigo">Struts</span>. Esto sin necesidad de crear ningún archivo de configuración ni usar ninguna anotación.<br />
<br />
Veremos el uso de este plugin en un tutorial exclusivo, un poco más adelante, por ahora debemos crear un nuevo proyecto web en el <span class="codigo">NetBeans</span> siguiendo los pasos anteriores, hasta antes de llegar al título que dice "<span class="codigo">Configuración de <span class="codigo">Struts 2</span> Usando un Archivo XML</span>" ^_^. O sea, debemos tener creado un proyecto web, configuramos el filtro "<span class="codigo">struts2</span>" en el archivo "<span class="codigo">web.xml</span>" y agregamos, en vez de la biblioteca "<span class="codigo">Stuts2</span>" que usamos para configurar usando archivos <span class="codigo">XML</span>, la biblioteca "<span class="codigo">Struts2Anotaciones</span>".<br />
<br />
Una vez llegado a este punto, creamos un paquete en nuestra aplicación, en mi caso será "<span class="codigo">com.javatutoriales.struts2.configuracion.acciones</span>", y será en este paquete en el que coloquemos todas las acciones que creemos en el tutorial.<br />
<br />
Dentro de este paquete crearemos una clase llamada "<span class="codigo">SaludoAction</span>", esta clase puede extender de "<span class="codigo">ActionSupport</span>", aunque como vimos no es necesario. Yo si extenderé de de "<span class="codigo">ActionSupport</span>":<br />
<br />
<pre><code>
public class SaludoAction extends ActionSupport
{
}
</code></pre><br />
<br />
A esta clase le agregaremos un atributo, de tipo <span class="codigo">String</span>, llamado "<span class="codigo">mensaje</span>", con su <span class="codigo">getter</span> correspondiente:<br />
<br />
<pre><code>
public class SaludoAction extends ActionSupport
{
private String mensaje;
public String getMensaje()
{
return mensaje;
}
}
</code></pre><br />
<br />
Lo siguiente es sobre-escribir el método "<span class="codigo">execute</span>" para que genere un mensaje de saludo al usuario:<br />
<br />
<pre><code>
@Override
public String execute()
{
mensaje = "Hola Mundo de <span class="codigo">Struts 2</span>";
return SUCCESS;
}
</code></pre><br />
<br />
La clase "<span class="codigo">SaludoAction</span>" hasta el momento se ve de esta forma:<br />
<br />
<pre><code>
public class SaludoAction extends ActionSupport
{
private String mensaje;
@Override
public String execute()
{
mensaje = "Hola Mundo de <span class="codigo">Struts 2</span>";
return SUCCESS;
}
public String getMensaje()
{
return mensaje;
}
}
</code></pre><br />
<br />
Ahora configuraremos este <span class="codigo">Action</span>. Para eso tenemos dos formas de hacerlo. Ambas son casi iguales, con diferencias muy sutiles. En la primera debemos colocar las anotaciones <span class="negritas">a nivel de la clase</span>. Las anotaciones son muy claras, y representan los elementos importantes del archivo <span class="codigo">XML</span>.<br />
<br />
Lo primero que debemos hacer es indicar el namespace para la acción, usando la anotación "<span class="codigo">@Namespace</span>":<br />
<br />
<pre><code>
@Namespace(value="/")
public class SaludoAction extends ActionSupport
</code></pre><br />
<br />
Ahora debemos usar la anotación "<span class="codigo">@Action</span>" para indicar el nombre de esta acción, usando su atributo "<span class="codigo">value</span>":<br />
<br />
<pre><code>
@Namespace(value="/")
@Action(value="saludo")
public class SaludoAction extends ActionSupport
</code></pre><br />
<br />
Lo último que debemos hacer es configurar los <span class="codigo">result</span>s para este <span class="codigo">Action</span>. Esto lo hacemos usando el atributo "<span class="codigo">results</span>" de la anotación "<span class="codigo">@Action</span>" y la anotación "<span class="codigo">@Result</span>". Dentro de esta última colocaremos el nombre del <span class="codigo">result</span>, en su atributo "<span class="codigo">name</span>", y la ubicación de la página que mostrará el resultado apropiado, usando su atributo "<span class="codigo">location</span>":<br />
<br />
<pre><code>
@Namespace(value="/")
@Action(value="saludo", results={@Result(name="success", location="/saludo.jsp")})
public class SaludoAction extends ActionSupport
</code></pre><br />
<br />
Esto es todo para la primera forma de configuración usando anotaciones. La segunda forma es muy parecida, solo que esta vez colocamos las anotaciones <span class="negritas">a nivel del método "<span class="codigo">execute</span>"</span>. Si lo hacemos así no podemos usar la anotación "<span class="codigo">@Namespace</span>", pero podemos agregar el namespace directamente en el atributo "<span class="codigo">value</span>" de la anotación "<span class="codigo">@Action</span>":<br />
<br />
<pre><code>
@Override
@Action(value="/saludo", results={@Result(name="success", location="/saludo.jsp")})
public String execute()
{
mensaje = "Hola Mundo de <span class="codigo">Struts 2</span>";
return SUCCESS;
}
</code></pre><br />
<br />
En lo personal prefiero la primera forma así que será esta la que usaremos en los tutoriales :D. La clase "<span class="codigo">SaludoAction</span>" anotada queda de la siguiente forma:<br />
<br />
<pre><code>
@Namespace(value="/")
@Action(value="saludo", results=@Result(name="success", location="/saludo.jsp"))
public class SaludoAction extends ActionSupport
{
private String mensaje;
@Override
public String execute()
{
mensaje = "Hola Mundo de <span class="codigo">Struts 2</span>";
return "success";
}
public String getMensaje()
{
return mensaje;
}
}
</code></pre><br />
<br />
Ahora podemos ejecutar nuestra aplicación y entrar a la siguiente dirección:<br />
<br />
<pre><code>
<a href="http://localhost:8080/strutsanotaciones/saludo.action">http://localhost:8080/strutsanotaciones/saludo.action</a>
</code></pre><br />
<br />
Con lo que deberemos ver la siguiente pantalla:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgiDIHSr2XVWtzuqBAfSk34j043E3-I0X2LcErLiAl1rFK0YzPI_iOBOZZ62LUwBlZ4DY4oqwnRvRmke8w0Vc-0_sVv4SO2NsKAIgktEocX8N23DlRP1e2J3Swh7VZnGS0BIC6X9I9SPAh_/s1600/S1_21.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="243" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgiDIHSr2XVWtzuqBAfSk34j043E3-I0X2LcErLiAl1rFK0YzPI_iOBOZZ62LUwBlZ4DY4oqwnRvRmke8w0Vc-0_sVv4SO2NsKAIgktEocX8N23DlRP1e2J3Swh7VZnGS0BIC6X9I9SPAh_/s320/S1_21.png" /></a></div><br />
<br />
Esto indica que la configuración ha funcionado correctamente.<br />
<br />
Como podemos ver, no es necesario dar ninguna indicación al filtro de <span class="codigo">Struts 2</span> de en dónde se encuentran las clases anotadas. Esto en realidad es un poco engañoso, ya que si, por ejemplo, cambiamos el nombre del paquete de "<span class="codigo">struts2</span>" a alguna otra cosa, como "<span class="codigo">aplicacion</span>", al volver a ejecutar nuestra aplicación obtendremos el siguiente error:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiLJkkpptNPIjjy7YFgwnwZ2UR8E7F58rvJVo4-rfY8GiBwypO3aqQJEteErASN_rAJTKFWK355UTrJLidV45ED55eBzX3Ck4pyUAFKB9DoaPo_j6I7by7C4oq-X7_2cBBK-2tyDrRN1KqN/s1600/S1_22.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="191" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiLJkkpptNPIjjy7YFgwnwZ2UR8E7F58rvJVo4-rfY8GiBwypO3aqQJEteErASN_rAJTKFWK355UTrJLidV45ED55eBzX3Ck4pyUAFKB9DoaPo_j6I7by7C4oq-X7_2cBBK-2tyDrRN1KqN/s320/S1_22.png" /></a></div><br />
<br />
Esto ocurre porque el plugin <span class="codigo">convention</span> usa, como habíamos dicho, una convención de nombres para saber en dónde se encuentra cierto contenido. En el caso de los <span class="codigo">Action</span>s, el plugin busca todas las clases <span class="codigo">Action</span> que implementen la interface "<span class="codigo">Action</span>" o cuyos nombres terminen con la palabra "<span class="codigo">Action</span>" y que se encuentren en paquetes específicos. Estos paquetes son encontrados por el plugin <span class="codigo">convention</span> usando una cierta metodología de búsqueda. Primero, el plugin busca los paquetes cuyos nombres sean "<span class="codigo">struts</span>", "<span class="codigo">struts2</span>", "<span class="codigo">action</span>" o "<span class="codigo">actions</span>". Cualquier paquete que coincida con estos nombres es considerado el paquete raíz de los <span class="codigo">Action</span> por el plugin <span class="codigo">convention</span>. Después, el plugin busca todas las clases en este paquete, y en sus sub-paquetes y determina si las clases implementan la interface "<span class="codigo">Action</span>" o su nombre termina con "<span class="codigo">Action</span>".<br />
<br />
Debido a que en este caso, hemos cambiado el nombre del paquete que el plugin usaba para saber en dónde localizar los <span class="codigo">Action</span>s, ya no puede encontrarlos. ¿Qué podemos hacer en este caso? Afortunadamente para esto existe un mecanismo que nos permite indicarle al plugin <span class="codigo">convention</span> dónde puede encontrar los <span class="codigo">Action</span>s de nuestra aplicación. Para este fin debemos agregar una constante: "<span class="codigo">struts.convention.action.packages</span>", y su valor será una lista de los paquetes en los que se encuentran los <span class="codigo">Action</span>s.<br />
<br />
Ya que en esta ocasión no tenemos un archivo de configuración, debemos colocar esta constante como un parámetro del filtro de "<span class="codigo">struts2</span>", en el archivo "<span class="codigo">web.xml</span>".<br />
<br />
Debido a que en mi caso el paquete en el que se encuentran las clases es "<span class="codigo">com.javatutoriales.aplicacion.configuracion.acciones</span>", mi configuración del filtro queda de la siguiente forma:<br />
<br />
<pre><code>
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
<init-param>
<param-name>struts.convention.action.packages</param-name>
<param-value>com.javatutoriales.aplicacion.configuracion.acciones</param-value>
</init-param>
</filter>
</code></pre><br />
<br />
El resto de la configuración queda como la teníamos.<br />
<br />
Ahora al ejecutar la aplicación, deberemos ver nuevamente la siguiente salida:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiTKdSXVYU0UmoyEPRE97IX-ZEPlq2wNsFKBiXNCIhJqRhQ7lK3tmku8ak6p7kl-pR4SaP86hBckwovqi75O5tteovbr2pta2_XV7Od2sjkS2L8-v-O_QXB95iwmuYxu8aHeHNQ4WWORcPM/s1600/S1_23.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="191" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiTKdSXVYU0UmoyEPRE97IX-ZEPlq2wNsFKBiXNCIhJqRhQ7lK3tmku8ak6p7kl-pR4SaP86hBckwovqi75O5tteovbr2pta2_XV7Od2sjkS2L8-v-O_QXB95iwmuYxu8aHeHNQ4WWORcPM/s320/S1_23.png" /></a></div><br />
<br />
La cual nos muestra que todo funciona correctamente ^_^.<br />
<br />
El ejemplo del formulario trabaja exactamente de la misma forma que en el caso del archivo de mapeo, así que no lo explicaré, pero lo colocaré en los archivos de descarga al final del tutorial.<br />
<br />
Esto es todo lo que respecta a este tutorial. En los siguientes veremos un poco más de las opciones que nos proporciona <span class="codigo">Struts 2</span> para trabajo con formularios, veremos cómo crear nuestros propios interceptores, y las opciones que nos ofrece el lenguaje <span class="codigo">OGNL</span>.<br />
<br />
Como siempre no olviden dejar sus dudas, comentarios y sugerencias.<br />
<br />
Saludos y gracias ^_^<br />
<br />
<span class="negritas">Descarga los archivos de este tutorial desde aquí:</span><br />
<ul><li><a href="https://sites.google.com/site/javatutoriales/struts2/Struts2XML.zip"><span class="ligaArchivo">Configuración de Struts 2 con archivos XML</span></a></li>
<li><a href="https://sites.google.com/site/javatutoriales/struts2/Struts2Anotaciones.zip"><span class="ligaArchivo">Configuración de Struts 2 con Anotaciones</span></a></li>
</ul><br />
<br />
<span class="negritas">Entradas Relacionadas:</span><br />
<br />
<ul><li><a href="http://www.javatutoriales.com/2011/06/struts-2-parte-2-ognl.html">Parte 2: OGNL</a></li>
<li><a href="http://www.javatutoriales.com/2011/10/struts-2-parte-3-trabajo-con.html">Parte 3: Trabajo con Formularios</a></li>
<li><a href="http://www.javatutoriales.com/2011/12/struts-2-parte-4-scopes-de-objetos-web.html">Parte 4: Scopes de Objetos Web</a></li>
<li><a href="http://www.javatutoriales.com/2012/04/struts-2-parte-5-tipos-de-results.html">Parte 5: Tipos de Results</a></li>
<li><a href="http://www.javatutoriales.com/2012/06/struts-2-parte-6-interceptores.html">Parte 6: Interceptores</a></li>
</ul></div>Alexhttp://www.blogger.com/profile/06974037481671868076noreply@blogger.comtag:blogger.com,1999:blog-2012232069289957092.post-18071517032469820572011-04-23T08:14:00.000-07:002011-04-23T08:17:42.427-07:00Log4j para Creación de Eventos de Log<div style="text-align: justify;">El uso de logging o de bitácoras dentro del contexto de desarrollo de aplicaciones constituye insertar sentencias dentro de la aplicación, que proporcionan un tipo de información de salida que es útil para el desarrollador. Un ejemplo de logging son las sentencias de rastreo o de seguimiento que colocamos en la aplicación para asegurarnos de que esta haya pasado por los flujos adecuados. Esto normalmente es realizado usando "<span class="codigo">System.out.println</span>".<br />
<br />
El problema con este tipo de sentencias, es que algunas veces olvidamos quitarlas después de que han servido a su propósito específico, y terminamos con una aplicación que genera cientos o miles de salidas innecesarias a la consola. Otro problema es que todas las llamadas tienen el mismo significado, es decir, no podemos saber exactamente cuál sentencia representa una salida de debug (que seguramente olvidamos quitar), cuál representa una salida de información últil, y cuál representa un error que se ha generado; las tres se ven exactamente igual para nosotros.<br />
<br />
En este tutorial aprenderemos cómo usar <span class="codigo">log4j</span>, un framework especializado para el logging o creación de bitácoras en las aplicaciones Java. También veremos dos formas distintas de configurarlo y las diferencias entre ambas formas.<br />
<br />
<a name='more'></a><span class="codigo">log4j</span> es un framework que ofrece una forma jerárquica de insertar sentencias de log dentro de una aplicación Java. Con él, se tienen disponibles múltiples formatos de salida, y múltiples niveles de información de log.<br />
<br />
Usando un paquete dedicado para realizar el log, se elimina la carga de mantener cientos de sentencias "<span class="codigo">System.out.println</span>", al mismo tiempo que el logging puede ser controlado en tiempo de ejecución por scripts de configuración.<br />
<br />
<span class="codigo">log4j</span> tiene tres componentes principales:<br />
<ul><li><span class="codigo">Loggers</span></li>
<li><span class="codigo">Appenders</span></li>
<li><span class="codigo">Layouts</span></li>
</ul><br />
Estos tres tipos de componentes trabajan juntos para permitir a los desarrolladores hacer el log de mensajes de acuerdo al tipo y nivel de mensaje, controlar en tiempo de ejecución como es que estos mensajes son formateados y dónde son reportados.<br />
<br />
<br />
<h1 class="subtitulo">Loggers</h1>Los <span class="codigo">loggers</span> son los componentes más esenciales del proceso de logging. Son los responsables de capturar la información de logging.<br />
<br />
Los <span class="codigo">loggers</span> son entidades con nombre. Los nombres de los <span class="codigo">loggers</span> son case-sensitive y siguen una regla jerárquica de nombres: Se dice que un logger es un ancestro de otro <span class="codigo">logger</span> si su nombre, seguido de un punto, es un prefijo del nombre del <span class="codigo">logger</span> descendiente. Un <span class="codigo">logger</span> se dice que es padre de un <span class="codigo">logger</span> hijo si no hay ancestros entre él mismo y el <span class="codigo">logger</span> descendiente.<br />
<br />
Por ejemplo, el <span class="codigo">logger</span> "<span class="codigo">com.foo</span>" es padre del <span class="codigo">logger</span> "<span class="codigo">con.foo.Bar</span>". De forma similar, "<span class="codigo">java</span>" es pariente de "<span class="codigo">java.util</span>" y ancestro de "<span class="codigo">java.util.Vector</span>".<br />
<br />
Esto es importante porque, como veremos más adelante, cuando realizamos una configuración para un <span class="codigo">logger</span>, digamos para "<span class="codigo">java.util</span>", esta configuración es heredada por todos sus descendientes. Normalmente tenemos un <span class="codigo">logger</span> por cada una de las clases de las cuales nos interesa obtener información, y <span class="codigo">logger</span> tiene <span class="negritas">el mismo nombre que la clase</span>.<br />
<br />
Existe un <span class="codigo">logger</span> especial llamado "<span class="codigo">rootLogger</span>", el cual reside en la cima de la jerarquía de <span class="codigo">loggers</span>. Este <span class="codigo">logger</span> tiene dos características:<br />
<ul><li>Siempre existe</li>
<li>No puede ser recuperado por nombre</li>
</ul><br />
Los <span class="codigo">loggers</span> pueden tener niveles asignados. Los niveles normales que puede tener un <span class="codigo">logger</span> son, de menor a mayor prioridad:<br />
<ul><li><span class="codigo">TRACE</span>: Se usa para información más detallada que el nivel debug.</li>
<li><span class="codigo">DEBUG</span>: Se utiliza para mensajes de información detallada que son útiles para debugear una aplicación.</li>
<li><span class="codigo">INFO</span>: Se utiliza para mensajes de información que resaltan el progreso de la aplicación de una forma general.</li>
<li><span class="codigo">WARN</span>: Se utiliza para situaciones que podrían ser potencialmente dañinas.</li>
<li><span class="codigo">ERROR</span>: Se usa para eventos de error que podrían permitir que la aplicación continúe ejecutándose.</li>
<li><span class="codigo">FATAL</span>: Se usa para errores muy graves, que podrían hacer que la aplicación dejara de funcionar.</li>
</ul><br />
Además hay otros dos niveles especiales que son:<br />
<ul><li><span class="codigo">ALL</span>: Tiene el nivel más bajo posible y se usa para activar todo el logging.</li>
<li><span class="codigo">OFF</span>: Tiene el nivel más alto posible y se usa para evitar cualquier mensaje de log</li>
</ul><br />
Si a un <span class="codigo">logger</span> no se le asigna un nivel de forma explícita, este hereda el de su ancestro más cercano. Si no tiene ancestros, hereda el del <span class="codigo">rootLogger</span>, que por default es <span class="codigo">DEBUG</span>.<br />
<br />
Las peticiones de logging son hechas invocando uno de los métodos de impresión de la instancia de logger. Estos métodos son: "<span class="codigo">trace</span>", "<span class="codigo">debug</span>", "<span class="codigo">info</span>", "<span class="codigo">warn</span>", "<span class="codigo">error</span>", y "<span class="codigo">fatal</span>".<br />
<br />
Por definición, cada método de impresión determina el nivel de una petición de logging. Por ejemplo "<span class="codigo">info</span>" es una petición de nivel <span class="codigo">INFO</span>.<br />
<br />
Una petición de logging está habilitada si su nivel es mayor que o igual que el nivel de su <span class="codigo">logger</span>. De otra forma se dice que la petición está deshabilitada. El comportamiento de los <span class="codigo">loggers</span> es jerárquico. La siguiente tabla ilustra esto:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjdkrzov5ojijxQfPa1dTsRn5lwXfqD9RSpyshWT1LI6OktvolpcZMCBmof0DmBQZfYe3VAb-KqP841LnlY3O57HWkmtqnf5dHbjnbEO_6MlEYWojtq0Sqf5zNFmdchZY7qTNGnNJkbNQwh/s1600/l4j_1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="190" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjdkrzov5ojijxQfPa1dTsRn5lwXfqD9RSpyshWT1LI6OktvolpcZMCBmof0DmBQZfYe3VAb-KqP841LnlY3O57HWkmtqnf5dHbjnbEO_6MlEYWojtq0Sqf5zNFmdchZY7qTNGnNJkbNQwh/s320/l4j_1.png" width="320" /></a></div><br />
<br />
Esto quiere decir que si establecemos el nivel de log en "<span class="codigo">DEBUG</span>" se mostrarán los mensajes de nivel "<span class="codigo">DEBUG</span>", "<span class="codigo">INFO</span>", "<span class="codigo">WARN</span>", "<span class="codigo">ERROR</span>", y "<span class="codigo">FATAL</span>". Si, por el contrario, establecemos el nivel en "<span class="codigo">ERROR</span>", solo se mostrarán los mensajes de nivel "<span class="codigo">ERROR</span>", y "<span class="codigo">FATAL</span>".<br />
<br />
<br />
<h1 class="subtitulo">Appenders</h1><span class="codigo">log4j</span> permite a las peticiones de logging envíen sus mensajes a múltiples destinos. En lenguaje de <span class="codigo">log4j</span>, un destino de salida es llamado un <span class="codigo">appender</span>. Existen <span class="codigo">appenders</span> para la consola, archivos, sockets, JMS, correo electrónico, bases de datos, etc. Además también es posible crear <span class="codigo">appenders</span> propios.<br />
<br />
Los <span class="codigo">appenders</span> disponibles son:<br />
<ul><li><span class="codigo"><a href="http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/AsyncAppender.html">AsyncAppender</a></span>: Permite enviar los mensajes de log de forma asíncrona. Este appender recolectará los mensajes enviados a los <span class="codigo">appenders</span> que estén asociados a él y los enviará de forma asíncrona.</li>
<li><span class="codigo"><a href="http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/ConsoleAppender.html">ConsoleAppender</a></span>: Envía los eventos de log a <span class="codigo">System.out</span> or <span class="codigo">System.err</span></li>
<li><span class="codigo"><a href="http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/FileAppender.html">FileAppender</a></span>: Envía los eventos de log a un archivo</li>
<li><span class="codigo"><a href="http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/DailyRollingFileAppender.html">DailyRollingFileAppender</a></span>: Extiende <span class="codigo">FileAppender</span> de modo que el archive es creado nuevamente con una frecuencia elegida por el usuario</li>
<li><span class="codigo"><a href="http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/RollingFileAppender.html">RollingFileAppender</a></span>: Extiende <span class="codigo">FileAppender</span> para respaldar los archivos de log cuando alcanza un tamaño determinado</li>
<li><span class="codigo"><a href="http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/varia/ExternallyRolledFileAppender.html">ExternallyRolledFileAppender</a></span>: Escucha en por un socket y cuando recibe un mensaje, lo agrega en un archivo, después envía una confirmación al emisor del mensaje.</li>
<li><span class="codigo"><a href="http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/jdbc/JDBCAppender.html">JDBCAppender</a></span>: Proporciona un mecanismo para enviar los mensajes de log a una base de datos.</li>
<li><span class="codigo"><a href="http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/net/JMSAppender.html">JMSAppender</a></span>: Publica mensajes de log como un tópico <span class="codigo">JMS</span>.</li>
<li><span class="codigo"><a href="http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/lf5/LF5Appender.html">LF5Appender</a></span>: Envía los mensajes de log a una consola basada en swing.</li>
<li><span class="codigo"><a href="http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/nt/NTEventLogAppender.html">NTEventLogAppender</a></span>: Agrega los mensajes a un sistema de eventos NT (solo para Windows)</li>
<li><span class="codigo"><a href="http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/varia/NullAppender.html">NullAppender</a></span>: Un <span class="codigo">appender</span> que no envía los mensajes a ningún lugar.</li>
<li><span class="codigo"><a href="http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/WriterAppender.html">WriterAppender</a></span>: Envía los eventos de log a un <span class="codigo">Writer</span> o a un <span class="codigo">OutputStream</span> dependiendo de la elección del usuario.</li>
<li><span class="codigo"><a href="http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/net/SMTPAppender.html">SMTPAppender</a></span>: Envía un e-mail cuando ocurre un evento específico de logging, típicamente errores o errores fatales.</li>
<li><span class="codigo"><a href="http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/net/SocketAppender.html">SocketAppender</a></span>: Envía un objeto <span class="codigo">LoggingEvent</span> a un servidor remoto, usualmente un <span class="codigo">SocketNode</span>.</li>
<li><span class="codigo"><a href="http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/net/SocketHubAppender.html">SocketHubAppender</a></span>: Envía un objeto <span class="codigo">LoggingEvent</span> a un servidor remoto de log.</li>
<li><span class="codigo"><a href="http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/net/SyslogAppender.html">SyslogAppender</a></span>: Envía mensajes a un demonio <span class="codigo">syslog</span> remoto</li>
<li><span class="codigo"><a href="http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/net/TelnetAppender.html">TelnetAppender</a></span>: Es un <span class="codigo">appender</span> que se especializa en escribir a un socket de solo lectura.</li>
</ul><br />
Un <span class="codigo">logger</span> puede tener asociado más de un appender.<br />
<br />
<br />
<h1 class="subtitulo">Layouts</h1>Son los responsables de dar formatos a los mensajes de salida, de acuerdo a lo que el usuario quiera.<br />
<br />
Permiten presentar el mensaje con formato para mostrarlo en la consola o guardarlo en un archivo de texto, en una tabla <span class="codigo">HTML</span>, un archivo <span class="codigo">XML</span>, etc.<br />
<br />
Ahora veremos cómo hacer uso de todo esto en un ejemplo. <br />
<br />
Lo primero que debemos hacer es descargar la última versión de <span class="codigo">log4j</span>, que actualmente es la "<span class="codigo"><span class="negritas">1.2.16</span></span>". Podemos descargarla de el <a href="http://logging.apache.org/log4j/1.2/download.html">sitio de <span class="codigo">log4j</span></a>. Una vez que realicemos la descarga extraemos, del archivo comprimido, el jar "<span class="codigo"><span class="codigo">log4j</span>-1.2.16.jar</span>" que es el único necesario.<br />
<br />
Ahora creamos un nuevo proyecto en <span class="codigo">NetBeans</span>. Para esto vamos al Menú "<span class="codigo">File->New Project...</span>". En la ventana que se abre seleccionamos la categoría "<span class="codigo">Java</span>" y en el tipo de proyecto "<span class="codigo">Java Application</span>". Le damos una ubicación y un nombre al proyecto, en mi caso será "<span class="codigo">log4j</span>". Nos aseguramos que las opciones "<span class="codigo">Create Main Class</span>" y "<span class="codigo">Set as Main Project</span>" estén habilitadas. Presionamos el botón "<span class="codigo">Finish</span>" y veremos aparecer en el editor nuestra clase "<span class="codigo">Main</span>". En mi caso la clase "<span class="codigo">Main</span>" se llamará "<span class="codigo">Principal</span>".<br />
<br />
Lo siguiente que debemos hacer es agregar el jar de <span class="codigo">log4j</span> a nuestro proyecto, para esto hacemos clic derecho sobre el nodo "<span class="codigo">Libraries</span>" de nuestro proyecto y hacemos clic sobre la opción "<span class="codigo">Add JAR/Folder…</span>" y en la ventana que aparece seleccionamos el jar de <span class="codigo">log4j</span>.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgghNbpdtyAwiDeRLfnW4zjfDTAbbfyMh2yg3ttxLMaH32HIZ8ad3TN1OwV7hC-xNyd3ISoc0lYT6b9_QxpBDuPyC_NRnsD4hQqJTbKyLccJfQadt442T1GgUxzFXdyWHJN7kXzjW8stiOg/s1600/l4j_2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgghNbpdtyAwiDeRLfnW4zjfDTAbbfyMh2yg3ttxLMaH32HIZ8ad3TN1OwV7hC-xNyd3ISoc0lYT6b9_QxpBDuPyC_NRnsD4hQqJTbKyLccJfQadt442T1GgUxzFXdyWHJN7kXzjW8stiOg/s1600/l4j_2.png" /></a></div><br />
<br />
El siguiente paso es crear un <span class="codigo">logger</span>. Hay muchas formas de crear un <span class="codigo">logger</span>, todas ellas a través de métodos estáticos de la clase "<span class="codigo">Logger</span>". La primera es recuperar el <span class="codigo">rootLogger</span>:<br />
<br />
<pre><code>
Logger log = Logger.getRootLogger();
</code></pre><br />
<br />
También es posible crear un nuevo <span class="codigo">logger</span>, asignándole un nombre:<br />
<br />
<pre><code>
Logger log = Logger.getLogger("<span class="codigo">MiLogger</span>");
</code></pre><br />
<br />
Lo más usual es instanciar un <span class="codigo">logger</span> <span class="negritas">estático</span> global para la clase, basado en <span class="negritas">el nombre de la clase</span>. "<span class="codigo">Logger</span>" proporciona una versión sobrecargada de "<span class="codigo">getLogger</span>" para esto:<br />
<br />
<pre><code>
private static Logger log = Logger.getLogger(Principal.class);
</code></pre><br />
<br />
Una vez que ya se tiene un <span class="codigo">logger</span>, lo siguiente es invocar sus <span class="negritas">métodos de petición de impresión</span> para que los mensajes con los niveles correspondientes sean enviados al <span class="codigo">appender</span> que definamos:<br />
<br />
<pre><code>
public static void main(String[] args)
{
log.trace("mensaje de trace");
log.debug("mensaje de debug");
log.info("mensaje de info");
log.warn("mensaje de warn");
log.error("mensaje de error");
log.fatal("mensaje de fatal");
}
</code></pre><br />
<br />
Unas cuantas notas para un <span class="negritas">uso correcto</span> de estos métodos ^_^:<br />
<br />
Existe una versión sobrecargada de cada uno de estos métodos, en los que además de un mensaje (en realidad el "<span class="codigo">mensaje</span>" puede ser cualquier objeto que nosotros queramos) acepta un objeto de tipo "<span class="codigo">Throwable</span>" (o sea cualquier excepción o error que ocurra). Esto es especialmente útil con los métodos "<span class="codigo">warn</span>", "<span class="codigo">error</span>", y "<span class="codigo">fatal</span>", con lo que además del mensaje también se mostrará el stack trace del error que haya ocasionado problemas.<br />
<br />
Los métodos "<span class="codigo">error</span>" y "<span class="codigo">fatal</span>" normalmente son colocados dentro de bloques "<span class="codigo">catch</span>" ya que es aquí donde podemos obtener la información sobre los errores que ocurren en nuestra aplicación (claro, si es que los estamos manejando de una forma adecuada ^_^!).<br />
<br />
La ejecución de cada uno de los métodos es, en teoría bastante rápida (se supone que entre 1 y 4 milisegundos). Sin embargo, la generación del mensaje (la cual puede incluir concatenación de cadenas, invocación de métodos, o cualquier otra cosa que se nos ocurra) puede tardar más que esto. Dependiendo del nivel de log en el que esté configurada nuestra aplicación, no siempre querremos esta carga adicional de tiempo para la generación de un mensaje que nunca veremos. Por ejemplo, podemos tener muchos métodos "<span class="codigo">debug</span>" en nuestra aplicación, los cuales realizan algunas concatenaciones de cadenas para verificar que todos los valores sean los que esperamos, pero el nivel de la misma puede estar establecido en "<span class="codigo">warn</span>" por lo que se perderá mucho tiempo generando estos mensajes de "<span class="codigo">debug</span>" que finalmente nunca aparecerán en ningún lado.<br />
<br />
A causa de esto, el objeto <span class="codigo">Logger</span> contiene una serie de métodos que nos permite saber si el <span class="codigo">logger</span> está configurado para un nivel de "<span class="codigo">TRACE</span>", "<span class="codigo">DEBUG</span>", o "<span class="codigo">INFO</span>" (normalmente los niveles de <span class="codigo">WARN</span> a <span class="codigo">FATAL</span> siempre querremos mostrarlos si es que ocurre algún problema). Estos métodos son:<br />
<br />
<ul><li><span class="codigo">isTraceEnabled()</span></li>
<li><span class="codigo">isDebugEnabled()</span></li>
<li><span class="codigo">isInfoEnabled()</span></li>
</ul><br />
Normalmente envolvemos las invocaciones a los métodos "<span class="codigo">trace</span>", "<span class="codigo">debug</span>", e "<span class="codigo">info</span>" en un "<span class="codigo">if</span>" que verificará si el nivel de <span class="codigo">logger</span> está establecido para aceptar estas llamadas. Modificaremos el código del método "<span class="codigo">main</span>" para hacer uso de estas condiciones:<br />
<br />
<pre><code>
public static void main(String[] args)
{
if (log.isTraceEnabled())
{
log.trace("mensaje de trace");
}
if (log.isDebugEnabled())
{
log.debug("mensaje de debug");
}
if (log.isInfoEnabled())
{
log.info("mensaje de info");
}
log.warn("mensaje de warn");
log.error("mensaje de error");
log.fatal("mensaje de fatal");
}
</code></pre><br />
<br />
Ahora es necesario configurar <span class="codigo">log4j</span> para realizar la asociación entre el <span class="codigo">logger</span> que acabamos de crear, con los <span class="codigo">appenders</span> a los que enviará los mensajes, y los <span class="codigo">layouts</span> que usarán para mostrar estos mensajes.<br />
<br />
<span class="nota">Nota: Yo usaré dos proyectos, uno con un archivo de propiedades y otro con un archivo XML, para que el código de ambos no se mezcle.</span><br />
<br />
<span class="nota">Nota2: Algunas veces al hacer cambios, ya sea en archivos XML o de propiedades, NetBeans no ve estos cambios y por lo tanto no recarga estos archivos. Para que los cambios tengan efecto algunas veces es necesario ejecutar un "<span class="codigo">clean and build</span>".</span><br />
<br />
<br />
<h1 class="titulo">Configuración</h1>El ambiente de <span class="codigo">log4j</span> es completamente configurable de forma programática. Sin embargo, es más flexible configurar <span class="codigo">log4j</span> usando archivos de configuración. Esto permite que una vez que las sentencias de log están colocadas en su lugar, se puedan controlar fácilmente usando el archivo de configuración, sin tener que modificar el código fuente.<br />
<br />
Los archivos de configuración pueden ser escritos en XML o en archivos de propiedades. Primero veremos cómo realizar la configuración con archivos de propiedades.<br />
<br />
<br />
<h2 class="subtitulo">Configuración de log4j con un Archivo de Propiedades</h2>Si queremos configurar <span class="codigo">log4j</span> haciendo uso de un archivo de propiedades, lo primero que debemos hacer es crear un archivo de propiedades (<span class="codigo">properties file</span>) en el paquete default de la aplicación. Este archivo debe llamarse "<span class="codigo"><span class="codigo">log4j</span>.properties</span>" (podríamos llamar este archivo de otra forma o colocarlo en otro lugar, pero <span class="codigo">log4j</span> busca por default este archivo en esta ubicación, haciéndolo de esta forma nos evitamos el tener realizar una configuración de forma programática en nuestra aplicación):<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiNWYnROcZPWiehQ7k3Xs5GojWwM16zj8SUwjfANC6AjQaor_sPYV4NlTaZ6eVeXaBFCciEjc62rJg3C5uP3PScOpO-9bMKieNeyBqf_T9fw-j2oDCnhC1S0pobXmr9hKsmcXPss86qp-Yg/s1600/l4j_3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiNWYnROcZPWiehQ7k3Xs5GojWwM16zj8SUwjfANC6AjQaor_sPYV4NlTaZ6eVeXaBFCciEjc62rJg3C5uP3PScOpO-9bMKieNeyBqf_T9fw-j2oDCnhC1S0pobXmr9hKsmcXPss86qp-Yg/s1600/l4j_3.png" /></a></div><br />
<br />
Lo primero que hay que hacer es crear un <span class="codigo">appender</span>, al cual <span class="negritas">le daremos un nombre arbitrario</span>, e indicaremos <span class="negritas">el tipo de <span class="codigo">appender</span></span> que será, usando una de las clases que mencionamos hace un momento, cuando hablamos de los <span class="codigo">appenders</span>. En este caso se creará un appender llamado "<span class="codigo">consola</span>" y que será de tipo "<span class="codigo">org.apache.<span class="codigo">log4j</span>.ConsoleAppender</span>", que permite enviar los mensjes a la salida estándar, además indicaremos que los mensajes deberán mostrarse en "<span class="codigo">System.out</span>", que es su valor por default (también podemos mandar los mensajes a "<span class="codigo">System.err</span>"):<br />
<br />
<pre><code>
log4j.appender.consola = org.apache.log4j.ConsoleAppender
log4j.appender.consola.target = System.out
</code></pre><br />
<br />
Lo que sigue es agregar un <span class="codigo">layout</span> para este <span class="codigo">appender</span>. Existen muchos tipos de <span class="codigo">layouts</span>. Para esta primera prueba usaremos el "<span class="codigo"><a href="http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/SimpleLayout.html">SimpleLayout</a></span>", que simplemente nos indica el nivel de log del <span class="codigo">logger</span>, y el mensaje asociado al evento:<br />
<br />
<pre><code>
log4j.appender.consola.layout = org.apache.log4j.SimpleLayout
</code></pre><br />
<br />
Una vez que se ha creado el <span class="codigo">appender</span>, y que se la ha establecido su <span class="codigo">layout</span>, se debe especificar cuáles <span class="codigo">loggers</span> usarán este <span class="codigo">appender</span>, y establecer su nivel de log. Si se especifica que "<span class="codigo">rootLogger</span>" hará uso de este appender, entonces todos los <span class="codigo">loggers</span> enviarán sus mensajes a este <span class="codigo">appender</span> (a menos que se especifique lo contrario de forma explícita). Como <span class="codigo">rootLogger</span> está en la cima de la jerarquía, todos los <span class="codigo">loggers</span> heredarán su nivel de log y su appender:<br />
<br />
<pre><code>
log4j.rootLogger = TRACE, consola
</code></pre><br />
<br />
El archivo de configuración queda de la siguiente forma:<br />
<br />
<pre><code>
log4j.appender.consola = org.apache.log4j.ConsoleAppender
log4j.appender.consola.target = System.out
log4j.appender.consola.layout = org.apache.log4j.SimpleLayout
log4j.rootLogger = TRACE, consola
</code></pre><br />
<br />
Al ejecutar el código anterior se obtiene la siguiente salida:<br />
<br />
<pre><code>
TRACE - mensaje de trace
DEBUG - mensaje de debug
INFO - mensaje de info
WARN - mensaje de warn
ERROR - mensaje de error
FATAL - mensaje de fatal
</code></pre><br />
<br />
Ahora, teniendo este archivo de configuración, es posible modificar toda la configuración de la salida. Por ejemplo, modificamos el layout del appender "<span class="codigo">consola</span>" para que en vez de ser de tipo "<span class="codigo">SimpleLayout</span>" ahora sea de tipo "<span class="codigo"><a href="http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/HTMLLayout.html">HTMLLayout</a></span>": <br />
<pre><code>
log4j.appender.consola.layout = org.apache.log4j.HTMLLayout
</code></pre><br />
<br />
Obtendremos la siguiente salida:<br />
<br />
<pre><code>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Log4j Log Messages</title>
<style type="text/css">
<!--
body, table {font-family: arial,sans-serif; font-size: x-small;}
th {background: #336699; color: #FFFFFF; text-align: left;}
-->
</style>
</head>
<body bgcolor="#FFFFFF" topmargin="6" leftmargin="6">
<hr size="1" noshade>
Log session start time Fri Apr 22 01:59:55 CDT 2011<br>
<br>
<table cellspacing="0" cellpadding="4" border="1" bordercolor="#224466" width="100%">
<tr>
<th>Time</th>
<th>Thread</th>
<th>Level</th>
<th>Category</th>
<th>Message</th>
</tr>
<tr>
<td>0</td>
<td title="main thread">main</td>
<td title="Level">TRACE</td>
<td title="com.javatutoriales.log4j.configuracion.Principal category">com.javatutoriales.log4j.configuracion.Principal</td>
<td title="Message">mensaje de trace</td>
</tr>
<tr>
<td>3</td>
<td title="main thread">main</td>
<td title="Level"><font color="#339933">DEBUG</font></td>
<td title="com.javatutoriales.log4j.configuracion.Principal category">com.javatutoriales.log4j.configuracion.Principal</td>
<td title="Message">mensaje de debug</td>
</tr>
<tr>
<td>3</td>
<td title="main thread">main</td>
<td title="Level">INFO</td>
<td title="com.javatutoriales.log4j.configuracion.Principal category">com.javatutoriales.log4j.configuracion.Principal</td>
<td title="Message">mensaje de info</td>
</tr>
<tr>
<td>17</td>
<td title="main thread">main</td>
<td title="Level"><font color="#993300"><strong>WARN</strong></font></td>
<td title="com.javatutoriales.log4j.configuracion.Principal category">com.javatutoriales.log4j.configuracion.Principal</td>
<td title="Message">mensaje de warn</td>
</tr>
<tr>
<td>20</td>
<td title="main thread">main</td>
<td title="Level"><font color="#993300"><strong>ERROR</strong></font></td>
<td title="com.javatutoriales.log4j.configuracion.Principal category">com.javatutoriales.log4j.configuracion.Principal</td>
<td title="Message">mensaje de error</td>
</tr>
<tr>
<td>25</td>
<td title="main thread">main</td>
<td title="Level"><font color="#993300"><strong>FATAL</strong></font></td>
<td title="com.javatutoriales.log4j.configuracion.Principal category">com.javatutoriales.log4j.configuracion.Principal</td>
<td title="Message">mensaje de fatal</td>
</tr>
</code></pre><br />
<br />
Que genera la siguiente página:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjkSn8SYiwz6egGxQaiJp7Y1IQ9lF_wsir-AnULquWKuwNzjUcb5-OT7U7tWfQEHDTsblCnGHvfIg3IpamTvdJvsXXyDT0QeFJit21w8aXtdpkAWx78blpmLGIQNTG9160p1ZnJJnaim__y/s1600/l4j_4.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="216" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjkSn8SYiwz6egGxQaiJp7Y1IQ9lF_wsir-AnULquWKuwNzjUcb5-OT7U7tWfQEHDTsblCnGHvfIg3IpamTvdJvsXXyDT0QeFJit21w8aXtdpkAWx78blpmLGIQNTG9160p1ZnJJnaim__y/s320/l4j_4.png" width="320" /></a></div><br />
<br />
Como podemos ver, hemos modificado completamente la salida, sin necesidad de mover una sola línea de la aplicación (bueno, solo una línea de un archivo de propiedades, que siempre será un archivo de texto plano).<br />
<br />
De la misma forma, si usamos un <span class="codigo"><a href="http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/xml/XMLLayout.html">XMLLayout</a></span>:<br />
<br />
<pre><code>
log4j.appender.consola.layout = org.apache.log4j.xml.XMLLayout
</code></pre><br />
<br />
Obtendremos la siguiente salida:<br />
<br />
<pre><code>
<log4j:event logger="com.javatutoriales.log4j.configuracion.Principal" timestamp="1303485881257" level="TRACE" thread="main">
<log4j:message><![CDATA[mensaje de trace]]></log4j:message>
</log4j:event>
<log4j:event logger="com.javatutoriales.log4j.configuracion.Principal" timestamp="1303485881259" level="DEBUG" thread="main">
<log4j:message><![CDATA[mensaje de debug]]></log4j:message>
</log4j:event>
<log4j:event logger="com.javatutoriales.log4j.configuracion.Principal" timestamp="1303485881259" level="INFO" thread="main">
<log4j:message><![CDATA[mensaje de info]]></log4j:message>
</log4j:event>
<log4j:event logger="com.javatutoriales.log4j.configuracion.Principal" timestamp="1303485881259" level="WARN" thread="main">
<log4j:message><![CDATA[mensaje de warn]]></log4j:message>
</log4j:event>
<log4j:event logger="com.javatutoriales.log4j.configuracion.Principal" timestamp="1303485881262" level="ERROR" thread="main">
<log4j:message><![CDATA[mensaje de error]]></log4j:message>
</log4j:event>
<log4j:event logger="com.javatutoriales.log4j.configuracion.Principal" timestamp="1303485881262" level="FATAL" thread="main">
<log4j:message><![CDATA[mensaje de fatal]]></log4j:message>
</log4j:event>
</code></pre><br />
<br />
Hagamos un último cambio de <span class="codigo">layout</span>. Con los <span class="codigo">layout</span> anteriores, la información que obtenemos podría no cumplir con nuestras expectativas, por ejemeplo, no hay forma de obtener información de qué método fue el que envió el mensaje, o usar un formato propio para la fecha, o agregar alguna información constante (alguna marca por ejemplo) en la salida generada. Para estos casos, en los que necesitamos hacer uso de un "<span class="codigo"><a href="http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/EnhancedPatternLayout.html">EnhancedPatternLayout</a></span>" (en versiones anteriores de <span class="codigo">log4j</span> usábamos solo un "<span class="codigo">PatternLayout</span>"). Este <span class="codigo">layout</span> permite definir cómo será la salida de nuestros mensajes, usando una cadena con el patrón conocida como <span class="negritas">patrón de conversión</span>.<br />
<br />
El patrón de conversión es similar al de la función "<span class="codigo">printf</span>", está compuesto de texto literal y expresiones de control de formato llamados <span class="negritas">especificadores de conversión</span>. Cada especificador de conversión inicia con un símbolo de porcentaje (<span class="codigo">%</span>) y es seguido de uno o más <span class="negritas">modificadores de formato</span> y por un <span class="negritas">caracter de conversión</span>. El caracter de conversión especifica el tipo de dato (por ejemplo, el <span class="codigo">logger</span>, prioridad, fecha, etc.). Los modificadores de formato controlan cosas como el ancho del campo, justificación izquierda o derecha, etc. Al final del tutorial colocaré dos tablas, una con los caracteres de conversión, y otra con los modificadores de formato. Ahora veamos cómo usar este <span class="codigo">layout</span>:<br />
<br />
<pre><code>
log4j.appender.consola.layout = org.apache.log4j.EnhancedPatternLayout
</code></pre><br />
<br />
Para este layout debemos indicar su patrón de conversión, en su propiedad "<span class="codigo">ConversionPattern</span>":<br />
<br />
<pre><code>
log4j.appender.consola.layout.ConversionPattern = [%-5p] %c{2} - %m%n
</code></pre><br />
<br />
Con la configuración anterior se producirá la siguiente salida:<br />
<br />
<pre><code>
[TRACE] configuracion.Principal - mensaje de trace
[DEBUG] configuracion.Principal - mensaje de debug
[INFO ] configuracion.Principal - mensaje de info
[WARN ] configuracion.Principal - mensaje de warn
[ERROR] configuracion.Principal - mensaje de error
[FATAL] configuracion.Principal - mensaje de fatal
</code></pre><br />
<br />
Podemos notar que ahora tenemos un poco más de información en la consola (como el nombre del <span class="codigo">logger</span>). También noten que agregamos unas cuantas cadenas literales solo para que el la salida sea un poco más clara (como los signos "<span class="codigo">[</span>" y "<span class="codigo">]</span>" alrededor del nivel de log, y el "<span class="codigo">-</span>" antes del mensaje).<br />
<br />
Si a la salida anterior quisiéramos agregarle, por ejemplo, la fecha; bastaría con modificar el "<span class="codigo">ConversionPattern</span>" de la siguiente forma:<br />
<br />
<pre><code>
log4j.appender.consola.layout.ConversionPattern = %d{dd MMM yyyy - HH:mm:ss} [%-5p] %c{2} - %m%n
</code></pre><br />
<br />
Con lo que obtendríamos la siguiente salida:<br />
<br />
<pre><code>
22 abr 2011 - 10:47:47 [TRACE] configuracion.Principal - mensaje de trace
22 abr 2011 - 10:47:47 [DEBUG] configuracion.Principal - mensaje de debug
22 abr 2011 - 10:47:47 [INFO ] configuracion.Principal - mensaje de info
22 abr 2011 - 10:47:47 [WARN ] configuracion.Principal - mensaje de warn
22 abr 2011 - 10:47:47 [ERROR] configuracion.Principal - mensaje de error
22 abr 2011 - 10:47:47 [FATAL] configuracion.Principal - mensaje de fatal
</code></pre><br />
<br />
De la misma forma que podemos modificar el <span class="codigo">layout</span> de salida, podemos elegir que se muestren solo ciertos mensajes de log de un nivel determinado hacia arriba. Por ejemplo, para mostrar solo nos mensajes de nivel "<span class="codigo">WARN</span>" a "<span class="codigo">FATAL</span>" debemos configurar el <span class="codigo">logger</span> de la siguiente forma:<br />
<br />
<pre><code>
log4j.rootLogger=WARN, consola
</code></pre><br />
<br />
Con lo que los mensajes que obtenemos son los siguientes:<br />
<br />
<pre><code>
22 abr 2011 - 10:50:03 [WARN ] configuracion.Principal - mensaje de warn
22 abr 2011 - 10:50:03 [ERROR] configuracion.Principal - mensaje de error
22 abr 2011 - 10:50:03 [FATAL] configuracion.Principal - mensaje de fatal
</code></pre><br />
<br />
Una última cosa importante que hay que saber, es que dentro del mismo <span class="codigo">appender</span> podemos definir un <span class="negritas">umbral inferior</span> para los mensajes que dicho <span class="codigo">appender</span> mostrará. Por ejemplo, definimos que el <span class="codigo">appender</span> "<span class="codigo">consola</span>" tendrá un umbral inferior (<span class="codigo">threshold</span>) para los mensajes de nivel "<span class="codigo">INFO</span>", con la siguiente línea:<br />
<br />
<pre><code>
log4j.appender.consola.threshold = INFO
</code></pre><br />
<br />
No importa que definamos el nivel del <span class="codigo">logger</span> como <span class="codigo">TRACE</span>:<br />
<br />
<pre><code>
log4j.rootLogger = TRACE, consola
</code></pre><br />
<br />
Los únicos mensajes que se mostrarán para este <span class="codigo">appender</span> son del nivel "<span class="codigo">INFO</span>" hacia arriba:<br />
<br />
<pre><code>
22 abr 2011 - 10:54:46 [INFO ] configuracion.Principal - mensaje de info
22 abr 2011 - 10:54:46 [WARN ] configuracion.Principal - mensaje de warn
22 abr 2011 - 10:54:46 [ERROR] configuracion.Principal - mensaje de error
22 abr 2011 - 10:54:46 [FATAL] configuracion.Principal - mensaje de fatal
</code></pre><br />
<br />
Lamentablemente en los archivos de propiedades solo podemos definir un nivel inferior, no uno superior (en los archivos de configuración XML es posible definir ambos niveles) haciendo uso de filtros.<br />
<br />
Hasta ahora nuestro archivo de configuración se ve de la siguiente forma:<br />
<br />
<pre><code>
log4j.appender.consola = org.apache.log4j.ConsoleAppender
log4j.appender.consola.threshold = INFO
log4j.appender.consola.target = System.out
log4j.appender.consola.layout = org.apache.log4j.EnhancedPatternLayout
log4j.appender.consola.layout.ConversionPattern = %d{dd MMM yyyy - HH:mm:ss} [%-5p] %c{2} - %m%n
log4j.rootLogger = TRACE, consola
</code></pre><br />
<br />
Como pudimos ver, con solo realizar una modificación al archivo de propiedades fue posible modificar el formato de los mensajes de salida, así como el nivel de log de la aplicación.<br />
<br />
Es posible definir múltiples <span class="codigo">appenders</span> en un mismo archivo de configuración, y asociar estos <span class="codigo">apennders</span> a un mismo, o a diferentes <span class="codigo">loggers</span>.<br />
<br />
Por ejemplo, para crear un <span class="codigo">appender</span> que envíe los mensajes de log a un archivo, sería necesario crear un <span class="codigo">FileAppender</span> como el siguiente:<br />
<br />
<pre><code>
log4j.appender.archivo = org.apache.log4j.FileAppender
log4j.appender.archivo.file = archivo.log
log4j.appender.archivo.layout = org.apache.log4j.EnhancedPatternLayout
log4j.appender.archivo.layout.ConversionPattern = %d [%-5p] %c{2} - %m%n
</code></pre><br />
<br />
Y para asociarlo al <span class="codigo">rootLogger</span>, junto con el appender "<span class="codigo">consola</span>" que ya se tenía configurado, se hace lo siguiente:<br />
<br />
<pre><code>
log4j.rootLogger = TRACE, consola, archivo
</code></pre><br />
<br />
En este caso ambos <span class="codigo">appenders</span> tienen el mismo nivel de log. <br &><br />
Cuando ejecutemos nuestra aplicación veremos aparecer un archivo llamado "<span class="codigo">archivo.log</span>" en el directorio raíz de la aplicación:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiiS7v0CU7kBh4YkzNhIvo2dfEkYQLBwgk4wWWMpZ2zuS6KrMpFGOOLzcBX093iiM_tDFuJNRgD-vMRBbGJDjQJv66-JeIjuU8KQd1ejCDTIHTOuNLEIsOAtAIJwnkH9CK4VlDpFDalpaLY/s1600/l4j_5.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="256" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiiS7v0CU7kBh4YkzNhIvo2dfEkYQLBwgk4wWWMpZ2zuS6KrMpFGOOLzcBX093iiM_tDFuJNRgD-vMRBbGJDjQJv66-JeIjuU8KQd1ejCDTIHTOuNLEIsOAtAIJwnkH9CK4VlDpFDalpaLY/s320/l4j_5.png" width="320" /></a></div><br />
<br />
Este archivo tiene el siguiente contenido:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhxhcAG0oBg0SLmgsTpWYhauKLm9S8zlURFvQh32i4St3wshPtLoV7zIVsryJh84V1GXwBcUBpxjCGSUNHPuTVCup9OXOQVDdr6kIM4mE63qvJE8a_voipebrKUlee5jL-ZVT3BBHheoF4_/s1600/l4j_6.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="138" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhxhcAG0oBg0SLmgsTpWYhauKLm9S8zlURFvQh32i4St3wshPtLoV7zIVsryJh84V1GXwBcUBpxjCGSUNHPuTVCup9OXOQVDdr6kIM4mE63qvJE8a_voipebrKUlee5jL-ZVT3BBHheoF4_/s320/l4j_6.png" width="320" /></a></div><br />
<br />
Hay que tener cuidado de no cometer el error de tratar de que unos mensajes de log, con cierta prioridad, se envíen a un <span class="codigo">appender</span>, y otros mensajes, con otra prioridad, se envíen a otro <span class="codigo">appender</span>, de la siguiente forma:<br />
<br />
<pre><code>
log4j.rootLogger = TRACE, consola
log4j.rootLogger = ERROR, archivo
</code></pre><br />
<br />
Con esto, lo único que se consigue es sobre-escribir el <span class="codigo">appender</span> que estaba asociado al <span class="codigo">rootLogger</span>, y que este deje de mostrar mensajes en la consola.<br />
<br />
De hecho el objetivo anterior no es posible lograrlo con archivos de propiedades, pero si con archivos XML.<br />
<br />
Es posible asociar <span class="codigo">appenders</span>, no solo al <span class="codigo">rootLogger</span>, sino también a <span class="codigo">loggers</span> particulares, haciendo uso de su nombre. El <span class="codigo">appender</span> que asociemos a un <span class="codigo">logger</span> será heredado por todos sus descendientes. Por ejemplo, para asociar el <span class="codigo">appender</span> "<span class="codigo">archivo</span>" al logger "<span class="codigo">com.javatutoriales.log4j.configuracion</span>", se hace lo siguiente:<br />
<br />
<pre><code>
log4j.logger.com.javatutoriales.log4j.configuracion = WARN, archivo
</code></pre><br />
<br />
Con lo que el archivo de configuración quedaría de la siguiente forma:<br />
<br />
<pre><code>
log4j.appender.consola = org.apache.log4j.ConsoleAppender
log4j.appender.consola.threshold = INFO
log4j.appender.consola.target = System.out
log4j.appender.consola.layout = org.apache.log4j.EnhancedPatternLayout
log4j.appender.consola.layout.ConversionPattern = %d{dd MMM yyyy - HH:mm:ss} [%-5p] %c{2} - %m%n
log4j.appender.archivo = org.apache.log4j.FileAppender
log4j.appender.archivo.file = archivo.log
log4j.appender.archivo.layout = org.apache.log4j.PatternLayout
log4j.appender.archivo.layout.ConversionPattern = %d [%-5p] %c{2} - %m%n
log4j.rootLogger=TRACE, consola
log4j.logger.com.javatutoriales.log4j.configuracion=WARN, archivo
</code></pre><br />
<br />
De la misma forma, es posible definir distintos <span class="codigo">appenders</span> para distintos <span class="codigo">loggers</span>. Suponiendo que la aplicación tiene dos paquetes, un paquete "<span class="codigo">com.javatutoriales.log4j.configuracion.datos</span>" y "<span class="codigo">com.javatutoriales.log4j.configuracion.conexiones</span>", se pueden asociar distintos <span class="codigo">loggers</span>, así como colocarles distintos niveles de log, a estos paquetes de la siguiente forma:<br />
<br />
<pre><code>
log4j.logger.com.javatutoriales.log4j.configuracion.datos = WARN, archivo
log4j.logger.com.javatutoriales.log4j.configuracion.conexiones = INFO, consola
</code></pre><br />
<br />
Esto es todo lo que podemos decir sobre la configuración de <span class="codigo">log4j</span> usando archivos de propiedades. Ahora veremos cómo realizar la configuración usando archivos XML.<br />
<br />
<br />
<h2 class="subtitulo">Configuración de log4j con un Archivo XML</h2>Es posible realizar esta misma configuración en un archivo xml. De hecho, es posible incluir una configuración más detallada en este tipo de archivos.<br />
<br />
Lo primero que debemos hacer es crear un archivo XML, llamado "<span class="codigo">log4j.xml</span>", en el paquete raíz de la aplicación. <span class="codigo">log4j</span> busca por default este archivo en esta ubicación. Igual que con los archivos de configuración podríamos llamar a este archivo de otra forma, pero en ese caso tendríamos que realizar un proceso de configuración dentro de nuestra aplicación.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiqym4nUgy3ha9LmMrF2aE3fxVjAHN9YbWA2M9HIJPhfbrnp6KHEGVH4pYTxH3uRmQKe6vnTMcNyDd3zcqBW-Bn4kbm_hxTKeyKUaPdw4doizmc0zWoJktOd4eSi89N1vUNM8mTNxqSjoDi/s1600/l4j_7.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiqym4nUgy3ha9LmMrF2aE3fxVjAHN9YbWA2M9HIJPhfbrnp6KHEGVH4pYTxH3uRmQKe6vnTMcNyDd3zcqBW-Bn4kbm_hxTKeyKUaPdw4doizmc0zWoJktOd4eSi89N1vUNM8mTNxqSjoDi/s1600/l4j_7.png" /></a></div><br />
<br />
El encabezado del archivo comienza con la declaración estándar de XML seguida de una declaración de un <span class="codigo">DOCTYPE</span>, la cual indica que el <span class="codigo">DTD</span> (Document Type Definition) se encuentra en el mismo sistema:<br />
<br />
<pre><code>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd" >
</code></pre><br />
<br />
Posteriormente debe incluirse el elemento raíz del archivo, el cual es el elemento "<span class="codigo">log4j:configuration</span>", dentro de este se indica que se usará el namespace "<span class="codigo">log4j</span>" para definir los elementos de configuración de <span class="codigo">log4j</span>:<br />
<br />
<pre><code>
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
</log4j:configuration>
</code></pre><br />
<br />
Dentro de estas etiquetas se hacen las declaraciones de los elementos que conformar la configuración de <span class="codigo">log4j</span> (<span class="codigo">loggers</span>, <span class="codigo">appenders</span>, y <span class="codigo">layouts</span>).<br />
<br />
Los <span class="codigo">appenders</span> son agregados usando el elemento "<span class="codigo"><appender></span>", al cual se le deben especificar un nombre, usando el atributo "<span class="codigo">name</span>", y un tipo, usando el atributo "<span class="codigo">class</span>". En este caso se creará un <span class="codigo">appender</span> llamado "<span class="codigo">consola</span>" que será de tipo "<span class="codigo">org.log4j.ConsoleAppender</span>":<br />
<br />
<pre><code>
<appender name="consola" class="org.apache.log4j.ConsoleAppender">
</appender>
</code></pre><br />
<br />
Podemos establecer el valor de las propiedades de los <span class="codigo">appenders</span>, y en general de cualquier elemento, usando la etiqueta "<span class="codigo"><param></span>". El nombre del parámetro que se establecerá se indica usando el atributo "<span class="codigo">name</span>" y el valor usando el atributo "<span class="codigo">value</span>". En este caso estableceremos el valor de su atributo "<span class="codigo">target</span>":<br />
<pre><code>
<appender name="consola" class="org.apache.log4j.ConsoleAppender">
<param name="target" value="System.out" />
</appender>
</code></pre><br />
<br />
Como un <span class="codigo">appender</span> debe tener asociado un <span class="codigo">layout</span>, esto se hace anidando un elemento "<span class="codigo"><layout></span>" dentro del elemento "<span class="codigo"><appender></span>" que se acaba de definir. En este caso se usará nuevamente un "<span class="codigo">EnhancedPatternLayout</span>", lo cual se define usando el elemento "<span class="codigo">class</span>". A este <span class="codigo">layout</span> se le pueden definir propiedades, como en este caso su "<span class="codigo">ConversionPattern</span>", usando el elemento "<span class="codigo"><param></span>":<br />
<br />
<pre><code>
<appender name="consola" class="org.apache.log4j.ConsoleAppender">
<param name="target" value="System.out" />
<layout class="org.apache.log4j.EnhancedPatternLayout">
<param name="ConversionPattern" value="%d{dd MMM yyyy - HH:mm:ss} [%-5p] %c{2} - %m%n" />
</layout>
</appender>
</code></pre><br />
<br />
Para asociar un <span class="codigo">appender</span> al <span class="codigo">rootLogger</span>, se usa el elemento "<span class="codigo"><root></span>". Dentro de este se establece el nivel de log usando la etiqueta "<span class="codigo"><priority></span>", con su atributo "<span class="codigo">value</span>", y se indican los <span class="codigo">appenders</span> que tendrá asociado usando el elemento "<span class="codigo"><appender-ref></span>", cuyo atributo "<span class="codigo">ref</span>" debe indicar el nombre del <span class="codigo">appender</span> que se asociará con este <span class="codigo">logger</span>:<br />
<br />
<pre><code>
<root>
<priority value="trace" />
<appender-ref ref="consola"/>
</root>
</code></pre><br />
<br />
Al final el archivo de configuración queda de la siguiente forma:<br />
<br />
<pre><code>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<appender name="consola" class="org.apache.log4j.ConsoleAppender">
<param name="target" value="System.out" />
<layout class="org.apache.log4j.EnhancedPatternLayout">
<param name="ConversionPattern" value="%d{dd MMM yyyy - HH:mm:ss} [%-5p] %c{2} - %m%n" />
</layout>
</appender>
<root>
<priority value="trace" />
<appender-ref ref="consola"/>
</root>
</log4j:configuration>
</code></pre><br />
<br />
Al ejecutar la aplicación se obtiene la siguiente salida en la consola:<br />
<br />
<pre><code>
22 abr 2011 - 11:37:50 [TRACE] configuracion.Principal - mensaje de trace
22 abr 2011 - 11:37:50 [DEBUG] configuracion.Principal - mensaje de debug
22 abr 2011 - 11:37:50 [INFO ] configuracion.Principal - mensaje de info
22 abr 2011 - 11:37:50 [WARN ] configuracion.Principal - mensaje de warn
22 abr 2011 - 11:37:50 [ERROR] configuracion.Principal - mensaje de error
22 abr 2011 - 11:37:50 [FATAL] configuracion.Principal - mensaje de fatal
</code></pre><br />
<br />
Que como puede apreciarse es igual a la obtenida usando el archivo de propiedades.<br />
<br />
<br />
<br />
Igual que con los archivos de propiedades, es posible agregar más de un <span class="codigo">appender</span> por archivo de configuración, agregando más elementos "<span class="codigo"><appender></span>". También es posible asociar <span class="codigo">appenders</span> a <span class="codigo">loggers</span> particulares, declarando el <span class="codigo">logger</span> usando el elemento "<span class="codigo"><logger></span>" y definiendo su nombre con el atributo "<span class="codigo">name</span>". El elemento <span class="codigo">logger</span>, es igual que "<span class="codigo">root</span>", por lo que también es posible definir su nivel de log (solo que en este caso usando el elemento "<span class="codigo"><level></span>") y los <span class="codigo">appenders</span> que tiene asociados:<br />
<br />
<pre><code>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<appender name="consola" class="org.apache.log4j.ConsoleAppender">
<layout class="org.apache.log4j.EnhancedPatternLayout">
<param name="ConversionPattern" value="%d{ABSOLUTE} [%t] %-5p %c %x - %m%n" />
</layout>
</appender>
<appender name="archivo" class="org.apache.log4j.FileAppender">
<param name="file" value="archivo.log" />
<layout class="org.apache.log4j.EnhancedPatternLayout">
<param name="ConversionPattern" value="%d{ABSOLUTE} [%t] %-5p %c %x - %m%n" />
</layout>
</appender>
<logger name="log4j.xml">
<level value="warn" />
<appender-ref ref="archivo" />
</logger>
<root>
<priority value="trace" />
<appender-ref ref="consola"/>
</root>
</log4j:configuration>
</code></pre><br />
<br />
También, si queremos agregar más de un <span class="codigo">appender</span> a un <span class="codigo">logger</span> particular, solo basta con agregar otro elemento "<span class="codigo"><appender-ref></span>" para indicar el otro <span class="codigo">appender</span>:<br />
<br />
<pre><code>
<logger name="log4j.xml">
<level value="warn" />
<appender-ref ref="archivo" />
<appender-ref ref="consola" />
</logger>
</code></pre><br />
<br />
Nuevamente, igual que con los archivos de propiedades, es posible definir un umbral mínimo para los mensajes de log, estableciendo el nivel en la propiedad "<span class="codigo">treshold</span>" del <span class="codigo">appender</span> correspondiente:<br />
<br />
<pre><code>
<appender name="consola" class="org.apache.log4j.ConsoleAppender">
<param name="threshold" value="INFO" />
<layout class="org.apache.log4j.EnhancedPatternLayout">
<param name="ConversionPattern" value="%d{ABSOLUTE} [%t] %-5p %c %x - %m%n" />
</layout>
</appender>
</code></pre><br />
<br />
Sin embargo, el los archivos <span class="codigo">XML</span> podemos usar un mecanismo aún más poderoso para limitar los mensajes que se muestran en un <span class="codigo">appender</span>, y esto se hace con el uso de filtros.<br />
<br />
<br />
<h3 class="subtitulo">Filtros de Mensajes</h3>Una cosa muy interesante que puede hacerse en el archivo de configuración en <span class="codigo">XML</span> es el uso de filtros. Estos filtros permiten indicar que el <span class="codigo">appender</span> solo mostrará, o no, los mensajes de cierto nivel. Los filtros se colocan en forma de cadena dentro del <span class="codigo">appender</span>, y la decisión de si un mensaje debe mostrarse o no se hace tomando en cuenta las decisiones de <span class="negritas">todos los filtros</span>.<br />
<br />
<span class="codigo">log4j</span> viene únicamente con <span class="negritas">4</span> filtros, pero encadenándolos tenemos lo necesario para mostrar cualquier nivel de mensaje que se les pueda ocurrir:<br />
<ul><li>"<span class="codigo"><a href="http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/varia/DenyAllFilter.html">DenyAllFilter</a></span>": Evita que se produzcan mensajes de log.</li>
<li>"<span class="codigo"><a href="http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/varia/LevelMatchFilter.html">LevelMatchFilter</a></span>": Muestra solo los mensajes que tengan el nivel indicado.</li>
<li>"<span class="codigo"><a href="http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/varia/LevelRangeFilter.html">LevelRangeFilter</a></span>": Muestra únicamente los mensajes que se encuentran entre un nivel inferior y superior de log.</li>
<li>"<span class="codigo"><a href="http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/varia/StringMatchFilter.html">StringMatchFilter</a></span>": Muestra solo los mensajes que en el mensaje de salida tengan la sub-cadena indicada.</li>
</ul><br />
Los filtros son colocados dentro de los <span class="codigo">appenders</span> usando el elemento "<span class="codigo"><filter></span>".<br />
<br />
Comencemos con el primero y el más sencillo: "<span class="codigo">DenyAllFilter</span>". Si colocamos este filtro en nuestro <span class="codigo">appender</span> de la siguiente forma:<br />
<br />
<pre><code>
<appender name="consola" class="org.apache.log4j.ConsoleAppender">
<param name="target" value="System.out" />
<layout class="org.apache.log4j.EnhancedPatternLayout">
<param name="ConversionPattern" value="%d{dd MMM yyyy - HH:mm:ss} [%-5p] %c{2} - %m%n" />
</layout>
<filter class="org.apache.log4j.varia.DenyAllFilter" />
</appender>
</code></pre><br />
<br />
Evitará que se muestren mensajes en la salida. ¿Para qué puede servirnos esto? Esto es útil en dos situaciones. La primera es cuando queremos que un <span class="codigo">appender</span> deje de enviar mensajes.<br />
<br />
La segunda, y creo que la más importante, es cuando lo colocamos <span class="negritas">al final de una cadena de filtros</span>. En ese caso cambia el comportamiento de "<span class="negritas">acepta todo a menos que se diga lo contrario</span>" a "<span class="negritas">niega todo a menos que se diga lo contrario</span>". Esto quedará más claro cuando veamos los siguientes ejemplos.<br />
<br />
"<span class="codigo">LevelRangeFilte</span>" permite que se muestren solo los mensajes del nivel indicado. Este filtro tiene dos propiedades "<span class="codigo">LevelToMatch</span>", que indica el nivel que aceptará el filtro, y "<span class="codigo">AcceptOnMatch</span>", el cual recibe un valor booleano que indica si el mensaje debe mostrarse o no.<br />
<br />
Si queremos que nuestro appender <span class="negritas">NO</span> muestre los mensajes de nivel "<span class="codigo">WARN</span>", lo configuramos de esta forma:<br />
<br />
<pre><code>
<appender name="consola" class="org.apache.log4j.ConsoleAppender">
<param name="target" value="System.out" />
<layout class="org.apache.log4j.EnhancedPatternLayout">
<param name="ConversionPattern" value="%d{dd MMM yyyy - HH:mm:ss} [%-5p] %c{2} - %m%n" />
</layout>
<filter class="org.apache.log4j.varia.LevelMatchFilter">
<param name="LevelToMatch" value="warn" />
<param name="AcceptOnMatch" value="false" />
</filter>
</appender>
</code></pre><br />
<br />
En donde podemos ver que el valor de "<span class="codigo">AcceptOnMatch</span>" es falso. Si queremos que <span class="negritas">solo</span> se muestren los mensaje de nivel "<span class="codigo">WARN</span>" debemos usar este filtro en combinación con el "<span class="codigo">DenyAllFilter</span>": <br />
<br />
<pre><code>
<appender name="consola" class="org.apache.log4j.ConsoleAppender">
<param name="target" value="System.out" />
<layout class="org.apache.log4j.EnhancedPatternLayout">
<param name="ConversionPattern" value="%d{dd MMM yyyy - HH:mm:ss} [%-5p] %c{2} - %m%n" />
</layout>
<filter class="org.apache.log4j.varia.LevelMatchFilter">
<param name="LevelToMatch" value="warn" />
<param name="AcceptOnMatch" value="true" />
</filter>
<filter class="org.apache.log4j.varia.DenyAllFilter" />
</appender>
</code></pre><br />
<br />
En este caso es necesario hacer un encadenamiento de los filtros. "<span class="codigo">LevelMatchFilter</span>" indica que debe aceptar los mensajes de nivel "<span class="codigo">WARN</span>", y "<span class="codigo">DenyAllFilter</span>" dice que niegue todo lo que no esté aceptado de forma explícita. En este caso todos los demás niveles. Para determinar qué mensajes son mostrados y cuáles no, en una cadena de filtros, se usa un sistema del que hablaré un poco más adelante.<br />
<br />
"<span class="codigo">LevelRangeFilter</span>" permite aceptar mensajes de log que se encuentran solo dentro de <span class="negritas">ciertos rangos establecidos</span>. Este filtro tiene tres propiedades: "<span class="codigo">LevelMin</span>", "<span class="codigo">LevelMax</span>" y "<span class="codigo">AcceptOnMatch</span>".<br />
<br />
Si queremos mostrar solo los mensajes que se encuentran entre los niveles "<span class="codigo">INFO</span>" y "<span class="codigo">ERROR</span>", podemos hacerlo de la siguiente forma:<br />
<br />
<pre><code>
<appender name="consola" class="org.apache.log4j.ConsoleAppender">
<param name="target" value="System.out" />
<layout class="org.apache.log4j.EnhancedPatternLayout">
<param name="ConversionPattern" value="%d{dd MMM yyyy - HH:mm:ss} [%-5p] %c{2} - %m%n" />
</layout>
<filter class="org.apache.log4j.varia.LevelRangeFilter">
<param name="LevelMin" value="info" />
<param name="LevelMax" value="error" />
<param name="AcceptOnMatch" value="true" />
</filter>
</appender>
</code></pre><br />
<br />
Con este filtro no hay una forma directa para hacer que se acepten todos los niveles, excepto los que se indican en el, para esto es necesario hacer varias combinaciones de filtros.<br />
<br />
Finalmente, el filtro "<span class="codigo">StringMatchFilter</span>" permite que se muestren o se rechacen solo los mensajes de log que contengan una sub-cadena dada. Este filtro tiene dos tributos: "<span class="codigo">StringToMatch</span>" que indica la subcadena que será buscada, y "<span class="codigo">AcceptOnMatch</span>" que indica si el mensaje será acpetado o rechazado.<br />
<br />
Por ejemplo, si queremos que <span class="negritas">NO</span> se muestren los mensajes que contengan la cadena "<span class="codigo">info</span>", debemos configurar el filtro de esta forma:<br />
<br />
<pre><code>
<appender name="consola" class="org.apache.log4j.ConsoleAppender">
<param name="target" value="System.out" />
<layout class="org.apache.log4j.EnhancedPatternLayout">
<param name="ConversionPattern" value="%d{dd MMM yyyy - HH:mm:ss} [%-5p] %c{2} - %m%n" />
</layout>
<filter class="org.apache.log4j.varia.StringMatchFilter">
<param name="StringToMatch" value="info" />
<param name="AcceptOnMatch" value="false" />
</filter>
</appender>
</code></pre><br />
<br />
Si queremos que <span class="negritas">solo</span> se muestren los mensajes que contengan la cadena "<span class="codigo">info</span>", debemos usarlo junto con el filtro "<span class="codigo">DenyAllFilter</span>":<br />
<br />
<pre><code>
<appender name="consola" class="org.apache.log4j.ConsoleAppender">
<param name="target" value="System.out" />
<layout class="org.apache.log4j.EnhancedPatternLayout">
<param name="ConversionPattern" value="%d{dd MMM yyyy - HH:mm:ss} [%-5p] %c{2} - %m%n" />
</layout>
<filter class="org.apache.log4j.varia.StringMatchFilter">
<param name="StringToMatch" value="info" />
<param name="AcceptOnMatch" value="true" />
</filter>
<filter class="org.apache.log4j.varia.DenyAllFilter" />
</appender>
</code></pre><br />
<br />
Ahora hablemos sobre el sistema aplicación de los filtros cuando se encuentran en cadenas. Como había dicho, cuando tenemos una cadena de filtros estos filtran los mensajes para decidir si debe mostrarse o no. Los filtros solo pueden aceptar, rechazar, o no tomar decisión sobre un mensaje (ser neutrales). Una vez que un filtro ha aceptado un mensaje, <span class="negritas">este no puede ser rechazado posteriormente por otro filtro</span>.<br />
<br />
Para determinar si un filtro aceptará o rechazará un mensaje dependerá del valor que coloquemos en su atributo "<span class="codigo">AcceptOnMatch</span>" y de si se encuentra o no una coincidencia para el nivel de mensaje (o para el mensaje en el caso del "<span class="codigo">StringMatchFilter</span>").<br />
<br />
Por ejemplo, con un "<span class="codigo">LevelMatchFilter</span>", si el mensaje no coincide con el nivel especificado, el filtro no acepta ni rechaza el mensaje (es neutral). Si encuentra una coincidencia, y "<span class="codigo">AcceptOnMatch</span>" es verdadero entonces el filtro <span class="negritas">aceptará el mensaje</span>, pero si "<span class="codigo">AcceptOnMatch</span>" es falso, el filtro <span class="negritas">rechazará el mensaje</span>.<br />
<br />
La siguiente tabla resume el comportamiento de cada uno de los filtros:<br />
<br />
<table><thead>
<tr><th>Filtro</th><th>AcceptOnMatch Verdadero</th><th>AcceptOnMartch Falso</th><th>Sin Coincidencias</th></tr>
</thead> <tbody>
<tr><td><span class="codigo">LevelMatchFilter</span></td><td class="centrado">Aceptado</td><td class="centrado">Rechazado</td><td class="centrado">Neutral</td></tr>
<tr><td><span class="codigo">LevelRangeFilter</span></td><td class="centrado">Aceptado</td><td class="centrado">Neutral</td><td class="centrado">Rechazado</td></tr>
<tr><td><span class="codigo">StringMatchFilter</span></td><td class="centrado">Aceptado</td><td class="centrado">Rechazado</td><td class="centrado">Neutral</td></tr>
</tbody> </table><br />
<br />
Si un mensaje no es rechazado de forma explícita este será mostrado, a menos que se aplique un "<span class="codigo">DenyAllFilter</span>" al final de la cadena. En este caso todo lo que no sea aceptado de forma explícita será rechazado.<br />
<br />
Veamos unos ejemplos para que esto quede más claro. Si declaramos el siguiente filtro de tipo "<span class="codigo">LevelRangeFilter</span>":<br />
<br />
<pre><code>
<filter class="org.apache.log4j.varia.LevelRangeFilter">
<param name="LevelMin" value="info" />
<param name="LevelMax" value="error" />
<param name="AcceptOnMatch" value="false" />
</filter>
</code></pre><br />
<br />
Como "<span class="codigo">AcceptOnMatch</span>" está establecido en "<span class="codigo">false</span>" los mensajes que coincidan con el rango ("<span class="codigo">INFO</span>", "<span class="codigo">WARN</span>" y "<span class="codigo">ERROR</span>") serán tomados de forma neutral, y los que no coincidan serán rechazados. Por lo que solo veremos los mensajes de nivel "<span class="codigo">INFO</span>", "<span class="codigo">WARN</span>", y "<span class="codigo">ERROR</span>".<br />
<br />
Pero si colocamos el filtro de la siguiente forma:<br />
<br />
<pre><code>
<filter class="org.apache.log4j.varia.LevelRangeFilter">
<param name="LevelMin" value="info" />
<param name="LevelMax" value="error" />
<param name="AcceptOnMatch" value="false" />
</filter>
<filter class="org.apache.log4j.varia.DenyAllFilter" />
</code></pre><br />
<br />
Como ahora estamos agregando un "<span class="codigo">DenyAllFilter</span>" al final de la cadena, todos los mensajes que no sean aceptados explícitamente serán rechazados. En este caso, como ningún mensaje es aceptado de forma explícita, todos serán rechazados y no veremos ningún mensaje.<br />
<br />
Si ahora colocamos los filtros, de esta forma:<br />
<pre><code>
<filter class="org.apache.log4j.varia.LevelMatchFilter">
<param name="LevelToMatch" value="warn" />
<param name="AcceptOnMatch" value="false" />
</filter>
<filter class="org.apache.log4j.varia.LevelRangeFilter">
<param name="LevelMin" value="info" />
<param name="LevelMax" value="error" />
<param name="AcceptOnMatch" value="true" />
</filter>
</code></pre><br />
<br />
El primer filtro, de tipo "<span class="codigo">LevelMatchFilter</span>" está configurado para un nivel de "<span class="codigo">WARN</span>" y con su valor de "<span class="codigo">AcceptOnMatch</span>" en false. Esto quiere decir que rechazará todos los mensajes de tipo "<span class="codigo">WARN</span>". <br />
<br />
El siguiente filtro, de tipo "<span class="codigo">LevelRangeFilter</span>", está configurado para aceptar mensajes de tipo "<span class="codigo">INFO</span>" a "<span class="codigo">ERROR</span>", y con su valor "<span class="codigo">AcceptOnMatch</span>" en true, por lo que aceptará mensajes de tipo "<span class="codigo">INFO</span>", "<span class="codigo">WARN</span>", y "<span class="codigo">ERROR</span>", y rechazará los demás.<br />
<br />
Cuando un mensaje entra al primer filtro, este rechaza los mensajes de tipo "<span class="codigo">WARN</span>" y dejará pasar los demás. En segundo filtro solo dejará pasar los mensajes de tipo "<span class="codigo">INFO</span>", y "<span class="codigo">ERROR</span>". Por lo que la única salida que obtendremos serán los mensajes de nivel "<span class="codigo">INFO</span>" y "<span class="codigo">ERROR</span>". <br />
<br />
Noten que si invertimos el orden de los filtros el resultado que obtendremos es que se mostrarán los mensajes de tipo "<span class="codigo">INFO</span>", "<span class="codigo">WARN</span>", y "<span class="codigo">ERROR</span>". Esto es porque el primer filtro ya ha aceptado los mensajes de tipo "<span class="codigo">WARN</span>" de forma explícita, y entonces el rechazo del segundo filtro ya no es tomado en cuenta.<br />
<br />
Estos son todos los ejemplos que se me ocurren para mostrar el uso de los filtros. Si alguien quiere que hagamos un ejemplo particular, colóquelo en los comentarios y lo agregaré a esta sección ^_^.<br />
<br />
Para terminar este tutorial, veamos las tablas que explican los caracteres de conversión y los modificadores de formato para "<span class="codigo">EnhancedPatternLayout</span>"<br />
<br />
<br />
<h1 class="subtitulo">Caracteres de Conversión de Formato de EnhancedPatternLayout</h1><br />
<table><thead>
<tr><th>Caracter de Conversión</th><th>Efecto</th></tr>
</thead> <tbody>
<tr><td class="centrado"><span class="codigo">c</span></td><td>Muestra el nombre del <span class="codigo">logger</span> que generó el mensaje. Se puede especificar un patrón de "<span class="codigo">NameAbbreviator</span>" dentro de llaves para limitar y modificar la información mostrada.<br />
<br />
<br />
Por ejemplo, para el <span class="codigo">logger</span> "<span class="codigo">com.javatutoriales.log4j.configuracion.Principal</span>", si usamos el patrón <span class="codigo">%c{2}</span> solo se mostrarán los dos elementos finales del nombre del <span class="codigo">logger</span>, osea "<span class="codigo">configuracion.Principal</span>". Con <span class="codigo">%c{-2}</span> se eliminan los dos primeros elementos dejando solo "<span class="codigo">log4j.configuracion.Principal</span>". Con <span class="codigo">%c{1.}</span> solo se mostrará el primer carácter de los elementos no finales del <span class="codigo">logger</span>, o sea "<span class="codigo">c.j.l.c.Principal</span>"; con <span class="codigo">%c{2.}</span> obtendremos "<span class="codigo">co.ja.lo.co.Principal</span>". Si hacemos <span class="codigo">%c{2~.1:}</span> se mostrarán los dos primeros caracteres del primer elemento usando una tilde para indicar los caracteres abreviados y el primer caracter de los siguientes elementos no finales usando dos puntos para indicar los caracteres abreviados, o sea: "<span class="codigo">co~.j:.l:.c:.Principal</span>"</td></tr>
<tr><td class="centrado"><span class="codigo">C</span></td><td>Se usa para mostrar el nombre de la clase donde se generó el mensaje de log. En este caso también se especifica un patrón de "<span class="codigo">NameAbbreviator</span>" que sigue las mismas reglas indicadas anteriormente.<br />
<br />
<br />
<span class="negritas">ADVERTENCIA</span>: Generar esta información es lento, por lo que debe evitarse el uso a menos que sea estrictamente necesario.</td></tr>
<tr><td class="centrado"><span class="codigo">d</span></td><td>Se usa para mostrar la fecha del evento de log. Se puede especificar un patrón para la fecha y hora usando los patrones de "<span class="codigo"><a href="http://download.oracle.com/javase/6/docs/api/java/text/SimpleDateFormat.html?is-external=true">SimpleDateFormat</a></span>", <span class="codigo">ABSOLUTE</span>, <span class="codigo">DATE</span>, o <span class="codigo">ISO8601</span>, por ejemplo: <span class="codigo">%d{HH:mm:ss,SSS}</span>, <span class="codigo">%d{dd MMM yyyy HH:mm:ss,SSS}</span>, <span class="codigo">%d{DATE}</span> o <span class="codigo">%d{HH:mm:ss}{GMT+0}</span></td></tr>
<tr><td class="centrado"><span class="codigo">F</span></td><td>Se usa para mostrar el nombre del archivo donde se generó el mensaje de log.<br />
<br />
<br />
<span class="negritas">ADVERTENCIA</span>: Generar esta información es lento, por lo que debe evitarse el uso a menos que sea estrictamente necesario.</td></tr>
<tr><td class="centrado"><span class="codigo">l</span></td><td>Se usa para mostrar la información de la ubicación de quien hizo la petición de evento de log.<br />
<br />
<br />
La información de la ubicación depende de la implementación de la JVM, pero usualmente consiste en el fully qualified name del método, seguido del nombre del archivo, y el número de línea en el que se encuentra la llamada.<br />
<br />
<br />
<span class="negritas">ADVERTENCIA</span>: Generar esta información es <span class="negritas">extremadamente</span>s lento, por lo que debe evitarse el uso a menos que sea estrictamente necesario.</td></tr>
<tr><td class="centrado"><span class="codigo">L</span></td><td>Se usa para mostrar el número de línea dónde si hizo la petición para el evento de log.<br />
<br />
<br />
<span class="negritas">ADVERTENCIA</span>: Generar esta información es lento, por lo que debe evitarse el uso a menos que sea estrictamente necesario.</td></tr>
<tr><td class="centrado"><span class="codigo">m</span></td><td>Se usa para mostrar el mensaje de log que es proporcionado por la aplicación al invocar el método del evento de log.</td></tr>
<tr><td class="centrado"><span class="codigo">M</span></td><td>Se usa para mostrar el nombre del método donde se hizo la petición para el evento de log.<br />
<br />
<br />
<span class="negritas">ADVERTENCIA</span>: Generar esta información es lento, por lo que debe evitarse el uso a menos que sea estrictamente necesario.</td></tr>
<tr><td class="centrado"><span class="codigo">n</span></td><td>Sirve para colocar un caracter de salto de línea.</td></tr>
<tr><td class="centrado"><span class="codigo">p</span></td><td>Se usa para mostrar el nivel de log del evento.</td></tr>
<tr><td class="centrado"><span class="codigo">r</span></td><td>Se usa para mostrar el número de milisegundos transcurridos desde la construcción del layout, hasta la creación del evento de log.</td></tr>
<tr><td class="centrado"><span class="codigo">t</span></td><td>Se usa para mostrar el nombre del thread que genera el evento de log.</td></tr>
<tr><td class="centrado"><span class="codigo">x</span></td><td>Se usa para mostrar el <span class="codigo">NDC</span> (Nested Diagnostic Context) asociado con el thread que genera el evento de log. Un <span class="codigo">NDC</span> es un instrumento para distinguir salidas de log intercaladas que provienen de distintas fuentes. Las salidas de log suelen ser intercaladas cuando un servidor maneja varios clientes al mismo tiempo.</td></tr>
<tr><td class="centrado"><span class="codigo">X</span></td><td>Se usa para mostrar el <span class="codigo">MDC</span> (Mapped Diagnostic Context) asociado con el thread que genera el evento de log. El <span class="codigo">MDC</span> es lo mismo que el <span class="codigo">NDC</span> solo que usando un Map como implementación.</td></tr>
<tr><td class="centrado"><span class="codigo">properties</span></td><td>Se usa para mostrar las propiedades asociadas con el evento de log. Adicionalmente podemos indicar la llave de la propiedad que queremos obtener, como por ejemplo <span class="codigo">%properties{aplicacion}</span></td></tr>
<tr><td class="centrado"><span class="codigo">throwable</span></td><td>Se usa para mostrar el stack trace asociado con el evento de log, por default se muestra el stack trace completo. Podemos controlar la profundidad del stack mostrado colocando el mismo entre llaves. Por ejemplo <span class="codigo">%throwable{short}</span> o <span class="codigo">%throwable{1}</span> mostrarán la primer línea del stack trace. <span class="codigo">throwable{none}</span> o <span class="codigo">throwable{0}</span> no mostrarán nada del stack trace. <span class="codigo">%throwable{n}</span> mostrará las primeras <span class="codigo">n</span> líneas del stack trace, si es un número positivo; si es un número negativo mostrará las últimas <span class="codigo">n</span> líneas del stack trace.</td></tr>
<tr><td class="centrado"><span class="codigo">%</span></td><td>La secuencia <span class="codigo">%%</span> muestra un solo signo de porcentaje ^_^</td></tr>
</tbody> </table><br />
<br />
<br />
<h1 class="subtitulo">Modificadores de Formato de EnhancedPatternLayout</h1>Los modificadores de formatos nos permiten controlar el número de caracteres que se reservarán para un dato de salida, así como la justificación del texto dentro de estos caracteres. También permite indicar la longitud máxima de un dato.<br />
<br />
En la siguiente tabla se muestran ejemplos de uso de estos modificadores, usando el carácter de conversión de nombre del <span class="codigo">logger</span> (<span class="codigo">%c</span>):<br />
<br />
<table><thead>
<tr><th>Modificador de Formato</th><th>Justificación Izquierda</th><th>Longitud mínima</th><th>Longitud máxima</th><th>Funcionamiento</th> </thead> <tbody>
<tr><td class="centrado"><span class="codigo">%20c</span></td><td class="centrado">No</td><td class="centrado">20</td><td class="centrado">ninguna</td><td>Justifica a la derecha dejando espacios en blanco si el nombre del logger tiene menos de 20 caracteres</td></tr>
<tr><td class="centrado"><span class="codigo">%-20c</span></td><td class="centrado">Si</td><td class="centrado">20</td><td class="centrado">ninguna</td><td>Justifica a la izquierda dejando espacios en blanco si el nombre del logger tiene menos de 20 caracteres</td></tr>
<tr><td class="centrado"><span class="codigo">%.30c</span></td><td class="centrado">NA</td><td class="centrado">ninguna</td><td class="centrado">30</td><td>Trunca a 30 caracteres, de derecha a izquierda, si el nombre del logger tiene más de 30 caracteres (o sea "<span class="codigo">.log4j.configuracion.Principal</span>")</td></tr>
<tr><td class="centrado"><span class="codigo">%20.30c</span></td><td class="centrado">No</td><td class="centrado">20</td><td class="centrado">30</td><td>Justifica a la derecha dejando espacios en blanco si el nombre del logger tiene menos de 20 caracteres. Pero si tiene más de 30 caracteres, lo trunca, de derecha a izquierda.</td></tr>
<tr><td class="centrado"><span class="codigo">%-20.30c</span></td><td class="centrado">Si</td><td class="centrado">30</td><td class="centrado">30</td><td>Justifica a la izquierda dejando espacios en blanco si el nombre del logger tiene menos de 20 caracteres. Pero si tiene más de 30 caracteres lo trunca, de derecha a izqueirda.</td></tr>
</tbody> </table><br />
<br />
<br />
Bueno, esto es todo para este tutorial de <span class="codigo">log4j</span>. Como podemos ver, es un framework muy completo que nos permite controlar prácticamente cada aspecto de la bitácora o logging de nuestra aplicación. De una forma muy completa y sencilla, solo modificando un archivo de configuración.<br />
<br />
Espero que les sea de utilidad. No olviden dejar sus dudas, comentarios y sugerencias.<br />
<br />
Saludos y gracias.<br />
<br />
<span class="negritas">Descarga los archivos de este tutorial desde aquí:</span><br />
<ul><li><a href="https://sites.google.com/site/javatutoriales/log4j/Log4JPropiedades.zip"><span class="ligaArchivo">Log4j con Archivo de Propiedades</span></a></li>
<li><a href="https://sites.google.com/site/javatutoriales/log4j/Log4jXML.zip"><span class="ligaArchivo">Log4j con Archivo XML</span></a></li>
</ul></div>Alexhttp://www.blogger.com/profile/06974037481671868076noreply@blogger.com