我有一个非常简单的Java webapp,它在开发系统上显示了一些非常奇怪的行为。问题始于注册处理程序,它按如下方式实现:
//XXX: this shouldn't really be 'synchronized', but I've declared it as such
// for the sake of debugging this issue
public synchronized ModelAndView submitRegister(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String email = request.getParameter("email");
String pass = request.getParameter("pass");
String conf = request.getParameter("conf");
String name = request.getParameter("name");
EntityManager em = DatabaseUtil.getEntityManager(request);
//[make sure required fields are present and valid, etc.]
User user = getUserForEmail(email, em);
if (user != null) {
//[user already exists, go to error page]
}
//create the new user
em.getTransaction().begin();
try {
user = new User();
//[set fields, etc.]
em.persist(user);
//[generate e-mail message contents]
boolean validEmail = EmailUtility.sendEmail(admin, recip, subject, message, null, recip);
if (validEmail) {
em.getTransaction().commit();
//[go to 'registration successful' page]
}
em.getTransaction().rollback();
//[go to error page]
}
catch (Exception e) {
em.getTransaction().rollback();
//[go to error page]
}
}
EmailUtility.sendEmail()
来电时出现问题。这个方法的代码非常简单:
public static boolean sendEmail(String fromAddress, String to, String subject, String message, String fromHeaderValue, String toHeaderValue) {
try {
Session session = getMailSession(to);
Message mailMessage = new MimeMessage(session);
mailMessage.setFrom(new InternetAddress(fromAddress));
if (fromHeaderValue != null) {
mailMessage.setHeader("From", fromHeaderValue);
}
if (toHeaderValue != null) {
mailMessage.setHeader("To", toHeaderValue);
}
mailMessage.setHeader("Date", new Date().toString());
mailMessage.setRecipients(RecipientType.TO, InternetAddress.parse(to, false));
mailMessage.setSubject(subject);
mailMessage.setContent(message, "text/html;charset=UTF-8");
Transport.send(mailMessage);
return true;
} catch (Throwable e) {
LOG.error("Failed to send e-mail!", e);
return false;
}
}
当代码到达EmailUtility.sendEmail()
的调用时,不会调用该方法执行通过submitRegister()进行递归。这很容易成为我见过的最离奇的事情之一。
有一段时间我甚至不相信那是实际发生的事情;但此时我通过同步所涉及的方法并在两个方法的每一行上添加print语句来确认它。 submitRegister()
递归,永远不会调用sendEmail()
。我不知道这怎么可能。
令人沮丧的是,完全相同的代码在生产服务器上运行完全相同。只有在开发系统上才会出现此问题。
欢迎任何有关可能导致此问题的原因以及我可以采取哪些措施来解决此问题。
答案 0 :(得分:2)
你是对的,这是不可能的:) 如果你不喜欢调试并看看会发生什么,我建议你去除所有其他代码,进行大量的日志记录。从以下内容开始:
public synchronized ModelAndView submitRegister(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
LOG.debug("submitRegister: " + this.toString);
EmailUtility.sendEmail("a@x.y", "b@x.y", "subject", "message", "from", "to");
}
public static boolean sendEmail(String fromAddress, String to, String subject, String message, String fromHeaderValue, String toHeaderValue) {
LOG.debug("sendEmail: " + this.toString());
}
toString
将显示所涉及的课程。
我的猜测是:
sendEmail
submitRegister
由其他人多次触发,而不是EmailUtility.sendEmail
声明。如果你让剥离的版本工作,开始放回你的代码,一次一个和平,看看它在哪里变坏:)
答案 1 :(得分:2)
好的,我将这个问题跟踪到几个不同的问题:
在开发系统上,缺少类路径javax.mail.Address
。这导致EmailUtility
类无法初始化,并且会在NoClassDefFoundError
调用之前抛出sendEmail()
,然后该方法的任何代码都可以执行。
submitRegister()
中的代码有catch Exception
个阻止,但NoClassDefFoundError
扩展Error
,而不是Exception
。所以它完全绕过catch Exception
块。
实际遇到Error
的Spring控制器有一些我遇到的最可疑的“错误处理”代码:
try {
Method serviceMethod = this.getControllerClass().getMethod(method, HttpServletRequest.class, HttpServletResponse.class);
if (this.doesMethodHaveAnnotation(serviceMethod, SynchronizedPerAccount.class)) {
synchronized(this.getAccountLock(request)) {
super.doService(request, response);
}
}
else {
//don't need to execute synchronously
super.doService(request, response);
}
}
catch (Throwable ignored) {
super.doService(request, response);
}
因此NoClassDefFoundError
传播回Spring控制器,Spring控制器正在捕获它并尝试重新调用doService()
方法,这导致submitRegister()
再次被调用。这不是递归(虽然没有办法通过查看调试输出来判断),但是Spring控制器为同一个请求调用了两次。对于给定的请求,它永远不会被调用超过两次,因为第二次doService()
调用没有尝试/捕获。
长话短说,我修补了这些问题并解决了问题。