문제

How could I send an email with Spring 4 (and Spring Boot) by using a pure annotation-based approach (according to the Java Configurations rules)?

도움이 되었습니까?

해결책

A simple solution (where you will be using an SMTP server with no authentication) for configuring the email service would by

@Configuration 
public class MailConfig {

    @Value("${email.host}")
    private String host;

    @Value("${email.port}")
    private Integer port;

    @Bean
    public JavaMailSender javaMailService() {
        JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl();

        javaMailSender.setHost(host);
        javaMailSender.setPort(port);

        javaMailSender.setJavaMailProperties(getMailProperties());

        return javaMailSender;
    }

    private Properties getMailProperties() {
        Properties properties = new Properties();
        properties.setProperty("mail.transport.protocol", "smtp");
        properties.setProperty("mail.smtp.auth", "false");
        properties.setProperty("mail.smtp.starttls.enable", "false");
        properties.setProperty("mail.debug", "false");
        return properties;
    }
}

Spring must be able to resolve the properties email.host and email.port in of the usual ways (in case of Spring Boot the simplest is to put then in application.properties)

In any class that needs the services of JavaMailSender, just inject with one of the usual ways (such as @Autowired private JavaMailSender javaMailSender)


UPDATE

Note that since version 1.2.0.RC1 Spring Boot can auto-configure JavaMailSender for you. Check out this part of the documentation. As you can see from the documentation, almost no configuration is required to get up and running!

다른 팁

In pom.xml:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
</dependency>

In application.properties:

spring.mail.host=...
spring.mail.port=...

In Foo.java:

@Component
public class Foo {

    @Autowired
    private JavaMailSender mailSender;

    public void send() {
        SimpleMailMessage message = new SimpleMailMessage();
        message.setFrom("foo@example.com");
        message.setTo("bar@example.com");
        message.setSubject("hello");
        mailSender.send(message);
    }
}

Personally, I recommend running a localhost MTA, and using it to relay to your real MTA (like Gmail or SES, or your own). This gives you a "free" asynchronous queue, and centralizes config. I like OpenSMTP.

With Spring-Boot it was close to trivial, with one adjustment needed for smtp.office365.com mail server - which is what this company is using.

Before making this adjustment the authentication to the Office365 SMTP server kept failing and we'd get an error along the lines of:

org.springframework.mail.MailSendException: Failed messages: com.sun.mail.smtp.SMTPSendFailedException: 530 5.7.57 SMTP; Client was not authenticated to send anonymous mail during MAIL FROM
    at org.springframework.mail.javamail.JavaMailSenderImpl.doSend(JavaMailSenderImpl.java:474)
    at org.springframework.mail.javamail.JavaMailSenderImpl.send(JavaMailSenderImpl.java:307)
    at org.springframework.mail.javamail.JavaMailSenderImpl.send(JavaMailSenderImpl.java:296)

This was even though we were setting a username and password that were being correctly picked up by Spring's MailProperties class.

Turns out Office365 needs TLS authentication enabled and Spring's current JavaMail implementation doesn't have a simple way to do that with properties. The solution is to create our own javax.mail.Session instance and register it with Spring.

The whole picture follows.

In the pom.xml file, add:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-mail</artifactId>
        <version>${spring-boot.version}</version>
    </dependency>

Where spring-boot.version is defined elsewhere (in a parent pom in this case) and in this case has a value of 1.3.1.RELEASE

Make sure to include the package and/or the auto-configuration class if you itemize those in your main application - if you use the default @SpringBootApplication this isn't needed, but in my case I added MailSenderAutoConfiguration.class to the @Import annotation's list of classes.

I created a simple EmailSender class:

@SuppressWarnings("SpringJavaAutowiringInspection")
@Slf4j
@Service
@ConditionalOnClass(JavaMailSender.class)
public class EmailSender {

    @Autowired
    private EventBus mmEventBus;

    @Autowired
    private JavaMailSender mailSender;

    @Autowired
    private MessageConfig messageConfig;

    @Subscribe
    public void sendEmail(EmailMessageEvent eme) {
        log.info("{}", eme);

        SimpleMailMessage msg = new SimpleMailMessage(messageConfig.getMessageTemplate());
        msg.setSubject(eme.getSubject());
        msg.setText(eme.getMessage());

        try {
            mailSender.send(msg);
        } catch (MailException ex) {
            log.error("Error sending mail message: " + eme.toString(), ex);
        }
    }

    @PostConstruct
    public void start() throws MessagingException {
        mmEventBus.register(this);
    }

}

Where the @Slf4j is a lombok annotation, and the @Subscribe is for Guava's EventBus, which is how this app lets the EmailSender know there's a message to send. I use a trivial EmailMessageEvent POJO that has a subject and message text - good enough for this app's purposes.

The MessageConfig class just makes it easy to set up message defaults along with the rest of the application's configuration, and it has the one piece of special sauce that was needed to make this work with an Office365 SMTP server, a custom javax.mail.Session instance registered as a Spring Bean:

@Data
@Component
@ConfigurationProperties(prefix = "spring.message", ignoreUnknownFields = false)
@Slf4j
public class MessageConfig {

    @SuppressWarnings("SpringJavaAutowiringInspection")
    @Autowired
    private MailProperties mailProperties;

    private String from;
    private String subject;
    private String[] recipients;

    private SimpleMailMessage messageTemplate;

    public void setRecipients(String... r) {
        this.recipients = r;
    }

    @PostConstruct
    public void createTemplate() {
        messageTemplate = new SimpleMailMessage();
        messageTemplate.setFrom(from);
        messageTemplate.setSubject(subject);
        messageTemplate.setTo(recipients);

        log.debug("Email Message Template defaults: {}", messageTemplate);
    }

    @Bean
    public SimpleMailMessage getMessageTemplate() {
        return messageTemplate;
    }

    @Bean
    public Session getSession() {
        log.debug("Creating javax.mail.Session with TLS enabled.");
        // We could be more flexible and have auth based on whether there's a username and starttls based on a property.
        Properties p = new Properties();
        p.setProperty("mail.smtp.auth", "true");
        p.setProperty("mail.smtp.starttls.enable", "true");
        p.setProperty("mail.smtp.host", mailProperties.getHost());
        p.setProperty("mail.smtp.port", mailProperties.getPort().toString());
        return Session.getDefaultInstance(p, new Authenticator() {
            @Override
            protected PasswordAuthentication getPasswordAuthentication() {
                return new PasswordAuthentication(mailProperties.getUsername(), mailProperties.getPassword());
            }
        });
    }

}

The @Data is again a Lombok annotation - automatagically adds mutator and accessor methods, toString(), equals() & hashCode(), etc.

The limitation of Spring's JavaMailSender (JavaMailSenderImpl) is that its Session is not directly configurable, in particular there's no way to enable TLS authentication via a property. However, if there's a javax.mail.Session Bean registered in the context, that will be injected (conditionally autowired) into the MailSenderAutoConfiguration and then used to construct the JavaMailSenderImpl instance.

So we register just such a Bean via the getSession() method. For good measure we make the Session we construct here the default one for the JVM - change it to call getInstance() if you don't want that behavior.

After adding this Session @Bean everything worked.

Using Spring Boot 1.2 onwards, a JavaMailSender could be auto-configured for you. I've got this video explaining how exactly to send mails using Spring Boot 1.2 onwards. Spring Lemon's source code could be referred to for the exact details.

In addition to geoand's answer: if you don't want to hard code the mail properties or to write XML, you can add your properties to a file (mail.properties for example) in your resources, and add this kind of code in the MailConfig class:

@Resource(name = "mailProperties")
private Properties mailProperties;

@Bean(name = "mailProperties")
public PropertiesFactoryBean mapper() {
    PropertiesFactoryBean bean = new PropertiesFactoryBean();
    bean.setLocation(new ClassPathResource("mail.properties"));
    return bean;
}

And the range of properties you can use is all defined on these pages

https://javamail.java.net/nonav/docs/api/

https://javamail.java.net/nonav/docs/api/com/sun/mail/smtp/package-summary.html

But you'll still have to set Host, Port, Username and Password from the JavaMailSenderImpl's methods as it wont directly use the ones set in your properties.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top