Core Guides

Aspectran Scheduler: Task Automation with Translets

Aspectran Scheduler is a powerful feature that allows specific tasks within an application to be executed at a set time or periodically. With this feature, developers can easily implement and manage various background tasks such as batch jobs, data synchronization, and report generation using Aspectran’s core component, the Translet.

1. Core Architecture: Built-in Quartz-based Framework

The most important feature of Aspectran Scheduler is that it has a powerful scheduling framework based on the Quartz scheduler built into its core. Developers can immediately activate and use the scheduling feature with just the configuration rules provided by Aspectran, without the complex process of integrating a separate scheduling library themselves.

The core is the SchedulerService interface, and Aspectran provides DefaultSchedulerService, which uses Quartz as the default implementation of this interface. Therefore, developers do not need to worry about the complex internal workings of scheduling and can easily automate the tasks they want using the Translet they know best.

2. How to Configure the Scheduler

Aspectran provides two main ways to configure the scheduler.

  1. XML/APON-based Configuration: A traditional and explicit method where all rules are defined in a configuration file.
  2. Annotation-based Configuration: A modern method of defining scheduling rules directly in a Java class using annotations.

Method 1: XML/APON-based Configuration

This approach explicitly defines the scheduler bean and schedule rules (<schedule>) in an XML or APON file.

Step 1: Define the Scheduler Bean

Define a bean that creates the scheduler instance. Using the QuartzSchedulerFactoryBean provided by Aspectran is the most concise and recommended method.

<bean id="scheduler1" class="com.aspectran.core.scheduler.support.QuartzSchedulerFactoryBean">
    <property type="properties" name="quartzProperties">
        <entry name="org.quartz.scheduler.instanceName">MyScheduler</entry>
        <entry name="org.quartz.threadPool.threadCount">10</entry>
        <!-- All other Quartz properties... -->
    </property>
</bean>

Quartz Property Settings The detailed behavior of the Quartz scheduler, such as the threadPool and jobStore, can be controlled through a wide variety of properties. For detailed information on all properties, please refer to the Quartz Official Configuration Documentation.

Step 2: Define the <schedule> Rule

Using the defined scheduler bean, define a <schedule> rule group. This rule represents a group of jobs that share a single execution cycle (trigger).

<schedule id="my-schedule">
    <!-- 1. Define when to execute (trigger) -->
    <scheduler bean="scheduler1">
        <trigger type="cron">
            expression: 0 0 2 * * ?
        </trigger>
    </scheduler>

    <!-- 2. Define what to execute (job) -->
    <job translet="/batch/daily-report"/>
    <job translet="/batch/log-archive"/>
</schedule>

Method 2: Annotation-based Configuration

Using the @Schedule annotation, you can declaratively handle everything from defining the scheduler bean to setting up jobs and triggers, all within a single Java class. This has the advantage of increasing code cohesion and minimizing XML configuration.

@Schedule Annotation Usage Example

The following example defines a scheduler bean, a schedule rule, and the Translet that the job will execute, all in a single AnnotatedScheduledTasks.java class.

@Component
@Bean("annotatedScheduledTasks")
@Schedule(
    id = "annotated-schedule",
    scheduler = "annotatedScheduler", // The name of the scheduler bean to be created in the @Bean method below
    cronTrigger = @CronTrigger(
        expression = "0/10 * * * * ?" // Run every 10 seconds
    ),
    jobs = {
        @Job(translet = "/annotated/job1") // The name of the Translet to execute
    }
)
public class AnnotatedScheduledTasks {

    // 1. Define the Translet that the job will execute
    @Request("/annotated/job1")
    @Transform(FormatType.TEXT)
    public String myScheduledTask() {
        return "Annotation-based job executed at " + LocalDateTime.now();
    }

    // 2. Define the scheduler bean
    @Bean("annotatedScheduler")
    public org.quartz.Scheduler createScheduler() throws SchedulerException {
        // Load or create Quartz properties as needed
        Properties props = new Properties();
        props.put("org.quartz.scheduler.instanceName", "AnnotatedScheduler");
        props.put("org.quartz.threadPool.threadCount", "5");

        SchedulerFactory factory = new StdSchedulerFactory(props);
        return factory.getScheduler();
    }

}
  • @Schedule: Attached at the class level to define a schedule rule. It has attributes for the id, the scheduler bean name, a trigger (cronTrigger or simpleTrigger), and an array of jobs to execute.
  • @Job: Specifies the Translet to be executed within the jobs attribute of @Schedule.
  • @Request: Defines the actual method that handles the translet name specified in @Job. In other words, this method becomes the body of the scheduled task.
  • @Bean Method: Creates and registers as a bean the Quartz Scheduler instance that will be referenced by the scheduler attribute of @Schedule.

Trigger Type Detailed Description

Triggers precisely control when a job group is executed. They can be configured through the <trigger> element in XML or the @SimpleTrigger and @CronTrigger annotations in the annotation-based approach.

simple Trigger: Interval-based Repetition

The simple trigger is used to repeat a task at simple intervals, such as “start 10 seconds from now, and run every hour, for a total of 5 times.” It is best suited for repeating a task a set number of times or indefinitely at a specific interval.

  • Main Attributes:
    • startDelaySeconds: The time to delay the first execution after the scheduler has started (in seconds)
    • intervalInSeconds, intervalInMinutes, intervalInHours: The time interval for repetition
    • repeatCount: The number of times to repeat after the first execution (-1 for infinite repeat)
    • repeatForever: If set to true, repeats indefinitely
  • Example (XML): Repeat indefinitely every hour
    <trigger type="simple">
        startDelaySeconds: 10
        intervalInHours: 1
        repeatForever: true
    </trigger>
    
  • Example (Annotation): Start after 5 minutes, and run a total of 10 times at 30-second intervals
    @Schedule(
        // ...
        simpleTrigger = @SimpleTrigger(
            startDelaySeconds = 300,
            intervalInSeconds = 30,
            repeatCount = 9 // 1 initial execution + 9 repeats = 10 total executions
        )
        // ...
    )
    

cron Trigger: Schedule-based Reservation

The cron trigger is used to execute tasks according to a complex calendar-related schedule, such as “every Friday at 5:30 PM” or “at 1 AM on the last day of every month.” It operates based on the powerful Cron expression, providing very flexible and powerful scheduling.

  • Main Attribute:
    • expression: A Cron expression string that defines the execution schedule.
  • Example (XML): Run every night at 11:50 PM
    <trigger type="cron">
        expression: 0 50 23 * * ?
    </trigger>
    
  • Example (Annotation): Run every week from Monday to Friday, at 9:30 AM
    @Schedule(
        // ...
        cronTrigger = @CronTrigger(expression = "0 30 9 ? * MON-FRI")
        // ...
    )
    

Misfire Policies: Handling Missed Executions

A misfire occurs when a trigger “misses” its firing time because the scheduler was down or there were no available threads in the Quartz thread pool. You can define how the scheduler should handle such situations using the misfirePolicy attribute.

How Misfires are Detected: misfireThreshold

The decision of whether a trigger has “misfired” depends on the org.quartz.jobStore.misfireThreshold property (defined in milliseconds). This threshold represents the “grace period” that the scheduler allows before a trigger is officially considered to have misfired.

  • Within Threshold: If the delay is within the threshold of the scheduled firing time, the scheduler simply fires the trigger as soon as possible without applying any misfire policy.
  • Exceeding Threshold: If the delay exceeds the threshold, the trigger is marked as misfired, and the configured misfirePolicy is then applied.

By default, Quartz sets this to 60,000 milliseconds (1 minute). You can adjust this value in the quartzProperties of your scheduler bean:

<bean id="scheduler1" class="com.aspectran.core.scheduler.support.QuartzSchedulerFactoryBean">
    <property type="properties" name="quartzProperties">
        <!-- Set misfire threshold to 30 seconds -->
        <entry name="org.quartz.jobStore.misfireThreshold">30000</entry>
        <!-- ... other properties ... -->
    </property>
</bean>

Available Policies for cron Trigger

  • ignoreMisfires: Executes all missed firings immediately.
  • smartPolicy (Default): Executes the first missed firing immediately and discards others. (Same as fireOnceNow)
  • fireOnceNow: Executes the first missed firing immediately and discards others.
  • doNothing: Discards all missed firings and waits for the next scheduled time.

Available Policies for simple Trigger

  • ignoreMisfires: Executes all missed firings as soon as possible.
  • smartPolicy (Default):
    • Fixed Repeat: Same as rescheduleNowWithExistingRepeatCount.
    • Infinite Repeat: Same as rescheduleNextWithRemainingCount.
  • fireNow: Executes the first missed firing immediately and discards others.
  • rescheduleNowWithExistingRepeatCount: Reschedules to fire immediately, keeping the repeat count unchanged.
  • rescheduleNowWithRemainingRepeatCount: Reschedules to fire immediately, but the first missed firing counts as one of the repeat counts.
  • rescheduleNextWithExistingCount: Waits for the next scheduled time, keeping the repeat count unchanged.
  • rescheduleNextWithRemainingCount: Waits for the next scheduled time, discarding all missed firings.

Configuration Examples

```xml
<trigger type="cron">
    expression: 0 0 2 * * ?
    misfirePolicy: doNothing
</trigger>
```
  • Example (Annotation):
    @CronTrigger(
        expression = "0 0 2 * * ?",
        misfirePolicy = MisfirePolicy.DO_NOTHING
    )
    

3. Schedule Job Logging and Monitoring

It is very important to check the execution status of scheduled tasks and to debug them. Aspectran supports detailed logging of schedule job execution events through Logback.

Logging Mechanism

Aspectran Scheduler logs events such as the start, success, and failure of a job through the com.aspectran.core.scheduler.activity.ActivityJobReporter class. This reporter is linked with Quartz’s JobListener to record key information that occurs during the job’s lifecycle.

Logging Job Execution Results

If you want to include the actual execution result of a task in the log message, you can specify a response method for the Translet that the job executes. When a Translet returns a value and has a transformation defined (e.g., @Transform(FormatType.TEXT)), Aspectran includes this response in the SUCCESS log entry.

For example, if a job is configured as follows:

@Request("test/schedule/count.job")
@Transform(FormatType.TEXT)
public String count() {
    int count = counter.incrementAndGet();
    return "Count: " + count;
}

The success log will include the response field: DEBUG ... SUCCESS {group=countSchedule, name=test/schedule/count.job, ..., response=Count: 1}

Tip for Log Readability While Translets support various response formats, using a concise format like FormatType.TEXT is highly recommended for log messages to ensure the logs remain readable and efficient.

Logback Configuration Example

To manage scheduler logs in a separate file, you can use Logback’s SiftingAppender and Aspectran’s LoggingGroupDiscriminator. The following is an example based on the configuration of the gs-scheduler example project.

<!-- app/config/logging/included/logback-scheduler.xml -->
<included>

    <appender name="SIFT-SCHEDULER" class="ch.qos.logback.classic.sift.SiftingAppender">
        <discriminator class="com.aspectran.logging.LoggingGroupDiscriminator">
            <key>LOGGING_GROUP</key>
            <defaultValue>root</defaultValue>
        </discriminator>
        <sift>
            <appender name="FILE-SCHEDULER-${LOGGING_GROUP}" class="ch.qos.logback.core.rolling.RollingFileAppender">
                <file>${aspectran.basePath:-app}/logs/${LOGGING_GROUP}-scheduler.log</file>
                <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
                    <fileNamePattern>${aspectran.basePath:-app}/logs/archived/${LOGGING_GROUP}-scheduler.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
                    <maxFileSize>10MB</maxFileSize>
                    <maxHistory>30</maxHistory>
                    <totalSizeCap>1GB</totalSizeCap>
                </rollingPolicy>
                <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
                    <charset>UTF-8</charset>
                    <pattern>%-5level %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %msg%n</pattern>
                </encoder>
            </appender>
        </sift>
    </appender>

    <!-- Sends logs from ActivityJobReporter to the SIFT-SCHEDULER appender. -->
    <logger name="com.aspectran.core.scheduler.activity.ActivityJobReporter" level="debug" additivity="false">
        <appender-ref ref="SIFT-SCHEDULER"/>
    </logger>

</included>

If you include the above configuration in your main logback.xml file (<include file=".../logback-scheduler.xml"/>), all logs from ActivityJobReporter will be recorded in a separate scheduler log file based on the LOGGING_GROUP value. The default LOGGING_GROUP is root, so the log file will be created at app/logs/root-scheduler.log.

Checking the Log File

After running the application, you can monitor the execution logs of the schedule jobs by checking the root-scheduler.log file in the logs directory under the configured aspectran.basePath (default app).

4. Distributed Scheduling in a Clustered Environment

In an environment where multiple nodes operate as a single cluster, it is crucial to prevent the same schedule from being executed multiple times. Aspectran ensures safe task execution across the cluster through its Redis-based Distributed Lock feature.

How Distributed Locking Works

Just before a scheduled task is executed, Aspectran attempts to acquire a unique lock key for that schedule in Redis for a very short time (TTL).

  1. Acquire Lock: The node attempting to perform the task tries to acquire the lock using Redis’s SET NX feature.
  2. Execute Task: Only the node that first reaches the execution time and acquires the lock performs the actual task.
  3. Skip Execution: Other nodes that fail to acquire the lock automatically skip the task for that cycle.
  4. Safety Guarantee: Even if a node terminates abnormally during a task, the lock is automatically released after the expiration time set on the lock, allowing another node to take over in the next cycle.

Execution Control via the isolated Attribute

Scheduling rules have an isolated attribute that determines whether the task is a target for distribution or should always run independently on each node.

  • isolated="false" (Default): Only one node in the entire cluster performs the task. (Distributed lock applied)
  • isolated="true": Every node performs the task at its scheduled time, regardless of the cluster state. This is suitable for tasks like system log collection or refreshing local caches.

Real-time Control and Monitoring (Scheduler Manager)

The Aspectran Console provides a Scheduler Manager screen that allows you to grasp and manipulate the schedulers of the entire cluster at a glance.

  • Unified View: Real-time checking of scheduler status and active job lists for all nodes in the cluster.
  • Dynamic Switching: Instantly enable or disable specific schedules or individual jobs without redeploying the application.
  • Status Visibility: Visually shows whether the distributed lock feature is working normally and which node you are connected to.

5. Full Configuration Example and References

A complete example of how to actually configure and use the Aspectran Scheduler can be found in the following GitHub project. This project is a good reference that actually implements all the concepts described in this document.

6. Conclusion

Aspectran Scheduler is a feature that well demonstrates the framework’s core philosophy of abstraction and modularization. Developers do not need to worry about the complexity of the Quartz API; they can easily automate the tasks they want using the Translet they know best, and can choose between explicit XML/APON-based configuration and convenient annotation-based configuration that best suits their project to perfectly integrate and manage the scheduler as part of the application. In particular, the Redis-based distributed lock feature perfectly guarantees data integrity and system stability even in large-scale clustered environments.