9

私はNetBeansを使用していますが、Tomcatが.warアプリケーションに 2 回デプロイされているようです。これは Web アプリケーションの 2 回の起動です。

Tomcat 6 と 7 の両方を試しましたが、同じ結果でした。

Spring MVC、Hibernate、および Thymeleaf アプリケーションがあります。META-INF の下の Context.xml には、次の内容があります。

<?xml version="1.0" encoding="UTF-8"?>
<Context path="/website"/>

これがログです。

**First deployment starts**

[ INFO] 07:13:09 ContextLoader - Root WebApplicationContext: initialization started
[ INFO] 07:13:09 XmlWebApplicationContext - Refreshing Root WebApplicationContext:     startup date [Thu May 23 07:13:09 EST 2013]; root of context hierarchy
2013-05-23 07:13:10 JRebel: Monitoring Spring bean definitions in     '/Users/pack/NetBeansProjects/mysite/site/src/main/webapp/WEB-INF/applicationContext-  data.xml'.
[ INFO] 07:13:10 XmlBeanDefinitionReader - Loading XML bean definitions from     ServletContext resource [/WEB-INF/applicationContext-data.xml]
[ INFO] 07:13:10 ClassPathScanningCandidateComponentProvider - JSR-330 'javax.inject.Named' annotation found and supported for component scanning
            ***(tomcat initializes hibernate and other spring beans)***
                                      ...
May 23, 2013 7:13:17 AM org.apache.catalina.startup.Catalina start
INFO: Server startup in 15552 ms
                                 ***Tomcat started***
                      ***Tomcat tries to shut down the context***

[ INFO] 07:13:18 XmlWebApplicationContext - Closing WebApplicationContext for namespace     'spring-mvc-servlet': startup date [Thu May 23 07:13:15 EST 2013]; parent: Root WebApplicationContext
[ INFO] 07:13:18 DefaultListableBeanFactory - Destroying singletons in  org.springframework.beans.factory.support.DefaultListableBeanFactory@5bbe2de2: defining beans    [blHeadProcessor,blHeadProcessorExtensionManager,navigationProcessor,blPaginationPageLinkPro cessor,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.context.annotation.internalPersistenceAnnotationProcessor,blRegisterCustomerValidator,blCategoryController,com.package.ui.thymeleaf.CategoryHandlerMapping#0,templateResolver,templateEngine,org.thymeleaf.spring3.view.ThymeleafViewResolver#0,org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor]; parent:   org.springframework.beans.factory.support.DefaultListableBeanFactory@521e7f21
[ INFO] 07:13:18 XmlWebApplicationContext - Closing Root WebApplicationContext: startup  date [Thu May 23 07:13:09 EST 2013]; root of context hierarchy 
[ INFO] 07:13:18 DefaultListableBeanFactory - Destroying singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@521e7f21: defining beans    [org.springframework.aop.config.internalAutoProxyCreator,org.springframework.transaction.annotation.AnnotationTransactionAttributeSource#0,org.springframework.transaction.interceptor.TransactionInterceptor#0,org.springframework.transaction.config.internalTransactionAdvisor,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.context.annotation.internalPersistenceAnnotationProcessor,org.springframework.context.support.PropertySourcesPlaceholderConfigurer#0,blCategoryDao,blCustomerDao,blIdGenerationDao,nlpDao,jpaTemplate,webDS,entityManagerFactory,transactionManager,org.springframework.security.filterChains,org.springframework.security.filterChainProxy,org.springframework.security.web.DefaultSecurityFilterChain#0,org.springframework.security.web.DefaultSecurityFilterChain#1,org.springframework.security.web.DefaultSecurityFilterChain#2,org.springframework.security.web.DefaultSecurityFilterChain#3,org.springframework.security.web.DefaultSecurityFilterChain#4,org.springframework.security.web.DefaultSecurityFilterChain#5,org.springframework.security.web.PortMapperImpl#0,org.springframework.security.web.PortResolverImpl#0,org.springframework.security.authentication.ProviderManager#0,org.springframework.security.web.context.HttpSessionSecurityContextRepository#0,org.springframework.security.web.savedrequest.HttpSessionRequestCache#0,org.springframework.security.web.access.channel.ChannelDecisionManagerImpl#0,org.springframework.security.access.vote.AffirmativeBased#0,org.springframework.security.web.access.intercept.FilterSecurityInterceptor#0,org.springframework.security.web.access.DefaultWebInvocationPrivilegeEvaluator#0,org.springframework.security.authentication.AnonymousAuthenticationProvider#0,org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter#0,org.springframework.security.userDetailsServiceFactory,org.springframework.security.web.DefaultSecurityFilterChain#6,org.springframework.security.authentication.dao.DaoAuthenticationProvider#0,org.springframework.security.authentication.DefaultAuthenticationEventPublisher#0,org.springframework.security.authenticationManager,blUserDetailsService,blCatalogService,blCustomerService,entityService,fbPageService,blIdGenerationService,blLoginService,nlpService,priceIncreaseValidator,searchFacetService,blEntityConfiguration,blPasswordEncoder,solrIndexService,solrEmbedded,textEncryptor,org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor]; root of factory hierarchy
[ INFO] 07:13:18 LocalContainerEntityManagerFactoryBean - Closing JPA EntityManagerFactory for persistence unit 'blPU'
[ INFO] 07:13:18 SessionFactoryImpl - closing
May 23, 2013 7:13:18 AM org.apache.catalina.loader.WebappClassLoader clearReferencesJdbc
SEVERE: The web application [/website] registered the JDBC driver    [com.mysql.jdbc.Driver] but failed to unregister it when the web application was stopped. To   prevent a memory leak, the JDBC Driver has been forcibly unregistered.
May 23, 2013 7:13:19 AM org.apache.catalina.startup.HostConfig deleteRedeployResources
INFO: Undeploying context [/website]
May 23, 2013 7:13:19 AM org.apache.catalina.startup.HostConfig deployDescriptor
INFO: Deploying configuration descriptor /Users/pack/Servers/apache-tomcat- 7.0.34/conf/Catalina/localhost/website.xml
2013-05-23 07:13:23 JRebel: Monitoring Log4j configuration in   'file:/Users/pack/NetBeansProjects/mysite/site/target/mycompany/WEB-INF/classes/log4j.xml'.

                          ***Tomcat tries to restart again***

[ INFO] 07:13:23 ContextLoader - Root WebApplicationContext: initialization started
[ INFO] 07:13:23 XmlWebApplicationContext - Refreshing Root WebApplicationContext:   startup date [Thu May 23 07:13:23 EST 2013]; root of context hierarchy
2013-05-23 07:13:24 JRebel: Monitoring Spring bean definitions in   '/Users/pack/NetBeansProjects/mysite/site/src/main/webapp/WEB-INF/applicationContext-  data.xml'.
[ INFO] 07:13:24 XmlBeanDefinitionReader - Loading XML bean definitions from ServletContext resource [/WEB-INF/applicationContext-data.xml]

そして無事起動。

私が理解できないのは、Tomcat がアプリケーションを 2 回デプロイしている理由です。これは、website.xml ファイルがまだ tomcats /conf/catalina/localhost フォルダーにないため、アプリケーションを新しい tomcat インスタンスに初めてデプロイするときには発生しません。しかし、netbeans から tomcat を停止して再度実行すると、website.xml ファイルはまだ /conf/catalina/localhost フォルダーにありますが、2 回目のデプロイが行われる直前に削除され、再デプロイされます。

Tomcat の server.xml ファイルを false に設定しようとしましautoDeployたが、役に立ちませんでした。

<Host name="localhost"  appBase="webapps"
        unpackWARs="true" autoDeploy="false">

Tomcat が停止するたびに、Tomcat が /conf/catalina/localhost フォルダーの下にある website.xml ファイルを削除する必要がある可能性があります。

これは、localhostフォルダーの下のwebsite.xmlファイルがどのように見えるかです

<?xml version="1.0" encoding="UTF-8"?>
<Context
    docBase="/Users/pack/NetBeansProjects/mysite/site/target/mycompany"  
    path="/website"
/>
4

4 に答える 4

9

ファイル conf/localhost/myappname.xml を削除すると、アプリが 2 回初期化されなくなります。基本的に、Tomcat は再起動し、アプリの古いバージョンを再起動します。その後、Netbeans がデプロイすると、再び開始されます。回避策として、ContextListener contextDestroyed() イベントに数行のコードを追加しました。

public void contextDestroyed(ServletContextEvent sce) {
...
String delme = sce.getServletContext().getInitParameter("eraseOnExit");
if (delme != null && delme.length() > 0) {
    File del = new File(delme);
    if (del.exists()) {
        System.out.println("Deleting file " + delme);
        del.delete();
    }
}

web.xml で、開発環境に以下を追加します。

<context-param>
    <description>Workaround for Tomcat starting webapp twice</description>
    <param-name>eraseOnExit</param-name>
    <param-value>/Users/xxx/apache-tomcat-7.0.42/conf/Catalina/localhost/myappname.xml</param-value>
</context-param>

その後、アプリが次にデプロイされるときに、デプロイ前に再度開始されることはないため、2 回開始されることはありません。展開前またはシャットダウン時にファイルを削除するための他のアイデアをいただければ幸いです。

于 2014-01-28T22:37:23.463 に答える
4

epoch による回答と Steven Neiner による回答に感謝ます。

これが私のバージョンのコードです。私の違い:

  • としてマークされたメソッドsynchronized
    • 理論的には必要ありませんが、ここで奇妙な複数の起動の問題を扱っていることを考えると、申し訳ありませんが安全であることをお勧めします。
  • サードパーティのユーティリティ (Spring?) への呼び出しを置き換えました。
  • パス内の特定の文言を探して、開発中かどうかを検出しcatalina.baseます。
  • static修飾子を削除しました。列挙型として実装されたシングルトンを使用します。
  • 2016-05 の時点で、読みやすく理解しやすいようにコードを書き直しました (少なくとも私にとっては)。簡単にテストしただけなので、使用する前に必ずソース コードを確認してください (完全に自己責任で使用する必要があります)。

概念

このバグに対する彼らの回避策の中心は、Web アプリの名前 (「サーブレット コンテキスト」) で名前が付けられ、.xml.

たとえば、Web アプリの名前が である場合、 というAcmeApp名前のファイルを見つけて削除しますAcmeApp.xml。このファイルは、「Catalina base」フォルダー内にネストされて保存されます。

この削除は、Web アプリの実行の最後のステップとして行います。そのため、Web アプリが再び起動すると、そのファイルは存在せず、再作成されます。これは開発モードのみであることを忘れないでください。本番環境で Tomcat を単独で使用する場合、このバグは発生しません。

では、この回避策のコードを Web アプリの実行の最後のアクションとして実行するにはどうすればよいでしょうか? 読む。

使い方

サーブレット仕様のバージョン 2.3 以降の標準部分として、すべてのサーブレット コンテナーには、Web の起動時と Web アプリのシャットダウン時にコードを呼び出すためのフックがあります。これは Tomcat 固有のものではありません。JettyGlassFishWildFly/JBossなどはすべて、サーブレット仕様で必要とされるこの機能を含んでいます。

上記のコードを使用するには、プロジェクトに新しいクラスを追加します。新しいクラスに「MyServletContextListener.java」のような名前を付けます。そのクラスをServletContextListenerインターフェースを実装するものとして宣言します。

このインターフェイスに必要な 2 つのメソッドを実装します。1 つのメソッドは、Web アプリの起動時にサーブレット コンテナー (Tomcat) によって呼び出され、最初のユーザーがアプリにアクセスする前に実行されることが保証されます。もう 1 つのメソッドは、Web アプリがサーブレット コンテナー (Tomcat) によってシャットダウンされるときに呼び出されます。

メソッド内で、上記のcontextDestroyedメソッドを呼び出します。このような:

@Override
public void contextInitialized ( ServletContextEvent sce )
{
    // Web app launching. 
    // This method runs *before* any execution of this web app’s servlets and filters.
    // Do nothing. No code needed here.
}

@Override
public void contextDestroyed ( ServletContextEvent sce )
{
    // Web app shutting down. 
    // This method runs *after* the last execution of this web app’s servlets and filters.
    // Workaround for NetBeans problem with launching Tomcat twice.
    this.workaroundTomcatNetbeansRedeployBug( sce );
}

構成は簡単です。このクラスをサーブレット クラスと一緒にWAR ファイル/フォルダーに含めるだけです。@WebListenerアノテーションにより、サーブレット コンテナーはこのリスナー クラスを「認識」し、それをロードしてインスタンス化し、必要に応じてその各メソッドを実行します。必要に応じて、注釈の代わりに構成の代替モードを使用できますが、注釈は最も単純な方法です。

完全な例として、 AppListenerクラス全体を次に示します。このコードの以前に投稿されたバージョンを読みやすく、理解しやすいように書き直しました。

package com.basilbourque;

import java.io.File;
import java.io.FilenameFilter;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

/**
 * Hooks into the web app launching and quitting, as a workaround for Tomcat
 * running from NetBeans causing the web app to rapidly deploy, undeploy and
 * redeploy.
 *
 * © 2016 Basil Bourque. This source code may be used freely, and entirely at
 * your own risk, according to terms of the ISC License at:
 * https://opensource.org/licenses/ISC
 *
 * @author Basil Bourque
 */
@WebListener
public class AppListener implements ServletContextListener {

    @Override
    public void contextInitialized ( final ServletContextEvent servletContextEventArg ) {
        System.out.println ( "Basil launch" );
    }

    @Override
    public void contextDestroyed ( final ServletContextEvent servletContextEventArg ) {
        System.out.println ( "Basil exit" );
        this.workaroundTomcatNetbeansRedeployBug ( servletContextEventArg );
    }

   synchronized private void workaroundTomcatNetbeansRedeployBug ( final ServletContextEvent servletContextEventArg ) {
        // When running Tomcat 8 from NetBeans 8, as we do in development, a bug causes the web app to rapidly deploy, undeploy, and redeploy.
        // This bug causes multiple bad side-effects.
        //
        // Workaround: When running in development mode with NetBeans & Tomcat, delete the XML file with name of web app, found in {catalina-base}/conf/Catalina/localhost/YourWebAppNameHere.xml.
        // Example of file name to delete: If your app is named “AcmeApp”, then in a Vaadin multi-module Maven archetype app, the target file might be named “AcmeApp-ui.xml”.
        // In a simpler project, the target file might be named “AcmeApp.xml”.
        // So we need to determine the name of the web app in a soft-coded fashino.
        // We extract from a context path. For example, '/AcmeApp-ui'. We need to remove that slash (SOLIDUS) at the front.
        // Then we append a “.xml” to create our target file name.
        // We look for that file in the folder nested in the Cataline base folder (see line above for path).
        // If file is found, add it to the list of files to be deleted. That list will have only one element.
        // Lastly, delete the file.
        if ( AppUtility.INSTANCE.isInDevelopmentMode () ) {  // Find a strategy to determine if you are in development mode.
            final String catalinaBase = System.getProperty ( "catalina.base" );// Path to the folder the working folder of this web app.

            final String contextPath = servletContextEventArg.getServletContext ().getContextPath ();
            final String contextName = contextPath.substring ( 1 ); // Strip the SOLIDUS (slash) from first character position. Example: '/AcmeApp-ui' becomes 'AcmeApp-ui'.
            final String fileNameToDelete = contextName + ".xml";

            final File catalinaBaseContext = new File ( catalinaBase , "conf/Catalina/localhost" ); // While in development, running Tomcat from NetBeans, the web app’s name is 'localhost'.
            if ( catalinaBaseContext.exists () && catalinaBaseContext.canRead () ) {  // Confirm that we found the expected configuration folder nested in Catalina’s 'base' folder.
                // Make an array of File objects that match our criterion of having one of our expected file names.
                // Populate this array by defining a filter of filenames via a functional interface, to be applied against each file found in folder.
                final File[] filesToDelete = catalinaBaseContext.listFiles ( new FilenameFilter () {
                    @Override
                    public boolean accept ( File dir , String name ) {
                        boolean accepting = ( name.equals ( fileNameToDelete ) );
                        return accepting;
                    }
                } );

                if ( filesToDelete.length == 0 ) {  // If list of files is empty…
                    // FIXME Handle error. Should always find one file to delete.
                    System.out.println ( "ERROR - Found no file to delete as workaround for NetBeans+Tomcat double-launch bug. Expected file name: " + fileNameToDelete + " | Message # 42ec5857-9c1b-431a-b5c1-2588669a0ee2." );
                    return;
                }

                if ( filesToDelete.length > 1 ) {  // If list of files has more than one file…
                    // FIXME Handle error. Should never find more than one file to delete.
                    System.out.println ( "ERROR - Found more than one file to delete as workaround for NetBeans+Tomcat double-launch bug." + " | Message # 0afbd6ca-3722-4739-81dc-b2916e9dbba4." );
                    return;
                }

                for ( File file : filesToDelete ) {
                    file.delete ();  // Delete first file found in our filtered array.
                    // FIXME You may want to log this deletion.
                    System.out.println ( "TRACE - Deleting file as workaround for NetBeans+Tomcat double-launch bug: " + file + " | Message # 5a78416c-6653-40dc-a98c-6d9b64766d96." );
                    break; // Should be exactly one element in this list. But out of abundant caution, we bail-out of the FOR loop.
                }
            }
        }

    }

}

そして、開発モードで実行されているかどうかを判断するためのヘルパー クラスを次に示します。詳細については、コメントをお読みください。つまり、Tomcat が単独で実行されているのではなく、NetBeans から Tomcat が実行されていることを検出する簡単でクリーンな方法はないようです。質問しましたが、これ以上の解決策はありません。

警告:特定の開発環境に合わせて、このメソッドを変更する必要があります。isInDevelopmentMode

package com.basilbourque;

import java.util.ArrayList;
import java.util.List;

/**
 * Detects if this web app is running in the Apache Tomcat web container from
 * within NetBeans during development time.
 *
 * © 2016 Basil Bourque. This source code may be used freely, and entirely at
 * your own risk, according to terms of the ISC License at:
 * https://opensource.org/licenses/ISC
 *
 * @author Basil Bourque.
 */
public enum AppUtility {

    INSTANCE;

    transient private Boolean isDevMode;

    synchronized public Boolean isInDevelopmentMode () {

        // There is no simple direct way to detect if running in development.
        // As a workaround, I use some facts specific to my running Tomcat from NetBeans while developing.
        //
        // The “Catalina base” is the folder used by Tomcat’s Catalina module to do the work of your servlets.
        // The names of the folders in the path to that folder can be a clue about running in development.
        //
        // By default, the Catalina base folder is nested within Tomcat’s own folder.
        //
        // If you run NetBeans with a bundled Tomcat installation that path may contain the word “NetBeans”.
        // At least this is the case on Mac OS X where that bundled Tomcat is stored within the NetBeans app (an app is actually a folder in Mac OS X).
        //
        // You mant to create your own folder to hold Tomcat’s “base” folder.
        // I do this on my development machine. I create a folder named something like "apache-tomcat-base-dev" in my home folder.
        // Nested inside that  folder are additional folders for each version of Tomcat I may be using, such as 'base-8.0.33'.
        // Since I do not use such a name on my production environment, I can example the path for that phrasing to indicate development mode.
        //
        if ( null == this.isDevMode ) {  // Lazy-loading.
            // Retrieve the folder path to the current Catalina base folder.
            String catalinaBaseFolderPath = System.getProperty ( "catalina.base" );

            this.isDevMode = Boolean.FALSE;

            // Examine that path for certain wording I expect to occur only in development and never in production.
            List<String> list = new ArrayList<> ();
            list.add ( "Application Support" );// Specific to Mac OS X only.
            list.add ( "NetBeans" );
            list.add ( "apache-tomcat-base-dev" ); // My own name for an external folder to keep Catalina base separate, outside of NetBeans and Tomcat.
            for ( String s : list ) {
                if ( catalinaBaseFolderPath.contains ( s ) ) {
                    this.isDevMode = Boolean.TRUE;
                    break;  // Bail-out of the FOR loop after first hit.
                }
            }
        }

        return this.isDevMode;
    }

}
于 2015-02-06T00:59:35.103 に答える
2

私の場合は (NetBeans で「実行」をクリックした後):

  • アプリを Tomcat にデプロイする (不要)
  • Tomcat でアプリのアンデプロイ (不要)
  • アプリを構築する
  • アプリを Tomcat にデプロイする

そして、実行する前にプロジェクトをクリーニングするだけでその問題を修正しました-「クリーン」->「実行」:)

于 2019-04-18T07:57:11.137 に答える