【问题标题】:Tomcat deploying the same application twice in netbeansTomcat在netbeans中两次部署相同的应用程序
【发布时间】:2013-05-18 02:37:44
【问题描述】:

我正在使用NetBeansTomcat 似乎在.war 应用程序中部署了两次,网络应用程序的两次启动。

我已经尝试过 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 部署应用程序两次。 这在我第一次在新的 tomcat 实例上部署应用程序时不会发生,因为 website.xml 文件尚未在 tomcats /conf/catalina/localhost 文件夹中。但是当我再次从 netbeans 停止并运行 tomcat 时,website.xml 文件仍在 /conf/catalina/localhost 文件夹中,但在第二次部署即将发生之前被删除并重新部署。

我尝试将 Tomcat 的 server.xml 文件中的 autoDeploy 设置为 false,但没有帮助。

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

可能是 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"
/>

【问题讨论】:

    标签: maven tomcat netbeans


    【解决方案1】:

    就我而言(在 NetBeans 中单击“运行”后):

    • 在 Tomcat 中部署应用程序(不必要)
    • 在 Tomcat 中取消部署应用程序(不必要)
    • 构建应用程序
    • 在 Tomcat 中部署应用程序

    我只是通过在运行项目之前清理项目来解决这个问题 - 'clean' -> 'run' :)

    【讨论】:

      【解决方案2】:

      感谢the answer by epochanswer by Steven Neiner

      这是我的他们的代码版本。我的不同之处:

      • 将方法标记为synchronized
        • 理论上不需要,但鉴于我们正在处理奇怪的多次启动问题,安全总比抱歉好。
      • 替换了对第三方实用程序(Spring?)的调用。
      • 通过在 catalina.base 路径中查找特定措辞来检测是否在开发中运行。
      • 删除了static 修饰符。使用作为枚举实现的 Singleton。
      • 截至 2016 年 5 月,重写了代码以更易于阅读和理解(至少对我而言)。仅进行了简单测试,因此请务必在使用前查看源代码(并且必须完全由您自担风险)。

      概念

      他们解决此错误的核心是删除以您的网络应用程序名称(您的“servlet 上下文”)命名并附加.xml 的文件。

      例如,如果您的 Web 应用程序名为 AcmeApp,请找到并删除名为 AcmeApp.xml 的文件。该文件嵌套存储在“Catalina base”文件夹中。

      将此删除作为网络应用运行的最后一步。因此,当 Web 应用程序再次启动时,该文件将不存在,并将重新创建。请记住,这仅在开发模式下。在生产环境中单独使用 Tomcat 时不会出现该错误。

      那么,作为网络应用执行的最后一步,我们如何运行这个解决方法代码呢?继续阅读。

      如何使用

      作为Servlet spec 2.3 版和更高版本的标准部分,每个Servlet container 都有挂钩,可在您的网络启动时调用您的代码,并在您的网络应用程序关闭时再次调用。这不是 Tomcat 特定的; JettyGlassFishWildFly/JBoss等等,都包含了这个Servlet规范要求的特性。

      要使用上面显示的代码,请将一个新类添加到您的项目中。将新类命名为“MyServletContextListener.java”。将该类声明为实现ServletContextListener 接口。

      实现该接口所需的两个方法。当您的 Web 应用程序启动时,您的 Servlet 容器 (Tomcat) 会调用一个方法,保证在第一个用户点击您的应用程序之前运行。另一种方法是在您的 Web 应用程序被 Servlet 容器 (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 );
      }
      

      配置很简单。只需将此类与您的 servlet 类一起包含在 WAR file/folder 中即可。 @WebListener 注释使 Servlet 容器“注意到”这个侦听器类,加载并实例化它,并在适当的时候执行它的每个方法。如果需要,您可以使用替代配置模式而不是注解,但注解是最简单的路线。

      这是一个完整的 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.
                      }
                  }
              }
      
          }
      
      }
      

      这里是确定是否在开发模式下运行的辅助类。阅读 cmets 进行更多讨论。结果是似乎没有简单干净的方法来检测何时处于开发状态,没有办法检测何时从 NetBeans 运行 Tomcat 而不是自己运行 Tomcat。 I have asked 但还没有收到更好的解决方案。

      CAVEAT:必须更改此 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;
          }
      
      }
      

      【讨论】:

      • 更新:我仍然需要使用 NetBeans 8.1 和 Tomcat 8.0.27 为我的 Vaadin 7.6.4 应用程序执行此操作。
      • Vaadin 7.7.3 在 NetBeans 8.2 和 Tomcat 8.5.6 中仍然存在问题。
      • 仅供参考,我已切换到使用 IntelliJ 2017 Ultimate 版,并且 Tomcat 没有这样的问题。一切都在 IntelliJ 中运行良好,没有任何愚蠢的解决方法。
      【解决方案3】:

      首先,谢谢史蒂文!这是一个更便携的修复版本:

      /**
       * tomcat workaround bug, in development mode, if tomcat is stopped and application is not un-deployed,
       * the old application will start up again on startup, and then the new code will be deployed, leading
       * to a the app starting two times and introducing subtle bugs, when this app is stopped and in dev mode
       * remove the deployment descriptor from catalina base
       */
      private static void preventTomcatNetbeansRedeployBug(final ServletContextEvent sce) {
          final String contextPath = sce.getServletContext().getContextPath();
          final String catalinaBase = System.getProperty("catalina.base");
      
          if (StringUtil.checkValidity(contextPath, catalinaBase)
                  && FrameworkContext.getInstance().isDevEnvironment()) {
              final File catalinaBaseContext = new File(catalinaBase, "conf/Catalina/localhost");
              if (catalinaBaseContext.exists() && catalinaBaseContext.canRead()) {
                  final File[] contexts = catalinaBaseContext.listFiles(new FilenameFilter() {
                      @Override
                      public boolean accept(File dir, String name) {
                          return name.equals(contextPath.substring(1) + ".xml");
                      }
                  });
      
                  if (contexts != null && contexts.length > 0) {
                      LOG.info("Deleting core context[" + contexts[0].getAbsolutePath() + "] since we are in dev");
                      contexts[0].delete();
                  }
              }
          }
      }
      

      PS:用你自己的版本替换未知的引用:)

      从您的ServletContextListener 实现的contextDestroyed 方法调用此自定义方法。

      【讨论】:

      • 你在哪里调用这个方法?在ServletContextListener 对象的contextDestroyed 方法中?
      • 是的 :),这就是我所说的
      • 我想知道那个自定义方法是否应该有一个synchronize。如果您快速启动和退出(就像我有时在 IDE 中所做的那样),调用可能会重叠。想法?
      • @BasilBourque,不要认为它会产生任何负面影响,另一方面我认为它不会被多次调用。
      【解决方案4】:

      我发现删除文件 conf/localhost/myappname.xml 会阻止应用程序初始化两次。基本上 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>
      

      那么下次部署应用时,不会在部署之前再次启动,因此不会启动两次。在部署之前或关闭时删除文件的任何其他想法将不胜感激。

      【讨论】:

      • 是的,这正是我为解决此问题所做的。我认为这与我在开发应用程序时使用的 tomcat 版本有关。
      • 这是我的两分钱。我通过在第一行包含以下命令修改了 tomcat/bin/catalina.bat:del "C:\Users\xxx\apache-tomcat-6.0.24\conf\Catalina\localhost*.xml"。 Netbeans 现在只部署一次。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-08-31
      • 1970-01-01
      • 2014-05-06
      相关资源
      最近更新 更多