Spring Integration

From Obsidian Scheduler
Jump to: navigation, search

Though Obsidian works great on its own, Obsidian also supports seamless integration with Spring. This page outlines how to set up Spring integration and execute jobs which are configured as Spring components. It also documents a simple component which lets you easily embed a scheduler process within Spring.

Dependency Injection via Spring

As of Obsidian 2.2.0, Obsidian has greatly improved the integration with your Spring dependency injection container, and now supports loading job implementations from the Spring context. Obsidian does not include any Spring libraries and will defer to the version and libraries you include with your application. It supports versions of Spring from 2.5 onward.

Obsidian uses the @Component and any Spring or custom extensions of those annotations (such as @Service and @Repository) as a basis for knowing which job implementations should be retrieved from the Spring context. It is important to note that if a job class does not have one of these annotations, Obsidian will not use the container version, and will instantiate the job directly.

Job classes which can be loaded from Spring can be either SchedulableJob implementations or annotated jobs. Even if you are not using Spring annotations to wire your classes, you will need to add the @Component annotation (or a subtype) to your job classes to serve as a marker to Obsidian. See Implementing Jobs for details on how to write and configure executable jobs.

Regardless of the method you use to configure your Spring components, Obsidian will load them directly from the container which means the actual singleton or prototype object obtained from Spring is executed by Obsidian. This makes creating jobs which use configured services very easy and convenient.

Spring will need to be configured and started in all Obsidian scheduler runtimes, but not standalone Admin UI web apps. Each environment will require the job classes to be wired in Spring, scheduler nodes to permit retrieval from the context for execution, admin deployments to allow retrieval from the context for configuration validation.

To allow Obsidian to locate your application's Spring context, you will need to ensure Obsidian's ApplicationContextAware implementation is configured by Spring. If you are using Spring's classpath scanning, you can use the following approach to add an additional package to your configuration:

<context:component-scan base-package="com.my.package.scan.base, com.carfey.ops.job.di"/>

Another option is to extend our com.carfey.ops.job.di.SpringContextAware class with an empty extension class in one of your currently scanned packages. When this object is configured by Spring, it will automatically initialize Obsidian's Spring integration.

If you are not using scanning, simply ensure you wire in our com.carfey.ops.job.di.SpringContextAware class via XML.

If you have multiple instances of a given bean type in your Spring context, please ensure you use the value="" when you annotate your job with @Component or its specializations as this is how Obsidian will uniquely identify the job within the Spring context.

As of Obsidian 2.2.1, implementations of SchedulableJob found in your ApplicationContext will automatically be available in the Job Admin screen thus avoiding needing to configure distinct Classpath Scanning. If you're using at least Spring 3.0, your annotated jobs will also be found in the Spring ApplicationContext.

Grails

Grails services can be used with the standard Spring Integration as outlined above, but they must have the appropriate @Component annotation, or a subtype of that annotation.

Spring Boot

See the Spring Boot sample project showing how to start and stop the Obsidian scheduler automatically, courtesy of Ajit Kulkarni.

Best Practices

  • Ensure Spring is initialized before Obsidian is started via either com.carfey.ops.job.SchedulerStarter or com.carfey.ops.servlet.StartupShutdownListener. This ensures the Spring context is fully available when Obsidian starts running jobs.
    • If using com.carfey.ops.servlet.StartupShutdownListener within a web.xml file, ensure the Spring context loader (e.g. org.springframework.web.context.ContextLoaderListener) is called first.
    • If using com.carfey.ops.job.SchedulerStarter called via Spring initialization, ensure it is called at the end of the context initialization (e.g. @PostConstruct, via an InitializingBean, etc).
  • If your job has internal state (i.e. members) which is used during job execution, Spring prototypes should be used to avoid concurrency issues.
  • In many cases, the majority of job logic can be left in your existing Spring components.
  • Use Obsidian to configure parameters and store results, but leave application-level configuration in Spring.
  • Make sure every job you want to be loaded from the Spring context has a @Component annotation, or a subtype of that annotation.

Sample Jobs

As a prototype @Component:

@Component
@Scope(value=ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Configuration(knownParameters={
		@Parameter(name="incrementCounter", type=Type.BOOLEAN, defaultValue="true", required=true)
})
@Description("This job is a Spring Bean that invokes an Autowired Counter Service.")
public class SpringComponentJob implements SchedulableJob {

    @Autowired private CounterService mCounterService; 
    private MyState mState;

    @Override
    public void execute(Context context) throws Exception {
        ...snip... 
        feel free to call methods that manipulate state since this is a prototype
        ...snip... 
        String prefix = SpringComponentJob.class.getName();
        if (context.getConfig().getBoolean("incrementCounter")) {
	    context.saveJobResult(prefix + ".incrementCounter", mCounterService.incrementCounter());
	} else {
	    context.saveJobResult(prefix + ".currentCounter", mCounterService.currentValue());
	}
    }
}


As a singleton @Service:

@Service
@Configuration(knownParameters={
		@Parameter(name="incrementCounter", type=Type.BOOLEAN, defaultValue="true", required=true)
})
@Description("This job is a Spring Service that makes various service calls.")
public class SpringServiceJob implements SchedulableJob {

    @Autowired private PricingService mPricingService; 
    @Autowired private AuditingService mAuditingService; 

    @Override
    public void execute(Context context) throws Exception {
        ...snip... 
        feel free to call other services or local service methods
        ...snip... 
    }
}

Wiring an Embedded Obsidian Scheduler

Obsidian can run scheduler nodes that do not include the full web application deployment. This is often used in conjunction with a separate standalone admin web application.

As of 2.2.1, Obsidian comes bundled with a simple Spring component to automatically start and stop an embedded scheduler. com.carfey.ops.job.di.SpringSchedulerStarter can be wired into your Spring application via XML:

 <!-- This should generally be as late in context startup as possible, using depends-on, etc. as required -->
 <bean id="obsidianStarter" class="com.carfey.ops.job.di.SpringSchedulerStarter" /> 

This wired bean will automatically start and stop the scheduler gracefully when the context initializes and shuts down.

Note: For safety reasons, this class does not possess a @Component annotation and cannot be auto-wired via Spring classpath scanning.