目录

今日说码

点滴记录中国代码进程

X

Spring 定时器在 Tomcat 上执行两次的问题

前言

  springboot项目中用到了定时器,用的是spring的@Scheduled注解,每天早上九点执行,发送公众号消息提醒。今天接收到了提醒,发现提醒发送了两次,然后查看日志文件发现定时任务到点执行了两次。百度了一下发现有几种说法,总结一下。

正文

  1. 第一种说法,因为没有移除springboot项目的内置tomcat,所以在使用外部tomcat运行时springboot项目启动了两次,移除内置的tomcat即可。这种方法对我没有用,因为我本来就已经移除了内置的tomcat。
    <!--移除内置tomcat -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    
  2. 第二种说法,tomcat的server.xml配置错误,项目启动时会去找appBase目录下的项目加载一次,然后再去找docBase目录下的项目再加载一次。所以同一个项目被加载了两次,定时器也被加载了两次。将文件中的<Host>修改为如下配置,appBase置空,docBase修改为项目的绝对路径即可。这种方法对我来说也没有用,因为我服务器上的tomcat本来就是这么配置的。
    <Host name="你的域名"  appBase="" unpackWARs="true" autoDeploy="true">
        <Context path="" docBase="项目的绝对路径,如/opt/tomcat/webapps/abc" reloadable="true" privileged="true" debug="0"/>
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="localhost_access_log." suffix=".txt" pattern="%h %l %u %t "%r" %s %b" />
    </Host>
    

      还有其他修改server.xml的方法,具体请参考https://www.cnblogs.com/Syney/p/7678714.html 。这篇博客里的其他方法我没有尝试,因为我在一个tomcat上部署了多个项目,其他方法并不适用,所以我也没有尝试。关于appBase和docBase的区别可以参考https://blog.csdn.net/sqiucheng/article/details/8510058
     

  3. 第三种说法,在java代码中手动阻止项目第一次启动加载。
      项目被部署在服务器tomcat的webapps目录里,导致项目被tomcat初始化了2次,部署成功了2次,一次访问路径是项目名,一次访问路径是/。实际中那个访问路径带项目名的是不需要的,所以直接阻止它启动,这样项目就只成功启动了一次,问题就可以解决。
      阻止具体方法,创建一个bean,实现ServletContextAware接口,重写setServletContext()方法。当这个bean创建时,会自动调用setServletContext(),在方法里我们判断下当前的项目访问路径是否为空或者是否为"/",如果是,正常通过。如果不是,说明当前tomcat正在初始化访问路径为项目名的项目,所以我们要阻止它,这时候抛出个运行异常,当前bean就会创建失败,这时候这个项目就会启动失败了。
     
    这个接口在哪儿实现都可以,我直接在定时器的配置类中实现了这个接口。
    public class AsyncConfig implements ServletContextAware
    @Override
    public void setServletContext(ServletContext servletContext) {
        String contextPath = servletContext.getContextPath();
        if ("".equals(contextPath) || "/".equals(contextPath)) {
    	// 这句日志打印可以不要
            logger.info("启动成功,本次启动映射路径为:" + contextPath);
        } else {
            throw new RuntimeException("为阻止tomcat初始化2次,导致spring定时器启动2次,阻止本次启动,本次启动映射路径为:" + contextPath);
        }
    }
    

    我用的是第三种方法,这种方法在项目第一次加载时会抛出大量重复的运行时异常,不止一次。第二次加载恢复正常,启动成功,然后测试一下,定时任务只会执行一次。


标题:Spring 定时器在 Tomcat 上执行两次的问题
作者:96XL
地址:https://solo.96xl.top/articles/2019/08/26/1566819770447.html