1. 程式人生 > 實用技巧 >SpringMVC自帶Cron定時器Demo及常見問題

SpringMVC自帶Cron定時器Demo及常見問題

該技術的不適用的場景

如果在叢集環境下,多臺伺服器中只希望有一臺執行,那 Spring 自帶的這種定時器方式可能不太符合你的需要。
但是,如果每臺伺服器都需要獨立執行該定時器任務,且相互之間不存在同步,那麼還是可以考慮的

SpringMVC 定時器

本文著重介紹的是 SpringMVC 配置定時器的方式,而不是 SpringBoot 配置定時器的方式。

註解方式

首先,在 Clock 類上新增 @Component,然後,在需要定時執行的方法上面加上 @Scheduled,最後指定 cron 表示式。

專案結構:

Clock.java

package coderead.spring.scheduled;

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.util.Date;

@Component
public class Clock {

    // 每5秒鐘執行一次
    @Scheduled(cron = "*/5 * * * * ?")
    public void testTime() {
        System.out.println(new Date());
    }
}

spring-mvc.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:task="http://www.springframework.org/schema/task"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/mvc
            http://www.springframework.org/schema/mvc/spring-mvc.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/task
            http://www.springframework.org/schema/task/spring-task.xsd">

    <mvc:annotation-driven />

    <context:component-scan base-package="coderead.spring.*" />
    
    <!-- 定時任務支援註解 -->
    <task:annotation-driven />

</beans>

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">
    <!--配置多個上下文會導致多次執行-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring/spring-mvc.xml</param-value>
    </context-param>

    <!-- ================================== listener ================================== -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>


    <!-- ================================== servlet ================================== -->
    <!-- 前端控制器 -->
    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value></param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>coderead.spring</groupId>
    <artifactId>spring-scheduled-test</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>


    <properties>
        <spring.version>5.1.6.RELEASE</spring.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.eclipse.jetty</groupId>
                <artifactId>jetty-maven-plugin</artifactId>
                <version>9.4.33.v20201020</version>
            </plugin>
        </plugins>
    </build>

</project>

如果你不知道怎麼用 jetty 啟動專案,你可以考慮參考 使用maven-Jetty9-plugin外掛執行第一個Servlet

xml 配置方式

如果你需要使用 xml 配置,你會發現 @Scheduled 註解和 <task:scheduled> 有著相同的屬性。因此我們將上一節的程式碼稍稍改動一下:

Clock.java 去掉註解

package coderead.spring.scheduled;

import java.util.Date;

public class Clock {

    public void testTime() {
        System.out.println(new Date());
    }
}

spring-mvc.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:task="http://www.springframework.org/schema/task"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/mvc
            http://www.springframework.org/schema/mvc/spring-mvc.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/task
            http://www.springframework.org/schema/task/spring-task.xsd">

    <mvc:annotation-driven />

    <context:component-scan base-package="coderead.spring.*" />

    <!--用 xml 方式注入 Clock Bean-->
    <bean id="clock" class="coderead.spring.scheduled.Clock" />
    <!--用 xml 方式設定定時器-->
    <task:scheduled-tasks>
        <task:scheduled ref="clock" method="testTime" cron="*/5 * * * * ?"/>
    </task:scheduled-tasks>

</beans>

常見問題

@Scheduled 定時任務不生效

@Scheduled定時任務不生效???

  1. 此方法不能有引數
  2. 此方法不能有返回值
  3. 此類中不能包含其他帶任何註解的方法(發現新大陸)

還有一種可能就是沒有在 spring-mvc.xml 檔案中加入 <task:annotation-driven /> 而不僅僅是加入 <mvc:annotation-driven />

@Scheduled 定時任務執行兩次

@Scheduled Spring定時任務每次執行兩次解決方案
主要原因是 web.xml 同時設定了 <context-param><init-param> 都設定了 contextConfigLocation,兩次載入配置檔案

<web-app ....>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring/spring-mvc.xml</param-value>
    </context-param>
    ...
    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring/spring-mvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    ...
</web-app>

cron 表示式

cron 表示式是用來規定程式碼執行週期的一種表示式,cron表示式詳解 這篇文章詳細的講解了 cron 表示式的使用細節。
以我的淺陋的經驗,我對 cron 表示式的記憶是:

  1. 常用的 cron 表示式由 6 個域組成,域和域之間以空格分開

  2. 域從左到右,時間單位從秒開始逐步增大。他們分別是 "秒 分 時 日期 月份 星期"

  3. 因為日期和星期會相互影響,通常如果其中一個用 ? 表示任意,則另一個必須用 ? 表示“任意”。

原因:通常,在指定日期條件之後,我們雖然希望“任意星期幾”,但是實際上,此時星期需要根據日期的變化而相應變化,做不到完全任意。

你還可以通過 線上 Cron 表示式 來幫助你理解前人程式碼中的 cron 表示式的含義,或者根據你的需求生成一個新的 cron 表示式。

參考文獻

使用SpringMVC自帶的@Scheduled完成定時任務

spring 框架自帶的定時器功能