Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add default methods to MailSender and JavaMailSender as appropriate #23651

Closed

Conversation

runeflobakk
Copy link
Contributor

@runeflobakk runeflobakk commented Sep 17, 2019

The mail sending API is structured around the two interfaces MailSender and JavaMailSender, together with the class JavaMailSenderImpl which implements the two aforementioned interfaces. The numerous overloads of send(..)-methods in the interfaces makes it a bit cumbersome and errorprone to extend these interfaces directly.

Default methods introduced with Java 8 can to a large degree resolve this issue by moving from JavaMailSenderImpl to default methods in the interfaces. For methods which is "piping" to another method of the public API, this is a trivial refactoring (a820753). But JavaMailSenderImpl.send(SimpleMailMessage...) can not be refactored in such a trivial manner, as this method delegates to the internal doSend-method of JavaMailSenderImpl. To rectify, this pull-request also contains a suggested change to allow passing SimpleMailMessages contained in SmartMimeMessages further down the method stack, and allow the doSend to resolve the original message instances in case of errors, so existing behaviour is preserved. (f95659b)

The interfaces are now simpler to extend directly, and this also allows for more clean composable decorators, as demonstrated with the new JavaMailSenderDecorator class in 39724cd. It "seals" the default methods as final, allowing decorating an arbitrary JavaMailSender with only overriding one method, which is part of the public API. It is simple to wrap several layers of JavaMailSenderDecorators, if one should need to, and eventually end in a JavaMailSenderImpl.

I have used this structure for a client to separately implement a whitelisting JavaMailSenderDecorator, and a performance monitoring "aspect" JavaMailSenderDecorator, which are composed together, and ultimately wrapping an instance of JavaMailSenderImpl.

This also may yield some advantages when testing, as one would only have to intercept the one send(..)-method which is not provided by the interfaces, and tests does not need to know which overload is used by the main code. JavaMailSenderImpl instances can be separated strictly away from testing. For instance, if using mock libraries as Mockito, mocking a JavaMailSenderDecorator, it will ensure that all real final methods are invoked, and one may verify invocations of only one method regardless of which overloaded method is invoked by the main code.

I hope this can be a relevant contribution, and I am very open for any feedback and discussion on this design!

Thank you :)

send(..) -methods of JavaMailSenderImpl which is only delegating to other
methods are pulled up as default methods in the interfaces
JavaMailSender and MailSender, to make these interfaces require less
methods to implement.
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Sep 17, 2019
Express the implementation of
JavaMailSenderImpl.send(SimpleMailMessage...) in terms of
send(MimeMessage...), and pull it up as a default method of the
JavaMailSender interface. This leaves send(MimeMessage...) as the only
sending method one is required to implement (in addition to the createMimeMessage methods), as all other send methods
eventually ends up invoking this.

Extended the implementation of SmartMimeMessage to also be a "bearer" of
an optional original message, to be able to keep the semantics of the
exceptions thrown from JavaMailSenderImpl.doSend containing the original
message instances which failed. The implementation in JavaMailSenderImpl
and its associated interfaces now does not pass the original messages as
a separate argument to doSend, although this parameter is kept for
backwards compatibility, and doSend's behavior is the same as before if
the parameter is used.
A class for implementing decorators for JavaMailSender.
@runeflobakk
Copy link
Contributor Author

The pull-request was inspired by a talk I saw with @jhoeller on last year's JavaZone where he encouraged contributions which took advantage of the new language features of Java 8.

Is it something I can do to enable some progress on this? Thank you for any feedback!

@rstoyanchev rstoyanchev added the in: core Issues in core modules (aop, beans, core, context, expression) label Nov 11, 2021
@snicoll snicoll changed the title Proposition: a restructuring of MailSender and JavaMailSender, with additional decorator Add default methods to MailSender and JavaMailSender when appropriate Sep 15, 2023
@snicoll snicoll self-assigned this Sep 15, 2023
@snicoll snicoll added type: enhancement A general enhancement and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Sep 15, 2023
@snicoll snicoll added this to the 6.1.0-RC1 milestone Sep 15, 2023
@snicoll
Copy link
Member

snicoll commented Sep 15, 2023

@runeflobakk thanks for the PR and sorry it took so long to review. I stopped at the bearer thing as it looks quite unrelated to me. If you want to pursue this, we'd need a bit more detail (feel free to create a separate issue).

snicoll pushed a commit that referenced this pull request Sep 15, 2023
send(..) -methods of JavaMailSenderImpl which is only delegating to
other methods are pulled up as default methods in the interfaces
JavaMailSender and MailSender, to make these interfaces require fewer
methods to implement.

See gh-23651
snicoll added a commit that referenced this pull request Sep 15, 2023
@snicoll snicoll closed this in c3d1a88 Sep 15, 2023
@sbrannen sbrannen changed the title Add default methods to MailSender and JavaMailSender when appropriate Add default methods to MailSender and JavaMailSender as appropriate Sep 15, 2023
@runeflobakk
Copy link
Contributor Author

runeflobakk commented Sep 15, 2023

Thanks for accepting the contribution, @snicoll! I understand that JavaMailSender may not have been a top priority, perhaps especially with the recent large effort to migrate to the jakarta namespace 😅

IMHO, the "bearer thing" is not actually unrelated, but a facility to preserve the information conveyed in the exception handling inside the JavaMailSenderImpl.doSend(MimeMessage[] mimeMessages, Object[] originalMessages) method.

JavaMailSenderImpl.send(SimpleMailMessage ... message), which is now pulled up as a default method to JavaMailSender, does no longer invoke doSend(MimeMessage[] mimeMessages, Object[] originalMessages) (because the method is not available). It instead calls send(MimeMessage... mimeMessages), which implementation in JavaMailSenderImpl invokes doSend(mimeMessages, null). So the original SimpleMailMessages are lost when they are translated to MimeMessage. The existing exception handling in JavaMailSenderImpl.doSend(..) includes the original message objects if they are available, and the change which keeps a reference to the original message object in the SmartMimeMessage is to preserve the semantics that you will get your actual passed argument back in an exception. Now the originalMessages parameter of doSend will always be null, and this is will be a subtle change in behavior from the old implementation. If you call any of the methods with accepts SimpleMailMessage, and a MailSendException is thrown in doSend(..), failedMessages will contain MimeMessages instead of the SimpleMailMessages which was originally passed.

This is maybe not a big issue in practise, and I am fine with leaving it as this. But I included the change to ensure that the existing behavior that you get the same message objects in an exception that you passed to .send(..). I can raise it as a separate issue if you think this behavior should be preserved.

It may be less of a surprise to get the actual message objects you passed in a caught exception, and if you rely on them being the same for correlating with what you tried to send, then this will break if not preserving this behavior.

The decorator is a value-add, and not strictly necessary, though I think it demonstrates some of the value of this restructuring/refactoring. You can implement this yourself as a user of Spring, though it would be nice to have such a facility provided by Spring. But again, may as well be a separate issue/PR.

@snicoll
Copy link
Member

snicoll commented Sep 15, 2023

@runeflobakk It is unrelated in the sense that it does not need to be done for your original proposal to be complete (including with the decorator). As I've said before, we're happy to review this is if you create a separate issue with a small sample that demonstrates the problem. We won't implement it the way you've done it though.

@runeflobakk
Copy link
Contributor Author

Absolutely. My intention was only to point out that c3d1a88, as it is now, introduces a minor behavioral change, and I wanted to make sure this was understood and intentional on your part.

@snicoll
Copy link
Member

snicoll commented Sep 15, 2023

JavaMailSenderImpl.send(SimpleMailMessage ... message) is part of MailSender and has not be pulled there. Can you clarify what you mean?

@runeflobakk
Copy link
Contributor Author

Apologize, I missed the detail that send(SimpleMailMessage... simpleMessages) is still present in JavaMailSenderImpl, and not pulled up as a default method in JavaMailSender.

So you still need to implement two methods if extending JavaMailSender, which the original intent of this PR was to avoid. (to allow for more ergonomics when decorating and /or mocking).

Totally fine with this, of course.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core Issues in core modules (aop, beans, core, context, expression) type: enhancement A general enhancement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants