前言
springboot 项目中用到了定时器,用的是 spring 的 @Scheduled 注解,每天早上九点执行,发送公众号消息提醒。今天接收到了提醒,发现提醒发送了两次,然后查看日志文件发现定时任务到点执行了两次。百度了一下发现有几种说法,总结一下。
正文
- 第一种说法,因为没有移除 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>
- 第二种说法,tomcat 的 server.xml 配置错误,项目启动时会去找 appBase 目录下的项目加载一次,然后再去找 docBase 目录下的项目再加载一次。所以同一个项目被加载了两次,定时器也被加载了两次。将文件中的 <Host>修改为如下配置,appBase 置空,docBase 修改为项目的绝对路径即可。这种方法对我来说也没有用,因为我服务器上的 tomcat 本来就是这么配置的。
还有其他修改 server.xml 的方法,具体请参考 https://www.cnblogs.com/Syney/p/7678714.html 。这篇博客里的其他方法我没有尝试,因为我在一个 tomcat 上部署了多个项目,其他方法并不适用,所以我也没有尝试。关于 appBase 和 docBase 的区别可以参考 https://blog.csdn.net/sqiucheng/article/details/8510058 。<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>
- 第三种说法,在 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); } }
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于