Monday, June 2, 2014

Speeding Up Spring's JavaMailSenderImpl With AOP


This material is based on an earlier version of Spring in Practice, chapter 8—one that predates the @Asyncannotation. Nowadays I would recommend using @Async and the Spring Task Execution API for making JavaMail calls asynchonous. The book covers the newer approach. The material in this post is still useful for understanding what you can do with AOP though.
In this article we’ll learn how we can speed up Spring’s JavaMailSenderImpl with some thread-forking AOP. Though we’re using JavaMail as an example, this tutorial should be useful to people looking for a code-based introduction to Spring’s support for AOP. Note at the outset that I don’t really go into AOP concepts and terminology, but I do show some simple code that you should be able to follow if you already know the basic concepts and just want to see what the code looks like.
There are lots of situations in which we want our application to send out an automated e-mail. You might for instance want to send a confirmation e-mail in response to new user registrations or mailing list subscriptions and unsubscriptions. In Spring this probably means that you would use one of the various JavaMailSenderImpl.send() methods. Here’s a sample applicationContext.xml file.

    
    
    
    
        
    ...
    
Here’s how it looks from the Java side:
package app.service;

... imports ...

public class MailingListServiceImpl implements MailingListService {
    private JavaMailSender mailSender;
    
    public void setMailSender(JavaMailSender mailSender) {
        this.mailSender = mailSender;
    }
    
    private void sendConfirmSubscriptionEmail(Subscriber subscriber) {
        MimeMessage message = mailSender.createMimeMessage();
        MimeMessageHelper helper = new MimeMessageHelper(message);
        
        String text = ...
        
        try {
            helper.setSubject("Please confirm your subscription");
            helper.setTo(subscriber.getEmail());
            helper.setFrom(noReplyEmailAddress);
            helper.setSentDate(subscriber.getDateCreated());
            helper.setText(text, true);
        } catch (MessagingException e) {
            throw new RuntimeException(e);
        }
        
        mailSender.send(message);
    }
    
    ...
}
(For more information on Spring/JavaMail integration, please see my article Send E-mail Using Spring and JavaMail.)
This works fine, but one thing your end users might notice is a fairly significant delay while JavaMailSenderImpl.send() does whatever it’s doing to send your e-mail (presumably negotiating with the SMTP server and sending the actual e-mail). While the delay probably isn’t large enough to provoke rioting in the streets, it’s certainly noticeable, and in many use cases it’s unnecessary. E-mail is itself an asynchronous communications medium, so unless there’s an important reason to let the end user know about errors that may occur while trying to send the e-mail (and there may be), one option you might consider is making the send()call on a separate thread.
Now let’s look at a few different ways to do that.

Method 1: Spawn a new thread manually

One possibility would be to spawn a new thread manually whenever you want to call JavaMailSenderImpl.send(). In other words, you implement the Runnable interface with a call to send(), you pass it into a Thread, and then you start the thread.
This technique has some advantages. It’s conceptually straightforward. Also it’s easy to be selective about the cases in which you do and don’t want to fork. Again, there may well be times where you want the end user to know if the send() call generated an exception, and if that’s true, then you simply refrain from forking the thread.
If you’re not careful, the approach can lead to widespread violation of the DRY principle. You might end up rewriting the same thread-forking code every time you send an e-mail. You can of course control this by creating one or more utility methods to send an e-mail on a separate thread, and that is a good approach.
One drawback with this approach, though, is that it may be either inconvenient or else a non-option. If you have an existing app with lots of calls to create e-mail, then you’d need to update all the instances of that code with the new code. In most cases that’s probably doable though it may be inconvenient. But it may be that you’re not in a position to change the client code. (Maybe it’s a third-party library, for instance.) The client code calls an injected JavaMailSender instance, say, and that’s the way it is. In that event you’ll want to consider one of the two following alternative methods.

Method 2: Create a JavaMailSender wrapper

Another method would be to implement a JavaMailSender wrapper. (JavaMailSender is of course the interface to which JavaMailSenderImpl conforms.) The JavaMailSender interface has six different send() methods (here are the Javadocs), and so you can just implement the thread-forking code for each of the six methods. (Probably each method would create a Runnable and then pass that to a thread-forking utility method.) Then you inject your wrapper into your service beans instead of injecting the JavaMailSenderImpl bean directly.
This approach is pretty good. It’s still straightforward, and it allows you to avoid violating DRY. Also, because it’s entirely transparent to client code, it can deal with cases in which you either can’t or else don’t want to modify said client code.
One possible challenge is that you may find it a little tough to exercise fine-grained control over the cases in which you use the wrapper and the cases in which you don’t. If it’s important for your code to exercise that kind of control, then arguably it would be reasonable to associate the forking/non-forking semantics injected JavaMailSender beans. You might for example inject two JavaMailSender instances into the service bean—one forking and one non-forking.
A minor grumble about the wrapper method is that it ties the thread-forking behavior to specific interfaces, such as JavaMailSender. That’s not too big a deal in this particular case, since it’s not such a problem to spawn a new thread. But if you have other cases where you decide you want to create a new thread, you might decide that you’d like to factor thread-forking out as a separate behavior and be able to apply that in multiple contexts.
So let’s see how to do that using Spring’s support for AspectJ-flavored AOP.

Method 3: Use AOP to wrap JavaMailSenderImpl.send()

This is a fun and elegant method. Even though this article is called “AOP 101”, I’m not planning to explain the concepts or weird terminology; rather I just want to show you the code and assume that you’ll be able to see what’s happening.
First we need to create an “advice” class. This is the code that we’re going to wrap around our send()invocations.
package app.aop;

import org.apache.log4j.Logger;
import org.aspectj.lang.ProceedingJoinPoint;

public class ForkAdvice {
    private static final Logger log = Logger.getLogger(ForkAdvice.class);
    
    public void fork(final ProceedingJoinPoint pjp) {
        new Thread(new Runnable() {
            public void run() {
                log.info("Forking method execution: " + pjp);
                try {
                    pjp.proceed();
                } catch (Throwable t) {
                    // All we can do is log the error.
                    log.error(t);
                }
            }
        }).start();
    }
}
The fork method is “around” advice that we’re going to use to advise our calls to JavaMailSenderImpl.send(). As you can see, it creates a new thread and starts it. In the run() body, we simply execute the advised method by calling pjp.proceed().
As an aside, the ProceedingJoinPoint class is provided by the AspectJ class library, but note that we’re not using full-blown AspectJ here—we’re in fact using Spring AOP. Full AspectJ involves a special aspect language and compiler to generate classes with the advice woven into the class bytecode itself. Spring AOP on the other hand uses dynamic proxies (either the interface variety that comes with Java, or else class proxies via CGLIB) to advise classes. While Spring AOP borrows classes and also the AspectJ pointcut language from AspectJ, its use of dynamic (runtime) proxies as opposed to bytecode-level advice integration distinguishes it from AspectJ.
Now it’s time to update our application context with our AOP configuration.

    
    
    
    
        
    
    
    
        
            
        
    
    
    ...
    
This is similar to what we had before, but there are a couple of differences. First, note that we’ve declared the aop namespace here. That of course allows us to use the namespace configuration feature that Spring 2.0 introduced. The other change is that we’ve added a definition for our advice bean as well as some AOP configuration. In aop:aspect we point to our forkAdvice as the advising class to be applied, we indicate that it will be “around” advice, we specify the advising method, and finally we specify a pointcut that indicates which method calls will be advised/wrapped. We use the AspectJ pointcut language to specify a pointcut. Here we’re indicating that we want all calls to any of the JavaMailSenderImpl.send() methods to be advised.
As mentioned previously, this technique is like the wrapper technique in that you can use it to add the forking behavior in a way that’s transparent to client code. Moreover you can use it not just for JavaMail but really for any method where you want to create a new thread before executing the method. You just add the appropriate aop:around definitions to the aop:aspect definition and you’re in business.

Spring TaskExecutor and JavaMailSender


Spring has implementation for concurrency tasks based on java concurrency. Next I will create mail sending service (JavaMailSender extended MailSender interface for JavaMail) using task executor
?
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
package com.test.service;
 
import java.io.File;
import java.util.Properties;
 
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
 
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.task.TaskExecutor;
import org.springframework.mail.MailParseException;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;
 
@Service("mailService")
public class MailService {
    @Autowired
    private JavaMailSender mailSender;
      
    @Autowired
    private TaskExecutor taskExecutor;
 
    private static Log log = LogFactory.getLog(MailService.class);
     
 /**
  * @param text - message
  * @param from - sender email
  * @param to - receiver email
  * @param subject - subject
  * @param filePath - file to attach, could be null if file not required
  * @throws Exception
  */
 public void sendMail(final String text,  final String from, final String to, final String subject, final File file) throws Exception {
   taskExecutor.execute( new Runnable() {
   public void run() {
    try {
      sendMailSimple(text, to, subject, file.getAbsolutePath());
    } catch (Exception e) {
     e.printStackTrace();
     log.error("Failed to send email to: " + to + " reason: "+e.getMessage());
    }
   }
  });
 }
  
  private void sendMailSimple(String text, String from, String to, String subject, String filePath) throws Exception {
  MimeMessage message = mailSender.createMimeMessage();
  try {
   MimeMessageHelper helper = new MimeMessageHelper(message, true);
   helper.setFrom(from);
   helper.setTo(to);
   helper.setSubject(subject);
   helper.setText(text);
   if(filePath != null){
    FileSystemResource file = new FileSystemResource(filePath);
    helper.addAttachment(file.getFilename(), file);
   }
  } catch (MessagingException e) {
    throw new MailParseException(e);
  }
  mailSender.send(message);
    
  if(log.isDebugEnabled()){
   log.debug("Mail was sent successfully to: " + to + " with file: "+filePath);
  }
  }
}
Spring mail config:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 <bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
  <property name="host"><value>mail.test.com</value></property>
         <property name="port"><value>25</value></property>
         <property name="protocol"><value>smtp</value></property>
         <property name="username"><value>no-reply@test.com</value></property>
         <property name="password"><value>pass</value></property>
         <property name="javaMailProperties">
             <props>
                 <prop key="mail.smtp.auth">true</prop>
                 <prop key="mail.smtp.quitwait">false</prop>
             </props>
         </property>
     </bean>
Task executor config:
?
1
2
3
4
5
6
<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
     <property name="corePoolSize" value="5"></property>
     <property name="maxPoolSize" value="10"></property>
     <property name="queueCapacity" value="40"></property>
     <property name="waitForTasksToCompleteOnShutdown" value="true"></property>
    </bean>