Запуск фоновых задач в Spring MVC

1- Spring @Scheduled Annotation

Иногда в приложении вам нужно создать задание работающее на фоне по готовому расписанию. Например создать файлы  sitemap, отправлять периодически  email,...
@Scheduled это Annotation использованное для конфигурации расписания (schedule), он прикреплен к методу, и этот метод работает по расписанию, конфигурированная с помощью @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 "";

}
Атрибут Описание
cron Это выражение cron, расширенное из обычного определения UN*X, которое содержит 6 полей "секунда, минута, час, день в месяце, день недели". Помогает определить сложное расписание. (Смотрите ниже).
  • @return возвращает выражение cron, содержащее информацию расписания.
zone Часовой пояс по которому будет использоваться cron. По умолчанию, этот атритубут является пустым String (то есть будет использован локальный часовой пояс сервера).
fixedDelay Выполнение аннотированных методов (annotated), после завершения через промежуток времени по милисекундам, выполнить следующий.
  • @return время остановки по милисекундам.
fixedDelayString Выполнение аннотированных методов (annotated), после завершения через промежуток времени по милисекундам, выполнить следующий.
  • @return время остановки по милисекундам.
fixedRate Выполнение аннотированных методов (annotated), c определенным промежутком времени между вызовами.
  • @return khoảng thời gian cố định giữa các lần gọi tính theo mili giây.
fixedRateString Выполнение аннотированных методов (annotated), c определенным промежутком времени между вызовами.
  • @return khoảng thời gian cố định giữa các lần gọi tính theo mili giây.
initialDelay Промежуток времени расчитанный в милисекундах приостановленный перед первым выполнением, используется с fixedRate() или fixedDelay().
  • @return возвращает время запоздания первого вызова
initialDelayString Промежуток времени расчитанный в милисекундах приостановленный перед первым выполнением, используется с fixedRate() или fixedDelay().
  • @return возвращает время запоздания первого вызова

2- Конфигурация 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- Конфигурация 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");

   // Config UTF-8 Encoding.
   @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 ...
   }

   // Static Resource Config
   // equivalents for <mvc:resources/> tags
   @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);
   }

   // Equivalent for <mvc:default-servlet-handler/> tag
   @Override
   public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
       configurer.enable();
   }

}
Конфигурация позволяет использовать расписание (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 {
 
    // Declaring the beans are related to the schedule here if necessary.
   
}

 

4- fixedDelay & fixedRate

Это самый простой пример, используя  @Schedule с атрибутом  fixedDelay. В данном примере,задание распечатает текущее время на экране  Consol, после завершения задания, он приостановит  fixedDelay за секунду перед тем, как перевыполнить данное задание.
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 second.
  @Scheduled(fixedDelay = 2 * 1000)
  public void writeCurrentTime() {
   
      Date now = new Date();
   
      String nowString = df.format(now);
   
      System.out.println("Now is: "+ nowString);
   
  }

}
Вы можете конфигурировать время запоздания для первого выполнения ( initialDelay), после  start приложения, задание выполняется когда истечет время запоздания.
// miliseconds.
@Scheduled(fixedDelay = 2 * 1000, initialDelay = 30 * 1000)
public void writeCurrentTime() {
 
   // Do something ..

}

fixedDelay по сравнению с fixedRate

Вы можете использовать только или  fixedDelay или  fixedRate в @Schedule annotation, вы не можете использовать одновременно оба.
  • fixedDelay это период отдыха после завершения предыдущего задания, только после этого он выполняет следующее задание.
  • fixedRate это промежуток времени между выполнением первого задания и выполнением следующего задания, он не зависит от того, завершилось ли предыдущее задание.

5- Выражение cron и zone

@Schedule позволяет вам создавать сложные расписания, например выполнять задание во все понедельники в 12 часов дня. Чтобы настроить сложные расписания вам нужно использовать атрибут  cron.
Выражение  cron является представителем 6 полей:
second, minute, hour, day of month, month, day(s) of week
Пример:
"0 0 * * * *" // the top of every hour of every day.

"*/10 * * * * *" // every ten seconds.

"0 0 8-10 * * *" // 8, 9 and 10 o'clock of every day.

"0 0/30 8-10 * * *" // 8:00, 8:30, 9:00, 9:30 and 10 o'clock every day.

"0 0 9-17 * * MON-FRI" // on the hour nine-to-seventeen, and monday to friday

"0 0 0 25 12 ?" // every Christmas Day at midnight
При этом:
Символ Описание
* Подходит любому
*/X Все X (Все деленное на X)
? ("не указывает значение") - полезно, когда вам нужно указать определенное в одном из 2 полей (field), при этом одно поле позволяет, а другое нет. Например, если я хочу 10 день месяца, но не важно какой это будет день недели, я должен настроить "10" в поле day-of-month, и настроить "?" в поле day-of-week.

"0 0 0 25 12 ?":

Рождество (25 Декабря) в 12 часов ночи (0 часов), не важно какой это день недели.
Атрибут  zone определят часовой пояс. По умолчанию значение  zone является пустым, то есть по часовому поясу Server.
@Scheduled(cron="0 1 1 * * *", zone="Europe/Istanbul")

public void doScheduledWork() {
    // Do something here
}
Таблица ниже это список часовых поясов (Time zones) поддерживаемый  Oracle Real-Time Collaboration.
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
В примере ниже, задание выполняется в 1.20 часов в понедельник, вторник, среду и четверг.
"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");

    }

}