Skip to content

作业开发与Spring和SpringBoot的集成

hebelala edited this page Oct 15, 2018 · 3 revisions

背景需求

业务代码工程,大多通过Spring或SpringBoot框架来构建。

原先提供的demo,是需要在每个作业类中重写getObject方法,其做的逻辑是实例化Spring或SpringBoot,并且从Spring上下文中获取作业实例。这样做有两个弊端:

每个类需要重写getObject方法,代码冗余; 初始化Spring或SpringBoot的时机是在初始化作业时,无法及时知道代码是否OK。 另外,某些业务也存在不合理使用的情况,比如将实例化Spring或SpringBoot的代码放在作业执行时的代码块(handleJavaJob或handleMsgJob)中,这样更加延迟了初始化的时机。

解决方案

1 在saturn-job-api提供一个SaturnApplication接口

package com.vip.saturn.job.application;
 
public interface SaturnApplication {

   void init();

   void destroy();

}

2 新增saturn-spring模块

 该模块依赖spring,并提供支持spring的SaturnApplication实现类,具体有如下文件:
package com.vip.saturn.job.spring;

import com.vip.saturn.job.application.SaturnApplication;

public interface SpringSaturnApplication extends SaturnApplication {

   <J> J getJobInstance(Class<J> jobClass);

}
package com.vip.saturn.job.spring;

import org.springframework.context.ApplicationContext;

public abstract class AbstractSpringSaturnApplication implements SpringSaturnApplication {

   protected ApplicationContext applicationContext;

   @Override
   public <J> J getJobInstance(Class<J> jobClass) {
      return applicationContext != null ? applicationContext.getBean(jobClass) : null;
   }
}
package com.vip.saturn.job.spring;

import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class GenericSpringSaturnApplication extends AbstractSpringSaturnApplication {

   private static final String[] CONFIG_LOCATIONS_DEFAULT = {"applicationContext.xml"};

   @Override
   public void init() {
      if (applicationContext != null) {
         destroy();
      }
      applicationContext = run();
   }

   @Override
   public void destroy() {
      if (applicationContext != null) {
         if (applicationContext instanceof ConfigurableApplicationContext) {
            ((ConfigurableApplicationContext) applicationContext).close();
         }
         applicationContext = null;
      }
   }

   /**
    * If the Spring container defaults aren’t to your taste, you can instead customize it
    * @return the running ApplicationContext
    */
   protected ApplicationContext run() {
      return new ClassPathXmlApplicationContext(getConfigLocations());
   }

   /**
    * You can override this method, to load the custom xml files. The <code>applicationContext.xml</code> will be loaded by default.
    * @return array of resource locations
    */
   protected String[] getConfigLocations() {
      return CONFIG_LOCATIONS_DEFAULT;
   }

}

3 新增saturn-springboot模块

该模块依赖saturn-spring和springboot,并提供支持springboot的SaturnApplication实现类,具体有如下文件:

package com.vip.saturn.job.springboot;

import com.vip.saturn.job.spring.AbstractSpringSaturnApplication;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ApplicationContext;

public class GenericSpringBootSaturnApplication extends AbstractSpringSaturnApplication {

   @Override
   public void init() {
      if (applicationContext != null) {
         destroy();
      }
      applicationContext = run();
   }

   @Override
   public void destroy() {
      if (applicationContext != null) {
         SpringApplication.exit(applicationContext);
         applicationContext = null;
      }
   }

   /**
    * If the SpringApplication defaults aren’t to your taste, you can instead customize it
    * @return the running ApplicationContext
    */
   protected ApplicationContext run() {
      return SpringApplication.run(source());
   }

   /**
    * If you use the SpringApplication defaults, maybe you could override this method to load the source
    * @return the source to load
    */
   protected Object source() {
      return this.getClass();
   }

}

4 新增saturn-embed-spring模块

该模块依赖saturn-embed和saturn-spring,并提供支持spring和springboot环境下的SaturnApplication实现类,具体有如下文件:

package com.vip.saturn.embed.spring;

import com.vip.saturn.embed.EmbeddedSaturn;
import com.vip.saturn.job.spring.AbstractSpringSaturnApplication;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.ContextRefreshedEvent;

public class EmbeddedSpringSaturnApplication extends AbstractSpringSaturnApplication implements ApplicationListener {

   // use spring log style
   protected final Log logger = LogFactory.getLog(getClass());

   private EmbeddedSaturn embeddedSaturn;

   private boolean ignoreExceptions;

   @Override
   public void init() {

   }

   @Override
   public void destroy() {

   }

   @Override
   public void onApplicationEvent(ApplicationEvent event) {
      try {
         if (event instanceof ContextRefreshedEvent) {
            ContextRefreshedEvent contextRefreshedEvent = (ContextRefreshedEvent) event;
            applicationContext = contextRefreshedEvent.getApplicationContext();
            if (embeddedSaturn == null) {
               embeddedSaturn = new EmbeddedSaturn();
               embeddedSaturn.setSaturnApplication(this);
               embeddedSaturn.start();
            }
         } else if (event instanceof ContextClosedEvent) {
            if (embeddedSaturn != null) {
               embeddedSaturn.stopGracefully();
               embeddedSaturn = null;
            }
         }
      } catch (Exception e) {
         logger.warn("exception happened on event: " + event, e);
         if (!ignoreExceptions) {
            throw new RuntimeException(e);
         }
      }
   }

   public boolean isIgnoreExceptions() {
      return ignoreExceptions;
   }

   public void setIgnoreExceptions(boolean ignoreExceptions) {
      this.ignoreExceptions = ignoreExceptions;
   }
}

5 修改saturn-embed模块,提供对SaturnApplication的支持

6 业务工程的使用方式

6.1 集成Spring

a. 依赖saturn-spring

<dependency>
    <groupId>com.vip.saturn</groupId>
    <artifactId>saturn-spring</artifactId>
    <version>${saturn.version}</version>
</dependency>

b. 创建saturn.properties文件,定义app.class为SaturnApplication的spring实现类,既可以使用默认的GenericSpringSaturnApplication,也可自定义实现类。

app.class=com.vip.saturn.demo.Application

c. 自定义的实现类继承GenericSpringSaturnApplication

package com.vip.saturn.demo;

import com.vip.saturn.job.spring.GenericSpringSaturnApplication;
import org.springframework.context.ApplicationContext;

public class Application extends GenericSpringSaturnApplication {

   /**
    * If the Spring container defaults aren’t to your taste, you can instead customize it
    * @return the running ApplicationContext
    */
   @Override
   protected ApplicationContext run() {
      return super.run();
   }

   /**
    * You can override this method, to load the custom xml files. The <code>applicationContext.xml</code> will be loaded by default.
    * @return array of resource locations
    */
   @Override
   protected String[] getConfigLocations() {
      return super.getConfigLocations();
   }

}

d. 运行mvn clean compile saturn:run 启动

6.2 集成SpringBoot

a. 依赖saturn-springboot

<dependency>
    <groupId>com.vip.saturn</groupId>
    <artifactId>saturn-springboot</artifactId>
    <version>${saturn.version}</version>
</dependency>

b. 创建saturn.properties文件,定义app.class为SaturnApplication的springboot实现类,既可以使用默认的GenericSpringBootSaturnApplication,也可自定义实现类。

app.class=com.vip.saturn.demo.Application

c. 自定义的实现类继承GenericSpringSaturnApplication

package com.vip.saturn.demo;

import com.vip.saturn.job.springboot.GenericSpringBootSaturnApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

@SpringBootApplication
public class Application extends GenericSpringBootSaturnApplication {

   /**
    * If the SpringApplication defaults aren’t to your taste, you can instead customize it
    * @return the running ApplicationContext
    */
   @Override
   protected ApplicationContext run() {
      return super.run();
   }

   /**
    * If you use the SpringApplication defaults, maybe you could override this method to load the source
    * @return the source to load
    */
   @Override
   protected Object source() {
      return super.source();
   }

}

d. 运行mvn clean compile saturn:run 启动

6.3 SpringBoot环境中嵌入式使用Saturn

a. 依赖saturn-embed-spring

<dependency>
   <groupId>com.vip.saturn</groupId>
   <artifactId>saturn-embed-spring</artifactId>
   <version>${saturn.version}</version>
</dependency>

b. 注册EmbeddedSpringSaturnApplication

package com.vip.saturn.demo;

import com.vip.saturn.embed.spring.EmbeddedSpringSaturnApplication;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class Application {

   public static void main(String[] args) {
      SpringApplication.run(Application.class, args);
   }

   @Bean
   public EmbeddedSpringSaturnApplication embeddedSpringSaturnApplication() {
      EmbeddedSpringSaturnApplication embeddedSpringSaturnApplication = new EmbeddedSpringSaturnApplication();
      embeddedSpringSaturnApplication.setIgnoreExceptions(false);
      return embeddedSpringSaturnApplication;
   }

}

c. 使用Main方法运行,或者springboot插件启动

7 Executor对SaturnApplication的支持

  • 如果是嵌入式启动,SaturnApplication由外部传入;否则,读取saturn.properties中的app.class属性值,来实例化;
  • 注册job watcher前,调用SaturnApplication的init方法,来初始化业务;
  • 初始化作业时,调用SaturnApplication是SpringSaturnApplication,则调用其getJobInstance方法,来获取作业实例,如果获取不到,则还是按照原来的逻辑获取;
  • 优雅退出时,调用SaturnApplication的destroy方法。

注意的是:

  • saturn.properties在业务代码中,只能存在一个文件,存在多个时,报错、启动失败;
  • SaturnApplication只调用一次init和destroy方法,生命周期不随reinitialize变化,只在优雅退出时调用destroy方法,防止业务没有处理好bean的destroy逻辑,造成内存泄漏等问题。