2

開発システムで非常に奇妙な動作を示している非常に単純なJavaWebアプリがあります。問題は、登録ハンドラーから始まります。これは、次のように強制されます。

//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()を介して実行が繰り返されます。これは、私が今まで見た中で最も奇妙なことの1つです。

しばらくの間、私はそれが実際に起こっていることだとさえ信じていませんでした。しかし、この時点で、関連するメソッドを同期し、両方のメソッドのすべての行にprintステートメントを追加することで確認しました。 submitRegister()再発し、sendEmail()呼び出されることはありません。これがどのように可能であるのか私にはわかりません。

苛立たしいことに、まったく同じコードが本番サーバーで実行されるのとまったく同じように実行されます。この問題が発生するのは開発システムのみです。

この問題を引き起こしている可能性のあるものと、それを修正するために私ができることに関する提案は大歓迎です。

4

2 に答える 2

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呼び出されることはありません
  • submitRegisterEmailUtility.sendEmailステートメントではなく、他の誰かによって複数回トリガーされます。

削除されたバージョンが機能するようになったら、コードを元に戻し、一度に 1 つずつ平和を保ち、どこが悪いのかを確認してください :)

于 2012-09-21T05:46:49.693 に答える
2

わかりました、私はこれを一緒に働くいくつかの異なる問題に突き止めました:

  1. 開発システムでは、クラスパスがありませんでしたjavax.mail.Address。これにより、EmailUtilityクラスが初期化に失敗し、そのメソッドからのコードが実行される前 NoClassDefFoundErrorに、呼び出しで がスローされました。sendEmail()

  2. のコードにsubmitRegister()catch Exceptionブロックがありましたが、 ではなくextendsNoClassDefFoundErrorです。そのため、ブロックを完全にバイパスしました。ErrorExceptioncatch Exception

  3. が実際にキャッチされた Spring コントローラーには、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);
    }
    

そのため、Spring コントローラーに戻って伝播し、Spring コントローラーはそれをキャッチしてメソッドNoClassDefFoundErrorを再呼び出ししようとしたため、再度呼び出されました。これは再帰ではなく (デバッグ出力を見ただけではそれを判断する方法はありませんでしたが)、Spring コントローラーが同じ要求に対して 2 回呼び出しました。2 回目の呼び出しには try/catch がないため、特定の要求に対して 2 回以上呼び出されることはありません。doService()submitRegister()doService()

簡単に言えば、これらの問題にパッチを当てて問題を解決しました。

于 2012-09-21T07:07:29.187 に答える