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.
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.