Integración de Spring con el envío de emails.
0. Índice de contenidos.
- 1. Introducción.
- 2. Entorno.
- 3. Maven: el pom.xml.
- 4. Creación de nuestro servicio de envío de emails.
- 5. Configuración del applicationContext.xml.
- 6. Creación de un test de JUnit.
- 7. Conclusiones.
1. Introducción
Siguiendo con la saga de tutoriales sobre Spring y el soporte a la integración con otras tencologías que proporciona, vamos a analizar la integración
con un servicio de correo electrónico.
Dentro del amplio catálogo de escenarios en el que podemos integrar Spring en nuestros desarrollos, solemos decir que no es una solución «todo o nada»,
de modo que nos podemos beneficiar de todo el conjunto de módulos que contiene para darnos soporte a la generación de una aplicación JEE o sólo de una parte
de ellos.
En este tutorial vamos a implementar un servicio de envío de emails con attachments, al que tendremos acceso:
- desde cualquier módulo de nuestra aplicación gestionado por Spring, haciendo uso de su inyección de depedencias, o
- desde cualquier módulo de nuestra aplicación, que sin estar gestionado por Spring, pueda acceder al contenedor de IoC de Spring para recuperar el servicio.
Para comprobar el funcionamiento del servicio haremos un test de JUnit que levantará el contexto de Spring.
La redacción de este tutorial se realiza dando por hecho que el lector tiene conocimientos suficientes sobre Spring IoC, Maven y JUnit 4.4.
2. Entorno.
El tutorial está escrito usando el siguiente entorno:
- Hardware: Sobremesa Dell Dimension 6400, 2.13 Ghz, 2 Gb RAM
- Sistema operativo: Windows XP Media center Edition
- JDK 1.5.0_15
- Eclipse 3.4.
3. Maven: el pom.xml.
El soporte que proporciona Spring para el envió de emails no es más que un wrapper sobre las librerías javax.mail y javax.activation, con lo que, además de las dependencias típicas para Spring y el entorno de test incluimos la dependencia a estas dos librerías.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.autentia.training.spring</groupId> <artifactId>SpringEmail</artifactId> <packaging>jar</packaging> <version>1.0-SNAPSHOT</version> <name>SpringEmail</name> <description>Ejemplo de uso de un servicio de Email en una aplicación con Spring.</description> <url>http://maven.apache.org</url> <dependencies> <!-- ========== Spring ========== --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring</artifactId> <version>2.5.5</version> </dependency> <!-- para poder hacer uso de la anotación @Resource con la jdk1.5 --> <dependency> <groupId>javax.annotation</groupId> <artifactId>jsr250-api</artifactId> <version>1.0</version> </dependency> <!-- ========== Mail ========== --> <dependency> <groupId>javax.mail</groupId> <artifactId>mail</artifactId> <version>1.4</version> </dependency> <dependency> <groupId>javax.activation</groupId> <artifactId>activation</artifactId> <version>1.1</version> </dependency> <!-- ========== Test ========== --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.4</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>2.5.5</version> <scope>test</scope> </dependency> </dependencies> <build> <finalName>SpringEmail</finalName> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.6</source> <target>1.6</target> <encoding>UTF-8</encoding> </configuration> </plugin> </plugins> </build> </project> |
4. Creación de nuestro servicio de envío de emails.
Comenzamos con la implementación del servicio, creando una interfaz, para obligarnos a implementar una serie de métodos, los típicos para el envío de correo electrónico.
1 2 3 4 5 6 7 8 9 10 11 |
package com.autentia.training.spring.mail; import java.io.File; public interface MailService { public void send(String to, String subject, String text); public void send(String to, String subject, String text, File... attachments); } |
Ahora creamos una clase que implemente la interaz creada
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
package com.autentia.training.spring.mail; import java.io.File; import javax.mail.MessagingException; import javax.mail.internet.MimeMessage; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.core.io.FileSystemResource; import org.springframework.mail.javamail.JavaMailSenderImpl; import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.util.Assert; /** * Servicio de envío de emails */ public class MailServiceImpl implements MailService { private static final Log log = LogFactory.getLog(MailServiceImpl.class); /** wrapper de Spring sobre javax.mail */ private JavaMailSenderImpl mailSender; public void setMailSender(JavaMailSenderImpl mailSender) { this.mailSender = mailSender; } /** correo electrónico del remitente */ private String from; public void setFrom(String from) { this.from = from; } public String getFrom() { return from; } /** flag para indicar si está activo el servicio */ public boolean active = true; public boolean isActive() { return active; } public void setActive(boolean active) { this.active = active; } private static final File[] NO_ATTACHMENTS = null; /** envío de email * @param to correo electrónico del destinatario * @param subject asunto del mensaje * @param text cuerpo del mensaje */ public void send(String to, String subject, String text) { send(to, subject, text, NO_ATTACHMENTS); } /** envío de email con attachments * @param to correo electrónico del destinatario * @param subject asunto del mensaje * @param text cuerpo del mensaje * @param attachments ficheros que se anexarán al mensaje */ public void send(String to, String subject, String text, File... attachments) { // chequeo de parámetros Assert.hasLength(to, "email 'to' needed"); Assert.hasLength(subject, "email 'subject' needed"); Assert.hasLength(text, "email 'text' needed"); // asegurando la trazabilidad if (log.isDebugEnabled()) { final boolean usingPassword = !"".equals(mailSender.getPassword()); log.debug("Sending email to: '" + to + "' [through host: '" + mailSender.getHost() + ":" + mailSender.getPort() + "', username: '" + mailSender.getUsername() + "' usingPassword:" + usingPassword + "]."); log.debug("isActive: " + active); } // el servicio esta activo? if (!active) return; // plantilla para el envío de email final MimeMessage message = mailSender.createMimeMessage(); try { // el flag a true indica que va a ser multipart final MimeMessageHelper helper = new MimeMessageHelper(message, true); // settings de los parámetros del envío helper.setTo(to); helper.setSubject(subject); helper.setFrom(getFrom()); helper.setText(text); // adjuntando los ficheros if (attachments != null) { for (int i = 0; i < attachments.length; i++) { FileSystemResource file = new FileSystemResource(attachments[i]); helper.addAttachment(attachments[i].getName(), file); if (log.isDebugEnabled()) { log.debug("File '" + file + "' attached."); } } } } catch (MessagingException e) { new RuntimeException(e); } // el envío this.mailSender.send(message); } } |
El código está comentado, pero añadimos lo siguiente:
- líneas 23 a 27: la inclusión del servicio de envío propio de Spring y la espera de ser inyectado mediante el método set,
- líneas 29 a 38: la asignación de un remitente de correo por defecto para todos nuestros emails, será también inyectado por Spring,
- línea 86: creación de la plantilla para el envío de emails. La clase MimeMessage tiene lo mínimo para el envío de emails a través del servicio de Spring.
- línea 90: creación de la plantilla para el envío de emails con attachments, permite multipart.
- líneas 92 a 107: poblamos las pantilla con los parámetros del método y el remitente por defecto,
- línea 114: el envío propiamente dicho a través del servicio de Spring.
5. Configuración del applicationContext.xml.
Publicamos nuestro servicio en el contexto del applicationContext.xml, inyectándole la dependencia al servicio de envío propio de Spring y el remitente por defecto.
Para la configuración del servicio de Spring nos apoyamos en un fichero de configuración administrable: app.properties, que tendrá los datos de conexión con el servidor de correo.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"> <context:annotation-config /> <context:component-scan base-package="com.autentia.training" /> <!--#lectura del fichero administrable --> <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="ignoreUnresolvablePlaceholders"> <value>true</value> </property> <property name="locations"> <list> <value>classpath:app.properties</value> </list> </property> </bean> <!--#Configuración del servicio de Spring: MailSernder --> <bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl"> <property name="host" value="${mail.host}"/> <property name="port" value="${mail.port}"/> <property name="username" value="${mail.username}"/> <property name="password" value="${mail.password}"/> <property name="defaultEncoding" value="UTF-8"/> </bean> <!--#Configuración de nuestro servicio:MailService --> <bean id="mailService" class="com.autentia.training.spring.mail.MailServiceImpl"> <property name="active" value="true"/> <property name="mailSender" ref="mailSender"/> <property name="from" value="default@unknown.com"/> </bean> </beans> |
Destacamos lo siguiente:
- líneas 12 y 14: configuración del contexto de Spring para el uso de anotaciones,
- líneas 16 a 26: configuración de un servicio de tipo PropertyPlaceholderConfigurer para la lectura de ficheros de propiedades externos, de modo que,
como si accediesemos a variables, obtener el valor de las claves declaradas en el fichero de properties, - líneas 29 a 35: configuración del servicio de Spring para el envío, al que inyectamos la configuración sobre el servidor de correo, obtenida del fichero de propiedades. Para comprobar qué podemos configurar del servicio basta con revisar el javadoc de la clase de modo que cada método set de la clase coincide con uno de nuestros <property (inyección de dependencias a través de propiedades),
- líneas 38 a 42: configuración de nuestro servicio de envío inyectándole el servicio de Spring y el remitente por defecto.
El código de nuestro fichero de propiedades app.properties:
1 2 3 4 |
mail.host=smtp.unknown.com mail.port=25 mail.username=user mail.password=passw |
Con la implementación de la interfaz y esta configuración ya podemos hacer uso de nuestro servicio de envío desde cualquiera de nuestras clases de negocio gestionadas por
Spring mediante la anotación @Resource.
6. Creación de un test de JUnit.
Vamos a probar su funcionamiento a mediante un test de JUnit. El test comprobará un error en el envío, puesto que la configuración de nuestro host es incorrecta,
pero nos sirve para ilustrar su funcionamiento y cómo realizar la inyección.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
package com.autentia.training.spring.mail; import javax.annotation.Resource; import junit.framework.Assert; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = { "/applicationContext.xml"}) public class MailServiceTest { private static final Log log = LogFactory.getLog(MailServiceTest.class); @Resource private MailService mailService; /** * Probamos el envío */ @Test public void cantSendMails() { try { mailService.send("jmsanchez@autentia.com", "Test de envío de email.", "Prueba del envío de correo electrónico."); Assert.fail("No debería realizar el envío puesto que el host no está correctamente configurado en el entorno de test."); } catch(Exception e){ log.trace("Excepción controlada, normal en el entorno de test",e); } } } |
Para la comprobación del correcto funcionamiento tendríamos que retocar el código de nuestro test.
Destacamos las anotaciones de la clase (líneas 14 y 15) que nos permiten levantar desde la ejecución del test el contexto de Spring, leyéndo la configuración del fichero applicationContext.xml.
7. Conclusiones.
Sencilla la configuración de un servicio de envío de emails con Spring, ¿verdad?. Si además podemos hacer uso de la inyección de dependencias tendremos el servicio
disponible desde cualquier clase de negocio.
El hecho de tener la configuración del servicio desacoplada mediante Spring, nos permitirá llevar a cabo una configuración del servidor smtp en función del entorno y, sobre todo, en función del entorno de tests.
Un saludo.
Jose
Buenas.
Para empezar gracias por el tutorial, explica todo de una manera muy correcta, pero tengo un pequeño problema con el.
Estoy tratatando de hacer funcionar el ejemplo, pero pese a configurar los parametros del app.properties con los de una cuenta de correo gmail me encuentro con la siguiente traza de error:
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@17ee8b8: defining beans [org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,propertyConfigurer,mailSender,mailService]; root of factory hierarchy
Exception in thread \\\»main\\\» org.springframework.mail.MailSendException; nested exception details (1) are:
Failed message 1:
com.sun.mail.smtp.SMTPSendFailedException: 530 5.7.0 Must issue a STARTTLS command first. b10sm3796567wer.17
at com.sun.mail.smtp.SMTPTransport.issueSendCommand(SMTPTransport.java:1388)
at com.sun.mail.smtp.SMTPTransport.mailFrom(SMTPTransport.java:959)
at com.sun.mail.smtp.SMTPTransport.sendMessage(SMTPTransport.java:583)
at org.springframework.mail.javamail.JavaMailSenderImpl.doSend(JavaMailSenderImpl.java:403)
at org.springframework.mail.javamail.JavaMailSenderImpl.send(JavaMailSenderImpl.java:342)
at org.springframework.mail.javamail.JavaMailSenderImpl.send(JavaMailSenderImpl.java:338)
at implementacion.MailServiceImpl.send(MailServiceImpl.java:119)
at implementacion.MailServiceImpl.send(MailServiceImpl.java:64)
at test0001.Main.enviarCorreo(Main.java:28)
at test0001.Main.main(Main.java:35)
Sabrías decirme de que se trata.
Gracias de antemano.
Este hilo te puede dar alguna pista:
http://forums.sun.com/thread.jspa?threadID=617974
y aquí tienes la solución:
http://forum.springsource.org/archive/index.php/t-17638.html
hay que configurar el servicio para el envío a través de ssl.
Un saludo.
Jose.
Efectivamente. Hice lo que indicaban en el segundo post y FUNCIONO!!!
Muchisimas gracias Jose !!!
el ejemplo me funciono bien!!! … el problema es que tengo definido una Template para en envio de mail a cuentas hotmail(outlook), al momento de leer el mensaje desde hotmail… quita todos los hiperlinks , formularios que tiene el mensaje enviado, es decir, o y solo lo visualiza como texto plano, hize porque tenia la necesidad de tener algun enlace para redireccionar a otra pagina…
Otra cosa cuando adiciono style/css para el Template de Mail la pagina en hotmail los quita y lo muestra como archivo adjunto….????
Esto me pasa al enviar un mail desde una cuenta Gmail a una Hotmaill…..
La verdad no entiendo porque pasa eso!!! :-/
necesito ayuda…. les agradeceria su respuesta!!!