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

本贴最后更新于 310 天前,其中的信息可能已经斗转星移

前言

  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 &quot;%r&quot; %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

    Spring 是一个开源框架,是于 2003 年兴起的一个轻量级的 Java 开发框架,由 Rod Johnson 在其著作《Expert One-On-One J2EE Development and Design》中阐述的部分理念和原型衍生而来。它是为了解决企业应用开发的复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许使用者选择使用哪一个组件,同时为 JavaEE 应用程序开发提供集成的框架。

    780 引用 • 1368 回帖 • 691 关注
  • 定时器
    1 引用 • 1 回帖
  • Tomcat

    Tomcat 最早是由 Sun Microsystems 开发的一个 Servlet 容器,在 1999 年被捐献给 ASF(Apache Software Foundation),隶属于 Jakarta 项目,现在已经独立为一个顶级项目。Tomcat 主要实现了 JavaEE 中的 Servlet、JSP 规范,同时也提供 HTTP 服务,是市场上非常流行的 Java Web 容器。

    146 引用 • 524 回帖

赞助商 我要投放

1 回帖
请输入回帖内容 ...
  • wmy0436

    大佬又遇见你了