Chạy các nhiệm vụ nền theo lịch trình trong Spring MVC
Công ty Vĩnh Cửu tuyển dụng lập trình viên Java

1- Spring @Scheduled Annotation

Đôi khi trong một ứng dụng bạn cần tạo ra một nhiệm vụ chạy ngầm bên dưới ứng dụng theo một lịch trình đặt sẵn. Chẳng hạn tạo ra các file sitemap, gửi email định kỳ,...
@Scheduled là một Annotation sử dụng để cấu hình một lịch trình (schedule), nó được gắn trên một phương thức, và phương thức này sẽ được chạy theo lịch được cấu hình bởi @Scheduled.
@Scheduled
public @interface Scheduled {
 
    String cron() default "";
 
    String zone() default "";
 
    long fixedDelay() default -1;
 
    String fixedDelayString() default "";
 
    long fixedRate() default -1;
 
    String fixedRateString() default "";
 
    long initialDelay() default -1;
 
    String initialDelayString() default "";

}
Thuộc tính Mô tả
cron Là một biểu thức cron, mở rộng từ định nghĩa thông dụng  UN*X, nó chứa 6 trường "giây, phút, giờ, ngày trong tháng, tháng, ngày của tuần". Giúp chỉ định một lịch trình phức tạp. (Xem thêm phía dưới tài liệu).
  • @return trả về một biểu thức cron, chứa thông tin lịch trình.
zone Một múi giờ mà biểu thức cron sẽ được dùng. Theo mặc định, thuộc tính này là String rỗng (nghĩa là múi giờ địa phương của máy chủ sẽ được sử dụng).
fixedDelay Thực thi các phương thức được chú thích, sau khi hoàn thành nghỉ một khoảng thời gian cố định theo mili giây, sau đó thực thi lượt tiếp theo.
  • @return thời gian nghỉ theo mili giây.
fixedDelayString Thực thi các phương thức được chú thích, sau khi hoàn thành nghỉ một khoảng thời gian cố định theo mili giây, sau đó thực thi lượt tiếp theo.
  • @return thời gian nghỉ theo mili giây.
fixedRate Thực thi phương thức được chú thích, với một khoảng thời gian cố định giữa các lần gọi.
  • @return khoảng thời gian cố định giữa các lần gọi tính theo mili giây.
fixedRateString Thực thi phương thức được chú thích, với một khoảng thời gian cố định giữa các lần gọi.
  • @return khoảng thời gian cố định giữa các lần gọi tính theo mili giây.
initialDelay Khoảng thời gian tính bằng mili giây tạm dừng trước khi thực thi lần đầu tiên, sử dụng cùng với fixedRate() hoặc fixedDelay().
  • @return trả về thời gian trễ cho lần gọi đầu tiên
initialDelayString Khoảng thời gian tính bằng mili giây tạm dừng trước khi thực thi lần đầu tiên, sử dụng cùng với fixedRate() hoặc fixedDelay().
  • @return trả về thời gian trễ cho lần gọi đầu tiên.

2- Cấu hình Maven & web.xml

pom.xml
<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/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.o7planning</groupId>
  <artifactId>SpringMVCSchedule</artifactId>
  <packaging>war</packaging>
  <version>0.0.1-SNAPSHOT</version>
  <name>SpringMVCSchedule Maven Webapp</name>
  <url>http://maven.apache.org</url>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
   
            <!-- Servlet Library -->
        <!-- http://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>

        <!-- Spring dependencies -->
        <!-- http://mvnrepository.com/artifact/org.springframework/spring-core -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>4.3.2.RELEASE</version>
        </dependency>

        <!-- http://mvnrepository.com/artifact/org.springframework/spring-web -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>4.3.2.RELEASE</version>
        </dependency>

        <!-- http://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>4.3.2.RELEASE</version>
        </dependency>
       
  </dependencies>
 
 
    <build>
      <finalName>SpringMVCSchedule</finalName>
      <plugins>
    
          <!-- Config: Maven Tomcat Plugin -->
          <!-- http://mvnrepository.com/artifact/org.apache.tomcat.maven/tomcat7-maven-plugin -->
          <plugin>
              <groupId>org.apache.tomcat.maven</groupId>
              <artifactId>tomcat7-maven-plugin</artifactId>
              <version>2.2</version>
              <!-- Config: contextPath and Port (Default: /SpringMVCSchedule : 8080) -->
              <!--
              <configuration>
                  <path>/</path>
                  <port>8899</port>
              </configuration>
              -->   
          </plugin>
      </plugins>
  </build>    
 
</project>

 
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns="http://java.sun.com/xml/ns/javaee"
 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
          http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
 id="WebApp_ID" version="3.0">

 <display-name>Spring MVC Schedule</display-name>
 
 
   
</web-app>

3- Cấu hình Spring MVC

ApplicationContextConfig.java
package org.o7planning.springmvcschedule.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

@Configuration
@ComponentScan("org.o7planning.springmvcschedule.*")
public class ApplicationContextConfig {

    @Bean(name = "viewResolver")
    public InternalResourceViewResolver getViewResolver() {
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setPrefix("/WEB-INF/pages/");
        viewResolver.setSuffix(".jsp");
        return viewResolver;
    }

}
SpringWebAppInitializer.java
package org.o7planning.springmvcschedule.config;


import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;

import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;

public class SpringWebAppInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        AnnotationConfigWebApplicationContext appContext = new AnnotationConfigWebApplicationContext();
        appContext.register(ApplicationContextConfig.class);

        ServletRegistration.Dynamic dispatcher = servletContext.addServlet("SpringDispatcher",
                new DispatcherServlet(appContext));
        dispatcher.setLoadOnStartup(1);
        dispatcher.addMapping("/");
    }

}
WebMvcConfig.java
package org.o7planning.springmvcschedule.config;


import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.List;

import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@Configuration
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {

   private static final Charset UTF8 = Charset.forName("UTF-8");
 
   // Cấu hình UTF-8 cho các trang.
   @Override
   public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
       StringHttpMessageConverter stringConverter = new StringHttpMessageConverter();
       stringConverter.setSupportedMediaTypes(Arrays.asList(new MediaType("text", "plain", UTF8)));
       converters.add(stringConverter);

       // Add other converters ...
   }
 

   // Cấu hình để sử dụng các file nguồn tĩnh (html, image, ..)
   // Tương đương với <mvc:resources/> trong cấu hình sử dụng XML.
   @Override
   public void addResourceHandlers(ResourceHandlerRegistry registry) {
       registry.addResourceHandler("/css/**").addResourceLocations("/css/").setCachePeriod(31556926);
       registry.addResourceHandler("/img/**").addResourceLocations("/img/").setCachePeriod(31556926);
       registry.addResourceHandler("/js/**").addResourceLocations("/js/").setCachePeriod(31556926);
   }
 
   // Tương đương cấu hình trong xml.
   @Override
   public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
       configurer.enable();
   }

}
Cấu hình cho phép sử dụng lịch trình (Schedule):
SchedulerConfig.java
package org.o7planning.springmvcschedule.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;

@Configuration
@EnableScheduling
public class SchedulerConfig {
 
   // Khai báo các Bean liên quan tới Schedule ở đây nếu cần thiết.
 
}
 

4- fixedDelay và fixedRate

Đây là một ví dụ đơn giản nhất, sử dụng @Schedule với thuộc tính fixedDelay. Trong ví dụ này nhiệm vụ sẽ in ra Console thời gian hiện tại, sau khi nhiệm vụ kết thúc nó sẽ tạm ngừng fixedDelay giây để thực thi lại nhiệm vụ này.
WriteCurrentTimeSchedule.java
package org.o7planning.springmvcschedule.schedule;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

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

@Component
public class WriteCurrentTimeSchedule {

 private static final DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss sss");


 // 2 giây
 @Scheduled(fixedDelay = 2 * 1000)
 public void writeCurrentTime() {
 
     Date now = new Date();
 
     String nowString = df.format(now);
 
     System.out.println("Now is: "+ nowString);
 
 }

}
Bạn có thể cấu hình thời gian trễ cho lần thực thi đầu tiên ( initialDelay), sau khi ứng dụng start, nhiệm vụ sẽ chờ hết thời gian trễ trước khi thực thi lần đầu tiên.
// mili giây
@Scheduled(fixedDelay = 2 * 1000, initialDelay = 30 * 1000)
public void writeCurrentTime() {

  // Làm gì đó ..

}

fixedDelay so với fixedRate

Bạn chỉ có thể sử dụng hoặc fixedDelay hoặc fixedRate trong @Schedule annotation, bạn không thể sử dụng đồng thời cả hai.
  • fixedDelay là khoảng thời gian nghỉ sau khi nhiệm vụ trước đã hoàn thành, sau đó nó mới thực thi lại nhiệm vụ lần tiếp theo.
  • fixedRate là khoảng thời gian giữa lần bắt đầu thực thi nhiệm vụ trước và lần bắt đầu thực thi nhiệm vụ tiếp theo, nó không phụ thuộc vào nhiệm vụ trước đã kết thúc hoặc chưa.

5- Biểu thức cron và zone

@Schedule cho phép bạn thiết lập các lịch trình phức tạp, chẳng hạn thực thi nhiệm vụ vào tất cả các ngày thứ hai lúc 12 giờ trưa. Để thiết lập các lịch trình phức tạp bạn cần sử dụng thuộc tính cron.
Biểu thức cron là đại diện của 6 trường:
second, minute, hour, day of month, month, day(s) of week
Ví dụ:
"0 0 * * * *" // Đầu giờ của tất cả các giờ của tất cả các ngày.

"*/10 * * * * *" // Mỗi 10 giây (số giây chia hết cho 10).

"0 0 8-10 * * *" // 8, 9 và 10 giờ các ngày

"0 0/30 8-10 * * *" // 8:00, 8:30, 9:00, 9:30 và 10 tất cả các ngày

"0 0 9-17 * * MON-FRI" // 9, .. 17 giờ các ngày thứ 2 tới thứ 6 (monday & friday)

"0 0 0 25 12 ?" // Tất cả các ngày giáng sinh, nửa đêm.
Trong đó:
Ký hiệu Ý nghĩa
* Khớp với bất kỳ
*/X Tất cả các X (Chia hết cho X)
? ("không chỉ định giá trị") - nó hữu ích khi bạn cần chỉ định gì đó trong một trong 2 trường, trong đó một trường cho phép còn cái kia thì không. Chẳng hạn, nếu tôi muốn ngày thứ 10 trong tháng, nhưng không quan tâm đó là ngày thứ mấy trong tuần, tôi cần đặt "10" vào trường day-of-month, và đặt "?" vào trường day-of-week.

"0 0 0 25 12 ?":

Ngày giáng sinh (Ngày 25 tháng 12) lúc 12 giờ đêm (0 giờ), không quan tâm ngày đó là ngày thứ mấy trong tuần.
Thuộc tính zone chỉ định múi giờ. Mặc định giá trị của zone là rỗng, nghĩa là lấy theo múi giờ của Server.
@Scheduled(cron="0 1 1 * * *", zone="Europe/Istanbul")

public void doScheduledWork() {
    // Làm gì đó ở đây.
}
Bảng dưới đây là danh sách các múi giờ (Time zones) được hỗ trợ bởi Oracle với các cộng tác.
Internal Name External User Visible Name
Pacific/Pago_Pago (-11:00) Pago Pago
Pacific/Honolulu (-10:00) Hawaii
America/Anchorage (-09:00) Alaska
America/Vancouver (-08:00) Canada Pacific Time
America/Los_Angeles (-08:00) US Pacific Time
America/Tijuana (-08:00) Tijuana
America/Edmonton (-07:00) Canada Mountain Time
America/Denver (-07:00) US Mountain Time
America/Phoenix (-07:00) Arizona
America/Mazatlan (-07:00) Mazatlan
America/Winnipeg (-06:00) Canada Central Time
America/Regina (-06:00) Saskatchewan
America/Chicago (-06:00) US Central Time
America/Mexico_City (-06:00) Mexico City
America/Guatemala (-06:00) Guatemala
America/El_Salvador (-06:00) El Salvador
America/Managua (-06:00) Managua
America/Costa_Rica (-06:00) Costa Rica
America/Montreal (-05:00) Canada Eastern Time
America/New_York (-05:00) US Eastern Time
America/Indianapolis (-05:00) East Indiana
America/Panama (-05:00) Panama
America/Bogota (-05:00) Bogota
America/Lima (-05:00) Lima
America/Halifax (-04:00) Canada Atlantic Time
America/Puerto_Rico (-04:00) Puerto Rico
America/Caracas (-04:00) Caracas
America/Santiago (-04:00) Santiago
America/St_Johns (-03:30) Newfoundland
America/Sao_Paulo (-03:00) Sao Paulo
Atlantic/Azores (-01:00) Azores
Etc./UTC (00:00) Universal Time
UTC (00:00) Universal Time
Atlantic/Reykjavik (00:00) Reykjavik
Europe/Dublin (00:00) Dublin
Europe/London (00:00) London
Europe/Lisbon (00:00) Lisbon
Africa/Casablanca (00:00) Casablanca
Africa/Nouakchott (00:00) Nouakchott
Europe/Oslo (+01:00) Oslo
Europe/Stockholm (+01:00) Stockholm
Europe/Copenhagen (+01:00) Copenhagen
Europe/Berlin (+01:00) Berlin
Europe/Amsterdam (+01:00) Amsterdam
Europe/Brussels (+01:00) Brussels
Europe/Luxembourg (+01:00) Luxembourg
Europe/Paris (+01:00) Paris
Europe/Zurich (+01:00) Zurich
Europe/Madrid (+01:00) Madrid
Europe/Rome (+01:00) Rome
Africa/Algiers (+01:00) Algiers
Africa/Tunis (+01:00) Tunis
Europe/Warsaw (+01:00) Warsaw
Europe/Prague (+01:00) Prague Bratislava
Europe/Vienna (+01:00) Vienna
Europe/Budapest (+01:00) Budapest
Europe/Sofia (+02:00) Sofia
Europe/Istanbul (+02:00) Istanbul
Europe/Athens (+02:00) Athens
Asia/Nicosia (+02:00) Nicosia
Asia/Beirut (+02:00) Beirut
Asia/Damascus (+02:00) Damascus
Asia/Jerusalem (+02:00) Jerusalem
Asia/Amman (+02:00) Amman
Africa/Tripoli (+02:00) Tripoli
Africa/Cairo (+02:00) Cairo
Africa/Johannesburg (+02:00) Johannesburg
Europe/Moscow (+03:00) Moscow
Asia/Baghdad (+03:00) Baghdad
Asia/Kuwait (+03:00) Kuwait
Asia/Riyadh (+03:00) Riyadh
Asia/Bahrain (+03:00) Bahrain
Asia/Qatar (+03:00) Qatar
Asia/Aden (+03:00) Aden
Africa/Khartoum (+03:00) Khartoum
Africa/Djibouti (+03:00) Djibouti
Africa/Mogadishu (+03:00) Mogadishu
Asia/Dubai (+04:00) Dubai
Asia/Muscat (+04:00) Muscat
Asia/Yekaterinburg (+05:00) Yekaterinburg
Asia/Tashkent (+05:00) Tashkent
Asia/Calcutta (+05:30) India
Asia/Novosibirsk (+06:00) Novosibirsk
Asia/Almaty (+06:00) Almaty
Asia/Dacca (+06:00) Dacca
Asia/Krasnoyarsk (+07:00) Krasnoyarsk
Asia/Bangkok (+07:00) Bangkok
Asia/Saigon (+07:00) Vietnam
Asia/Jakarta (+07:00) Jakarta
Asia/Irkutsk (+08:00) Irkutsk
Asia/Shanghai (+08:00) Beijing, Shanghai
Asia/Hong_Kong (+08:00) Hong Kong
Asia/Taipei (+08:00) Taipei
Asia/Kuala_Lumpur (+08:00) Kuala Lumpur
Asia/Singapore (+08:00) Singapore
Australia/Perth (+08:00) Perth
Asia/Yakutsk (+09:00) Yakutsk
Asia/Seoul (+09:00) Seoul
Asia/Tokyo (+09:00) Tokyo
Australia/Darwin (+09:30) Darwin
Australia/Adelaide (+09:30) Adelaide
Asia/Vladivostok (+10:00) Vladivostok
Australia/Brisbane (+10:00) Brisbane
Australia/Sydney (+10:00) Sydney Canberra
Australia/Hobart (+10:00) Hobart
Asia/Magadan (+11:00) Magadan
Asia/Kamchatka (+12:00) Kamchatka
Pacific/Auckland (+12:00) Auckland
Ví dụ dưới đây, nhiệm vụ được thực thi vào 1h 20 phút các ngày thứ 2, 3, 4, 5.
"0 20 1 * * MON-THU"
CronSchedule.java
package org.o7planning.springmvcschedule.schedule;

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

@Component
public class CronSchedule {

    @Scheduled(cron = "0 20 1 * * MON-THU")
    public void doSomething() {

        System.out.println("Do some thing");

    }

}