2012-09-21 32 views
1

我有一個非常簡單的Java Web應用程序,它在開發系統上顯示出一些非常奇怪的行爲。這個執行流程如何實現?

//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()呼叫時發生該問題:問題與註冊處理程序,其按如下方式implented開始。該方法的代碼是非常簡單的:

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()。這很容易成爲我見過的最奇異的事情之一。

有一段時間我甚至都不相信那是實際發生的事情;但在這一點上,我已經通過同步所涉及的方法並在兩種方法的每一行中添加了打印語句來確認它。 submitRegister()遞歸,而sendEmail()永遠不會被調用。我不知道這怎麼可能。

令人沮喪的是,完全相同的代碼就像它應該在生產服務器上運行一樣。只有在開發系統上纔會出現這個問題。

任何有關可能導致此問題的建議以及我可以如何解決該問題都是值得歡迎的。

+0

你在調試版本嗎? – Azodious

回答

2

你是對的,這是不可能的:) 如果你不喜歡調試,看看會發生什麼,我建議你去除所有其他代碼,放入大量的日誌記錄。從類似的開始:

public synchronized ModelAndView submitRegister(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 
    LOG.debug("submitRegister: " + this.toString); 
    EmailUtility.sendEmail("[email protected]", "[email protected]", "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聲明。

如果你得到的剝奪版本的工作,開始把回你的代碼,看到一次在一個和平的地方都變壞:)

+0

這個*不應該*是可能的,我同意100%。但很明顯的是。我在每個方法的第一行都有調試語句,'submitRegister()'肯定是調用的,'sendMail()'絕對不會被調用。代碼肯定會到達*應該*調用'sendEmail()'的行,並且如果我將對sendEmail()的調用註釋掉了,該方法就可以正常工作。 – aroth

+0

我會在這裏同意Thobias的建議。剝離無關的東西,只留下有問題的代碼。你的問題在那個時候還存在嗎?如果是這樣,它可能會更容易調試。如果沒有,那麼開始添加其他的東西,直到問題再次發生;那麼,你會更好地瞭解真正的問題所在。 –

+0

好的。然後你發現一些非常酷的東西! :)如果你能證明它,我會喜歡自己運行它。 –

2

好吧,我跟蹤下來到幾個不同的問題一起工作:

  1. 在開發系統上,類路徑丟失javax.mail.Address。這導致EmailUtility類無法初始化,並且會在調用sendEmail()調用時拋出NoClassDefFoundError,然後該方法的任何代碼都可以執行。

  2. submitRegister()的代碼有一個catch Exception塊,但NoClassDefFoundError延伸Error,不Exception。所以它完全繞過catch Exception塊。

  3. 春季控制器,其中Error實際上是抓住了一些最有問題的「錯誤處理」的代碼,我曾經遇到過:

    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控制器,它正在捕獲它並試圖重新調用doService()方法,這導致submitRegister()被再次調用。這不是遞歸(儘管沒有辦法通過查看調試輸出來說明),但是Spring控制器爲相同的請求調用了兩次。對於給定的請求,它從來沒有被調用過兩次以上,因爲圍繞第二個doService()調用沒有嘗試/抓住。

長話短說,我修補了這些問題並解決了問題。

+0

好的......有趣的是,我認爲前兩點是合理的,但是3:de中的重新定位是可怕的,它可能記錄在某些模糊的文檔中,但不是我所期望的,雖然我很好的調查,但我猜這裏的教訓是在這種情況下捕獲Throwable而不是Exception。 –