[英]Should I use a Spring singleton bean to build up an email message?
从@Service
类,我在其他@Service
类上调用了几个方法。 服务方法会做一些工作并将一些文本添加到电子邮件中。 该电子邮件将发送给管理员,让他们知道已完成的工作。
这是一个简单的示例,但通常电子邮件消息可以附加在任何服务方法中,包括不直接由MainService
调用的方法。 与日志记录类似,消息始终附加到现有消息。 它没有以任何其他方式修改。
@Service
public class MainService {
private final Service1 service1;
private final Service2 service2;
private final Mail mail;
public void doWork() {
StringBuilder emailMessage = new StringBuilder();
List<Item> items = service1.method1(emailMessage);
Employee employee = service1.method2(emailMessage);
service1.method3(emailMessage);
service2.method1(emailMessage);
service2.method2(emailMessage);
//send one email to the admins
mail.sendEmailToAdmins("Batch process completed", emailMessage.toString());
}
}
@Service
public class Service1 {
public List<Item> method1(StringBuilder emailMessage) {
//some work to remove items
String message = String.format("Following items were removed", items);
log.info(message);
emailMessage.append(message);
return items;
}
}
有没有办法避免传递emailMessage
? 使用单例来保存状态(即电子邮件文本)并将其注入每个服务类是否是不好的做法?
一些注意事项是:
这真的取决于你的目标。
您编写的代码似乎有效,因此您的问题实际上是关于尝试实现设计完美。 当然还有其他模式可以应用——例如某种调度或通知程序——但为什么呢? 如果它有效,并且速度足够快,则继续处理其他问题。 它很慢,或者无法满足您的需求,那是另一回事。 但在这种情况下,您应该收集性能指标并有条不紊地进行攻击。
我不确定你的单身想法。 本质上,emailMessage 代表了经典模型-视图-控制器范式中的“模型”。 当然,你可以用它做一个单例,但真正做的只是将模型类从在堆栈上传递到永久驻留在(一次)内存中。 如果您的记忆因创建十亿封电子邮件而不断更新/删除,那可能是有道理的,但我怀疑您是否有这个问题。 Singleton 可能不会受到伤害,但它提供了什么帮助?
不要让完美成为善的敌人
创建一个 MailBuilder 类作为您的域模型并传递它。 根据哪个服务执行的操作,您可以为每个特定的服务调用提供一个方法。 例如 MailBuilder.appendForServiceA(args)、MailBuilder.appendForServiceB(args)。 通过这种方式,您实际上可以自行测试构建器。
您的思路是对的,如果您看到不同服务与某个对象的类似交互,您可以将整个过程提取为 Beans 的形式。
例如,我们可以将这个过程描述如下:
这是我将如何使用 Spring Beans 实现它的方法,这里的@Order
注释是设置的基础。
@lombok.Data
@lombok.Builder
static class Email {
private String to;
private String from;
@Singular
private List<String> sections;
}
@Bean
@Order(1)
Consumer<Email.EmailBuilder> addFromFieldToEmail() {
return emailBuilder -> emailBuilder.from("abcd@efghijk.xyz");
}
@Bean
@Order(2)
Consumer<Email.EmailBuilder> addToFieldToEmail() {
return emailBuilder -> {
// Get user email from context for example
// SecurityContextHolder.getContext().getAuthentication().getPrincipal();
emailBuilder.to("user@domain.com");
};
}
@Bean
@Order(3) // the bean order is taken in consideration when we autowire
// a list of similar beans. This ensures the processing order
Consumer<Email.EmailBuilder> addGreetingSectionToEmailBody() {
return emailBuilder -> emailBuilder.section(
String.format("Dear %s,", emailBuilder.to)
+ "\nwelcome to our website!"
+ "\n"
);
}
@Bean
@Order(4)
Consumer<Email.EmailBuilder> addMarketingSectionToEmailBody() {
return emailBuilder -> emailBuilder.section(
"Do you know that if you invite your friends you get 200 Schmeckles in credit?"
+ "[Invitation Link](https://efghijk.xyz/invite?user=123&token=abcdef123)"
);
}
@Bean // creating a Callable bean, and not an EmailBuilder ensures
// the creation of a new builder each time.
// An alternative would be to create an EmailBuilder bean of scope prototype
// see https://www.baeldung.com/spring-bean-scopes#prototype
Callable<Email.EmailBuilder> emailBuilderProvider(final List<Consumer<Email.EmailBuilder>> customizers) {
return () -> {
final Email.EmailBuilder builder = Email.builder();
customizers.forEach(customizer -> customizer.accept(builder));
return builder;
};
}
@Bean
MailService mailService(final Callable<Email.EmailBuilder> emailBuilderProvider) {
return new MailService(emailBuilderProvider);
}
@RequiredArgsConstructor
class MailService {
private final Callable<Email.EmailBuilder> emailBuilderProvider;
void sendMail() throws Exception {
Email email = emailBuilderProvider.call().build();
// mail.sendEmailToAdmins("Batch process completed", email.toString());
}
}
坚持您的业务逻辑并考虑您共享的内容,我认为考虑到StringBuilder 不是线程安全的,对同一个对象使用多个副作用调用可能不是最好的方法。
我宁愿从您想要跟踪结果的每个调用中返回一条消息,并将它们存储在一个数据结构中,就像一个堆栈一样,只是为了做一个例子,并考虑收集到的数据来构建邮件消息。
如果需要,可以轻松调整这种方法以获得线程安全的实现。
是的,您可以使用 Bean 发送电子邮件:
@Bean
public JavaMailSender getJavaMailSender() {
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
mailSender.setHost("host");
mailSender.setPort(<port number>);
mailSender.setUsername("user_name");
mailSender.setPassword("password");
///////////////////////////////
//Set subject, to
///////////////////////////////
Properties props = mailSender.getJavaMailProperties();
props.put("mail.transport.protocol", "smtp");
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.starttls.enable", "true");
props.put("mail.debug", "true");
return mailSender;
}
并将其自动连接到您的服务类,如下所示:
@Autowired
public JavaMailSender emailSender;
public void sendSimpleMessage(
String to, String subject, String text) {
...
SimpleMailMessage message = new SimpleMailMessage();
message.setTo(to);
message.setSubject(subject);
message.setText(text);
emailSender.send(message);
...
}
在您的情况下,因为您的电子邮件收件人和主题是不变的,您可以在JavaMailSender
Bean 中设置这些参数并调用emailSender.send()
只使用一个参数是电子邮件正文,并且您不需要实例化发送SimpleMailMessage
类来调用emailSender.send()
方法。 在另一种语言中,您在 bean 中发送消息的常量部分并将可变部分作为参数发送到发送方法。
我觉得更好的方法是从您的子服务中返回一个包装类。 包装类可以有两个属性,一个是通用类型的payload
,用于包含服务实际重新调整的任何数据,另一个是String
类型的字段summary
,其中包含您可以在电子邮件内容中使用的消息。
// our wrapper class
class ServiceReply<T> {
private String summary;
private T payload;
// constructors/getters/setters... prefer a builder though
}
您的服务如下所示
@Service
public class Service1 {
public ServiceReply<List<Item>> method1() {
//some work to remove items
String message = String.format("Following items were removed", items);
log.info(message);
return new ServiceReply<>(message, items);
}
}
并且您在MainService.java
中的doWork
方法简化为如下所示
public void doWork() {
StringBuilder emailMessage = new StringBuilder();
final ServiceReply<List<Item>> replyService1Method1 = service1.method1();
emailMessage.append(replyService1Method1.getSummary());
List<Item> items = replyService1Method1.getPayload();
// other method calls omitted for brevity
//send one email to the admins
mail.sendEmailToAdmins("Batch process completed", emailMessage.toString());
}
这样,尽管您最终会编写更多代码(在上面的示例中,每个方法调用多一行),但您不必费心在方法之间传递消息对象。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.